From 7653cc925351ae05af2fd2ca50bfbb7100f4b1ef Mon Sep 17 00:00:00 2001 From: Maki-Grz Date: Sat, 20 Dec 2025 16:50:27 +0100 Subject: [PATCH 1/3] feat(examples): add integration examples for Actix, Axum, and Rocket Add three new examples demonstrating how to use the Qdrant client within popular Rust web frameworks. Each example shows how to manage shared state and handle gRPC-to-JSON serialization. - Added actix-web, axum, and rocket to dev-dependencies - Declared examples with 'serde' required-feature in Cargo.toml - Implemented state sharing and basic endpoints in each framework --- Cargo.toml | 16 +++++++++++++ examples/actix_web.rs | 53 +++++++++++++++++++++++++++++++++++++++++++ examples/axum.rs | 39 +++++++++++++++++++++++++++++++ examples/rocket.rs | 32 ++++++++++++++++++++++++++ 4 files changed, 140 insertions(+) create mode 100644 examples/actix_web.rs create mode 100644 examples/axum.rs create mode 100644 examples/rocket.rs diff --git a/Cargo.toml b/Cargo.toml index 9edcb13f..7fad9a45 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,10 @@ parking_lot = "0.12.4" [dev-dependencies] tonic-build = { version = "0.12.3", features = ["prost"] } +actix-web = "4.12.1" +axum = "0.8.7" +rocket = { version = "0.5.1", features = ["json"] } +serde_json = "1.0.128" [features] default = ["download_snapshots", "serde", "generate-snippets"] @@ -47,6 +51,18 @@ required-features = ["serde"] name = "deprecated_search" required-features = ["serde"] +[[example]] +name = "actix_web" +required-features = ["serde"] + +[[example]] +name = "axum" +required-features = ["serde"] + +[[example]] +name = "rocket" +required-features = ["serde"] + [package.metadata.docs.rs] features = ["download_snapshots", "serde"] no-default-features = true diff --git a/examples/actix_web.rs b/examples/actix_web.rs new file mode 100644 index 00000000..f73d1c77 --- /dev/null +++ b/examples/actix_web.rs @@ -0,0 +1,53 @@ +use std::sync::Arc; + +use actix_web::{web, App, HttpResponse, HttpServer, Responder}; +use qdrant_client::Qdrant; +use serde_json::json; + +struct AppState { + client: Arc, +} + +async fn list_collections(data: web::Data) -> impl Responder { + match data.client.list_collections().await { + Ok(collections) => { + let names: Vec = collections + .collections + .into_iter() + .map(|c| c.name) + .collect(); + HttpResponse::Ok().json(json!({ "collections": names })) + } + Err(e) => HttpResponse::InternalServerError().body(e.to_string()), + } +} + +async fn health_check(data: web::Data) -> impl Responder { + match data.client.health_check().await { + Ok(resp) => HttpResponse::Ok().json(json!({ "result": format!("{:?}", resp) })), + Err(e) => HttpResponse::InternalServerError().body(e.to_string()), + } +} + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + let client = Qdrant::from_url("http://localhost:6334") + .build() + .expect("Failed to create Qdrant client"); + + let state = web::Data::new(AppState { + client: Arc::new(client), + }); + + println!("Starting Actix server on http://localhost:8080"); + + HttpServer::new(move || { + App::new() + .app_data(state.clone()) + .route("/collections", web::get().to(list_collections)) + .route("/health", web::get().to(health_check)) + }) + .bind(("127.0.0.1", 8080))? + .run() + .await +} diff --git a/examples/axum.rs b/examples/axum.rs new file mode 100644 index 00000000..79adee79 --- /dev/null +++ b/examples/axum.rs @@ -0,0 +1,39 @@ +use std::sync::Arc; + +use axum::routing::get; +use axum::{Json, Router}; +use qdrant_client::Qdrant; +use serde_json::{json, Value}; + +async fn list_collections( + axum::extract::State(client): axum::extract::State>, +) -> Json { + match client.list_collections().await { + Ok(collections) => { + let names: Vec = collections + .collections + .into_iter() + .map(|c| c.name) + .collect(); + Json(json!({ "collections": names })) + } + Err(e) => Json(json!({"error": e.to_string()})), + } +} + +#[tokio::main] +async fn main() { + let client = Qdrant::from_url("http://localhost:6334") + .build() + .expect("Failed to create Qdrant client"); + + let app = Router::new() + .route("/collections", get(list_collections)) + .with_state(Arc::new(client)); + + let listener = tokio::net::TcpListener::bind("127.0.0.1:3000") + .await + .unwrap(); + println!("Starting Axum server on http://localhost:3000"); + axum::serve(listener, app).await.unwrap(); +} diff --git a/examples/rocket.rs b/examples/rocket.rs new file mode 100644 index 00000000..ce1dd540 --- /dev/null +++ b/examples/rocket.rs @@ -0,0 +1,32 @@ +#[macro_use] +extern crate rocket; +use qdrant_client::Qdrant; +use rocket::serde::json::Json; +use rocket::State; +use serde_json::{json, Value}; + +#[get("/collections")] +async fn list_collections(client: &State) -> Json { + match client.list_collections().await { + Ok(collections) => { + let names: Vec = collections + .collections + .into_iter() + .map(|c| c.name) + .collect(); + Json(json!({ "collections": names })) + } + Err(e) => Json(json!({"error": e.to_string()})), + } +} + +#[launch] +async fn rocket() -> _ { + let client = Qdrant::from_url("http://localhost:6334") + .build() + .expect("Failed to create Qdrant client"); + + rocket::build() + .manage(client) + .mount("/", routes![list_collections]) +} From 268e393ac1e5ae4ead7fbc6931a4c605f89214ee Mon Sep 17 00:00:00 2001 From: Maximilien Grzeczka Date: Sat, 7 Feb 2026 11:22:54 +0100 Subject: [PATCH 2/3] refactor: web integration examples into workspace Move web framework examples into a dedicated workspace member to streamline management and testing. This change also removes the individual example definitions from the root Cargo.toml. --- Cargo.toml | 21 +++++----------- examples/web-integrations/Cargo.toml | 25 +++++++++++++++++++ .../{ => web-integrations/src}/actix_web.rs | 0 examples/{ => web-integrations/src}/axum.rs | 0 examples/{ => web-integrations/src}/rocket.rs | 0 5 files changed, 31 insertions(+), 15 deletions(-) create mode 100644 examples/web-integrations/Cargo.toml rename examples/{ => web-integrations/src}/actix_web.rs (100%) rename examples/{ => web-integrations/src}/axum.rs (100%) rename examples/{ => web-integrations/src}/rocket.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 7fad9a45..552f979b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,11 +31,14 @@ parking_lot = "0.12.4" [dev-dependencies] tonic-build = { version = "0.12.3", features = ["prost"] } -actix-web = "4.12.1" -axum = "0.8.7" -rocket = { version = "0.5.1", features = ["json"] } serde_json = "1.0.128" +[workspace] +members = [ + ".", + "examples/web-integrations", +] + [features] default = ["download_snapshots", "serde", "generate-snippets"] download_snapshots = ["reqwest", "futures-util"] @@ -51,18 +54,6 @@ required-features = ["serde"] name = "deprecated_search" required-features = ["serde"] -[[example]] -name = "actix_web" -required-features = ["serde"] - -[[example]] -name = "axum" -required-features = ["serde"] - -[[example]] -name = "rocket" -required-features = ["serde"] - [package.metadata.docs.rs] features = ["download_snapshots", "serde"] no-default-features = true diff --git a/examples/web-integrations/Cargo.toml b/examples/web-integrations/Cargo.toml new file mode 100644 index 00000000..2450a840 --- /dev/null +++ b/examples/web-integrations/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "web-integrations-examples" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +qdrant-client = { path = "../../", features = ["serde"] } +actix-web = "4.12.1" +axum = "0.8.7" +rocket = { version = "0.5.1", features = ["json"] } +serde_json = "1.0.128" +tokio = { version = "1.40.0", features = ["rt-multi-thread", "macros", "net"] } + +[[bin]] +name = "actix_web" +path = "src/actix_web.rs" + +[[bin]] +name = "axum" +path = "src/axum.rs" + +[[bin]] +name = "rocket" +path = "src/rocket.rs" diff --git a/examples/actix_web.rs b/examples/web-integrations/src/actix_web.rs similarity index 100% rename from examples/actix_web.rs rename to examples/web-integrations/src/actix_web.rs diff --git a/examples/axum.rs b/examples/web-integrations/src/axum.rs similarity index 100% rename from examples/axum.rs rename to examples/web-integrations/src/axum.rs diff --git a/examples/rocket.rs b/examples/web-integrations/src/rocket.rs similarity index 100% rename from examples/rocket.rs rename to examples/web-integrations/src/rocket.rs From 5299c700bd91b519439a5da8e1b7c7ba99467de1 Mon Sep 17 00:00:00 2001 From: Maximilien Grzeczka Date: Sat, 7 Feb 2026 11:36:07 +0100 Subject: [PATCH 3/3] refactor: web examples to return JSON errors This commit updates the web examples to consistently return JSON formatted error responses instead of plain text. This provides a more consistent and machine-readable error handling mechanism for API consumers. Additionally, the `serde_json` dependency has been removed from the main workspace as it's no longer directly used there. --- Cargo.toml | 2 -- examples/web-integrations/.gitignore | 1 + examples/web-integrations/Cargo.toml | 2 ++ examples/web-integrations/src/actix_web.rs | 4 ++-- examples/web-integrations/src/axum.rs | 10 +++++++--- examples/web-integrations/src/rocket.rs | 13 ++++++++++--- 6 files changed, 22 insertions(+), 10 deletions(-) create mode 100644 examples/web-integrations/.gitignore diff --git a/Cargo.toml b/Cargo.toml index 552f979b..bffcb7b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,12 +31,10 @@ parking_lot = "0.12.4" [dev-dependencies] tonic-build = { version = "0.12.3", features = ["prost"] } -serde_json = "1.0.128" [workspace] members = [ ".", - "examples/web-integrations", ] [features] diff --git a/examples/web-integrations/.gitignore b/examples/web-integrations/.gitignore new file mode 100644 index 00000000..eb5a316c --- /dev/null +++ b/examples/web-integrations/.gitignore @@ -0,0 +1 @@ +target diff --git a/examples/web-integrations/Cargo.toml b/examples/web-integrations/Cargo.toml index 2450a840..c7e6277a 100644 --- a/examples/web-integrations/Cargo.toml +++ b/examples/web-integrations/Cargo.toml @@ -4,6 +4,8 @@ version = "0.1.0" edition = "2021" publish = false +[workspace] + [dependencies] qdrant-client = { path = "../../", features = ["serde"] } actix-web = "4.12.1" diff --git a/examples/web-integrations/src/actix_web.rs b/examples/web-integrations/src/actix_web.rs index f73d1c77..33a17fc2 100644 --- a/examples/web-integrations/src/actix_web.rs +++ b/examples/web-integrations/src/actix_web.rs @@ -18,14 +18,14 @@ async fn list_collections(data: web::Data) -> impl Responder { .collect(); HttpResponse::Ok().json(json!({ "collections": names })) } - Err(e) => HttpResponse::InternalServerError().body(e.to_string()), + Err(e) => HttpResponse::InternalServerError().json(json!({ "error": e.to_string() })), } } async fn health_check(data: web::Data) -> impl Responder { match data.client.health_check().await { Ok(resp) => HttpResponse::Ok().json(json!({ "result": format!("{:?}", resp) })), - Err(e) => HttpResponse::InternalServerError().body(e.to_string()), + Err(e) => HttpResponse::InternalServerError().json(json!({ "error": e.to_string() })), } } diff --git a/examples/web-integrations/src/axum.rs b/examples/web-integrations/src/axum.rs index 79adee79..d125d140 100644 --- a/examples/web-integrations/src/axum.rs +++ b/examples/web-integrations/src/axum.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use axum::http::StatusCode; use axum::routing::get; use axum::{Json, Router}; use qdrant_client::Qdrant; @@ -7,7 +8,7 @@ use serde_json::{json, Value}; async fn list_collections( axum::extract::State(client): axum::extract::State>, -) -> Json { +) -> (StatusCode, Json) { match client.list_collections().await { Ok(collections) => { let names: Vec = collections @@ -15,9 +16,12 @@ async fn list_collections( .into_iter() .map(|c| c.name) .collect(); - Json(json!({ "collections": names })) + (StatusCode::OK, Json(json!({ "collections": names }))) } - Err(e) => Json(json!({"error": e.to_string()})), + Err(e) => ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({ "error": e.to_string() })), + ), } } diff --git a/examples/web-integrations/src/rocket.rs b/examples/web-integrations/src/rocket.rs index ce1dd540..041f2b7e 100644 --- a/examples/web-integrations/src/rocket.rs +++ b/examples/web-integrations/src/rocket.rs @@ -1,12 +1,16 @@ #[macro_use] extern crate rocket; use qdrant_client::Qdrant; +use rocket::http::Status; +use rocket::response::status; use rocket::serde::json::Json; use rocket::State; use serde_json::{json, Value}; #[get("/collections")] -async fn list_collections(client: &State) -> Json { +async fn list_collections( + client: &State, +) -> Result, status::Custom>> { match client.list_collections().await { Ok(collections) => { let names: Vec = collections @@ -14,9 +18,12 @@ async fn list_collections(client: &State) -> Json { .into_iter() .map(|c| c.name) .collect(); - Json(json!({ "collections": names })) + Ok(Json(json!({ "collections": names }))) } - Err(e) => Json(json!({"error": e.to_string()})), + Err(e) => Err(status::Custom( + Status::InternalServerError, + Json(json!({ "error": e.to_string() })), + )), } }