cronus_spec/
lib.rs

1use std::{collections::{HashMap, VecDeque}, error::Error, fs, path::{Path, PathBuf}, sync::Arc};
2use anyhow::{bail, Result};
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Serialize, Deserialize, Clone)]
6pub struct RawSchemaEnumItem {
7    pub name: String,
8    pub value: Option<i32>,
9}
10
11
12#[derive(Debug, Serialize, Deserialize)]
13#[serde(deny_unknown_fields)]
14pub struct GlobalOption {
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub generator: Option<GeneratorOption>,
17
18    /// Default: "usecase", other popular choices are: "service", etc..
19    #[serde(skip_serializing_if = "Option::is_none")]
20    pub usecase_suffix: Option<String>,
21
22    /// Default: "request", other popular choices are: "input", "req", etc..
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub usecase_request_suffix: Option<String>,
25
26    /// Default: "response", other popular choices are: "output", "res", etc..
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub usecase_response_suffix: Option<String>,
29
30    /// If given type(s) is/are mentioned in the spec, do not generate
31    pub skip_types: Option<Vec<String>>
32}
33
34
35#[derive(Debug, Serialize, Deserialize)]
36#[serde(deny_unknown_fields)]
37pub struct GeneratorOption {
38    pub rust: Option<RustGeneratorOption>,
39    pub golang: Option<GolangGeneratorOption>,
40    pub golang_gin: Option<GolangGinGeneratorOption>,
41    pub python: Option<PythonGeneratorOption>,
42    pub python_fastapi: Option<PythonFastApiGeneratorOption>,
43    pub python_redis: Option<PythonRedisGeneratorOption>,
44    pub rust_axum: Option<RustAxumGeneratorOption>,
45    pub openapi: Option<OpenapiGeneratorOption>,
46    pub typescript: Option<TypescriptGeneratorOption>,
47    pub typescript_nestjs: Option<TypescriptNestjsGeneratorOption>
48}
49
50#[derive(Debug, Serialize, Deserialize)]
51#[serde(deny_unknown_fields)]
52pub struct GolangGinGeneratorOption {
53    #[serde(skip)]
54    pub def_loc: Arc<DefLoc>,
55
56    /// Output .go file
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub file: Option<String>,
59
60    /// Golang Package Name
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub package: Option<String>,
63
64    /// Golang Domain Generated Package Name
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub domain_package: Option<String>,
67
68    /// Golang Domain Generated Package Import
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub domain_import: Option<String>,
71
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub extra_request_fields: Option<Vec<String>>
74}
75
76#[derive(Debug, Serialize, Deserialize)]
77#[serde(deny_unknown_fields)]
78pub struct PythonRedisGeneratorOption {
79    #[serde(skip)]
80    pub def_loc: Arc<DefLoc>,
81
82    /// Output .py file
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub file: Option<String>,
85
86    #[serde(rename = "async", skip_serializing_if = "Option::is_none")]
87    pub async_flag: Option<bool>,
88
89    #[serde(skip_serializing_if = "Option::is_none")]
90    pub usecase_from: Option<String>,
91
92}
93
94#[derive(Debug, Serialize, Deserialize)]
95#[serde(deny_unknown_fields)]
96pub struct GolangGeneratorOption {
97    #[serde(skip)]
98    pub def_loc: Arc<DefLoc>,
99
100    /// Output .go file
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub file: Option<String>,
103
104    /// Golang Package Name
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub package: Option<String>,
107}
108
109#[derive(Debug, Serialize, Deserialize)]
110#[serde(deny_unknown_fields)]
111pub struct PythonFastApiGeneratorOption {
112    #[serde(skip)]
113    pub def_loc: Arc<DefLoc>,
114
115    /// Output .py file
116    #[serde(skip_serializing_if = "Option::is_none")]
117    pub file: Option<String>,
118
119    #[serde(rename = "async", skip_serializing_if = "Option::is_none")]
120    pub async_flag: Option<bool>,
121
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub get_ctx_from: Option<String>,
124
125    /// where the python usecase types are defined (which module)
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub usecase_from: Option<String>,
128    
129    pub extra_imports: Option<Vec<String>>,
130
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub extra_method_args: Option<Vec<String>>,
133
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub extra_request_fields: Option<Vec<String>>
136}
137
138
139
140
141
142#[derive(Debug, Serialize, Deserialize)]
143#[serde(deny_unknown_fields)]
144pub struct TypescriptNestjsGeneratorOption {
145    #[serde(skip)]
146    pub def_loc: Arc<DefLoc>,
147
148    /// Output .ts file
149    pub file: Option<String>
150}
151
152#[derive(Debug, Serialize, Deserialize)]
153#[serde(deny_unknown_fields)]
154pub struct TypescriptGeneratorOption {
155    #[serde(skip)]
156    pub def_loc: Arc<DefLoc>,
157
158    /// Output .ts file
159    pub file: Option<String>,
160
161}
162
163#[derive(Debug, Serialize, Deserialize)]
164#[serde(deny_unknown_fields)]
165pub struct PythonGeneratorOption {
166    #[serde(skip)]
167    pub def_loc: Arc<DefLoc>,
168
169    /// Output .py file
170    #[serde(skip_serializing_if = "Option::is_none")]
171    pub file: Option<String>,
172
173    #[serde(rename = "async", skip_serializing_if = "Option::is_none")]
174    pub async_flag: Option<bool>,
175
176
177}
178
179#[derive(Debug, Serialize, Deserialize)]
180#[serde(deny_unknown_fields)]
181pub struct RustGeneratorOption {
182    #[serde(skip)]
183    pub def_loc: Arc<DefLoc>,
184
185    /// Output .rs file
186    #[serde(skip_serializing_if = "Option::is_none")]
187    pub file: Option<String>,
188
189    /// Do not place default derive for struct
190    #[serde(skip_serializing_if = "Option::is_none")]
191    pub no_default_derive: Option<bool>,
192
193    /// Override the built-in default derive for struct
194    #[serde(skip_serializing_if = "Option::is_none")]
195    pub default_derive: Option<Vec<String>>,
196
197    /// Custom extra uses
198    #[serde(skip_serializing_if = "Option::is_none")]
199    pub uses: Option<Vec<String>>,
200
201    
202
203    #[serde(skip_serializing_if = "Option::is_none")]
204    pub no_error_type: Option<bool>,
205
206    #[serde(skip_serializing_if = "Option::is_none")]
207    pub error_type: Option<String>,
208
209    #[serde(rename = "async", skip_serializing_if = "Option::is_none")]
210    pub async_flag: Option<bool>,
211
212    #[serde(skip_serializing_if = "Option::is_none")]
213    pub async_trait: Option<bool>,
214}
215
216#[derive(Debug, Serialize, Deserialize)]
217#[serde(deny_unknown_fields)]
218pub struct RustAxumGeneratorOption {
219    #[serde(skip)]
220    pub def_loc: Arc<DefLoc>,
221
222    /// Output .rs file
223    pub file: Option<String>
224}
225
226
227#[derive(Debug, Serialize, Deserialize)]
228pub struct OpenapiGeneratorOption {
229    #[serde(skip)]
230    pub def_loc: Arc<DefLoc>,
231
232    /// Output .rs file
233    pub file: Option<String>
234}
235
236
237#[derive(Debug)]
238pub struct DefLoc {
239    pub file: PathBuf
240}
241
242impl DefLoc {
243    pub fn new(file: PathBuf) -> Self {
244        Self {
245            file
246        }
247    }
248}
249
250impl Default for DefLoc {
251    fn default() -> Self {
252        Self {
253            file: PathBuf::new()
254        }
255    }
256}
257
258
259
260#[derive(Debug, Serialize, Deserialize, Clone)]
261pub struct RawSchema {
262    #[serde(skip)]
263    pub def_loc: Arc<DefLoc>,
264
265    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
266    pub ty: Option<String>, // 'type' is a reserved keyword in Rust, hence the rename
267
268    #[serde(skip_serializing_if = "Option::is_none")]
269    pub items: Option<Box<RawSchema>>,
270
271    #[serde(skip_serializing_if = "Option::is_none")]
272    pub properties: Option<HashMap<String, RawSchema>>,
273
274    #[serde(skip_serializing_if = "Option::is_none")]
275    pub required: Option<bool>,
276
277    #[serde(skip_serializing_if = "Option::is_none")]
278    pub namespace: Option<String>,
279
280    #[serde(skip_serializing_if = "Option::is_none")]
281    pub enum_items: Option<Vec<RawSchemaEnumItem>>,
282
283    #[serde(skip_serializing_if = "Option::is_none")]
284    pub option: Option<RawSchemaPropertyOption>,
285
286    #[serde(skip_serializing_if = "Option::is_none")]
287    pub extends: Option<HashMap<String, String>>,
288
289    #[serde(skip_serializing_if = "Option::is_none")]
290    pub flat_extends: Option<Vec<String>>,
291}
292
293impl RawSchema {
294    pub fn new(def_loc: Arc<DefLoc>, ty: String) -> Self {
295        Self {
296            def_loc,
297            ty: Some(ty),
298            items: None,
299            properties: None,
300            required: None,
301            namespace: None,
302            enum_items: None,
303            option: None,
304            extends: None,
305            flat_extends: None,
306        }
307    }
308
309    pub fn new_array_ty(def_loc: Arc<DefLoc>, items_ty: String) -> Self {
310        Self {
311            def_loc: def_loc.clone(),
312            ty: None,
313            items: Some(Box::new(RawSchema::new(def_loc, items_ty))),
314            properties: None,
315            required: None,
316            namespace: None,
317            enum_items: None,
318            option: None,
319            extends: None,
320            flat_extends: None,
321        }
322    }
323}
324
325#[derive(Debug, Serialize, Deserialize, Clone)]
326#[serde(deny_unknown_fields)]
327pub struct RawSchemaPropertyOption {
328    #[serde(skip_serializing_if = "Option::is_none")]
329    pub rest: Option<RawSchemaPropertyRestOption>,
330
331    #[serde(skip_serializing_if = "Option::is_none")]
332    pub python: Option<RawSchemaPropertyPythonOption>,
333
334    #[serde(skip_serializing_if = "Option::is_none")]
335    pub rust: Option<RawSchemaPropertyRustOption>,
336
337    #[serde(skip_serializing_if = "Option::is_none")]
338    pub openapi: Option<RawSchemaPropertyOpenApiOption>,
339
340    #[serde(skip_serializing_if = "Option::is_none")]
341    pub python_fastapi: Option<RawSchemaPropertyPythonFastApiOption>,
342
343    #[serde(skip_serializing_if = "Option::is_none")]
344    pub golang_gin: Option<RawSchemaPropertyGolangGinOption>,
345
346    #[serde(skip_serializing_if = "Option::is_none")]
347    pub description: Option<String>
348
349}
350
351#[derive(Debug, Serialize, Deserialize, Clone)]
352#[serde(deny_unknown_fields)]
353pub struct RawSchemaPropertyPythonOption {
354
355}
356
357#[derive(Debug, Serialize, Deserialize, Clone)]
358#[serde(deny_unknown_fields)]
359pub struct RawSchemaPropertyOpenApiOption {
360    #[serde(skip_serializing_if = "Option::is_none")]
361    pub exclude: Option<bool>
362}
363
364#[derive(Debug, Serialize, Deserialize, Clone)]
365#[serde(deny_unknown_fields)]
366pub struct RawSchemaPropertyGolangGinOption {
367    #[serde(skip_serializing_if = "Option::is_none")]
368    pub exclude: Option<bool>
369}
370
371#[derive(Debug, Serialize, Deserialize, Clone)]
372#[serde(deny_unknown_fields)]
373pub struct RawSchemaPropertyPythonFastApiOption {
374    #[serde(skip_serializing_if = "Option::is_none")]
375    pub exclude: Option<bool>
376}
377
378
379#[derive(Debug, Serialize, Deserialize, Clone)]
380#[serde(deny_unknown_fields)]
381pub struct RawSchemaPropertyRestOption {
382    #[serde(skip_serializing_if = "Option::is_none")]
383    pub query: Option<bool>
384}
385
386#[derive(Debug, Serialize, Deserialize, Clone)]
387#[serde(deny_unknown_fields)]
388pub struct RawSchemaPropertyRustOption {
389    #[serde(skip_serializing_if = "Option::is_none")]
390    pub attrs: Option<Vec<String>>
391}
392#[derive(Debug, Serialize, Deserialize)]
393pub struct RawUsecaseMethod {
394    #[serde(skip_serializing_if = "Option::is_none")]
395    pub req: Option<RawSchema>,
396    #[serde(skip_serializing_if = "Option::is_none")]
397    pub res: Option<RawSchema>,
398
399    #[serde(skip_serializing_if = "Option::is_none")]
400    pub option: Option<RawUsecaseMethodOption>
401}
402
403  
404#[derive(Debug, Serialize, Deserialize)]
405pub  struct RawUsecase {
406    #[serde(skip)]
407    pub def_loc: Arc<DefLoc>,
408
409    pub methods: HashMap<String, RawUsecaseMethod>,
410
411    #[serde(skip_serializing_if = "Option::is_none")]
412    pub option: Option<RawUsecaseOption>
413}
414
415#[derive(Debug, Serialize, Deserialize)]
416#[serde(deny_unknown_fields)]
417pub struct RawUsecaseOption {
418
419    #[serde(skip_serializing_if = "Option::is_none")]
420    pub rest: Option<RawUsecaseRestOption>
421}
422
423#[derive(Debug, Serialize, Deserialize)]
424#[serde(deny_unknown_fields)]
425pub struct RawUsecaseRestOption {
426    /// Http endpoint prefix
427    #[serde(skip_serializing_if = "Option::is_none")]
428    pub path: Option<String>
429}
430
431
432#[derive(Debug, Serialize, Deserialize)]
433#[serde(deny_unknown_fields)]
434pub struct RawUsecaseMethodOption {
435    #[serde(skip_serializing_if = "Option::is_none")]
436    pub rest: Option<RawUsecaseMethodRestOption>,
437
438    #[serde(skip_serializing_if = "Option::is_none")]
439    pub redis: Option<RawUsecaseMethodRedisOption>,
440
441    #[serde(skip_serializing_if = "Option::is_none")]
442    pub python_fastapi: Option<RawUsecaseMethodPythonFastApiOption>,
443
444    #[serde(skip_serializing_if = "Option::is_none")]
445    pub golang_gin: Option<RawUsecaseMethodGolangGinOption>,
446
447    #[serde(skip_serializing_if = "Option::is_none")]
448    pub description: Option<String>
449}
450
451#[derive(Debug, Serialize, Deserialize)]
452#[serde(deny_unknown_fields)]
453pub struct RawUsecaseMethodRedisOption {
454
455    #[serde(skip_serializing_if = "Option::is_none")]
456    pub queue_name: Option<String>,
457
458    #[serde(skip_serializing_if = "Option::is_none")]
459    pub ack_queue_name: Option<String>
460}
461
462#[derive(Debug, Serialize, Deserialize)]
463pub struct RawUsecaseMethodGolangGinOption {
464
465    #[serde(skip_serializing_if = "Option::is_none")]
466    pub extra_request_fields: Option<Vec<String>>
467}
468
469#[derive(Debug, Serialize, Deserialize)]
470pub struct RawUsecaseMethodPythonFastApiOption {
471    #[serde(skip_serializing_if = "Option::is_none")]
472    pub extra_method_args: Option<Vec<String>>,
473
474    #[serde(skip_serializing_if = "Option::is_none")]
475    pub extra_request_fields: Option<Vec<String>>
476}
477
478#[derive(Clone, Debug, Serialize, Deserialize)]
479pub struct RawUsecaseMethodRestOption {
480    pub method: String,
481    pub path: Option<String>
482}
483
484/// The schema for a spec
485/// 
486#[derive(Debug, Serialize, Deserialize)]
487#[serde(deny_unknown_fields)]
488pub struct RawSpec {
489
490
491    #[serde(rename = "types", skip_serializing_if = "Option::is_none")]
492    pub ty: Option<HashMap<String, RawSchema>>,
493
494    #[serde(skip_serializing_if = "Option::is_none")]
495    pub usecases: Option<HashMap<String, RawUsecase>>,
496
497
498    #[serde(skip_serializing_if = "Option::is_none")]
499    pub option: Option<GlobalOption>,
500
501
502    #[serde(skip_serializing_if = "Option::is_none")]
503    pub imports: Option<Vec<String>>,
504
505}
506
507
508impl RawSpec {
509
510
511    pub fn merge(&mut self, to_merge: RawSpec)-> Result<()> {
512        if let Some(to_merge_ty) = to_merge.ty {
513            let ty_map = self.ty.get_or_insert_with(HashMap::new);
514            for (key, value) in to_merge_ty {
515                if ty_map.contains_key(&key) {
516                    bail!("Conflict for key '{}' in 'ty' hashmap", key);
517                }
518                ty_map.insert(key, value);
519            }
520        }
521
522        // Merge 'usecase' HashMap
523        if let Some(to_merge_usecase) = to_merge.usecases {
524            let usecase_map = self.usecases.get_or_insert_with(HashMap::new);
525            for (key, value) in to_merge_usecase {
526                if usecase_map.contains_key(&key) {
527                    bail!("Conflict for key '{}' in 'usecase' hashmap", key)
528                }
529                usecase_map.insert(key, value);
530            }
531        }
532
533        // Global option has to be putted into the entry .api or .yaml file
534
535        // Merge 'imports' Vec
536        if let Some(to_merge_imports) = to_merge.imports {
537            if let Some(imports) = &mut self.imports {
538                for import in &to_merge_imports {
539                    if imports.contains(import) {
540                        continue;
541                    }
542                }
543                imports.extend(to_merge_imports);
544            } else {
545                self.imports = Some(to_merge_imports);
546            }
547        }
548
549        Ok(())
550    }
551
552    pub fn new() -> Self {
553         Self { 
554            ty: Default::default(), 
555            usecases: Default::default(), 
556            option: Default::default(), 
557            imports: Default::default()
558         }
559    }
560}
561