axum_openapi3/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use std::{
4    ops::Deref,
5    sync::{Mutex, MutexGuard},
6};
7
8use axum::Router;
9use once_cell::sync::Lazy;
10use serde::Serialize;
11use utoipa::openapi::Paths;
12
13#[cfg(feature = "derive")]
14extern crate axum_openapi3_derive;
15/// Derive macro available if axum-openapi3 is built with `features = ["derive"]`.
16#[cfg(feature = "derive")]
17pub use axum_openapi3_derive::endpoint;
18
19/// Re-export utoipa. Used internally to generate the openapi spec from rust structs.
20pub use utoipa;
21
22/// Mutex to store the endpoints.
23/// Don't use directly, use the `endpoint` macro instead.
24pub static ENDPOINTS: std::sync::Mutex<Vec<utoipa::openapi::Paths>> = std::sync::Mutex::new(vec![]);
25
26/// Add `add` method to `Router` to add routes also to the openapi spec.
27pub trait AddRoute<S> {
28    fn add(
29        self,
30        r: (
31            &str,
32            axum::routing::MethodRouter<S, std::convert::Infallible>,
33        ),
34    ) -> Self;
35}
36
37impl<S: std::clone::Clone + std::marker::Send + std::marker::Sync + 'static> AddRoute<S>
38    for Router<S>
39{
40    fn add(
41        self,
42        r: (
43            &str,
44            axum::routing::MethodRouter<S, std::convert::Infallible>,
45        ),
46    ) -> Self {
47        self.route(r.0, r.1)
48    }
49}
50
51static OPENAPI_BUILT: Lazy<Mutex<Option<utoipa::openapi::OpenApi>>> =
52    Lazy::new(|| Mutex::new(None));
53
54/// Reset the openapi spec. Mostly used for testing.
55pub fn reset_openapi() {
56    let mut endpoints = ENDPOINTS.lock().unwrap();
57    *endpoints = vec![];
58    *OPENAPI_BUILT.lock().unwrap() = None;
59}
60
61/// Wrapper around the openapi spec.
62/// This wrapper is used to serialize the openapi spec.
63pub struct OpenApiWrapper<'a> {
64    guard: MutexGuard<'a, Option<utoipa::openapi::OpenApi>>,
65}
66impl Deref for OpenApiWrapper<'_> {
67    type Target = utoipa::openapi::OpenApi;
68
69    fn deref(&self) -> &Self::Target {
70        self.guard.as_ref().unwrap()
71    }
72}
73
74impl Serialize for OpenApiWrapper<'_> {
75    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
76    where
77        S: serde::Serializer,
78    {
79        self.guard.as_ref().unwrap().serialize(serializer)
80    }
81}
82
83/// Build the openapi spec.
84/// This function should be called after all the endpoints are defined.
85/// Because the openapi spec is cached, it's cheap to call this function multiple times.
86/// The `f` function is called only when the openapi spec is not built yet.
87pub fn build_openapi<'openapi, F>(f: F) -> OpenApiWrapper<'openapi>
88where
89    F: Fn() -> utoipa::openapi::OpenApiBuilder,
90{
91    let mut openapi = OPENAPI_BUILT.lock().unwrap();
92    if openapi.is_none() {
93        let mut endpoints = ENDPOINTS.lock().unwrap();
94
95        let paths = endpoints.drain(..).fold(Paths::default(), |mut acc, x| {
96            acc.merge(x);
97            acc
98        });
99        let openapi_builder = f().paths(paths);
100
101        *openapi = Some(openapi_builder.build());
102    }
103
104    OpenApiWrapper { guard: openapi }
105}