1#![deny(missing_docs)]
2#[cfg(feature = "actix-web")]
20use actix_web::{dev::HttpServiceFactory, web, HttpRequest, HttpResponse, Responder};
21#[cfg(any(feature = "axum-07", feature = "axum-08"))]
22use axum::{http::header, routing::get, Router};
23#[cfg(feature = "axum-07")]
24use axum_07 as axum;
25#[cfg(feature = "axum-08")]
26use axum_08 as axum;
27#[cfg(any(feature = "axum-07", feature = "axum-08"))]
28use axum_core::{body::Body, extract::Request, response::Response};
29#[cfg(feature = "axum-07")]
30use axum_core_04 as axum_core;
31#[cfg(feature = "axum-08")]
32use axum_core_05 as axum_core;
33
34#[cfg(any(feature = "axum-07", feature = "axum-08"))]
35async fn serve_index_axum(api_def: String, title: String, req: Request) -> Response {
36 let uri = req.uri().to_string();
37
38 let response_str = serve_index(api_def, title, uri);
39
40 Response::builder()
41 .status(200)
42 .header(header::CONTENT_TYPE, "text/html")
43 .body(Body::from(response_str))
44 .unwrap()
45}
46
47#[cfg(feature = "actix-web")]
48async fn serve_index_actix(api_def: String, title: String, req: HttpRequest) -> impl Responder {
49 let uri = req.uri().to_string();
50 let uri = if uri.ends_with("/") {
51 uri.trim_end_matches("/").to_string()
52 } else {
53 uri
54 };
55
56 let response_str = serve_index(api_def, title, uri);
57
58 HttpResponse::Ok()
59 .content_type("text/html")
60 .body(response_str)
61}
62
63fn serve_index(api_def: String, title: String, uri: String) -> String {
64 format!(
65 r#"<!DOCTYPE html>
66<html lang="en">
67<head>
68 <meta charset="utf-8" />
69 <meta name="viewport" content="width=device-width, initial-scale=1" />
70 <title>{title}</title>
71 <link rel="stylesheet" href="{uri}/swagger-ui.css" />
72</head>
73<body>
74<div id="swagger-ui"></div>
75<script src="{uri}/swagger-ui-bundle.js" crossorigin></script>
76<script>
77 window.onload = () => {{
78 window.ui = SwaggerUIBundle({{
79 url: '{api_def}',
80 dom_id: '#swagger-ui',
81 }});
82 }};
83</script>
84</body>
85</html>"#
86 )
87}
88
89#[cfg(any(feature = "axum-07", feature = "axum-08"))]
90async fn serve_js_axum() -> Response {
91 let js: &str = include_str!("../assets/swagger-ui-bundle.js");
92 Response::builder()
93 .status(200)
94 .header("Content-Type", "text/javascript")
95 .body(Body::from(js))
96 .unwrap()
97}
98
99#[cfg(feature = "actix-web")]
100async fn serve_js_actix() -> impl Responder {
101 let js: &str = include_str!("../assets/swagger-ui-bundle.js");
102 HttpResponse::Ok().content_type("text/javascript").body(js)
103}
104
105#[cfg(any(feature = "axum-07", feature = "axum-08"))]
106async fn serve_js_map_axum() -> Response {
107 let js: &str = include_str!("../assets/swagger-ui-bundle.js.map");
108 Response::builder()
109 .status(200)
110 .header("Content-Type", "application/json")
111 .body(Body::from(js))
112 .unwrap()
113}
114
115#[cfg(feature = "actix-web")]
116async fn serve_js_map_actix() -> impl Responder {
117 let js: &str = include_str!("../assets/swagger-ui-bundle.js.map");
118 HttpResponse::Ok().content_type("application/json").body(js)
119}
120
121#[cfg(any(feature = "axum-07", feature = "axum-08"))]
122async fn serve_css_axum() -> Response {
123 let css: &str = include_str!("../assets/swagger-ui.css");
124 Response::builder()
125 .status(200)
126 .header("Content-Type", "text/css")
127 .body(Body::from(css))
128 .unwrap()
129}
130
131#[cfg(feature = "actix-web")]
132async fn serve_css_actix() -> impl Responder {
133 let css: &str = include_str!("../assets/swagger-ui.css");
134 HttpResponse::Ok().content_type("text/css").body(css)
135}
136
137#[cfg(any(feature = "axum-07", feature = "axum-08"))]
138async fn serve_css_map_axum() -> Response {
139 let js: &str = include_str!("../assets/swagger-ui.css.map");
140 Response::builder()
141 .status(200)
142 .header("Content-Type", "application/json")
143 .body(Body::from(js))
144 .unwrap()
145}
146
147#[cfg(feature = "actix-web")]
148async fn serve_css_map_actix() -> impl Responder {
149 let js: &str = include_str!("../assets/swagger-ui.css.map");
150 HttpResponse::Ok().content_type("application/json").body(js)
151}
152
153#[derive(Debug, Clone)]
155pub enum OpenApiSource<S: Into<String>> {
156 Inline(S),
158 InlineWithName {
160 definition: S,
162 uri: S,
164 },
165 Uri(S),
167}
168
169#[derive(Debug, Clone)]
171pub struct ApiDefinition<S: Into<String> + Clone> {
172 pub uri_prefix: S,
174 pub api_definition: OpenApiSource<S>,
176 pub title: Option<S>,
178}
179
180#[cfg(any(feature = "axum-07", feature = "axum-08"))]
182pub fn generate_routes<S: Into<String> + Clone>(def: ApiDefinition<S>) -> Router {
183 let prefix = def.uri_prefix.into();
184 let prefix2 = format!("{prefix}/");
185 let def2 = def.api_definition.clone();
186 let api_def_uri = match def.api_definition {
187 OpenApiSource::Uri(val) => val.into(),
188 OpenApiSource::Inline(_val) => format!("{prefix}/openapi.yaml"),
189 OpenApiSource::InlineWithName { definition: _, uri } => uri.into(),
190 };
191 let api_def2 = api_def_uri.clone();
192 let api_def3 = api_def_uri.clone();
193 let title = match def.title {
194 Some(val) => val.into(),
195 None => "SwaggerUI".to_string(),
196 };
197 let title2 = title.clone();
198 let mut router = Router::new()
199 .route(
200 &prefix,
201 get(|req: Request| async move { serve_index_axum(api_def_uri, title, req).await }),
202 )
203 .route(
204 &prefix2,
205 get(|req: Request| async move { serve_index_axum(api_def2, title2, req).await }),
206 )
207 .route(&format!("{prefix}/swagger-ui.css"), get(serve_css_axum))
208 .route(
209 &format!("{prefix}/swagger-ui-bundle.js"),
210 get(serve_js_axum),
211 )
212 .route(
213 &format!("{prefix}/swagger-ui.css.map"),
214 get(serve_css_map_axum),
215 )
216 .route(
217 &format!("{prefix}/swagger-ui-bundle.js.map"),
218 get(serve_js_map_axum),
219 );
220 if let OpenApiSource::Inline(source) = def2 {
221 let yaml = source.into();
222 router = router.route(&api_def3, get(|| async { yaml }));
223 } else if let OpenApiSource::InlineWithName { definition, uri: _ } = def2 {
224 let yaml = definition.into();
225 router = router.route(&api_def3, get(|| async { yaml }));
226 }
227 router
228}
229
230#[cfg(feature = "actix-web")]
232pub fn generate_scope<S: Into<String> + Clone>(def: ApiDefinition<S>) -> impl HttpServiceFactory {
233 let prefix = def.uri_prefix.into();
234 let (uri, yaml) = match def.api_definition {
235 OpenApiSource::Uri(val) => (val.into(), "".to_string()),
236 OpenApiSource::Inline(val) => (format!("{prefix}/openapi.yaml"), val.into()),
237 OpenApiSource::InlineWithName { definition, uri } => (uri.into(), definition.into()),
238 };
239 let title = match def.title {
240 Some(val) => val.into(),
241 None => "SwaggerUI".to_string(),
242 };
243 let source = ApiDefinition::<String> {
244 uri_prefix: prefix.clone(),
245 api_definition: OpenApiSource::InlineWithName {
246 definition: yaml,
247 uri: uri.clone(),
248 },
249 title: Some(title),
250 };
251 web::scope(&prefix)
252 .app_data(web::Data::new(source))
253 .route(
254 "/",
255 web::get().to(
256 |req: HttpRequest, data: web::Data<ApiDefinition<String>>| async move {
257 let uri = match data.api_definition.clone() {
258 OpenApiSource::InlineWithName { definition: _, uri } => uri,
259 _ => "".to_string(),
260 };
261 serve_index_actix(uri, data.title.clone().unwrap(), req).await
262 },
263 ),
264 )
265 .route("/swagger-ui.css", web::get().to(serve_css_actix))
266 .route("/swagger-ui-bundle.js", web::get().to(serve_js_actix))
267 .route("/swagger-ui.css.map", web::get().to(serve_css_map_actix))
268 .route(
269 "/swagger-ui-bundle.js.map",
270 web::get().to(serve_js_map_actix),
271 )
272 .route(
273 uri.trim_start_matches(prefix.as_str()),
274 web::get().to(|data: web::Data<ApiDefinition<String>>| async move {
275 let yaml = match data.api_definition.clone() {
276 OpenApiSource::InlineWithName { definition, uri: _ } => definition,
277 _ => "".to_string(),
278 };
279 HttpResponse::Ok().body(yaml)
280 }),
281 )
282}