-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
219 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
use axum::{ | ||
extract::{Path, State}, | ||
http::StatusCode, | ||
response::IntoResponse, | ||
routing::{get, post}, | ||
Json, Router, | ||
}; | ||
use std::sync::Arc; | ||
use utoipa::OpenApi; | ||
use utoipa_swagger_ui::SwaggerUi; | ||
|
||
use crate::filter::SlidingBloomFilter; | ||
use crate::types::{AppState, ErrorResponse, InsertRequest, QueryResponse}; | ||
|
||
#[derive(OpenApi)] | ||
#[openapi( | ||
paths( | ||
health_check, | ||
insert_item, | ||
query_item, | ||
cleanup_expired, | ||
), | ||
components( | ||
schemas(InsertRequest, QueryResponse, ErrorResponse) | ||
), | ||
tags( | ||
(name = "bloom-filter", description = "Time-Decaying Bloom Filter API") | ||
) | ||
)] | ||
struct ApiDoc; | ||
|
||
/// Check API health | ||
#[utoipa::path( | ||
get, | ||
path = "/health", | ||
tag = "bloom-filter", | ||
responses( | ||
(status = 200, description = "API is healthy") | ||
) | ||
)] | ||
async fn health_check() -> impl IntoResponse { | ||
StatusCode::OK | ||
} | ||
|
||
/// Insert an item into the Bloom filter | ||
#[utoipa::path( | ||
post, | ||
path = "/items", | ||
tag = "bloom-filter", | ||
request_body = InsertRequest, | ||
responses( | ||
(status = 200, description = "Item inserted successfully"), | ||
(status = 500, description = "Internal server error", body = ErrorResponse) | ||
) | ||
)] | ||
async fn insert_item( | ||
State(state): State<Arc<AppState>>, | ||
Json(request): Json<InsertRequest>, | ||
) -> impl IntoResponse { | ||
let mut filter = state.filter.lock().await; | ||
match filter.insert(request.value.as_bytes()) { | ||
Ok(_) => StatusCode::OK.into_response(), | ||
Err(e) => ( | ||
StatusCode::INTERNAL_SERVER_ERROR, | ||
Json(ErrorResponse { | ||
message: e.to_string(), | ||
}), | ||
) | ||
.into_response(), | ||
} | ||
} | ||
|
||
/// Query if an item exists in the Bloom filter | ||
#[utoipa::path( | ||
get, | ||
path = "/items/{value}", | ||
tag = "bloom-filter", | ||
params( | ||
("value" = String, Path, description = "Value to query") | ||
), | ||
responses( | ||
(status = 200, description = "Query successful", body = QueryResponse), | ||
(status = 500, description = "Internal server error", body = ErrorResponse) | ||
) | ||
)] | ||
async fn query_item( | ||
State(state): State<Arc<AppState>>, | ||
Path(value): Path<String>, | ||
) -> impl IntoResponse { | ||
let filter = state.filter.lock().await; | ||
match filter.query(value.as_bytes()) { | ||
Ok(exists) => { | ||
(StatusCode::OK, Json(QueryResponse { exists })).into_response() | ||
} | ||
Err(e) => ( | ||
StatusCode::INTERNAL_SERVER_ERROR, | ||
Json(ErrorResponse { | ||
message: e.to_string(), | ||
}), | ||
) | ||
.into_response(), | ||
} | ||
} | ||
|
||
/// Clean up expired items | ||
#[utoipa::path( | ||
post, | ||
path = "/cleanup", | ||
tag = "bloom-filter", | ||
responses( | ||
(status = 200, description = "Cleanup successful"), | ||
(status = 500, description = "Internal server error", body = ErrorResponse) | ||
) | ||
)] | ||
async fn cleanup_expired( | ||
State(state): State<Arc<AppState>>, | ||
) -> impl IntoResponse { | ||
let mut filter = state.filter.lock().await; | ||
match filter.cleanup_expired_levels() { | ||
Ok(_) => StatusCode::OK.into_response(), | ||
Err(e) => ( | ||
StatusCode::INTERNAL_SERVER_ERROR, | ||
Json(ErrorResponse { | ||
message: e.to_string(), | ||
}), | ||
) | ||
.into_response(), | ||
} | ||
} | ||
|
||
pub fn create_router(state: Arc<AppState>) -> Router { | ||
let openapi = ApiDoc::openapi(); | ||
|
||
Router::new() | ||
.merge( | ||
SwaggerUi::new("/swagger-ui").url("/api-docs/openapi.json", openapi), | ||
) | ||
.route("/health", get(health_check)) | ||
.route("/items", post(insert_item)) | ||
.route("/items/{value}", get(query_item)) | ||
.route("/cleanup", post(cleanup_expired)) | ||
.with_state(state) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
use expiring_bloom_rs::api::create_router; | ||
use expiring_bloom_rs::types::AppState; | ||
use expiring_bloom_rs::{FilterConfigBuilder, RedbSlidingBloomFilter}; | ||
use std::{sync::Arc, time::Duration}; | ||
|
||
#[tokio::main] | ||
async fn main() { | ||
// Initialize the Bloom filter | ||
let config = FilterConfigBuilder::default() | ||
.capacity(1000) | ||
.false_positive_rate(0.01) | ||
.level_duration(Duration::from_secs(60)) | ||
.max_levels(3) | ||
.build() | ||
.expect("Failed to build filter config"); | ||
|
||
let filter = RedbSlidingBloomFilter::new(config, "bloom.redb".into()) | ||
.expect("Failed to create filter"); | ||
|
||
// Create application state | ||
let state = Arc::new(AppState { | ||
filter: tokio::sync::Mutex::new(filter), | ||
}); | ||
|
||
// Create router | ||
let app = create_router(state); | ||
|
||
// Start the server | ||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); | ||
println!("Server running on http://localhost:3000"); | ||
println!("API documentation available at http://localhost:3000/swagger-ui/"); | ||
|
||
axum::serve(listener, app).await.unwrap(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
use crate::RedbSlidingBloomFilter; | ||
use serde::{Deserialize, Serialize}; | ||
use tokio::sync::Mutex; | ||
use utoipa::ToSchema; | ||
|
||
#[derive(Debug, Serialize, Deserialize, ToSchema)] | ||
pub struct InsertRequest { | ||
pub value: String, | ||
} | ||
|
||
#[derive(Debug, Serialize, Deserialize, ToSchema)] | ||
pub struct QueryResponse { | ||
pub exists: bool, | ||
} | ||
|
||
#[derive(Debug, Serialize, Deserialize, ToSchema)] | ||
pub struct ErrorResponse { | ||
pub message: String, | ||
} | ||
|
||
pub struct AppState { | ||
pub filter: Mutex<RedbSlidingBloomFilter>, | ||
} |