swagg/printer/
mod.rs

1pub mod api;
2pub mod components;
3pub mod paths;
4
5pub trait Printable {
6    fn print(&self) -> proc_macro2::TokenStream;
7}
8
9impl<T> Printable for Vec<T>
10where
11    T: Printable,
12{
13    fn print(&self) -> proc_macro2::TokenStream {
14        use quote::quote;
15
16        let list = self.iter().map(|x| x.print());
17
18        quote! {
19            #(#list)*
20        }
21    }
22}
23
24#[derive(Default)]
25pub struct GeneratedModule {
26    pub api: api::module::ApiModule,
27    pub components: components::module::ComponentsModule,
28    pub paths: paths::module::PathsModule,
29}
30
31impl GeneratedModule {}
32
33impl Printable for GeneratedModule {
34    fn print(&self) -> proc_macro2::TokenStream {
35        let api_module = self.api.print();
36        let components_module = self.components.print();
37        let paths_module = self.paths.print();
38
39        quote::quote! {
40            #![allow(dead_code, unused_imports)]
41
42            #api_module
43            #components_module
44            #paths_module
45        }
46    }
47}
48
49#[cfg(test)]
50mod tests {
51    use super::{
52        api::{ApiModule, ApiStruct, BindApiMethod, HttpMethod, ImplApi},
53        components::{
54            parameters::ParametersModule, request_bodies::RequestBodiesModule,
55            responses::ResponsesModule, schemas::SchemasModule, Component, ComponentsModule,
56            EnumVariant, Field, FieldType, FormatFloat, FormatInteger, FormatString, NativeType,
57        },
58        paths::{
59            ContentType, Path, PathsModule, QueryParam, ResponseEnum, ResponseStatus, StatusVariant,
60        },
61        GeneratedModule,
62    };
63    use crate::test::shot;
64    use insta::assert_snapshot;
65
66    #[test]
67    fn huge_test_all_generated() {
68        let api: ApiStruct = ApiStruct {
69        api_name: "ExampleApiDef".to_owned(),
70        description: Some("Public API for frontend and OAuth applications [Review Github](https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/)".to_owned()),
71        terms_of_service: None,
72    };
73
74        let m1 = BindApiMethod {
75            method: HttpMethod::Get,
76            name: "sessionGet".to_owned(),
77            path: "/session".to_owned(),
78            request_body: None,
79        };
80
81        let m2 = BindApiMethod {
82            method: HttpMethod::Post,
83            name: "sessionCreate".to_owned(),
84            path: "/session".to_owned(),
85            request_body: Some("SessionCreateBody".to_owned()),
86        };
87
88        let m3 = BindApiMethod {
89            method: HttpMethod::Post,
90            name: "registerConfirmation".to_owned(),
91            path: "/register/confirmation".to_owned(),
92            request_body: Some("RegisterConfirmation".to_owned()),
93        };
94
95        let methods = ImplApi {
96            api_name: api.api_name.clone(),
97            methods: vec![m1, m2, m3],
98        };
99
100        let api_module = ApiModule {
101            structure: api,
102            methods,
103        };
104
105        let components_module = ComponentsModule {
106            parameters: ParametersModule {
107                list: vec![
108                Component::Enum {
109                    name: "OAuthResponseType".to_owned(),
110                    description: Some(
111                        "response_type is set to code indicating that you want an authorization code as the response."
112                            .to_owned(),
113                    ),
114                    variants: vec![EnumVariant {
115                        name: "code".to_owned(),
116                        description: None,
117                    }],
118                },
119                Component::Type {
120                    name: "OAuthClientId".to_owned(),
121                    description: Some("The client_id is the identifier for your app".to_owned()),
122                    type_value: FieldType::Internal("uuid::Uuid".to_owned()),
123                },
124                Component::Type {
125                    name: "OAuthRedirectUri".to_owned(),
126                    description: Some(
127                        "redirect_uri may be optional depending on the API, but is highly recommended".to_owned(),
128                    ),
129                    type_value: FieldType::Native(NativeType::String { format: Default::default() }),
130                },
131            ],
132            },
133            responses: ResponsesModule {
134                list: vec![
135                    Component::Object {
136                        name: "RegisterConfirmationFailed".to_owned(),
137                        fields: vec![Field {
138                            name: "error".to_owned(),
139                            required: true,
140                            description: None,
141                            field_type: FieldType::Custom("RegisterConfirmationFailedError".to_owned()),
142                        }],
143                        description: Some("Answer for registration confirmation".to_owned()),
144                    },
145                    Component::Enum {
146                        name: "RegisterConfirmationFailedError".to_owned(),
147                        variants: vec![
148                            EnumVariant {
149                                name: "code_invalid_or_expired".to_owned(),
150                                description: None,
151                            },
152                            EnumVariant {
153                                name: "email_already_activated".to_owned(),
154                                description: None,
155                            },
156                            EnumVariant {
157                                name: "invalid_form".to_owned(),
158                                description: None,
159                            },
160                        ],
161                        description: None,
162                    },
163                    Component::Object {
164                        name: "RegistrationRequestCreated".to_owned(),
165                        description: Some(
166                            "Registration link sent to email, now user can find out when the link expires".to_owned(),
167                        ),
168                        fields: vec![Field {
169                            name: "expiresAt".to_owned(),
170                            required: true,
171                            description: Some("UTC Unix TimeStamp when the link expires".to_owned()),
172                            field_type: FieldType::Native(NativeType::Integer {
173                                format: FormatInteger::Int64,
174                            }),
175                        }],
176                    },
177                ],
178            },
179            request_bodies: RequestBodiesModule {
180                list: vec![
181                    Component::Object {
182                        name: "Register".to_owned(),
183                        description: None,
184                        fields: vec![
185                            Field {
186                                name: "email".to_owned(),
187                                required: true,
188                                description: None,
189                                field_type: FieldType::Native(NativeType::String {
190                                    format: FormatString::Email,
191                                }),
192                            },
193                            Field {
194                                name: "demo".to_owned(),
195                                required: false,
196                                description: None,
197                                field_type: FieldType::Array(Box::new(FieldType::Array(Box::new(FieldType::Native(
198                                    NativeType::String {
199                                        format: FormatString::Email,
200                                    },
201                                ))))),
202                            },
203                        ],
204                    },
205                    Component::Object {
206                        name: "RegisterConfirmation".to_owned(),
207                        description: None,
208                        fields: vec![
209                            Field {
210                                name: "confirmationCode".to_owned(),
211                                required: true,
212                                description: None,
213                                field_type: FieldType::Native(NativeType::String {
214                                    format: FormatString::default(),
215                                }),
216                            },
217                            Field {
218                                name: "firstName".to_owned(),
219                                required: true,
220                                description: None,
221                                field_type: FieldType::Native(NativeType::String {
222                                    format: FormatString::default(),
223                                }),
224                            },
225                            Field {
226                                name: "lastName".to_owned(),
227                                required: true,
228                                description: None,
229                                field_type: FieldType::Native(NativeType::String {
230                                    format: FormatString::default(),
231                                }),
232                            },
233                            Field {
234                                name: "password".to_owned(),
235                                required: true,
236                                description: None,
237                                field_type: FieldType::Native(NativeType::String {
238                                    format: FormatString::default(),
239                                }),
240                            },
241                            Field {
242                                name: "demo".to_owned(),
243                                required: false,
244                                description: None,
245                                field_type: FieldType::Native(NativeType::Float {
246                                    format: FormatFloat::default(),
247                                }),
248                            },
249                            Field {
250                                name: "customizer".to_owned(),
251                                required: false,
252                                description: None,
253                                field_type: FieldType::Internal("crate::app::MySuperType".to_owned()),
254                            },
255                        ],
256                    },
257                ],
258            },
259            schemas: SchemasModule {
260                list: vec![
261                    Component::Object {
262                        name: "RegisterConfirmation".to_owned(),
263                        description: None,
264                        fields: vec![
265                            Field {
266                                name: "confirmationCode".to_owned(),
267                                required: true,
268                                description: None,
269                                field_type: FieldType::Native(NativeType::String {
270                                    format: FormatString::default(),
271                                }),
272                            },
273                            Field {
274                                name: "firstName".to_owned(),
275                                required: true,
276                                description: None,
277                                field_type: FieldType::Native(NativeType::String {
278                                    format: FormatString::default(),
279                                }),
280                            },
281                            Field {
282                                name: "lastName".to_owned(),
283                                required: true,
284                                description: None,
285                                field_type: FieldType::Native(NativeType::String {
286                                    format: FormatString::default(),
287                                }),
288                            },
289                            Field {
290                                name: "password".to_owned(),
291                                required: true,
292                                description: None,
293                                field_type: FieldType::Native(NativeType::String {
294                                    format: FormatString::default(),
295                                }),
296                            },
297                            Field {
298                                name: "demo".to_owned(),
299                                required: false,
300                                description: None,
301                                field_type: FieldType::Native(NativeType::Float {
302                                    format: FormatFloat::default(),
303                                }),
304                            },
305                            Field {
306                                name: "customizer".to_owned(),
307                                required: false,
308                                description: None,
309                                field_type: FieldType::Internal("crate::app::MySuperType".to_owned()),
310                            },
311                        ],
312                    },
313                ],
314            },
315        };
316
317        let p1 = Path {
318            name: "registerConfirmation".to_owned(),
319            query_params: vec![],
320            response: ResponseEnum {
321                responses: vec![
322                    StatusVariant {
323                        status: ResponseStatus::Created,
324                        response_type_name: None,
325                        description: None,
326                        content_type: None,
327                        x_variant_name: None,
328                    },
329                    StatusVariant {
330                        status: ResponseStatus::BadRequest,
331                        response_type_name: Some("RegisterConfirmationFailed".to_owned()),
332                        description: None,
333                        content_type: Some(ContentType::Json),
334                        x_variant_name: None,
335                    },
336                    StatusVariant {
337                        status: ResponseStatus::InternalServerError,
338                        response_type_name: None,
339                        description: None,
340                        content_type: Some(ContentType::Json),
341                        x_variant_name: Some("Unexpected".to_owned()),
342                    },
343                ],
344            },
345        };
346
347        let p2 = Path {
348            name: "sessionCreate".to_owned(),
349            query_params: vec![
350                QueryParam {
351                    name: "responseType".to_owned(),
352                    type_ref: "OAuthResponseType".to_owned(),
353                    description: Some(
354                        "response_type is set to code indicating that you want an authorization code as the response."
355                            .to_owned(),
356                    ),
357                    required: true,
358                },
359                QueryParam {
360                    name: "redirect_uri".to_owned(),
361                    type_ref: "OAuthRedirectUri".to_owned(),
362                    description: None,
363                    required: false,
364                },
365                QueryParam {
366                    name: "GlobalNameOfTheUniverse".to_owned(),
367                    type_ref: "OAuthClientId".to_owned(),
368                    description: None,
369                    required: false,
370                },
371            ],
372            response: ResponseEnum {
373                responses: vec![
374                    StatusVariant {
375                        status: ResponseStatus::Created,
376                        response_type_name: None,
377                        description: Some("User logined, cookies writed\nFoo".to_owned()),
378                        content_type: None,
379                        x_variant_name: None,
380                    },
381                    StatusVariant {
382                        status: ResponseStatus::BadRequest,
383                        response_type_name: Some("sessionCreateFailed".to_owned()),
384                        description: None,
385                        content_type: Some(ContentType::Json),
386                        x_variant_name: None,
387                    },
388                    StatusVariant {
389                        status: ResponseStatus::InternalServerError,
390                        response_type_name: None,
391                        description: None,
392                        content_type: Some(ContentType::Json),
393                        x_variant_name: Some("Unexpected".to_owned()),
394                    },
395                ],
396            },
397        };
398
399        let paths_module = PathsModule {
400            paths: vec![p1, p2],
401        };
402
403        let generated_module = GeneratedModule {
404            api: api_module,
405            components: components_module,
406            paths: paths_module,
407        };
408
409        assert_snapshot!(shot(generated_module), @r###"
410        #![allow(dead_code, unused_imports)]
411        pub mod api {
412            #[doc = "Public API for frontend and OAuth applications [Review Github](https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/)"]
413            pub struct ExampleApiDef {
414                api: actix_swagger::Api,
415            }
416            impl ExampleApiDef {
417                pub fn new() -> Self {
418                    Self {
419                        api: actix_swagger::Api::new(),
420                    }
421                }
422            }
423            impl Default for ExampleApiDef {
424                fn default() -> Self {
425                    let api = Self::new();
426                    api
427                }
428            }
429            impl actix_web::dev::HttpServiceFactory for ExampleApiDef {
430                fn register(self, config: &mut actix_web::dev::AppService) {
431                    self.api.register(config);
432                }
433            }
434            use super::paths;
435            use actix_swagger::{Answer, Method};
436            use actix_web::{dev::Factory, FromRequest};
437            use std::future::Future;
438            impl ExampleApiDef {
439                pub fn bind_session_get<F, T, R>(mut self, handler: F) -> Self
440                where
441                    F: Factory<T, R, Answer<'static, paths::session_get::Response>>,
442                    T: FromRequest + 'static,
443                    R: Future<Output = Answer<'static, paths::session_get::Response>> + 'static,
444                {
445                    self.api = self.api.bind("/session".to_owned(), Method::GET, handler);
446                    self
447                }
448                #[doc = "Request body - super::requst_bodies::SessionCreateBody"]
449                pub fn bind_session_create<F, T, R>(mut self, handler: F) -> Self
450                where
451                    F: Factory<T, R, Answer<'static, paths::session_create::Response>>,
452                    T: FromRequest + 'static,
453                    R: Future<Output = Answer<'static, paths::session_create::Response>> + 'static,
454                {
455                    self.api = self.api.bind("/session".to_owned(), Method::POST, handler);
456                    self
457                }
458                #[doc = "Request body - super::requst_bodies::RegisterConfirmation"]
459                pub fn bind_register_confirmation<F, T, R>(mut self, handler: F) -> Self
460                where
461                    F: Factory<T, R, Answer<'static, paths::register_confirmation::Response>>,
462                    T: FromRequest + 'static,
463                    R: Future<Output = Answer<'static, paths::register_confirmation::Response>> + 'static,
464                {
465                    self.api = self
466                        .api
467                        .bind("/register/confirmation".to_owned(), Method::POST, handler);
468                    self
469                }
470            }
471        }
472        pub mod components {
473            pub mod parameters {
474                use serde::{Deserialize, Serialize};
475                #[doc = "response_type is set to code indicating that you want an authorization code as the response."]
476                #[derive(Debug, Serialize, Deserialize)]
477                pub enum OauthResponseType {
478                    #[serde(rename = "code")]
479                    Code,
480                }
481                #[doc = "The client_id is the identifier for your app"]
482                pub type OauthClientId = uuid::Uuid;
483                #[doc = "redirect_uri may be optional depending on the API, but is highly recommended"]
484                pub type OauthRedirectUri = String;
485            }
486            pub mod request_bodies {
487                use serde::{Deserialize, Serialize};
488                #[derive(Debug, Serialize, Deserialize)]
489                pub struct Register {
490                    pub email: String,
491                    pub demo: Option<Vec<Vec<String>>>,
492                }
493                #[derive(Debug, Serialize, Deserialize)]
494                pub struct RegisterConfirmation {
495                    #[serde(rename = "confirmationCode")]
496                    pub confirmation_code: String,
497                    #[serde(rename = "firstName")]
498                    pub first_name: String,
499                    #[serde(rename = "lastName")]
500                    pub last_name: String,
501                    pub password: String,
502                    pub demo: Option<f32>,
503                    pub customizer: Option<crate::app::MySuperType>,
504                }
505            }
506            pub mod responses {
507                use serde::{Deserialize, Serialize};
508                #[doc = "Answer for registration confirmation"]
509                #[derive(Debug, Serialize, Deserialize)]
510                pub struct RegisterConfirmationFailed {
511                    pub error: RegisterConfirmationFailedError,
512                }
513                #[derive(Debug, Serialize, Deserialize)]
514                pub enum RegisterConfirmationFailedError {
515                    #[serde(rename = "code_invalid_or_expired")]
516                    CodeInvalidOrExpired,
517                    #[serde(rename = "email_already_activated")]
518                    EmailAlreadyActivated,
519                    #[serde(rename = "invalid_form")]
520                    InvalidForm,
521                }
522                #[doc = "Registration link sent to email, now user can find out when the link expires"]
523                #[derive(Debug, Serialize, Deserialize)]
524                pub struct RegistrationRequestCreated {
525                    #[doc = "UTC Unix TimeStamp when the link expires"]
526                    #[serde(rename = "expiresAt")]
527                    pub expires_at: i64,
528                }
529            }
530            pub mod schemas {
531                use serde::{Deserialize, Serialize};
532                #[derive(Debug, Serialize, Deserialize)]
533                pub struct RegisterConfirmation {
534                    #[serde(rename = "confirmationCode")]
535                    pub confirmation_code: String,
536                    #[serde(rename = "firstName")]
537                    pub first_name: String,
538                    #[serde(rename = "lastName")]
539                    pub last_name: String,
540                    pub password: String,
541                    pub demo: Option<f32>,
542                    pub customizer: Option<crate::app::MySuperType>,
543                }
544            }
545        }
546        pub mod paths {
547            use super::components::{parameters, responses};
548            pub mod register_confirmation {
549                use super::responses;
550                use actix_swagger::{Answer, ContentType, StatusCode};
551                use serde::{Deserialize, Serialize};
552                #[derive(Debug, Serialize)]
553                #[serde(untagged)]
554                pub enum Response {
555                    Created,
556                    BadRequest(responses::RegisterConfirmationFailed),
557                    Unexpected,
558                }
559                impl Response {
560                    #[inline]
561                    pub fn to_answer(self) -> Answer<'static, Self> {
562                        let status = match self {
563                            Self::Created => StatusCode::CREATED,
564                            Self::BadRequest(_) => StatusCode::BAD_REQUEST,
565                            Self::Unexpected => StatusCode::INTERNAL_SERVER_ERROR,
566                        };
567                        let content_type = match self {
568                            Self::Created => None,
569                            Self::BadRequest(_) => Some(ContentType::Json),
570                            Self::Unexpected => Some(ContentType::Json),
571                        };
572                        Answer::new(self).status(status).content_type(content_type)
573                    }
574                }
575            }
576            pub mod session_create {
577                use super::responses;
578                use actix_swagger::{Answer, ContentType, StatusCode};
579                use serde::{Deserialize, Serialize};
580                #[derive(Debug, Serialize)]
581                #[serde(untagged)]
582                pub enum Response {
583                    #[doc = "User logined, cookies writed\nFoo"]
584                    Created,
585                    BadRequest(responses::SessionCreateFailed),
586                    Unexpected,
587                }
588                impl Response {
589                    #[inline]
590                    pub fn to_answer(self) -> Answer<'static, Self> {
591                        let status = match self {
592                            Self::Created => StatusCode::CREATED,
593                            Self::BadRequest(_) => StatusCode::BAD_REQUEST,
594                            Self::Unexpected => StatusCode::INTERNAL_SERVER_ERROR,
595                        };
596                        let content_type = match self {
597                            Self::Created => None,
598                            Self::BadRequest(_) => Some(ContentType::Json),
599                            Self::Unexpected => Some(ContentType::Json),
600                        };
601                        Answer::new(self).status(status).content_type(content_type)
602                    }
603                }
604                use super::parameters;
605                #[derive(Debug, Deserialize)]
606                pub struct QueryParams {
607                    #[doc = "response_type is set to code indicating that you want an authorization code as the response."]
608                    #[serde(rename = "responseType")]
609                    pub response_type: parameters::OauthResponseType,
610                    pub redirect_uri: Option<parameters::OauthRedirectUri>,
611                    #[serde(rename = "GlobalNameOfTheUniverse")]
612                    pub global_name_of_the_universe: Option<parameters::OauthClientId>,
613                }
614                pub type Query = actix_web::http::Query<QueryParams>;
615            }
616        }
617        "###);
618    }
619}