From d30b0dc54870c34f5b27f9ea76c48404bd634a3d Mon Sep 17 00:00:00 2001 From: Frank Elsinga Date: Tue, 3 Dec 2024 02:45:11 +0100 Subject: [PATCH] migrate the api docs to be utoipa based --- server/Cargo.lock | 146 ++++++++++++++++++ server/Cargo.toml | 3 + server/src/calendar/mod.rs | 8 +- server/src/feedback/mod.rs | 30 +--- server/src/feedback/post_feedback.rs | 10 +- .../src/feedback/proposed_edits/coordinate.rs | 2 +- server/src/feedback/proposed_edits/image.rs | 8 +- server/src/feedback/proposed_edits/mod.rs | 12 +- server/src/feedback/tokens.rs | 9 +- server/src/limited/hash_map.rs | 2 +- server/src/limited/vec.rs | 2 +- server/src/locations/details.rs | 7 +- server/src/locations/mod.rs | 18 +-- server/src/locations/nearby.rs | 7 +- server/src/locations/preview.rs | 7 +- server/src/main.rs | 63 +++++++- server/src/maps/indoor.rs | 13 ++ server/src/search/mod.rs | 9 +- 18 files changed, 293 insertions(+), 63 deletions(-) diff --git a/server/Cargo.lock b/server/Cargo.lock index e4762f193..6797b7141 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -346,6 +346,55 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +dependencies = [ + "anstyle", + "windows-sys 0.59.0", +] + [[package]] name = "anyhow" version = "1.0.93" @@ -950,6 +999,46 @@ dependencies = [ "libloading", ] +[[package]] +name = "clap" +version = "4.5.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "clap_lex" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" + [[package]] name = "cmake" version = "0.1.51" @@ -965,6 +1054,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + [[package]] name = "compact_str" version = "0.8.0" @@ -2657,6 +2752,12 @@ version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "iso8601" version = "0.6.1" @@ -3170,6 +3271,7 @@ dependencies = [ "base64 0.22.1", "cached", "chrono", + "clap", "futures", "geo", "geo-types", @@ -3204,6 +3306,8 @@ dependencies = [ "tracing-subscriber", "tracing-test", "unicode-truncate", + "utoipa", + "utoipa-actix-web", ] [[package]] @@ -6855,6 +6959,48 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "utoipa" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514a48569e4e21c86d0b84b5612b5e73c0b2cf09db63260134ba426d4e8ea714" +dependencies = [ + "indexmap 2.6.0", + "serde", + "serde_json", + "serde_yaml", + "utoipa-gen", +] + +[[package]] +name = "utoipa-actix-web" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7eda9c23c05af0fb812f6a177514047331dac4851a2c8e9c4b895d6d826967f" +dependencies = [ + "actix-service", + "actix-web", + "utoipa", +] + +[[package]] +name = "utoipa-gen" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5629efe65599d0ccd5d493688cbf6e03aa7c1da07fe59ff97cf5977ed0637f66" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.87", +] + [[package]] name = "uuid" version = "1.11.0" diff --git a/server/Cargo.toml b/server/Cargo.toml index 3414aa774..6461307e9 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -77,6 +77,9 @@ geo = { version = "0.29.0", features = ["use-serde"], default-features = false } geozero = { version = "0.14.0", features = ["with-postgis-sqlx", "with-geo"], default-features = false } geo-types = { version = "0.7.13", default-features = false } actix-middleware-etag = "0.4.1" +utoipa-actix-web = "0.1.2" +utoipa = { version = "5.2.0", features = ["yaml", "chrono", "actix_extras"] } +clap = { version = "4.5.21", features = ["derive"] } [dev-dependencies] insta = { version = "1.39.0", features = ["json", "redactions", "yaml"] } diff --git a/server/src/calendar/mod.rs b/server/src/calendar/mod.rs index 38e61b8c3..95d2f1f21 100644 --- a/server/src/calendar/mod.rs +++ b/server/src/calendar/mod.rs @@ -15,7 +15,7 @@ mod connectum; mod models; pub mod refresh; -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug, utoipa::ToSchema)] pub struct Arguments { ids: Vec, /// eg. 2039-01-19T03:14:07+1 @@ -42,6 +42,12 @@ impl Arguments { } } +/// Get Pet by id +#[utoipa::path( + responses( + (status = 200, description = "Pet found from database") + ) +)] #[post("/api/calendar")] pub async fn calendar_handler( web::Json(args): web::Json, diff --git a/server/src/feedback/mod.rs b/server/src/feedback/mod.rs index 80a3760aa..c99e6e39d 100644 --- a/server/src/feedback/mod.rs +++ b/server/src/feedback/mod.rs @@ -1,28 +1,4 @@ -use actix_governor::{GlobalKeyExtractor, Governor, GovernorConfigBuilder}; -use actix_web::web; - mod github; -mod post_feedback; -mod proposed_edits; -mod tokens; - -const SECONDS_PER_DAY: u64 = 60 * 60 * 24; - -pub fn configure(cfg: &mut web::ServiceConfig) { - let feedback_ratelimit = GovernorConfigBuilder::default() - .key_extractor(GlobalKeyExtractor) - .seconds_per_request(SECONDS_PER_DAY / 300) // replenish new token every .. seconds - .burst_size(50) - .finish() - .expect("Invalid configuration of the governor"); - - let recorded_tokens = web::Data::new(tokens::RecordedTokens::default()); - cfg.app_data(recorded_tokens.clone()) - .service(post_feedback::send_feedback) - .service(proposed_edits::propose_edits) - .service( - web::scope("/get_token") - .wrap(Governor::new(&feedback_ratelimit)) - .route("", web::post().to(tokens::get_token)), - ); -} +pub mod post_feedback; +pub mod proposed_edits; +pub mod tokens; diff --git a/server/src/feedback/post_feedback.rs b/server/src/feedback/post_feedback.rs index 9eab82d01..a8fd56f2d 100644 --- a/server/src/feedback/post_feedback.rs +++ b/server/src/feedback/post_feedback.rs @@ -6,7 +6,7 @@ use serde::Deserialize; use super::github; use super::tokens::RecordedTokens; -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub struct FeedbackPostData { token: String, category: String, @@ -16,7 +16,13 @@ pub struct FeedbackPostData { deletion_requested: bool, } -#[post("/feedback")] +/// Get Pet by id +#[utoipa::path( + responses( + (status = 200, description = "Pet found from database") + ) +)] +#[post("/api/feedback/feedback")] pub async fn send_feedback( recorded_tokens: Data, req_data: Json, diff --git a/server/src/feedback/proposed_edits/coordinate.rs b/server/src/feedback/proposed_edits/coordinate.rs index 56f39a364..c32c0feae 100644 --- a/server/src/feedback/proposed_edits/coordinate.rs +++ b/server/src/feedback/proposed_edits/coordinate.rs @@ -38,7 +38,7 @@ impl CoordinateFile { } } -#[derive(Deserialize, Debug, Clone, Copy, Default, PartialEq)] +#[derive(Deserialize, Debug, Clone, Copy, Default, PartialEq, utoipa::ToSchema)] pub struct Coordinate { lat: f64, lon: f64, diff --git a/server/src/feedback/proposed_edits/image.rs b/server/src/feedback/proposed_edits/image.rs index e4de53755..ec588a7b9 100644 --- a/server/src/feedback/proposed_edits/image.rs +++ b/server/src/feedback/proposed_edits/image.rs @@ -11,7 +11,7 @@ use tracing::error; use super::AppliableEdit; -#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] +#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, utoipa::ToSchema)] pub struct Source { author: String, license: Property, @@ -21,21 +21,21 @@ pub struct Source { #[serde(skip_serializing_if = "Option::is_none")] meta: Option>, } -#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] +#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, utoipa::ToSchema)] struct Property { text: String, #[serde(skip_serializing_if = "Option::is_none")] url: Option, } -#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] +#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, utoipa::ToSchema)] pub struct Offsets { #[serde(skip_serializing_if = "Option::is_none")] header: Option, #[serde(skip_serializing_if = "Option::is_none")] thumb: Option, } -#[derive(Debug, Deserialize, Clone, Eq, PartialEq)] +#[derive(Debug, Deserialize, Clone, Eq, PartialEq, utoipa::ToSchema)] pub struct Image { content: String, metadata: Source, diff --git a/server/src/feedback/proposed_edits/mod.rs b/server/src/feedback/proposed_edits/mod.rs index a79faa7e8..f6e91b85f 100644 --- a/server/src/feedback/proposed_edits/mod.rs +++ b/server/src/feedback/proposed_edits/mod.rs @@ -19,7 +19,7 @@ mod discription; mod image; mod tmp_repo; -#[derive(Debug, Deserialize, Clone)] +#[derive(Debug, Deserialize, Clone, utoipa::ToSchema)] struct Edit { coordinate: Option, image: Option, @@ -28,7 +28,7 @@ pub trait AppliableEdit { fn apply(&self, key: &str, base_dir: &Path) -> String; } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, utoipa::ToSchema)] pub struct EditRequest { token: String, edits: LimitedHashMap, @@ -93,7 +93,13 @@ impl EditRequest { } } -#[post("/propose_edit")] +/// Get Pet by id +#[utoipa::path( + responses( + (status = 200, description = "Pet found from database") + ) +)] +#[post("/api/feedback/propose_edit")] pub async fn propose_edits( recorded_tokens: Data, req_data: Json, diff --git a/server/src/feedback/tokens.rs b/server/src/feedback/tokens.rs index cfdade370..409003f33 100644 --- a/server/src/feedback/tokens.rs +++ b/server/src/feedback/tokens.rs @@ -1,6 +1,6 @@ use std::fmt; -use actix_web::HttpResponse; +use actix_web::{post, HttpResponse}; use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation}; use serde::{Deserialize, Serialize}; use tokio::sync::Mutex; @@ -114,6 +114,13 @@ struct Token { token: String, } +/// Get Pet by id +#[utoipa::path( + responses( + (status = 200, description = "Pet found from database") + ) +)] +#[post("")] pub async fn get_token() -> HttpResponse { if !able_to_process_feedback() { return HttpResponse::ServiceUnavailable() diff --git a/server/src/limited/hash_map.rs b/server/src/limited/hash_map.rs index da4322032..b7de05de6 100644 --- a/server/src/limited/hash_map.rs +++ b/server/src/limited/hash_map.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use crate::limited::OrMore; -#[derive(Serialize, Deserialize, Clone, Default)] +#[derive(Serialize, Deserialize, Clone, Default, utoipa::ToSchema)] pub struct LimitedHashMap(pub HashMap); impl From> for LimitedHashMap { diff --git a/server/src/limited/vec.rs b/server/src/limited/vec.rs index da6723ff0..7b1c9801f 100644 --- a/server/src/limited/vec.rs +++ b/server/src/limited/vec.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use crate::limited::OrMore; -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, utoipa::ToSchema)] pub struct LimitedVec(pub Vec); impl AsRef<[T]> for LimitedVec { diff --git a/server/src/locations/details.rs b/server/src/locations/details.rs index 658a63a86..81b37f5ef 100644 --- a/server/src/locations/details.rs +++ b/server/src/locations/details.rs @@ -8,7 +8,12 @@ use tracing::error; use crate::localisation; use crate::models::LocationKeyAlias; -#[get("/{id}")] +#[utoipa::path( + responses( + (status = 200, description = "Pet found from database") + ) +)] +#[get("/api/locations/{id}")] pub async fn get_handler( params: web::Path, web::Query(args): web::Query, diff --git a/server/src/locations/mod.rs b/server/src/locations/mod.rs index bf9be4ed9..c604ff5c0 100644 --- a/server/src/locations/mod.rs +++ b/server/src/locations/mod.rs @@ -1,15 +1,3 @@ -use actix_web::web; - -mod details; -mod nearby; -mod preview; - -pub fn configure(cfg: &mut web::ServiceConfig) { - cfg.service(details::get_handler) - .service(nearby::nearby_handler) - .service(preview::maps_handler); - let tile_cache = std::env::temp_dir().join("tiles"); - if !tile_cache.exists() { - std::fs::create_dir(tile_cache).unwrap(); - } -} +pub mod details; +pub mod nearby; +pub mod preview; diff --git a/server/src/locations/nearby.rs b/server/src/locations/nearby.rs index 157b9deb4..35549a140 100644 --- a/server/src/locations/nearby.rs +++ b/server/src/locations/nearby.rs @@ -21,7 +21,12 @@ struct NearbyResponse { public_transport: Vec, } -#[get("/{id}/nearby")] +#[utoipa::path( + responses( + (status = 200, description = "Pet found from database") + ) +)] +#[get("/api/locations/{id}/nearby")] pub async fn nearby_handler( params: web::Path, data: web::Data, diff --git a/server/src/locations/preview.rs b/server/src/locations/preview.rs index d07911aec..6f510993b 100644 --- a/server/src/locations/preview.rs +++ b/server/src/locations/preview.rs @@ -197,7 +197,12 @@ struct QueryArgs { format: PreviewFormat, } -#[get("/{id}/preview")] +#[utoipa::path( + responses( + (status = 200, description = "Pet found from database") + ) +)] +#[get("/api/locations/{id}/preview")] pub async fn maps_handler( params: web::Path, web::Query(args): web::Query, diff --git a/server/src/main.rs b/server/src/main.rs index a28ab5c7a..1b1bd4488 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use std::sync::Arc; use actix_cors::Cors; +use actix_governor::{GlobalKeyExtractor, GovernorConfigBuilder}; use actix_middleware_etag::Etag; use actix_web::web::Redirect; use actix_web::{get, middleware, web, App, HttpResponse, HttpServer, Responder}; @@ -25,9 +26,12 @@ mod maps; mod models; mod search; mod setup; +use utoipa_actix_web::{scope, AppExt}; const MAX_JSON_PAYLOAD: usize = 1024 * 1024; // 1 MB +const SECONDS_PER_DAY: u64 = 60 * 60 * 24; + #[derive(Clone, Debug)] pub struct AppData { /// shared [sqlx::PgPool] to connect to postgis @@ -50,6 +54,13 @@ impl AppData { } } + +/// Get Pet by id +#[utoipa::path( + responses( + (status = 200, description = "Pet found from database") + ) +)] #[get("/api/status")] async fn health_status_handler(data: web::Data) -> HttpResponse { let github_link = match option_env!("GIT_COMMIT_SHA") { @@ -68,17 +79,40 @@ async fn health_status_handler(data: web::Data) -> HttpResponse { } } } + +#[utoipa::path( + responses( + (status = 200, description = "Pet found from database") + ) +)] #[get("/api/get/{id}")] async fn details_redirect(params: web::Path) -> impl Responder { let id = params.into_inner(); Redirect::to(format!("https://nav.tum.de/locations/{id}")).permanent() } + +#[utoipa::path( + responses( + (status = 200, description = "Pet found from database") + ) +)] #[get("/api/preview/{id}")] async fn preview_redirect(params: web::Path) -> impl Responder { let id = params.into_inner(); Redirect::to(format!("https://nav.tum.de/locations/{id}/preview")).permanent() } + +#[utoipa::path( + responses( + (status = 200, description = "Pet found from database") + ) +)] +#[get("/api/openapi.json")] +async fn openapi_doc(openapi: web::Data)->impl Responder { + HttpResponse::Ok().content_type("application/json").json(openapi) +} + fn connection_string() -> String { let username = std::env::var("POSTGRES_USER").unwrap_or_else(|_| "postgres".to_string()); let password = std::env::var("POSTGRES_PASSWORD").unwrap_or_else(|_| "CHANGE_ME".to_string()); @@ -188,6 +222,15 @@ async fn run() -> anyhow::Result<()> { let prometheus = build_metrics(); let shutdown_pool_clone = data.pool.clone(); initialisation_started.wait().await; + // feedback specific initialisation + let feedback_ratelimit = GovernorConfigBuilder::default() + .key_extractor(GlobalKeyExtractor) + .seconds_per_request(SECONDS_PER_DAY / 300) // replenish new token every .. seconds + .burst_size(50) + .finish() + .expect("Invalid configuration of the governor"); + let recorded_tokens = web::Data::new(crate::feedback::tokens::RecordedTokens::default()); + info!("running the server"); HttpServer::new(move || { let cors = Cors::default() @@ -197,13 +240,14 @@ async fn run() -> anyhow::Result<()> { .max_age(3600) .send_wildcard(); - App::new() + let (app,api)=App::new() .wrap(Etag) .wrap(prometheus.clone()) .wrap(cors) .wrap(TracingLogger::default()) .wrap(middleware::Compress::default()) .wrap(sentry_actix::Sentry::new()) + .into_utoipa_app() .app_data(web::JsonConfig::default().limit(MAX_JSON_PAYLOAD)) .app_data(web::Data::new(data.clone())) .service(health_status_handler) @@ -211,10 +255,23 @@ async fn run() -> anyhow::Result<()> { .service(maps::indoor::list_indoor_maps) .service(maps::indoor::get_indoor_map) .service(search::search_handler) - .service(web::scope("/api/feedback").configure(feedback::configure)) - .service(web::scope("/api/locations").configure(locations::configure)) + .service(locations::details::get_handler) + .service(locations::nearby::nearby_handler) + .service(locations::preview::maps_handler) + .app_data(recorded_tokens.clone()) + .service(feedback::post_feedback::send_feedback) + .service(feedback::proposed_edits::propose_edits) + .service( + scope("/api/feedback/get_token") + .wrap(actix_governor::Governor::new(&feedback_ratelimit)) + .service(feedback::tokens::get_token), + ) .service(details_redirect) .service(preview_redirect) + .service(openapi_doc) + .split_for_parts(); + app + .app_data(web::Data::new(api.clone())) }) .bind(std::env::var("BIND_ADDRESS").unwrap_or_else(|_| "0.0.0.0:3003".to_string()))? .run() diff --git a/server/src/maps/indoor.rs b/server/src/maps/indoor.rs index c98c88c92..b40c9d64f 100644 --- a/server/src/maps/indoor.rs +++ b/server/src/maps/indoor.rs @@ -48,6 +48,13 @@ pub async fn fetch_indoor_map(pool: &PgPool, id: i64) -> anyhow::Result, @@ -97,6 +104,12 @@ impl Arguments { } } +/// Get Pet by id +#[utoipa::path( + responses( + (status = 200, description = "Pet found from database") + ) +)] #[get("/api/maps/indoor")] pub async fn list_indoor_maps( web::Query(args): web::Query, diff --git a/server/src/search/mod.rs b/server/src/search/mod.rs index 19ddb9de3..df8e7b052 100644 --- a/server/src/search/mod.rs +++ b/server/src/search/mod.rs @@ -12,7 +12,7 @@ use unicode_truncate::UnicodeTruncateStr; mod search_executor; -#[derive(Deserialize, Debug, Default)] +#[derive(Deserialize, Debug, Default, utoipa::ToSchema)] pub struct SearchQueryArgs { q: String, limit_buildings: Option, @@ -126,6 +126,13 @@ impl From<&SearchQueryArgs> for Highlighting { Self { pre, post } } } + +/// Get Pet by id +#[utoipa::path( + responses( + (status = 200, description = "Pet found from database") + ) +)] #[get("/api/search")] pub async fn search_handler( data: web::Data,