Skip to main content

okapi_operation/axum_integration/
mod.rs

1#![doc = include_str!("../../docs/axum_integration.md")]
2
3#[doc(hidden)]
4pub use paste::paste;
5
6pub use self::{
7    handler_traits::{HandlerExt, HandlerWithOperation, ServiceExt, ServiceWithOperation},
8    method_router::*,
9    router::{DEFAULT_OPENAPI_PATH, Router},
10};
11
12#[cfg(feature = "yaml")]
13mod yaml;
14
15mod handler_traits;
16mod method_router;
17mod operations;
18mod router;
19mod trait_impls;
20
21use axum::{
22    Json,
23    extract::State,
24    response::{IntoResponse, Response},
25};
26use http::{
27    HeaderMap, HeaderValue, StatusCode,
28    header::{self, ACCEPT},
29};
30use okapi::openapi3::OpenApi;
31
32use crate::*;
33
34/// Serves OpenAPI specification, passed as extension.
35#[openapi(
36    summary = "OpenAPI specification",
37    external_docs(url = "https://swagger.io/specification/"),
38    operation_id = "openapi_spec",
39    tags = "openapi",
40    responses(
41        ignore_return_type = true,
42        response(
43            status = "200",
44            description = "",
45            content = "axum::Json<std::collections::HashMap<String, String>>"
46        )
47    ),
48    crate = "crate"
49)]
50pub async fn serve_openapi_spec(spec: State<OpenApi>, headers: HeaderMap) -> Response {
51    let accept_header = headers
52        .get(ACCEPT)
53        .and_then(|h| h.to_str().ok())
54        .map(|h| h.to_ascii_lowercase());
55
56    match accept_header {
57        #[cfg(feature = "yaml")]
58        Some(accept_header) if accept_header.contains("yaml") => yaml::Yaml(spec.0).into_response(),
59        Some(accept_header) if accept_header.contains("json") | accept_header.contains("*/*") => {
60            Json(spec.0).into_response()
61        }
62        Some(_) => {
63            let status = StatusCode::BAD_REQUEST;
64            let headers = [(
65                header::CONTENT_TYPE,
66                HeaderValue::from_static("text/plain; charset=utf-8"),
67            )];
68            let err = if cfg!(feature = "yaml") {
69                "Bad Accept header value, should contain either 'json', 'yaml' or empty"
70            } else {
71                "Bad Accept header value, should contain either 'json' or empty"
72            };
73            (status, headers, err).into_response()
74        }
75        None => {
76            // Defaults to json
77            Json(spec.0).into_response()
78        }
79    }
80}
81
82/// Macro for expanding and binding OpenAPI operation specification
83/// generator to handler or service.
84#[rustfmt::skip]
85#[macro_export]
86macro_rules! openapi_handler {
87    // Entry point
88    ($($va:ident)::+ $(:: <$($gen_param:tt),+>)?) => {
89        $crate::openapi_handler!(@inner $($va)+; ; $($($gen_param)+)?)
90    };
91
92    // Each rule have semicolon-separated "arguments"
93
94    // Split input into path and function name, consuming left path segment
95    // and pushing it to accumulator.
96    //
97    // Arguments:
98    //   - unprocessed input
99    //   - accumulator
100    (@inner $va:ident $($vb:ident)+ ; $(:: $acc:ident)*; $($gen_param:tt)*) => {
101        $crate::openapi_handler!(@inner $($vb)+; $(:: $acc)* :: $va; $($gen_param)*)
102    };
103    (@inner $va:ident ; $(:: $acc:ident)*; $($gen_param:tt)*) => {
104        $crate::openapi_handler!(@final $va; $($acc)::*; $($gen_param)*)
105    };
106    
107    // Generate code
108    //
109    // Arguments:
110    //   - function name
111    //   - path to function
112    (@final $fn_name:ident ; $($prefix_path_part:ident)::* ; $($gen_param:tt)*) => {
113        $crate::axum_integration::paste!{
114            {
115                #[allow(unused_imports)]
116                use $crate::axum_integration::{HandlerExt, ServiceExt};
117
118                $($prefix_path_part ::)* $fn_name :: <$($gen_param),*>
119                    .with_openapi($($prefix_path_part ::)* [<$fn_name __openapi>])
120            }
121        }
122    };
123}
124
125/// Macro for expanding and binding OpenAPI operation specification
126/// generator to handler or service (shortcut to [`openapi_handler`])
127#[rustfmt::skip]
128#[macro_export]
129macro_rules! oh {
130    ($($v:tt)+) => {
131        $crate::openapi_handler!($($v)+)
132    };
133
134}
135
136/// Macro for expanding and binding OpenAPI operation specification
137/// generator to handler or service.
138#[rustfmt::skip]
139#[macro_export]
140#[deprecated = "Use `openapi_handler` instead"]
141macro_rules! openapi_service {
142    ($($t:tt)+) => {
143        {
144            $crate::openapi_handler!($($t)+)
145        }
146    }
147}
148
149// tests in tests/axum_integration.rs because of https://github.com/rust-lang/rust/issues/52234