integrationos_domain/domain/schema/
common_model.rs

1use crate::{
2    api_model_config::Lang,
3    id::{prefix::IdPrefix, Id},
4    prelude::{shared::record_metadata::RecordMetadata, MongoStore, StringExt},
5    IntegrationOSError, InternalError,
6};
7use async_recursion::async_recursion;
8use bson::doc;
9use indexmap::IndexMap;
10use openapiv3::*;
11use semver::Version;
12use serde::{Deserialize, Serialize};
13use serde_json::{json, Map, Value};
14use std::{
15    collections::{HashMap, HashSet},
16    hash::Hash,
17    ops::Deref,
18};
19
20#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
21#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
22pub struct CommonModel {
23    #[serde(rename = "_id")]
24    pub id: Id,
25    pub name: String,
26    pub fields: Vec<Field>,
27    #[serde(default)]
28    pub sample: Value,
29    #[serde(default)]
30    pub primary: bool,
31    pub category: String,
32    #[serde(default)]
33    pub interface: HashMap<Lang, String>,
34    #[serde(flatten, default)]
35    pub record_metadata: RecordMetadata,
36}
37
38impl Hash for CommonModel {
39    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
40        self.id.hash(state);
41    }
42}
43
44pub enum TypeGenerationStrategy<'a> {
45    /// Generates the type in a cumulative way, meaning that it will only generate the types for the
46    /// models and enums that have not been generated before keeping track of the already generated
47    /// types.
48    Cumulative {
49        visited_enums: &'a mut HashSet<Id>,
50        visited_common_models: &'a mut HashSet<Id>,
51    },
52    /// Generates the type in a unique way, meaning that it will generate the types for a single
53    /// model, using this with multiple models will result in type duplication.
54    Unique,
55}
56
57#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
58pub struct UnsavedCommonModel {
59    pub name: String,
60    pub fields: Vec<Field<UnsavedCommonModel>>,
61    pub category: String,
62    #[serde(default)]
63    pub sample: Value,
64    #[serde(default)]
65    pub interface: HashMap<Lang, String>,
66    #[serde(default)]
67    pub primary: bool,
68}
69
70impl Default for CommonModel {
71    fn default() -> Self {
72        Self {
73            id: Id::new(IdPrefix::CommonModel, chrono::Utc::now()),
74            name: Default::default(),
75            fields: Default::default(),
76            sample: Default::default(),
77            primary: Default::default(),
78            category: Default::default(),
79            interface: Default::default(),
80            record_metadata: Default::default(),
81        }
82    }
83}
84
85#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq, Copy)]
86#[serde(rename_all = "kebab-case")]
87#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
88pub enum SchemaType {
89    Lax,
90    Strict,
91}
92
93#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
94#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
95pub struct Field<T = CommonModel> {
96    pub name: String,
97    #[serde(flatten)]
98    #[cfg_attr(feature = "dummy", dummy(default))]
99    pub datatype: DataType<T>,
100    #[serde(default, skip_serializing_if = "Option::is_none")]
101    pub description: Option<String>,
102    #[serde(default)]
103    pub required: bool,
104}
105
106impl Field {
107    fn is_expandable(&self) -> bool {
108        self.datatype.is_expandable()
109    }
110
111    fn is_primitive(&self) -> bool {
112        self.datatype.is_primitive()
113    }
114
115    fn is_enum_reference(&self) -> bool {
116        self.datatype.is_enum_reference()
117    }
118
119    fn is_enum_field(&self) -> bool {
120        self.datatype.is_enum_field()
121    }
122
123    fn as_rust_ref(&self) -> String {
124        format!(
125            "pub {}: Option<{}>",
126            replace_reserved_keyword(&self.name, Lang::Rust).snake_case(),
127            self.datatype.as_rust_ref(self.name.clone())
128        )
129    }
130
131    fn as_typescript_ref(&self) -> String {
132        format!(
133            "{}?: {}",
134            replace_reserved_keyword(&self.name, Lang::TypeScript).camel_case(),
135            self.datatype.as_typescript_ref(self.name.clone())
136        )
137    }
138
139    fn as_typescript_schema(&self, r#type: SchemaType) -> String {
140        format!(
141            "{}: {}",
142            replace_reserved_keyword(&self.name, Lang::TypeScript).camel_case(),
143            self.datatype
144                .as_typescript_schema(self.name.clone(), r#type)
145        )
146    }
147}
148
149#[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq)]
150#[serde(tag = "datatype")]
151pub enum DataType<T = CommonModel> {
152    #[default]
153    String,
154    Number,
155    Boolean,
156    Date,
157    Enum {
158        options: Option<Vec<String>>,
159        #[serde(default)]
160        reference: String,
161    },
162    Expandable(Expandable<T>),
163    Array {
164        #[serde(rename = "elementType")]
165        element_type: Box<DataType<T>>,
166    },
167    Unknown,
168}
169
170#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
171pub struct CommonEnum {
172    #[serde(rename = "_id")]
173    pub id: Id,
174    pub name: String,
175    pub options: Vec<String>,
176}
177
178fn replace_reserved_keyword(name: &str, lang: Lang) -> String {
179    match lang {
180        Lang::Rust => match name.to_lowercase().as_str() {
181            "type" => "r#type".to_owned(),
182            "enum" => "r#enum".to_owned(),
183            "struct" => "r#struct".to_owned(),
184            _ => name.to_owned(),
185        },
186        Lang::TypeScript => match name.to_lowercase().as_str() {
187            "type" => "type_".to_owned(),
188            "enum" => "enum_".to_owned(),
189            "interface" => "interface_".to_owned(),
190            _ => name.to_owned(),
191        },
192        _ => name.to_owned(),
193    }
194}
195
196impl CommonEnum {
197    pub fn as_rust_type(&self) -> String {
198        format!(
199            "pub enum {} {{ {} }}\n",
200            replace_reserved_keyword(&self.name, Lang::Rust)
201                .replace("::", "")
202                .pascal_case(),
203            self.options
204                .iter()
205                .map(|option| option.pascal_case())
206                .collect::<HashSet<String>>()
207                .into_iter()
208                .collect::<Vec<_>>()
209                .join(", ")
210        )
211    }
212
213    /// Generates a napi annotated enum for the enum rust type
214    pub fn as_rust_schema(&self) -> String {
215        let name = replace_reserved_keyword(&self.name, Lang::Rust)
216            .replace("::", "")
217            .pascal_case();
218        let napi = format!("#[napi(string_enum = \"kebab-case\", js_name = {})]", name);
219        format!(
220            "{} pub enum {} {{ {} }}\n",
221            napi,
222            name,
223            self.options
224                .iter()
225                .map(|option| {
226                    let option_name = option.pascal_case();
227                    let option_value = if option.chars().all(char::is_uppercase) {
228                        option.to_lowercase()
229                    } else {
230                        option.kebab_case()
231                    };
232
233                    let option_annotation = format!("#[napi(value = \"{}\")]", option_value);
234
235                    format!("{} {}", option_annotation, option_name)
236                })
237                .collect::<HashSet<String>>()
238                .into_iter()
239                .collect::<Vec<_>>()
240                .join(", ")
241        )
242    }
243
244    pub fn as_typescript_type(&self) -> String {
245        // let's add the value directly to the enum
246        format!(
247            "export const enum {} {{ {} }}\n",
248            replace_reserved_keyword(&self.name, Lang::TypeScript)
249                .replace("::", "")
250                .pascal_case(),
251            self.options
252                .iter()
253                .map(|option| {
254                    let option_name = option.pascal_case();
255                    let option_value = if option.chars().all(char::is_uppercase) {
256                        option.to_lowercase()
257                    } else {
258                        option.kebab_case()
259                    };
260
261                    format!("{} = '{}'", option_name, option_value)
262                })
263                .collect::<HashSet<String>>()
264                .into_iter()
265                .collect::<Vec<_>>()
266                .join(", ")
267        )
268    }
269
270    /// Generates a effect Schema for the enum
271    pub fn as_typescript_schema(&self) -> String {
272        let name = replace_reserved_keyword(&self.name, Lang::TypeScript)
273            .replace("::", "")
274            .pascal_case();
275        let native_enum = format!(
276            "export enum {}Enum {{ {} }}\n",
277            name,
278            self.options
279                .iter()
280                .map(|option| {
281                    let option_name = option.pascal_case();
282                    let option_value = if option.chars().all(char::is_uppercase) {
283                        option.to_lowercase()
284                    } else {
285                        option.kebab_case()
286                    };
287
288                    format!("{} = '{}'", option_name, option_value)
289                })
290                .collect::<HashSet<_>>()
291                .into_iter()
292                .collect::<Vec<_>>()
293                .join(", ")
294        );
295
296        let schema = format!(
297            "export const {} = Schema.Enums({}Enum)\n // __SEPARATOR__\n",
298            name, name
299        );
300
301        format!("{}\n{}", native_enum, schema)
302    }
303}
304
305impl DataType {
306    fn as_rust_ref(&self, e_name: String) -> String {
307        match self {
308            DataType::String => "String".into(),
309            DataType::Number => "f64".into(),
310            DataType::Boolean => "bool".into(),
311            DataType::Date => "String".into(),
312            DataType::Enum { reference, .. } => {
313                if reference.is_empty() {
314                    e_name.pascal_case()
315                } else {
316                    reference.into()
317                }
318            }
319            DataType::Expandable(expandable) => expandable.reference(),
320            DataType::Array { element_type } => {
321                let name = (*element_type).as_rust_ref(e_name);
322                format!("Vec<{}>", name)
323            }
324            DataType::Unknown => "serde_json::Value".into(),
325        }
326    }
327
328    fn as_typescript_ref(&self, enum_name: String) -> String {
329        match self {
330            DataType::String => "string".into(),
331            DataType::Number => "number".into(),
332            DataType::Boolean => "boolean".into(),
333            DataType::Date => "string".into(),
334            DataType::Enum { reference, .. } => {
335                if reference.is_empty() {
336                    enum_name.pascal_case()
337                } else {
338                    reference.into()
339                }
340            }
341            DataType::Expandable(expandable) => expandable.reference(),
342            DataType::Array { element_type } => {
343                let name = (*element_type).as_typescript_ref(enum_name);
344                format!("{}[]", name)
345            }
346            DataType::Unknown => "unknown".into(),
347        }
348    }
349
350    fn as_typescript_schema(&self, enum_name: String, r#type: SchemaType) -> String {
351        match self {
352            DataType::String => {
353                match r#type {
354                    SchemaType::Lax => "Schema.optional(Schema.NullishOr(Schema.String))".into(),
355                    SchemaType::Strict => "Schema.String".into()
356                }
357            },
358            DataType::Number => {
359                match r#type {
360                    SchemaType::Lax => "Schema.optional(Schema.NullishOr(Schema.Number))".into(),
361                    SchemaType::Strict => "Schema.Number".into()
362                } },
363            DataType::Boolean => {
364                match r#type {
365                    SchemaType::Lax => "Schema.optional(Schema.NullishOr(Schema.Boolean))".into(),
366                    SchemaType::Strict => "Schema.Boolean".into()
367                }
368
369                },
370            DataType::Date => {
371                match r#type {
372                    SchemaType::Lax => "Schema.optional(Schema.NullishOr(Schema.String.pipe(Schema.filter((d) => !isNaN(new Date(d).getTime())))))".into(),
373                    SchemaType::Strict => "Schema.String.pipe(Schema.filter((d) => !isNaN(new Date(d).getTime())))".into()
374                }
375            },
376            DataType::Enum { reference, .. } => {
377                match r#type {
378                    SchemaType::Lax => {
379                        if reference.is_empty() {
380                            format!(
381                                "Schema.optional(Schema.NullishOr({}))",
382                                enum_name.pascal_case()
383                            )
384                        } else {
385                            format!("Schema.optional(Schema.NullishOr({}))", reference)
386                        }
387                    },
388                    SchemaType::Strict => {
389                        if reference.is_empty() {
390                            enum_name.pascal_case()
391                        } else {
392                            reference.into()
393                        }
394                    }
395                }
396
397            }
398            DataType::Expandable(expandable) => {
399                match r#type {
400                    SchemaType::Lax => {
401                        format!(
402                            "Schema.optional(Schema.NullishOr({}))",
403                            expandable.reference()
404                        )
405                    },
406                    SchemaType::Strict => {
407                        expandable.reference()
408                    }
409                }
410            }
411            DataType::Array { element_type } => {
412                match r#type {
413                    SchemaType::Lax => {
414                        let name = (*element_type).as_typescript_schema(enum_name, r#type);
415
416                        let refined = if name.starts_with("Schema.optional(") && name.ends_with(')') {
417                        let without_optional = name.strip_prefix("Schema.optional(").unwrap_or(&name); 
418
419                        if without_optional.starts_with("Schema.NullishOr(") && without_optional.ends_with(')') {
420                            // Strip "Schema.NullishOr(" and the last closing ')'
421                            let without_nullish = without_optional.strip_prefix("Schema.NullishOr(")
422                                .unwrap_or(without_optional)
423                                .strip_suffix(')')
424                                .unwrap_or(without_optional);
425
426                            // Now strip the final closing ')' from the outer "Schema.optional("
427                            without_nullish.strip_suffix(')').unwrap_or(without_nullish)
428                            } else {
429                                // Just strip the closing ')' from "Schema.optional("
430                                without_optional.strip_suffix(')').unwrap_or(without_optional)
431                            }
432                        } else {
433                            &name
434                        };
435
436
437                        format!(
438                            "Schema.optional(Schema.NullishOr(Schema.Array({})))",
439                            refined
440                        )
441                    },
442                    SchemaType::Strict => {
443                        let name = (*element_type).as_typescript_schema(enum_name, r#type);
444                        format!(
445                            "Schema.NonEmptyArray({})",
446                            name
447                        )
448                    }
449                }
450            }
451            DataType::Unknown =>  {
452                match r#type {
453                    SchemaType::Lax => "Schema.optional(Schema.NullishOr(Schema.Unknown))".to_string(),
454                    SchemaType::Strict => "Schema.Unknown".to_string()
455                }
456            }
457        }
458    }
459
460    pub fn schema(&self, format: Option<String>) -> ReferenceOr<Box<Schema>> {
461        match self {
462            DataType::String => ReferenceOr::Item(Box::new(Schema {
463                schema_data: Default::default(),
464                schema_kind: SchemaKind::Type(Type::String(StringType {
465                    format: VariantOrUnknownOrEmpty::Unknown(format.unwrap_or_default()),
466                    pattern: None,
467                    ..Default::default()
468                })),
469            })),
470            DataType::Number => ReferenceOr::Item(Box::new(Schema {
471                schema_data: Default::default(),
472                schema_kind: SchemaKind::Type(Type::Number(NumberType {
473                    format: VariantOrUnknownOrEmpty::Unknown(format.unwrap_or_default()),
474                    ..Default::default()
475                })),
476            })),
477            DataType::Boolean => ReferenceOr::Item(Box::new(Schema {
478                schema_data: Default::default(),
479                schema_kind: SchemaKind::Type(Type::Boolean(BooleanType {
480                    ..Default::default()
481                })),
482            })),
483            DataType::Date => ReferenceOr::Item(Box::new(Schema {
484                schema_data: Default::default(),
485                schema_kind: SchemaKind::Type(Type::String(StringType {
486                    format: VariantOrUnknownOrEmpty::Unknown("date-time".to_string()),
487                    ..Default::default()
488                })),
489            })),
490            DataType::Enum { options, reference } => match options {
491                Some(options) => ReferenceOr::Item(Box::new(Schema {
492                    schema_data: Default::default(),
493                    schema_kind: SchemaKind::Type(Type::String(StringType {
494                        format: VariantOrUnknownOrEmpty::Unknown(format.unwrap_or_default()),
495                        enumeration: options
496                            .iter()
497                            .map(|option| Some(option.to_owned()))
498                            .collect(),
499                        ..Default::default()
500                    })),
501                })),
502                None => ReferenceOr::Reference {
503                    reference: "#/components/schemas/".to_string() + reference,
504                },
505            },
506            DataType::Array { element_type } => ReferenceOr::Item(Box::new(Schema {
507                schema_data: Default::default(),
508                schema_kind: SchemaKind::Type(Type::Array(ArrayType {
509                    items: Some(element_type.schema(format)),
510                    min_items: None,
511                    max_items: None,
512                    unique_items: false,
513                })),
514            })),
515            DataType::Expandable(expandable) => match expandable {
516                Expandable::Expanded { model, .. } => ReferenceOr::Item(Box::new(Schema {
517                    schema_data: Default::default(),
518                    schema_kind: SchemaKind::Type(Type::Object(ObjectType {
519                        properties: {
520                            IndexMap::from_iter(
521                                model
522                                    .fields
523                                    .iter()
524                                    .map(|field| (field.name.clone(), field.datatype.schema(None)))
525                                    .collect::<Vec<_>>(),
526                            )
527                        },
528                        ..Default::default()
529                    })),
530                })),
531                Expandable::Unexpanded { reference } => ReferenceOr::Reference {
532                    reference: "#/components/schemas/".to_string() + reference,
533                },
534                _ => ReferenceOr::Item(Box::new(Schema {
535                    schema_data: Default::default(),
536                    schema_kind: SchemaKind::Type(Type::Object(Default::default())),
537                })),
538            },
539            DataType::Unknown => ReferenceOr::Item(Box::new(Schema {
540                schema_data: Default::default(),
541                schema_kind: SchemaKind::Type(Type::Object(Default::default())),
542            })),
543        }
544    }
545
546    fn is_enum_reference(&self) -> bool {
547        match self {
548            DataType::Enum { reference, .. } => !reference.is_empty(),
549            DataType::Array { element_type } => element_type.is_enum_reference(),
550            _ => false,
551        }
552    }
553
554    fn is_enum_field(&self) -> bool {
555        match self {
556            DataType::Enum { options, .. } => options.is_some(),
557            DataType::Array { element_type } => element_type.is_enum_field(),
558            _ => false,
559        }
560    }
561
562    fn is_expandable(&self) -> bool {
563        match self {
564            DataType::Expandable { .. } => true,
565            DataType::Array { element_type } => element_type.is_expandable(),
566            _ => false,
567        }
568    }
569
570    fn is_primitive(&self) -> bool {
571        match self {
572            DataType::String | DataType::Number | DataType::Boolean | DataType::Date => true,
573            DataType::Array { element_type } => element_type.is_primitive(),
574            _ => false,
575        }
576    }
577
578    pub fn to_name(&self) -> String {
579        match &self {
580            DataType::String => "String".to_owned(),
581            DataType::Number => "Number".to_owned(),
582            DataType::Boolean => "Boolean".to_owned(),
583            DataType::Date => "Date".to_owned(),
584            DataType::Enum { options, .. } => {
585                let options = options.as_ref().unwrap_or(&vec![]).join("|");
586                format!("Enum<{}>", options)
587            }
588            DataType::Expandable(expandable) => match expandable {
589                Expandable::Expanded { reference, .. } => {
590                    format!("Expandable<{reference}>")
591                }
592                Expandable::Unexpanded { reference } => {
593                    format!("Expandable<{reference}>")
594                }
595                Expandable::NotFound { reference } => format!("Expandable<{reference}>"),
596            },
597            DataType::Array { element_type } => {
598                let name = (*element_type).to_name();
599                format!("Array<{name}>")
600            }
601            DataType::Unknown => "unknown".into(),
602        }
603    }
604}
605
606#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
607#[serde(untagged)]
608pub enum Expandable<T = CommonModel> {
609    Expanded { reference: String, model: T },
610    Unexpanded { reference: String },
611    NotFound { reference: String },
612}
613
614impl<T> Expandable<T> {
615    pub fn reference(&self) -> String {
616        match self {
617            Expandable::Expanded { reference, .. } => reference.clone(),
618            Expandable::Unexpanded { reference } => reference.clone(),
619            Expandable::NotFound { reference } => reference.clone(),
620        }
621    }
622}
623
624impl From<UnsavedCommonModel> for CommonModel {
625    fn from(model: UnsavedCommonModel) -> Self {
626        Self {
627            id: Id::now(IdPrefix::CommonModel),
628            name: model.name,
629            fields: model.fields.into_iter().map(|f| f.into()).collect(),
630            sample: model.sample,
631            category: model.category,
632            primary: model.primary,
633            interface: model.interface,
634            record_metadata: Default::default(),
635        }
636    }
637}
638
639impl From<Field<UnsavedCommonModel>> for Field {
640    fn from(field: Field<UnsavedCommonModel>) -> Self {
641        Self {
642            name: field.name,
643            datatype: field.datatype.into(),
644            description: field.description,
645            required: field.required,
646        }
647    }
648}
649
650impl From<DataType<UnsavedCommonModel>> for DataType {
651    fn from(data_type: DataType<UnsavedCommonModel>) -> Self {
652        match data_type {
653            DataType::String => DataType::String,
654            DataType::Number => DataType::Number,
655            DataType::Boolean => DataType::Boolean,
656            DataType::Date => DataType::Date,
657            DataType::Enum { options, reference } => DataType::Enum { options, reference },
658            DataType::Expandable(e) => DataType::Expandable(e.into()),
659            DataType::Array { element_type } => DataType::Array {
660                element_type: Box::new(element_type.deref().clone().into()),
661            },
662            DataType::Unknown => DataType::Unknown,
663        }
664    }
665}
666
667impl From<Expandable<UnsavedCommonModel>> for Expandable {
668    fn from(expandable: Expandable<UnsavedCommonModel>) -> Self {
669        match expandable {
670            Expandable::Expanded { reference, model } => Expandable::Expanded {
671                reference,
672                model: model.into(),
673            },
674            Expandable::Unexpanded { reference } => Expandable::Unexpanded { reference },
675            Expandable::NotFound { reference } => Expandable::NotFound { reference },
676        }
677    }
678}
679
680impl CommonModel {
681    pub fn new(
682        name: String,
683        version: Version,
684        fields: Vec<Field>,
685        category: String,
686        sample: Value,
687        primary: bool,
688        interface: HashMap<Lang, String>,
689    ) -> Self {
690        let mut record = Self {
691            id: Id::new(IdPrefix::CommonModel, chrono::Utc::now()),
692            name,
693            fields,
694            primary,
695            sample,
696            category,
697            interface,
698            record_metadata: Default::default(),
699        };
700        record.record_metadata.version = version;
701        record
702    }
703
704    pub fn new_empty() -> Self {
705        Self {
706            id: Id::new(IdPrefix::CommonModel, chrono::Utc::now()),
707            ..Default::default()
708        }
709    }
710
711    pub fn reference(&self) -> Schema {
712        Schema {
713            schema_data: Default::default(),
714            schema_kind: SchemaKind::Type(Type::Object(ObjectType {
715                properties: self.schema(),
716                ..Default::default()
717            })),
718        }
719    }
720
721    /// Generates the model as a string in the specified language
722    /// without recursively expanding inner models and enums. Simply
723    /// provides a reference to the inner model or enum.
724    ///
725    /// # Arguments
726    /// * `lang` - The language to generate the model in
727    pub fn generate_as(&self, lang: &Lang) -> String {
728        match lang {
729            Lang::Rust => self.as_rust_ref(),
730            Lang::TypeScript => self.as_typescript_ref(),
731            _ => unimplemented!(),
732        }
733    }
734
735    /// Generates the model as a string in the specified language
736    /// with recursively expanded inner models and enums.
737    /// This is useful for generating the entire model and its
738    /// dependencies in a single file.
739    ///
740    /// # Arguments
741    /// * `lang` - The language to generate the model in
742    /// * `cm_store` - The store for common models
743    /// * `ce_store` - The store for common enums
744    /// * `strategy` - The strategy to use for generating the type
745    pub async fn generate_as_expanded<'a>(
746        &self,
747        lang: &Lang,
748        cm_store: &MongoStore<CommonModel>,
749        ce_store: &MongoStore<CommonEnum>,
750        strategy: TypeGenerationStrategy<'a>,
751    ) -> String {
752        match lang {
753            Lang::Rust => self.as_rust_expanded(cm_store, ce_store, strategy).await,
754            Lang::TypeScript => {
755                self.as_typescript_expanded(cm_store, ce_store, strategy)
756                    .await
757            }
758            _ => unimplemented!(),
759        }
760    }
761
762    fn as_rust_ref(&self) -> String {
763        format!(
764            "pub struct {} {{ {} }}\n",
765            replace_reserved_keyword(&self.name, Lang::Rust)
766                .replace("::", "")
767                .pascal_case(),
768            self.fields
769                .iter()
770                .map(|field| field.as_rust_ref())
771                .collect::<HashSet<String>>()
772                .into_iter()
773                .collect::<Vec<_>>()
774                .join(",\n    ")
775        )
776    }
777
778    /// Generates an effect schema for the model in TypeScript
779    fn as_typescript_schema(&self, r#type: SchemaType) -> String {
780        format!(
781            "export const {} = Schema.Struct({{ {} }}).annotations({{ title: '{}' }});\n",
782            replace_reserved_keyword(&self.name, Lang::TypeScript)
783                .replace("::", "")
784                .pascal_case(),
785            self.fields
786                .iter()
787                .map(|field| field.as_typescript_schema(r#type))
788                .collect::<HashSet<String>>()
789                .into_iter()
790                .collect::<Vec<_>>()
791                .join(",\n    "),
792            self.name
793        )
794    }
795
796    fn as_typescript_ref(&self) -> String {
797        format!(
798            "export interface {} {{ {} }}\n",
799            replace_reserved_keyword(&self.name, Lang::TypeScript)
800                .replace("::", "")
801                .pascal_case(),
802            self.fields
803                .iter()
804                .map(|field| field.as_typescript_ref())
805                .collect::<HashSet<String>>()
806                .into_iter()
807                .collect::<Vec<_>>()
808                .join(";\n    ")
809        )
810    }
811
812    pub async fn as_typescript_schema_expanded(
813        &self,
814        cm_store: &MongoStore<CommonModel>,
815        ce_store: &MongoStore<CommonEnum>,
816        r#type: SchemaType,
817    ) -> String {
818        let mut visited_enums = HashSet::new();
819        let mut visited_common_models = HashSet::new();
820
821        let enums = self
822            .fetch_all_enum_references(cm_store.clone(), ce_store.clone())
823            .await
824            .map(|enums| {
825                enums
826                    .iter()
827                    .filter_map(|enum_model| {
828                        if visited_enums.contains(&enum_model.id) {
829                            return None;
830                        }
831
832                        visited_enums.insert(enum_model.id);
833
834                        Some(enum_model.as_typescript_schema())
835                    })
836                    .collect::<HashSet<String>>()
837                    .into_iter()
838                    .collect::<Vec<_>>()
839            })
840            .ok()
841            .unwrap_or_default()
842            .into_iter()
843            .collect::<Vec<_>>();
844
845        let children = self
846            .fetch_all_children_common_models(cm_store.clone())
847            .await
848            .ok()
849            .unwrap_or_default();
850
851        let children_types = children
852            .0
853            .into_values()
854            .filter_map(|child| {
855                if visited_common_models.contains(&child.id) {
856                    return None;
857                }
858                visited_common_models.insert(child.id);
859
860                Some(child.as_typescript_schema(r#type))
861            })
862            .collect::<Vec<_>>()
863            .join("\n // __SEPARATOR__ \n");
864
865        let ce_types = enums.join("\n");
866
867        let cm_types = self.as_typescript_schema(r#type);
868
869        if visited_common_models.contains(&self.id) {
870            format!(
871                "// __SEPARATOR \n {}\n // __SEPARATOR__ \n {}",
872                ce_types, children_types
873            )
874        } else {
875            format!(
876                "// __SEPARATOR__ \n {}\n{}\n // __SEPARATOR__ \n{}",
877                ce_types, children_types, cm_types
878            )
879        }
880    }
881
882    /// Generates the model as a string in the specified language
883    /// with recursively expanded inner models and enums.
884    /// This is useful for generating the entire model and its
885    /// dependencies in a single file.
886    ///
887    /// # Arguments
888    /// * `cm_store` - The store to fetch the common models from
889    /// * `ce_store` - The store to fetch the common enums from
890    ///
891    /// # Returns
892    /// A string of all the enums and models recursively expanded in the specified language
893    async fn as_typescript_expanded<'a>(
894        &self,
895        cm_store: &MongoStore<CommonModel>,
896        ce_store: &MongoStore<CommonEnum>,
897        strategy: TypeGenerationStrategy<'a>,
898    ) -> String {
899        let mut long_lived_visited_enums = HashSet::new();
900        let mut long_lived_visited_common_models = HashSet::new();
901
902        let (visited_enums, visited_common_models) = match strategy {
903            TypeGenerationStrategy::Cumulative {
904                visited_enums,
905                visited_common_models,
906            } => (visited_enums, visited_common_models),
907            TypeGenerationStrategy::Unique => (
908                &mut long_lived_visited_enums,
909                &mut long_lived_visited_common_models,
910            ),
911        };
912
913        let enums = self
914            .fetch_all_enum_references(cm_store.clone(), ce_store.clone())
915            .await
916            .map(|enums| {
917                enums
918                    .iter()
919                    .filter_map(|enum_model| {
920                        if visited_enums.contains(&enum_model.id) {
921                            return None;
922                        }
923
924                        visited_enums.insert(enum_model.id);
925
926                        Some(enum_model.as_typescript_type())
927                    })
928                    .collect::<HashSet<String>>()
929                    .into_iter()
930                    .collect::<Vec<_>>()
931            })
932            .ok()
933            .unwrap_or_default()
934            .into_iter()
935            .collect::<Vec<_>>();
936
937        let children = self
938            .fetch_all_children_common_models(cm_store.clone())
939            .await
940            .ok()
941            .unwrap_or_default();
942
943        let children_types = children
944            .0
945            .into_values()
946            .filter_map(|child| {
947                if visited_common_models.contains(&child.id) {
948                    return None;
949                }
950                visited_common_models.insert(child.id);
951                Some(format!(
952                    "export interface {} {{ {} }}\n",
953                    replace_reserved_keyword(&child.name, Lang::TypeScript)
954                        .replace("::", "")
955                        .pascal_case(),
956                    child
957                        .fields
958                        .iter()
959                        .map(|field| field.as_typescript_ref())
960                        .collect::<HashSet<String>>()
961                        .into_iter()
962                        .collect::<Vec<_>>()
963                        .join(";\n    ")
964                ))
965            })
966            .collect::<Vec<_>>()
967            .join("\n");
968
969        let ce_types = enums.join("\n");
970
971        let cm_types = format!(
972            "export interface {} {{ {} }}\n",
973            replace_reserved_keyword(&self.name, Lang::TypeScript)
974                .replace("::", "")
975                .pascal_case(),
976            self.fields
977                .iter()
978                .map(|field| field.as_typescript_ref())
979                .collect::<HashSet<String>>()
980                .into_iter()
981                .collect::<Vec<_>>()
982                .join(";\n    ")
983        );
984
985        if visited_common_models.contains(&self.id) {
986            format!("{}\n{}", ce_types, children_types)
987        } else {
988            format!("{}\n{}\n{}", ce_types, children_types, cm_types)
989        }
990    }
991
992    /// Generates the model as a string in the specified language
993    /// with recursively expanded inner models and enums.
994    /// This is useful for generating the entire model and its
995    /// dependencies in a single file.
996    ///
997    /// # Arguments
998    /// * `cm_store` - The store to fetch the common models from
999    /// * `ce_store` - The store to fetch the common enums from
1000    ///
1001    /// # Returns
1002    /// A string of all the enums and models recursively expanded in the specified language
1003    async fn as_rust_expanded<'a>(
1004        &self,
1005        cm_store: &MongoStore<CommonModel>,
1006        ce_store: &MongoStore<CommonEnum>,
1007        strategy: TypeGenerationStrategy<'a>,
1008    ) -> String {
1009        let mut long_lived_visited_enums = HashSet::new();
1010        let mut long_lived_visited_common_models = HashSet::new();
1011
1012        let (visited_enums, visited_common_models) = match strategy {
1013            TypeGenerationStrategy::Cumulative {
1014                visited_enums,
1015                visited_common_models,
1016            } => (visited_enums, visited_common_models),
1017            TypeGenerationStrategy::Unique => (
1018                &mut long_lived_visited_enums,
1019                &mut long_lived_visited_common_models,
1020            ),
1021        };
1022
1023        let enums = self
1024            .fetch_all_enum_references(cm_store.clone(), ce_store.clone())
1025            .await
1026            .map(|enums| {
1027                enums
1028                    .iter()
1029                    .filter_map(|enum_model| {
1030                        if visited_enums.contains(&enum_model.id) {
1031                            return None;
1032                        }
1033
1034                        visited_enums.insert(enum_model.id);
1035                        Some(enum_model.as_rust_type())
1036                    })
1037                    .collect::<HashSet<String>>()
1038                    .into_iter()
1039                    .collect::<Vec<_>>()
1040            })
1041            .ok()
1042            .unwrap_or_default()
1043            .into_iter()
1044            .collect::<Vec<_>>();
1045
1046        let children = self
1047            .fetch_all_children_common_models(cm_store.clone())
1048            .await
1049            .ok()
1050            .unwrap_or_default();
1051
1052        let children_types = children
1053            .0
1054            .into_values()
1055            .filter_map(|child| {
1056                if visited_common_models.contains(&child.id) {
1057                    return None;
1058                }
1059                visited_common_models.insert(child.id);
1060                Some(format!(
1061                    "pub struct {} {{ {} }}\n",
1062                    replace_reserved_keyword(&child.name, Lang::Rust)
1063                        .replace("::", "")
1064                        .pascal_case(),
1065                    child
1066                        .fields
1067                        .iter()
1068                        .map(|field| field.as_rust_ref())
1069                        .collect::<HashSet<String>>()
1070                        .into_iter()
1071                        .collect::<Vec<_>>()
1072                        .join(",\n    ")
1073                ))
1074            })
1075            .collect::<Vec<_>>()
1076            .join("\n");
1077
1078        let ce_types = enums.join("\n");
1079
1080        let cm_types = format!(
1081            "pub struct {} {{ {} }}\n",
1082            replace_reserved_keyword(&self.name, Lang::Rust)
1083                .replace("::", "")
1084                .pascal_case(),
1085            self.fields
1086                .iter()
1087                .map(|field| field.as_rust_ref())
1088                .collect::<HashSet<String>>()
1089                .into_iter()
1090                .collect::<Vec<_>>()
1091                .join(",\n    ")
1092        );
1093
1094        if visited_common_models.contains(&self.id) {
1095            format!("{}\n{}", ce_types, children_types)
1096        } else {
1097            format!("{}\n{}\n{}", ce_types, children_types, cm_types)
1098        }
1099    }
1100
1101    fn schema(&self) -> IndexMap<String, ReferenceOr<Box<Schema>>> {
1102        self.fields
1103            .iter()
1104            .fold(IndexMap::new(), |mut index, field| {
1105                let schema = field.datatype.schema(Some(field.name.to_owned()));
1106
1107                index.insert(field.name.clone(), schema);
1108                index
1109            })
1110    }
1111
1112    pub fn request_body(&self, required: bool) -> RequestBody {
1113        let mut content = IndexMap::new();
1114        content.insert(
1115            "application/json".to_string(),
1116            MediaType {
1117                schema: Some(ReferenceOr::Reference {
1118                    reference: "#/components/schemas/".to_owned() + self.name.as_str(),
1119                }),
1120                ..Default::default()
1121            },
1122        );
1123
1124        RequestBody {
1125            content,
1126            required,
1127            ..Default::default()
1128        }
1129    }
1130
1131    pub fn get_expandable_fields(&self) -> Vec<Field> {
1132        self.fields
1133            .iter()
1134            .filter(|field| field.is_expandable())
1135            .cloned()
1136            .collect()
1137    }
1138
1139    pub fn get_primitive_fields(&self) -> Vec<Field> {
1140        self.fields
1141            .iter()
1142            .filter(|field| field.is_primitive())
1143            .cloned()
1144            .collect()
1145    }
1146
1147    pub fn get_enum_references(&self) -> Vec<Field> {
1148        self.fields
1149            .iter()
1150            .filter(|field| field.is_enum_reference())
1151            .map(|field| {
1152                if let DataType::Array { element_type } = &field.datatype {
1153                    Field {
1154                        name: field.name.clone(),
1155                        datatype: element_type.deref().clone(),
1156                        description: field.description.clone(),
1157                        required: field.required,
1158                    }
1159                } else {
1160                    field.clone()
1161                }
1162            })
1163            .collect()
1164    }
1165
1166    pub fn get_enum_fields(&self) -> Vec<Field> {
1167        self.fields
1168            .iter()
1169            .filter(|field| field.is_enum_field())
1170            .map(|field| {
1171                if let DataType::Array { element_type } = &field.datatype {
1172                    Field {
1173                        name: field.name.clone(),
1174                        datatype: element_type.deref().clone(),
1175                        description: field.description.clone(),
1176                        required: field.required,
1177                    }
1178                } else {
1179                    field.clone()
1180                }
1181            })
1182            .collect()
1183    }
1184
1185    pub fn flatten(mut self) -> Vec<CommonModel> {
1186        let mut models = vec![];
1187        for field in &self.fields {
1188            match &field.datatype {
1189                DataType::Expandable(Expandable::Expanded { model, .. }) => {
1190                    models.extend(model.clone().flatten());
1191                }
1192                DataType::Array { element_type } => {
1193                    if let DataType::Expandable(Expandable::Expanded { model, .. }) =
1194                        element_type.deref()
1195                    {
1196                        models.extend(model.clone().flatten());
1197                    }
1198                }
1199                _ => {}
1200            }
1201        }
1202
1203        for field in self.fields.iter_mut() {
1204            match field.datatype {
1205                DataType::Expandable(Expandable::Expanded { ref reference, .. }) => {
1206                    field.datatype = DataType::Expandable(Expandable::Unexpanded {
1207                        reference: reference.clone(),
1208                    })
1209                }
1210                DataType::Array { ref element_type } => {
1211                    if let DataType::Expandable(Expandable::Expanded { ref reference, .. }) =
1212                        element_type.deref()
1213                    {
1214                        field.datatype = DataType::Array {
1215                            element_type: Box::new(DataType::Expandable(Expandable::Unexpanded {
1216                                reference: reference.clone(),
1217                            })),
1218                        }
1219                    }
1220                }
1221                _ => {}
1222            }
1223        }
1224
1225        models.push(self);
1226
1227        models
1228    }
1229
1230    pub async fn expand_all(
1231        &self,
1232        cm_store: MongoStore<CommonModel>,
1233        ce_store: MongoStore<CommonEnum>,
1234    ) -> Result<Self, IntegrationOSError> {
1235        const MAX_NESTING_LEVEL: u8 = 100; // Maximum nesting level of 10; adjust as needed
1236        self.expand_all_recursive(cm_store, ce_store, MAX_NESTING_LEVEL)
1237            .await
1238    }
1239
1240    #[async_recursion]
1241    async fn expand_all_recursive(
1242        &self,
1243        cm_store: MongoStore<CommonModel>,
1244        ce_store: MongoStore<CommonEnum>,
1245        nesting: u8,
1246    ) -> Result<Self, IntegrationOSError> {
1247        if nesting == 0 {
1248            return Ok(self.clone()); // Avoid infinite recursion
1249        }
1250
1251        let mut new_model = self.clone();
1252        let ts = self
1253            .generate_as_expanded(
1254                &Lang::TypeScript,
1255                &cm_store,
1256                &ce_store,
1257                TypeGenerationStrategy::Unique,
1258            )
1259            .await;
1260        let rust = self
1261            .generate_as_expanded(
1262                &Lang::Rust,
1263                &cm_store,
1264                &ce_store,
1265                TypeGenerationStrategy::Unique,
1266            )
1267            .await;
1268        let interface = HashMap::from_iter(vec![(Lang::Rust, rust), (Lang::TypeScript, ts)]);
1269        new_model.interface = interface;
1270        new_model.fields = Vec::new(); // Clear the fields to populate them freshly
1271
1272        for field in &self.fields {
1273            match &field.datatype {
1274                DataType::Expandable(expandable) => {
1275                    let expanded = expandable.expand(cm_store.clone()).await?;
1276                    let expanded_field = Field {
1277                        name: field.name.clone(),
1278                        datatype: DataType::Expandable(expanded),
1279                        required: field.required,
1280                        description: field.description.clone(),
1281                    };
1282
1283                    match &expanded_field.datatype {
1284                        DataType::Expandable(Expandable::Expanded { model, .. }) => {
1285                            let recursively_expanded_model = model
1286                                .expand_all_recursive(
1287                                    cm_store.clone(),
1288                                    ce_store.clone(),
1289                                    nesting - 1,
1290                                )
1291                                .await?;
1292                            new_model.fields.push(Field {
1293                                name: field.name.clone(),
1294                                datatype: DataType::Expandable(Expandable::Expanded {
1295                                    reference: model.name.clone(),
1296                                    model: recursively_expanded_model,
1297                                }),
1298                                required: field.required,
1299                                description: field.description.clone(),
1300                            });
1301                        }
1302                        _ => {
1303                            new_model.fields.push(expanded_field);
1304                        }
1305                    }
1306                }
1307                DataType::Array { element_type } => match &**element_type {
1308                    DataType::Expandable(expandable) => {
1309                        let mut expanded = expandable.expand(cm_store.clone()).await?;
1310                        if let Expandable::Expanded { model, .. } = &expanded {
1311                            let recursively_expanded_model = model
1312                                .expand_all_recursive(
1313                                    cm_store.clone(),
1314                                    ce_store.clone(),
1315                                    nesting - 1,
1316                                )
1317                                .await?;
1318                            expanded = Expandable::Expanded {
1319                                reference: model.name.clone(),
1320                                model: recursively_expanded_model,
1321                            };
1322                        }
1323                        let expanded_field = Field {
1324                            name: field.name.clone(),
1325                            datatype: DataType::Array {
1326                                element_type: Box::new(DataType::Expandable(expanded)),
1327                            },
1328                            required: field.required,
1329                            description: field.description.clone(),
1330                        };
1331                        new_model.fields.push(expanded_field);
1332                    }
1333                    DataType::Enum { reference, .. } if !reference.is_empty() => {
1334                        let enum_model = ce_store.get_one(doc! { "name": reference }).await?;
1335                        if let Some(enum_model) = enum_model {
1336                            new_model.fields.push(Field {
1337                                name: field.name.clone(),
1338                                datatype: DataType::Enum {
1339                                    options: Some(
1340                                        enum_model
1341                                            .options
1342                                            .iter()
1343                                            .map(|option| option.to_owned())
1344                                            .collect(),
1345                                    ),
1346                                    reference: reference.clone(),
1347                                },
1348                                required: field.required,
1349                                description: field.description.clone(),
1350                            });
1351                        }
1352                    }
1353                    _ => {
1354                        new_model.fields.push(field.clone());
1355                    }
1356                },
1357                DataType::Enum { reference, .. } if !reference.is_empty() => {
1358                    let enum_model = ce_store.get_one(doc! { "name": reference }).await?;
1359                    if let Some(enum_model) = enum_model {
1360                        new_model.fields.push(Field {
1361                            name: field.name.clone(),
1362                            datatype: DataType::Enum {
1363                                options: Some(
1364                                    enum_model
1365                                        .options
1366                                        .iter()
1367                                        .map(|option| option.to_owned())
1368                                        .collect(),
1369                                ),
1370                                reference: reference.clone(),
1371                            },
1372                            required: field.required,
1373                            description: field.description.clone(),
1374                        });
1375                    }
1376                }
1377                _ => {
1378                    new_model.fields.push(field.clone());
1379                }
1380            }
1381        }
1382
1383        Ok(new_model)
1384    }
1385
1386    /// Fetches all the enum references and non-enum references of the current model and its children
1387    ///
1388    /// # Arguments
1389    /// * `cm_store` - The store to fetch the common models from
1390    /// * `ce_store` - The store to fetch the common enums from
1391    ///
1392    /// # Returns
1393    /// A vector of all the enum references and flat enums that are not common
1394    pub async fn fetch_all_enum_references(
1395        &self,
1396        cm_store: MongoStore<CommonModel>,
1397        ce_store: MongoStore<CommonEnum>,
1398    ) -> Result<Vec<CommonEnum>, IntegrationOSError> {
1399        let mut enum_references = self
1400            .get_enum_references()
1401            .into_iter()
1402            .filter_map(|x| match x.datatype {
1403                DataType::Enum { reference, .. } => Some(reference.pascal_case()),
1404                _ => None,
1405            })
1406            .collect::<HashSet<_>>();
1407
1408        let mut flat_enums = self
1409            .get_enum_fields()
1410            .into_iter()
1411            .filter_map(|e| match e.datatype {
1412                DataType::Enum { options, .. } => Some(CommonEnum {
1413                    id: Id::now(IdPrefix::CommonEnum),
1414                    name: e.name.pascal_case(),
1415                    options: options.unwrap_or_default(),
1416                }),
1417                _ => None,
1418            })
1419            .collect::<HashSet<_>>();
1420
1421        for (_, child) in self
1422            .fetch_all_children_common_models(cm_store.clone())
1423            .await?
1424            .0
1425        {
1426            enum_references.extend(child.get_enum_references().into_iter().filter_map(|x| {
1427                match x.datatype {
1428                    DataType::Enum { reference, .. } => Some(reference.pascal_case()),
1429                    _ => None,
1430                }
1431            }));
1432
1433            let child_enums = child
1434                .get_enum_fields()
1435                .into_iter()
1436                .filter_map(|e| match e.datatype {
1437                    DataType::Enum { options, .. } => Some(CommonEnum {
1438                        id: Id::now(IdPrefix::CommonEnum),
1439                        name: e.name.pascal_case(),
1440                        options: options.unwrap_or_default(),
1441                    }),
1442                    _ => None,
1443                })
1444                .collect::<HashSet<_>>();
1445
1446            flat_enums.extend(child_enums);
1447        }
1448
1449        let enums = ce_store
1450            .get_many(
1451                Some(doc! {
1452                    "name": {
1453                        "$in": bson::to_bson(&enum_references).map_err(|e| InternalError::invalid_argument(&e.to_string(), Some("enum references")))?,
1454                    }
1455                }),
1456                None,
1457                None,
1458                None,
1459                None,
1460            )
1461            .await?;
1462
1463        let enums = enums
1464            .into_iter()
1465            .chain(flat_enums.into_iter())
1466            .collect::<HashSet<_>>()
1467            .into_iter()
1468            .collect();
1469
1470        Ok(enums)
1471    }
1472
1473    /// Fetches all the children of the current model and returns two values:
1474    ///
1475    /// * A map of the children models with their names as keys
1476    /// * A set of the names of the children models that were not found
1477    pub async fn fetch_all_children_common_models(
1478        &self,
1479        store: MongoStore<CommonModel>,
1480    ) -> Result<(HashMap<String, CommonModel>, HashSet<String>), IntegrationOSError> {
1481        let mut map = HashMap::new();
1482        let mut queue = vec![self.clone()];
1483        let mut not_found = HashSet::new();
1484
1485        while !queue.is_empty() {
1486            let mut refs = HashSet::new();
1487
1488            while let Some(common_model) = queue.pop() {
1489                for field in &common_model.fields {
1490                    let expandable = match &field.datatype {
1491                        DataType::Array { element_type } => {
1492                            let DataType::Expandable(expandable) = &**element_type else {
1493                                continue;
1494                            };
1495
1496                            expandable
1497                        }
1498                        DataType::Expandable(expandable) => expandable,
1499                        _ => {
1500                            continue;
1501                        }
1502                    };
1503
1504                    match expandable {
1505                        Expandable::Expanded { model, .. } => {
1506                            if map.contains_key(&model.name) {
1507                                continue;
1508                            }
1509                            map.insert(model.name.clone(), model.clone());
1510                            queue.push(model.clone());
1511                        }
1512                        Expandable::Unexpanded { reference } => {
1513                            if map.contains_key(reference) {
1514                                continue;
1515                            }
1516                            refs.insert(reference.clone());
1517                        }
1518                        _ => {
1519                            continue;
1520                        }
1521                    };
1522                }
1523            }
1524
1525            let models = store
1526                .get_many(
1527                    Some(doc! {
1528                        "name": {
1529                            "$in": bson::to_bson(&refs).map_err(|e| InternalError::invalid_argument(&e.to_string(), Some("model references")))?,
1530                        }
1531                    }),
1532                    None,
1533                    None,
1534                    None,
1535                    None,
1536                )
1537                .await?;
1538
1539            let not_found_refs: HashSet<String> = refs
1540                .difference(&models.iter().map(|model| model.name.clone()).collect())
1541                .cloned()
1542                .collect();
1543
1544            not_found.extend(not_found_refs);
1545
1546            for model in models {
1547                if map.contains_key(&model.name) {
1548                    continue;
1549                }
1550                map.insert(model.name.clone(), model.clone());
1551                queue.push(model.clone());
1552            }
1553        }
1554        Ok((map, not_found))
1555    }
1556
1557    pub async fn get_all_common_models(
1558        store: MongoStore<CommonModel>,
1559    ) -> Result<Vec<String>, IntegrationOSError> {
1560        let docs = store
1561            .aggregate(vec![doc! {
1562                "$group": {
1563                    "_id": "",
1564                    "list": {"$addToSet": "$name"}
1565                }
1566            }])
1567            .await?;
1568
1569        let first_doc = docs.first().unwrap_or(&doc! {}).clone();
1570
1571        #[derive(Debug, Serialize, Deserialize)]
1572        struct AggregateResult {
1573            list: Vec<String>,
1574        }
1575        Ok(bson::from_document::<AggregateResult>(first_doc)
1576            .map_err(|e| {
1577                InternalError::invalid_argument(&e.to_string(), Some("common model names"))
1578            })?
1579            .list)
1580    }
1581
1582    pub fn to_flat_json(&self) -> Value {
1583        let mut map = Map::new();
1584
1585        for field in &self.fields {
1586            let name = field.datatype.to_name();
1587            map.insert(field.name.clone(), Value::String(name));
1588        }
1589
1590        json!({
1591            "name": self.name,
1592            "fields": Value::Object(map)
1593        })
1594    }
1595}
1596
1597impl Expandable {
1598    pub async fn expand(&self, store: MongoStore<CommonModel>) -> Result<Self, IntegrationOSError> {
1599        Ok(match self {
1600            Expandable::Unexpanded { reference } => {
1601                if let Some(model) = store.get_one(doc! { "name": &reference }).await? {
1602                    Expandable::Expanded {
1603                        reference: reference.clone(),
1604                        model,
1605                    }
1606                } else {
1607                    Expandable::NotFound {
1608                        reference: reference.clone(),
1609                    }
1610                }
1611            }
1612            _ => self.clone(),
1613        })
1614    }
1615}
1616
1617#[cfg(test)]
1618mod tests {
1619    use super::*;
1620
1621    #[test]
1622    fn test_field_as_rust_ref_is_correct() {
1623        let field = Field {
1624            name: "name".to_string(),
1625            datatype: DataType::String,
1626            description: None,
1627            required: true,
1628        };
1629
1630        assert_eq!(field.as_rust_ref(), "pub name: Option<String>");
1631    }
1632
1633    #[test]
1634    fn test_data_type_as_rust_reference_is_correct() {
1635        let data_type = DataType::String;
1636        assert_eq!(data_type.as_rust_ref("String".into()), "String");
1637
1638        let data_type = DataType::Number;
1639        assert_eq!(data_type.as_rust_ref("String".into()), "f64");
1640
1641        let data_type = DataType::Boolean;
1642        assert_eq!(data_type.as_rust_ref("".into()), "bool");
1643
1644        let data_type = DataType::Date;
1645        assert_eq!(data_type.as_rust_ref("".into()), "String");
1646
1647        let data_type = DataType::Enum {
1648            options: Some(vec!["option1".to_string(), "option2".to_string()]),
1649            reference: "Reference".to_string(),
1650        };
1651        assert_eq!(data_type.as_rust_ref("".into()), "Reference");
1652
1653        let data_type = DataType::Expandable(Expandable::Unexpanded {
1654            reference: "Reference".to_string(),
1655        });
1656        assert_eq!(data_type.as_rust_ref("".into()), "Reference");
1657
1658        let data_type = DataType::Array {
1659            element_type: Box::new(DataType::String),
1660        };
1661        assert_eq!(data_type.as_rust_ref("String".into()), "Vec<String>");
1662    }
1663
1664    #[test]
1665    fn test_common_model_as_rust_struct_is_correct() {
1666        let common_model = CommonModel {
1667            id: Id::new(IdPrefix::CommonModel, chrono::Utc::now()),
1668            name: "Model".to_string(),
1669            fields: vec![
1670                Field {
1671                    name: "name".to_string(),
1672                    datatype: DataType::String,
1673                    description: None,
1674                    required: true,
1675                },
1676                Field {
1677                    name: "age".to_string(),
1678                    datatype: DataType::Number,
1679                    description: None,
1680                    required: true,
1681                },
1682            ],
1683            sample: json!({
1684                "name": "John Doe",
1685                "age": 25
1686            }),
1687            primary: true,
1688            category: "Category".to_string(),
1689            interface: Default::default(),
1690            record_metadata: Default::default(),
1691        };
1692
1693        let rust_struct = common_model.as_rust_ref();
1694        let typescript_interface = common_model.as_typescript_ref();
1695
1696        assert!(
1697            rust_struct.contains(
1698                "pub struct Model { pub age: Option<f64>,\n    pub name: Option<String> }"
1699            ) || rust_struct.contains(
1700                "pub struct Model { pub name: Option<String>,\n    pub age: Option<f64> }"
1701            )
1702        );
1703
1704        assert!(
1705            typescript_interface
1706                .contains("export interface Model { age?: number;\n    name?: string }")
1707                || typescript_interface
1708                    .contains("export interface Model { name?: string;\n    age?: number }")
1709        );
1710    }
1711
1712    #[test]
1713    fn test_common_model_as_lax_schema_is_correct() {
1714        let common_model = CommonModel {
1715            id: Id::new(IdPrefix::CommonModel, chrono::Utc::now()),
1716            name: "Model".to_string(),
1717            fields: vec![
1718                Field {
1719                    name: "name".to_string(),
1720                    datatype: DataType::String,
1721                    description: None,
1722                    required: true,
1723                },
1724                Field {
1725                    name: "age".to_string(),
1726                    datatype: DataType::Number,
1727                    description: None,
1728                    required: true,
1729                },
1730            ],
1731            sample: json!({
1732                "name": "John Doe",
1733                "age": 25
1734            }),
1735            primary: true,
1736            category: "Category".to_string(),
1737            interface: Default::default(),
1738            record_metadata: Default::default(),
1739        };
1740
1741        let lax_schema = common_model.as_typescript_schema(SchemaType::Lax);
1742        assert!(
1743            lax_schema.contains(
1744            "export const Model = Schema.Struct({ age: Schema.optional(Schema.NullishOr(Schema.Number)),\n    name: Schema.optional(Schema.NullishOr(Schema.String)) }).annotations({ title: 'Model' });\n") ||
1745            lax_schema.contains(
1746                "export const Model = Schema.Struct({ name: Schema.optional(Schema.NullishOr(Schema.String)),\n    age: Schema.optional(Schema.NullishOr(Schema.Number)) }).annotations({ title: 'Model' });\n"
1747            )
1748        );
1749    }
1750
1751    #[test]
1752    fn test_common_model_as_strict_schema_is_correct() {
1753        let common_model = CommonModel {
1754            id: Id::new(IdPrefix::CommonModel, chrono::Utc::now()),
1755            name: "Model".to_string(),
1756            fields: vec![
1757                Field {
1758                    name: "name".to_string(),
1759                    datatype: DataType::String,
1760                    description: None,
1761                    required: true,
1762                },
1763                Field {
1764                    name: "age".to_string(),
1765                    datatype: DataType::Number,
1766                    description: None,
1767                    required: true,
1768                },
1769            ],
1770            sample: json!({
1771                "name": "John Doe",
1772                "age": 25
1773            }),
1774            primary: true,
1775            category: "Category".to_string(),
1776            interface: Default::default(),
1777            record_metadata: Default::default(),
1778        };
1779
1780        let strict_schema = common_model.as_typescript_schema(SchemaType::Strict);
1781        assert!(
1782            strict_schema.contains(
1783            "export const Model = Schema.Struct({ age: Schema.Number,\n    name: Schema.String }).annotations({ title: 'Model' });\n") ||
1784            strict_schema.contains(
1785                "export const Model = Schema.Struct({ name: Schema.String,\n    age: Schema.Number }).annotations({ title: 'Model' });\n"
1786            )
1787        );
1788    }
1789}