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#[cfg(any(feature = "axum-07", feature = "axum-08"))]
154async fn serve_oauth2_redirect_html_axum() -> Response {
155 let js: &str = include_str!("../assets/oauth2-redirect.html");
156 Response::builder()
157 .status(200)
158 .header("Content-Type", "text/html")
159 .body(Body::from(js))
160 .unwrap()
161}
162
163#[cfg(feature = "actix-web")]
164async fn serve_oauth2_redirect_html_actix() -> impl Responder {
165 let js: &str = include_str!("../assets/oauth2-redirect.html");
166 HttpResponse::Ok().content_type("text/html").body(js)
167}
168
169#[cfg(any(feature = "axum-07", feature = "axum-08"))]
170async fn serve_oauth2_redirect_js_axum() -> Response {
171 let js: &str = include_str!("../assets/oauth2-redirect.js");
172 Response::builder()
173 .status(200)
174 .header("Content-Type", "text/javascript")
175 .body(Body::from(js))
176 .unwrap()
177}
178
179#[cfg(feature = "actix-web")]
180async fn serve_oauth2_redirect_js_actix() -> impl Responder {
181 let js: &str = include_str!("../assets/oauth2-redirect.js");
182 HttpResponse::Ok().content_type("text/javascript").body(js)
183}
184
185#[derive(Debug, Clone)]
187pub enum OpenApiSource<S: Into<String>> {
188 Inline(S),
190 InlineWithName {
192 definition: S,
194 uri: S,
196 },
197 Uri(S),
199}
200
201#[derive(Debug, Clone)]
203pub struct ApiDefinition<S: Into<String> + Clone> {
204 pub uri_prefix: S,
206 pub api_definition: OpenApiSource<S>,
208 pub title: Option<S>,
210}
211
212#[cfg(any(feature = "axum-07", feature = "axum-08"))]
214pub fn generate_routes<S: Into<String> + Clone>(def: ApiDefinition<S>) -> Router {
215 let prefix = def.uri_prefix.into();
216 let prefix2 = format!("{prefix}/");
217 let def2 = def.api_definition.clone();
218 let api_def_uri = match def.api_definition {
219 OpenApiSource::Uri(val) => val.into(),
220 OpenApiSource::Inline(_val) => format!("{prefix}/openapi.yaml"),
221 OpenApiSource::InlineWithName { definition: _, uri } => uri.into(),
222 };
223 let api_def2 = api_def_uri.clone();
224 let api_def3 = api_def_uri.clone();
225 let title = match def.title {
226 Some(val) => val.into(),
227 None => "SwaggerUI".to_string(),
228 };
229 let title2 = title.clone();
230 let mut router = Router::new()
231 .route(
232 &prefix,
233 get(|req: Request| async move { serve_index_axum(api_def_uri, title, req).await }),
234 )
235 .route(
236 &prefix2,
237 get(|req: Request| async move { serve_index_axum(api_def2, title2, req).await }),
238 )
239 .route(&format!("{prefix}/swagger-ui.css"), get(serve_css_axum))
240 .route(
241 &format!("{prefix}/swagger-ui-bundle.js"),
242 get(serve_js_axum),
243 )
244 .route(
245 &format!("{prefix}/swagger-ui.css.map"),
246 get(serve_css_map_axum),
247 )
248 .route(
249 &format!("{prefix}/swagger-ui-bundle.js.map"),
250 get(serve_js_map_axum),
251 )
252 .route(
253 &format!("{prefix}/oauth2-redirect.html"),
254 get(serve_oauth2_redirect_html_axum),
255 )
256 .route(
257 &format!("{prefix}/oauth2-redirect.js"),
258 get(serve_oauth2_redirect_js_axum),
259 );
260 if let OpenApiSource::Inline(source) = def2 {
261 let yaml = source.into();
262 router = router.route(&api_def3, get(|| async { yaml }));
263 } else if let OpenApiSource::InlineWithName { definition, uri: _ } = def2 {
264 let yaml = definition.into();
265 router = router.route(&api_def3, get(|| async { yaml }));
266 }
267 router
268}
269
270#[cfg(feature = "actix-web")]
272pub fn generate_scope<S: Into<String> + Clone>(def: ApiDefinition<S>) -> impl HttpServiceFactory {
273 let prefix = def.uri_prefix.into();
274 let (uri, yaml) = match def.api_definition {
275 OpenApiSource::Uri(val) => (val.into(), "".to_string()),
276 OpenApiSource::Inline(val) => (format!("{prefix}/openapi.yaml"), val.into()),
277 OpenApiSource::InlineWithName { definition, uri } => (uri.into(), definition.into()),
278 };
279 let title = match def.title {
280 Some(val) => val.into(),
281 None => "SwaggerUI".to_string(),
282 };
283 let source = ApiDefinition::<String> {
284 uri_prefix: prefix.clone(),
285 api_definition: OpenApiSource::InlineWithName {
286 definition: yaml,
287 uri: uri.clone(),
288 },
289 title: Some(title),
290 };
291 web::scope(&prefix)
292 .app_data(web::Data::new(source))
293 .route(
294 "/",
295 web::get().to(
296 |req: HttpRequest, data: web::Data<ApiDefinition<String>>| async move {
297 let uri = match data.api_definition.clone() {
298 OpenApiSource::InlineWithName { definition: _, uri } => uri,
299 _ => "".to_string(),
300 };
301 serve_index_actix(uri, data.title.clone().unwrap(), req).await
302 },
303 ),
304 )
305 .route("/swagger-ui.css", web::get().to(serve_css_actix))
306 .route("/swagger-ui-bundle.js", web::get().to(serve_js_actix))
307 .route("/swagger-ui.css.map", web::get().to(serve_css_map_actix))
308 .route(
309 "/swagger-ui-bundle.js.map",
310 web::get().to(serve_js_map_actix),
311 )
312 .route(
313 "/oauth2-redirect.html",
314 web::get().to(serve_oauth2_redirect_html_actix),
315 )
316 .route(
317 "/oauth2-redirect.js",
318 web::get().to(serve_oauth2_redirect_js_actix),
319 )
320 .route(
321 uri.trim_start_matches(prefix.as_str()),
322 web::get().to(|data: web::Data<ApiDefinition<String>>| async move {
323 let yaml = match data.api_definition.clone() {
324 OpenApiSource::InlineWithName { definition, uri: _ } => definition,
325 _ => "".to_string(),
326 };
327 HttpResponse::Ok().body(yaml)
328 }),
329 )
330}