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}