rupring/swagger/
context.rs

1use 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            // TODO: 추후에는 swagger ignore 속성을 추가해서 그걸로 처리
138            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
173// /:id/do-something -> /{id}/do-something
174fn 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}