1#![feature(type_alias_impl_trait)]
2
3pub mod config;
4
5pub use utoipa;
6pub use utoipauto::{utoipa_ignore, utoipauto};
7
8use config::UtoipaConfig;
9use spring::async_trait;
10use spring::config::ConfigRegistry;
11use spring::plugin::{MutableComponentRegistry, Plugin};
12use spring::{app::AppBuilder, plugin::ComponentRegistry};
13use spring_web::{Router, WebConfigurator};
14use utoipa::openapi::OpenApi;
15
16#[cfg(not(any(feature = "rapidoc", feature = "swagger-ui")))]
17use spring_web::axum::{Json, routing};
18#[cfg(feature = "rapidoc")]
19use utoipa_rapidoc::RapiDoc;
20#[cfg(feature = "redoc")]
21use utoipa_redoc::{Redoc, Servable};
22#[cfg(feature = "scalar")]
23use utoipa_scalar::{Scalar, Servable};
24#[cfg(feature = "swagger-ui")]
25use utoipa_swagger_ui::SwaggerUi;
26
27pub trait UtoipaConfigurator {
28 fn with_openapi(&mut self, openapi: OpenApi) -> &mut Self;
29}
30impl UtoipaConfigurator for AppBuilder {
31 fn with_openapi(&mut self, openapi: OpenApi) -> &mut Self {
32 if let Some(_wrapper) = self.get_component_ref::<OpenApi>() {
33 panic!("Error adding OpenApi: OpenApi was already added in application")
34 } else {
35 self.add_component(openapi)
36 }
37 }
38}
39
40pub struct UtoipaPlugin;
41
42#[async_trait]
43impl Plugin for UtoipaPlugin {
44 async fn build(&self, app: &mut AppBuilder) {
45 let config = app
46 .get_config::<UtoipaConfig>()
47 .expect("utoipa plugin config load failed");
48
49 let openapi = app
50 .get_component::<OpenApi>()
51 .expect("Expected openapi to be configured");
52
53 let mut router = Router::new();
54
55 #[cfg(any(
56 all(feature = "rapidoc", feature = "redoc"),
57 all(feature = "rapidoc", feature = "scalar"),
58 all(feature = "rapidoc", feature = "swagger-ui"),
59 all(feature = "redoc", feature = "scalar"),
60 all(feature = "redoc", feature = "swagger-ui"),
61 all(feature = "scalar", feature = "swagger-ui"),
62 ))]
63 panic!("Only one OpenApi visualizer can be used!");
64
65 #[cfg(not(any(feature = "rapidoc", feature = "swagger-ui")))]
66 {
67 let openapi = openapi.clone();
68 router = router.route(&config.path, routing::get(|| async move { Json(openapi) }));
69 }
70
71 #[cfg(any(
72 feature = "rapidoc",
73 feature = "redoc",
74 feature = "scalar",
75 feature = "swagger-ui"
76 ))]
77 assert_ne!(
78 config.path, config.visualizer_path,
79 "openapi.json path shouldn't collide with OpenAPI visualizer path"
80 );
81
82 #[cfg(feature = "rapidoc")]
83 {
84 router = router
85 .merge(RapiDoc::with_openapi(config.path, openapi).path(config.visualizer_path));
86 }
87
88 #[cfg(feature = "redoc")]
89 {
90 router = router.merge(Redoc::with_url(config.visualizer_path, openapi));
91 }
92
93 #[cfg(feature = "scalar")]
94 {
95 router = router.merge(Scalar::with_url(config.visualizer_path, openapi));
96 }
97
98 #[cfg(feature = "swagger-ui")]
99 {
100 assert_ne!(
101 config.visualizer_path, "",
102 "Swagger-UI shouldn't be mounted on root!"
103 );
104 assert_ne!(
105 config.visualizer_path, "/",
106 "Swagger-UI shouldn't be mounted on root!"
107 );
108 router = router.merge(SwaggerUi::new(config.visualizer_path).url(config.path, openapi));
109 }
110
111 app.add_router(router);
112 }
113}