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