diff --git a/utoipa-axum/Cargo.toml b/utoipa-axum/Cargo.toml index 49332db9..41b2cd23 100644 --- a/utoipa-axum/Cargo.toml +++ b/utoipa-axum/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "utoipa-axum" description = "Utoipa's axum bindings for seamless integration for the two" -version = "0.1.0-beta.0" +version = "0.1.0-beta.1" edition = "2021" license = "MIT OR Apache-2.0" readme = "README.md" @@ -16,7 +16,7 @@ debug = [] [dependencies] axum = { version = "0.7", default-features = false } -utoipa = { version = "5.0.0-alpha", path = "../utoipa", default-features = false } +utoipa = { version = "5.0.0-beta", path = "../utoipa", default-features = false } tower-service = "0.3" tower-layer = "0.3.2" paste = "1.0" diff --git a/utoipa-axum/src/router.rs b/utoipa-axum/src/router.rs index e0ee1adb..d8d36c7c 100644 --- a/utoipa-axum/src/router.rs +++ b/utoipa-axum/src/router.rs @@ -1,4 +1,5 @@ //! Implements Router for composing handlers and collecting OpenAPI information. +use std::borrow::Cow; use std::convert::Infallible; use axum::extract::Request; @@ -17,6 +18,21 @@ where String::from(path).replace('}', "").replace('{', ":") } +#[inline] +fn path_template>(path: S) -> String { + path.as_ref() + .split('/') + .map(|segment| { + if !segment.is_empty() && segment[0..1] == *":" { + Cow::Owned(format!("{{{}}}", &segment[1..])) + } else { + Cow::Borrowed(segment) + } + }) + .collect::>() + .join("/") +} + /// Wrapper type for [`utoipa::openapi::path::Paths`] and [`axum::routing::MethodRouter`]. /// /// This is used with [`OpenApiRouter::routes`] method to register current _`paths`_ to the @@ -25,8 +41,87 @@ where /// See [`routes`][routes] for usage. /// /// [routes]: ../macro.routes.html -pub type UtoipaMethodRouter = - (utoipa::openapi::path::Paths, axum::routing::MethodRouter); +pub type UtoipaMethodRouter = ( + utoipa::openapi::path::Paths, + axum::routing::MethodRouter, +); + +/// Extension trait for [`UtoipaMethodRouter`] to expose typically used methods of +/// [`axum::routing::MethodRouter`] and to extend [`UtoipaMethodRouter`] with useful convenience +/// methods. +pub trait UtoipaMethodRouterExt +where + S: Send + Sync + Clone + 'static, +{ + /// Pass through method for [`axum::routing::MethodRouter::layer`]. + /// + /// This method is provided as convenience for defining layers to [`axum::routing::MethodRouter`] + /// routes. + fn layer(self, layer: L) -> UtoipaMethodRouter + where + L: Layer> + Clone + Send + 'static, + L::Service: Service + Clone + Send + 'static, + >::Response: IntoResponse + 'static, + >::Error: Into + 'static, + >::Future: Send + 'static, + E: 'static, + S: 'static, + NewError: 'static; + + /// Pass through method for [`axum::routing::MethodRouter::with_state`]. + /// + /// Allows quick state definition for underlying [`axum::routing::MethodRouter`]. + fn with_state(self, state: S) -> UtoipaMethodRouter; + + /// Convenience method that allows custom mapping for [`axum::routing::MethodRouter`] via + /// methods that not exposed directly through [`UtoipaMethodRouterExt`]. + /// + /// This method could be used to add layers, route layers or fallback handlers for the method + /// router. + /// ```rust + /// # use utoipa_axum::{routes, router::{UtoipaMethodRouter, UtoipaMethodRouterExt}}; + /// # #[utoipa::path(get, path = "")] + /// # async fn search_user() {} + /// let _: UtoipaMethodRouter = routes!(search_user).map(|method_router| { + /// // .. implementation here + /// method_router + /// }); + /// ``` + fn map( + self, + op: impl FnOnce(MethodRouter) -> MethodRouter, + ) -> UtoipaMethodRouter; +} + +impl UtoipaMethodRouterExt for UtoipaMethodRouter +where + S: Send + Sync + Clone + 'static, +{ + fn layer(self, layer: L) -> UtoipaMethodRouter + where + L: Layer> + Clone + Send + 'static, + L::Service: Service + Clone + Send + 'static, + >::Response: IntoResponse + 'static, + >::Error: Into + 'static, + >::Future: Send + 'static, + E: 'static, + S: 'static, + NewError: 'static, + { + (self.0, self.1.layer(layer)) + } + + fn with_state(self, state: S) -> UtoipaMethodRouter { + (self.0, self.1.with_state(state)) + } + + fn map( + self, + op: impl FnOnce(MethodRouter) -> MethodRouter, + ) -> UtoipaMethodRouter { + (self.0, op(self.1)) + } +} /// A wrapper struct for [`axum::Router`] and [`utoipa::openapi::OpenApi`] for composing handlers /// and services with collecting OpenAPI information from the handlers. @@ -189,7 +284,7 @@ where /// .nest("/api", search_router); /// ``` pub fn nest(self, path: &str, router: OpenApiRouter) -> Self { - let api = self.1.nest(path, router.1); + let api = self.1.nest(path_template(path), router.1); let path = if path.is_empty() { "/" } else { path }; let router = self.0.nest(&colonized_params(path), router.0); diff --git a/utoipa/src/lib.rs b/utoipa/src/lib.rs index e96d87f9..7073a169 100644 --- a/utoipa/src/lib.rs +++ b/utoipa/src/lib.rs @@ -42,7 +42,7 @@ //! //! # Crate Features //! -//! - **`macros`** Enable `utoipa-gen` macros. **This is enabled by default.** +//! * **`macros`** Enable `utoipa-gen` macros. **This is enabled by default.** //! * **`yaml`** Enables **serde_yaml** serialization of OpenAPI objects. //! * **`actix_extras`** Enhances [actix-web](https://github.com/actix/actix-web/) integration with being able to //! parse `path`, `path` and `query` parameters from actix web path attribute macros. See [actix extras support][actix_path] or