1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#![cfg(feature = "axum")]

use std::sync::Arc;

use axum::{
    extract::Path, http::StatusCode, response::IntoResponse, routing, Extension, Json, Router,
};

use crate::{ApiDoc, Config, SwaggerUi, Url};

impl<S> From<SwaggerUi> for Router<S>
where
    S: Clone + Send + Sync + 'static,
{
    fn from(swagger_ui: SwaggerUi) -> Self {
        let urls_capacity = swagger_ui.urls.len();
        let external_urls_capacity = swagger_ui.external_urls.len();

        let (router, urls) = swagger_ui.urls.into_iter().fold(
            (
                Router::<S>::new(),
                Vec::<Url>::with_capacity(urls_capacity + external_urls_capacity),
            ),
            |router_and_urls, (url, openapi)| {
                add_api_doc_to_urls(router_and_urls, (url, ApiDoc::Utoipa(openapi)))
            },
        );
        let (router, urls) = swagger_ui.external_urls.into_iter().fold(
            (router, urls),
            |router_and_urls, (url, openapi)| {
                add_api_doc_to_urls(router_and_urls, (url, ApiDoc::Value(openapi)))
            },
        );

        let config = if let Some(config) = swagger_ui.config {
            if config.url.is_some() || !config.urls.is_empty() {
                config
            } else {
                config.configure_defaults(urls)
            }
        } else {
            Config::new(urls)
        };

        let handler = routing::get(serve_swagger_ui).layer(Extension(Arc::new(config)));
        let path: &str = swagger_ui.path.as_ref();
        let slash_path = format!("{}/", path);

        router
            .route(
                path,
                routing::get(|| async move { axum::response::Redirect::to(&slash_path) }),
            )
            .route(&format!("{}/", path), handler.clone())
            .route(&format!("{}/*rest", path), handler)
    }
}

fn add_api_doc_to_urls<S>(
    router_and_urls: (Router<S>, Vec<Url<'static>>),
    url: (Url<'static>, ApiDoc),
) -> (Router<S>, Vec<Url<'static>>)
where
    S: Clone + Send + Sync + 'static,
{
    let (router, mut urls) = router_and_urls;
    let (url, openapi) = url;
    (
        router.route(
            url.url.as_ref(),
            routing::get(move || async { Json(openapi) }),
        ),
        {
            urls.push(url);
            urls
        },
    )
}

async fn serve_swagger_ui(
    path: Option<Path<String>>,
    Extension(state): Extension<Arc<Config<'static>>>,
) -> impl IntoResponse {
    let tail = match path.as_ref() {
        Some(tail) => tail,
        None => "",
    };

    match super::serve(tail, state) {
        Ok(file) => file
            .map(|file| {
                (
                    StatusCode::OK,
                    [("Content-Type", file.content_type)],
                    file.bytes,
                )
                    .into_response()
            })
            .unwrap_or_else(|| StatusCode::NOT_FOUND.into_response()),
        Err(error) => (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response(),
    }
}