rupring/swagger/
context.rs1use std::sync::{Arc, RwLock};
2
3use crate::IModule;
4use crate::{self as rupring};
5
6use super::{
7 json::{SwaggerPath, SwaggerSchema},
8 SwaggerTags,
9};
10use super::{
11 SwaggerParameter, SwaggerParameterCategory, SwaggerReference, SwaggerResponse,
12 SwaggerTypeOrReference,
13};
14
15#[derive(Debug, Clone, Default)]
16pub struct SwaggerContext {
17 pub openapi_json: Arc<RwLock<String>>,
18}
19
20impl SwaggerContext {
21 pub fn initialize_from_module(&self, module: impl IModule + Clone + 'static) {
22 let mut swagger = SwaggerSchema {
23 tags: { SWAGGER_TAGS.0.clone() },
24 ..Default::default()
25 };
26
27 generate_swagger(&mut swagger, Box::new(module));
28
29 let mut openapi_json = self.openapi_json.write().unwrap();
30 *openapi_json = serde_json::to_string(&swagger).unwrap();
31 }
32}
33
34#[rupring::Component(name=InjectSwaggerContext)]
35pub fn inject_swagger_context() -> SwaggerContext {
36 SwaggerContext::default()
37}
38
39fn to_string(method: hyper::Method) -> String {
40 match method {
41 hyper::Method::GET => "get".to_string(),
42 hyper::Method::POST => "post".to_string(),
43 hyper::Method::PUT => "put".to_string(),
44 hyper::Method::DELETE => "delete".to_string(),
45 hyper::Method::HEAD => "head".to_string(),
46 hyper::Method::OPTIONS => "options".to_string(),
47 hyper::Method::CONNECT => "connect".to_string(),
48 hyper::Method::PATCH => "patch".to_string(),
49 hyper::Method::TRACE => "trace".to_string(),
50 _ => "UNKNOWN".to_string(),
51 }
52}
53
54static SWAGGER_TAGS: SwaggerTags = SwaggerTags::new();
55
56fn generate_swagger(swagger: &mut SwaggerSchema, root_module: Box<dyn crate::IModule>) {
57 for controller in root_module.controllers() {
58 let prefix = controller.prefix();
59
60 for route in controller.routes() {
61 let normalized_path = crate::core::route::normalize_path(prefix.clone(), route.path());
62 let normalized_path = swaggerize_url(normalized_path.as_str());
63 let mut operation = route.swagger();
64
65 for security in route.swagger_security_info() {
66 operation.security.push(security);
67 }
68
69 let request_info = route.swagger_request_info();
70
71 if let Some(swagger_request_body) = request_info {
72 operation.parameters.push(SwaggerParameter {
73 name: swagger_request_body
74 .definition_name
75 .split("::")
76 .last()
77 .unwrap_or("Request Body")
78 .to_string(),
79 in_: SwaggerParameterCategory::Body,
80 description: "Request Body".to_string(),
81 required: true,
82 schema: Some(SwaggerTypeOrReference::Reference(SwaggerReference {
83 reference: "#/definitions/".to_string()
84 + swagger_request_body.definition_name.as_str(),
85 })),
86 type_: None,
87 });
88
89 swagger.definitions.insert(
90 swagger_request_body.definition_name.clone(),
91 swagger_request_body.definition_value,
92 );
93
94 for dependency in swagger_request_body.dependencies {
95 swagger.definitions.insert(
96 dependency.definition_name.clone(),
97 dependency.definition_value,
98 );
99 }
100
101 for swagger_parameter in swagger_request_body.path_parameters {
102 operation.parameters.push(swagger_parameter);
103 }
104
105 for swagger_parameter in swagger_request_body.query_parameters {
106 operation.parameters.push(swagger_parameter);
107 }
108 }
109
110 let response_info = route.swagger_response_info();
111
112 if let Some(swagger_response_body) = response_info {
113 swagger.definitions.insert(
114 swagger_response_body.definition_name.clone(),
115 swagger_response_body.definition_value,
116 );
117
118 operation.responses.insert(
119 "200".to_string(),
120 SwaggerResponse {
121 description: "OK".to_string(),
122 schema: Some(SwaggerReference {
123 reference: "#/definitions/".to_string()
124 + swagger_response_body.definition_name.as_str(),
125 }),
126 },
127 );
128
129 for dependency in swagger_response_body.dependencies {
130 swagger.definitions.insert(
131 dependency.definition_name.clone(),
132 dependency.definition_value,
133 );
134 }
135 }
136
137 match normalized_path.as_str() {
139 "/docs/swagger.json"
140 | "/docs"
141 | "/docs/favicon-16x16.png"
142 | "/docs/favicon-32x32.png"
143 | "/docs/swagger-initializer.js"
144 | "/docs/swagger-ui.css"
145 | "/docs/swagger-ui-standalone-preset.js"
146 | "/docs/swagger-ui-bundle.js" => continue,
147 _ => {}
148 }
149
150 let method = to_string(route.method());
151
152 if let Some(path) = swagger.paths.get_mut(&normalized_path) {
153 if path.get(&method).is_some() {
154 continue;
155 }
156
157 path.insert(method, operation);
158 continue;
159 }
160
161 let mut path = SwaggerPath::default();
162
163 path.insert(method, operation);
164 swagger.paths.insert(normalized_path, path);
165 }
166 }
167
168 for child_module in root_module.child_modules() {
169 generate_swagger(swagger, child_module);
170 }
171}
172
173fn swaggerize_url(url: &str) -> String {
175 let mut result = String::new();
176
177 let mut is_segment_start = true;
178 let mut in_path_param = false;
179
180 for c in url.chars() {
181 match c {
182 '/' => {
183 if in_path_param {
184 result.push('}');
185 in_path_param = false;
186 }
187
188 is_segment_start = true;
189
190 result.push(c);
191
192 continue;
193 }
194 ':' if is_segment_start => {
195 is_segment_start = false;
196 in_path_param = true;
197 result.push('{');
198 continue;
199 }
200 _ => {
201 is_segment_start = false;
202 result.push(c);
203 }
204 }
205 }
206
207 if in_path_param {
208 result.push('}');
209 }
210
211 result
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217
218 #[test]
219 fn test_swaggerize_url() {
220 assert_eq!(swaggerize_url("/users/:id"), "/users/{id}");
221 assert_eq!(swaggerize_url("users/:id"), "users/{id}");
222 assert_eq!(
223 swaggerize_url("/users/:id/do-something"),
224 "/users/{id}/do-something"
225 );
226 assert_eq!(
227 swaggerize_url("/users/:id/do-something/:id2"),
228 "/users/{id}/do-something/{id2}"
229 );
230 assert_eq!(
231 swaggerize_url("/users/:id/do-something/:id2/"),
232 "/users/{id}/do-something/{id2}/"
233 );
234 }
235}