torn_api_codegen/model/
mod.rs

1use indexmap::IndexMap;
2use newtype::Newtype;
3use object::Object;
4use parameter::Parameter;
5use path::Path;
6use proc_macro2::TokenStream;
7use r#enum::Enum;
8use scope::Scope;
9
10use crate::openapi::{r#type::OpenApiType, schema::OpenApiSchema};
11
12pub mod r#enum;
13pub mod newtype;
14pub mod object;
15pub mod parameter;
16pub mod path;
17pub mod scope;
18pub mod union;
19
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub enum Model {
22    Newtype(Newtype),
23    Enum(Enum),
24    Object(Object),
25    Unresolved,
26}
27
28impl Model {
29    pub fn is_display(&self, resolved: &ResolvedSchema) -> bool {
30        match self {
31            Self::Enum(r#enum) => r#enum.is_display(resolved),
32            Self::Newtype(_) => true,
33            _ => false,
34        }
35    }
36}
37
38#[derive(Debug, Default)]
39pub struct ResolvedSchema {
40    pub models: IndexMap<String, Model>,
41    pub paths: IndexMap<String, Path>,
42    pub parameters: Vec<Parameter>,
43}
44
45impl ResolvedSchema {
46    pub fn from_open_api(schema: &OpenApiSchema) -> Self {
47        let mut result = Self::default();
48
49        for (name, r#type) in &schema.components.schemas {
50            result.models.insert(
51                name.to_string(),
52                resolve(r#type, name, &schema.components.schemas),
53            );
54        }
55
56        for (path, body) in &schema.paths {
57            result.paths.insert(
58                path.to_string(),
59                Path::from_schema(path, body, &schema.components.parameters).unwrap(),
60            );
61        }
62
63        for (name, param) in &schema.components.parameters {
64            result
65                .parameters
66                .push(Parameter::from_schema(name, param).unwrap());
67        }
68
69        result
70    }
71
72    pub fn codegen_models(&self) -> TokenStream {
73        let mut output = TokenStream::default();
74
75        for model in self.models.values() {
76            output.extend(model.codegen(self));
77        }
78
79        output
80    }
81
82    pub fn codegen_requests(&self) -> TokenStream {
83        let mut output = TokenStream::default();
84
85        for path in self.paths.values() {
86            output.extend(path.codegen_request(self));
87        }
88
89        output
90    }
91
92    pub fn codegen_parameters(&self) -> TokenStream {
93        let mut output = TokenStream::default();
94
95        for param in &self.parameters {
96            output.extend(param.codegen(self));
97        }
98
99        output
100    }
101
102    pub fn codegen_scopes(&self) -> TokenStream {
103        let mut output = TokenStream::default();
104
105        let scopes = Scope::from_paths(self.paths.values().cloned().collect());
106
107        for scope in scopes {
108            output.extend(scope.codegen());
109        }
110
111        output
112    }
113}
114
115pub fn resolve(r#type: &OpenApiType, name: &str, schemas: &IndexMap<&str, OpenApiType>) -> Model {
116    match r#type {
117        OpenApiType {
118            r#enum: Some(_), ..
119        } => Enum::from_schema(name, r#type).map_or(Model::Unresolved, Model::Enum),
120        OpenApiType {
121            r#type: Some("object"),
122            ..
123        } => Object::from_schema_object(name, r#type, schemas)
124            .map_or(Model::Unresolved, Model::Object),
125        OpenApiType {
126            r#type: Some(_), ..
127        } => Newtype::from_schema(name, r#type).map_or(Model::Unresolved, Model::Newtype),
128        OpenApiType {
129            one_of: Some(types),
130            ..
131        } => Enum::from_one_of(name, types).map_or(Model::Unresolved, Model::Enum),
132        OpenApiType {
133            all_of: Some(types),
134            ..
135        } => Object::from_all_of(name, types, schemas).map_or(Model::Unresolved, Model::Object),
136        _ => Model::Unresolved,
137    }
138}
139
140impl Model {
141    pub fn codegen(&self, resolved: &ResolvedSchema) -> Option<TokenStream> {
142        match self {
143            Self::Newtype(newtype) => newtype.codegen(),
144            Self::Enum(r#enum) => r#enum.codegen(resolved),
145            Self::Object(object) => object.codegen(resolved),
146            Self::Unresolved => None,
147        }
148    }
149}
150
151#[cfg(test)]
152mod test {
153    use super::*;
154    use crate::openapi::schema::test::get_schema;
155
156    #[test]
157    fn resolve_newtypes() {
158        let schema = get_schema();
159
160        let user_id_schema = schema.components.schemas.get("UserId").unwrap();
161
162        let user_id = resolve(user_id_schema, "UserId", &schema.components.schemas);
163
164        assert_eq!(
165            user_id,
166            Model::Newtype(Newtype {
167                name: "UserId".to_owned(),
168                description: None,
169                inner: newtype::NewtypeInner::I32,
170                copy: true,
171                ord: true
172            })
173        );
174
175        let attack_code_schema = schema.components.schemas.get("AttackCode").unwrap();
176
177        let attack_code = resolve(attack_code_schema, "AttackCode", &schema.components.schemas);
178
179        assert_eq!(
180            attack_code,
181            Model::Newtype(Newtype {
182                name: "AttackCode".to_owned(),
183                description: None,
184                inner: newtype::NewtypeInner::Str,
185                copy: false,
186                ord: false
187            })
188        );
189    }
190
191    #[test]
192    fn resolve_all() {
193        let schema = get_schema();
194
195        let mut unresolved = vec![];
196        let total = schema.components.schemas.len();
197
198        for (name, desc) in &schema.components.schemas {
199            if resolve(desc, name, &schema.components.schemas) == Model::Unresolved {
200                unresolved.push(name);
201            }
202        }
203
204        if !unresolved.is_empty() {
205            panic!(
206                "Failed to resolve {}/{} types. Could not resolve [{}]",
207                unresolved.len(),
208                total,
209                unresolved
210                    .into_iter()
211                    .map(|u| format!("`{u}`"))
212                    .collect::<Vec<_>>()
213                    .join(", ")
214            )
215        }
216    }
217}