utoipa_swagger_ui/
actix.rs1#![cfg(feature = "actix-web")]
2
3use std::future;
4
5use actix_web::{
6 dev::{HttpServiceFactory, Service, ServiceResponse},
7 guard::Get,
8 web,
9 web::Data,
10 HttpResponse, Resource, Responder as ActixResponder,
11};
12use base64::Engine;
13
14use crate::{ApiDoc, BasicAuth, Config, SwaggerUi};
15
16impl HttpServiceFactory for SwaggerUi {
17 fn register(self, config: &mut actix_web::dev::AppService) {
18 let mut urls = self
19 .urls
20 .into_iter()
21 .map(|(url, openapi)| {
22 register_api_doc_url_resource(url.url.as_ref(), ApiDoc::Utoipa(openapi), config);
23 url
24 })
25 .collect::<Vec<_>>();
26 let external_api_docs = self.external_urls.into_iter().map(|(url, api_doc)| {
27 register_api_doc_url_resource(url.url.as_ref(), ApiDoc::Value(api_doc), config);
28 url
29 });
30 urls.extend(external_api_docs);
31
32 let swagger_resource = Resource::new(self.path.as_ref())
33 .guard(Get())
34 .app_data(Data::new(if let Some(config) = self.config.clone() {
35 if config.url.is_some() || !config.urls.is_empty() {
36 config
37 } else {
38 config.configure_defaults(urls)
39 }
40 } else {
41 Config::new(urls)
42 }))
43 .wrap_fn(move |req, srv| {
44 if let Some(BasicAuth { username, password }) = self
45 .config
46 .as_ref()
47 .and_then(|config| config.basic_auth.clone())
48 {
49 let encoded_credentials = format!(
50 "Basic {}",
51 base64::prelude::BASE64_STANDARD.encode(format!("{username}:{password}"))
52 );
53 if let Some(auth_header) = req.headers().get("Authorization") {
54 if auth_header.to_str().unwrap() == encoded_credentials {
55 return srv.call(req);
56 }
57 }
58 return Box::pin(future::ready(Ok(ServiceResponse::new(
59 req.request().clone(),
60 HttpResponse::Unauthorized()
61 .insert_header(("WWW-Authenticate", "Basic realm=\":\""))
62 .finish(),
63 ))));
64 }
65 srv.call(req)
66 })
67 .to(serve_swagger_ui);
68
69 HttpServiceFactory::register(swagger_resource, config);
70 }
71}
72
73fn register_api_doc_url_resource(url: &str, api: ApiDoc, config: &mut actix_web::dev::AppService) {
74 async fn get_api_doc(api_doc: web::Data<ApiDoc>) -> impl ActixResponder {
75 HttpResponse::Ok().json(api_doc.as_ref())
76 }
77
78 let url_resource = Resource::new(url)
79 .guard(Get())
80 .app_data(Data::new(api))
81 .to(get_api_doc);
82 HttpServiceFactory::register(url_resource, config);
83}
84
85async fn serve_swagger_ui(path: web::Path<String>, data: web::Data<Config<'_>>) -> HttpResponse {
86 match super::serve(&path.into_inner(), data.into_inner()) {
87 Ok(swagger_file) => swagger_file
88 .map(|file| {
89 HttpResponse::Ok()
90 .content_type(file.content_type)
91 .body(file.bytes.to_vec())
92 })
93 .unwrap_or_else(|| HttpResponse::NotFound().finish()),
94 Err(error) => HttpResponse::InternalServerError().body(error.to_string()),
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use actix_web::{http::StatusCode, test, App};
101 use base64::prelude::BASE64_STANDARD;
102
103 use super::*;
104 #[actix_web::test]
105 async fn mount_onto_path_with_slash() {
106 let swagger_ui = SwaggerUi::new("/swagger-ui/{_:.*}");
107
108 let app = test::init_service(App::new().service(swagger_ui)).await;
109 let req = test::TestRequest::get().uri("/swagger-ui/").to_request();
110 let resp = test::call_service(&app, req).await;
111
112 assert!(resp.status().is_success());
113 }
114
115 #[actix_web::test]
116 async fn basic_auth() {
117 let swagger_ui =
118 SwaggerUi::new("/swagger-ui/{_:.*}").config(Config::default().basic_auth(BasicAuth {
119 username: "admin".to_string(),
120 password: "password".to_string(),
121 }));
122
123 let app = test::init_service(App::new().service(swagger_ui)).await;
124 let req = test::TestRequest::get().uri("/swagger-ui/").to_request();
125 let resp = test::call_service(&app, req).await;
126 assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
127 let encoded_credentials = BASE64_STANDARD.encode("admin:password");
128 let req = test::TestRequest::get()
129 .uri("/swagger-ui/")
130 .insert_header(("Authorization", format!("Basic {}", encoded_credentials)))
131 .to_request();
132 let resp = test::call_service(&app, req).await;
133 assert!(resp.status().is_success());
134 }
135}