use indexmap::IndexMap; use newtype::Newtype; use object::Object; use parameter::Parameter; use path::Path; use proc_macro2::TokenStream; use r#enum::Enum; use scope::Scope; use crate::openapi::{r#type::OpenApiType, schema::OpenApiSchema}; pub mod r#enum; pub mod newtype; pub mod object; pub mod parameter; pub mod path; pub mod scope; pub mod union; #[derive(Debug, Clone, PartialEq, Eq)] pub enum Model { Newtype(Newtype), Enum(Enum), Object(Object), Unresolved, } impl Model { pub fn is_display(&self, resolved: &ResolvedSchema) -> bool { match self { Self::Enum(r#enum) => r#enum.is_display(resolved), Self::Newtype(_) => true, _ => false, } } } #[derive(Debug, Default)] pub struct ResolvedSchema { pub models: IndexMap, pub paths: IndexMap, pub parameters: Vec, } impl ResolvedSchema { pub fn from_open_api(schema: &OpenApiSchema) -> Self { let mut result = Self::default(); for (name, r#type) in &schema.components.schemas { result.models.insert( name.to_string(), resolve(r#type, name, &schema.components.schemas), ); } for (path, body) in &schema.paths { result.paths.insert( path.to_string(), Path::from_schema(path, body, &schema.components.parameters).unwrap(), ); } for (name, param) in &schema.components.parameters { result .parameters .push(Parameter::from_schema(name, param).unwrap()); } result } pub fn codegen_models(&self) -> TokenStream { let mut output = TokenStream::default(); for model in self.models.values() { output.extend(model.codegen(self)); } output } pub fn codegen_requests(&self) -> TokenStream { let mut output = TokenStream::default(); for path in self.paths.values() { output.extend(path.codegen_request(self)); } output } pub fn codegen_parameters(&self) -> TokenStream { let mut output = TokenStream::default(); for param in &self.parameters { output.extend(param.codegen(self)); } output } pub fn codegen_scopes(&self) -> TokenStream { let mut output = TokenStream::default(); let scopes = Scope::from_paths(self.paths.values().cloned().collect()); for scope in scopes { output.extend(scope.codegen()); } output } } pub fn resolve(r#type: &OpenApiType, name: &str, schemas: &IndexMap<&str, OpenApiType>) -> Model { match r#type { OpenApiType { r#enum: Some(_), .. } => Enum::from_schema(name, r#type).map_or(Model::Unresolved, Model::Enum), OpenApiType { r#type: Some("object"), .. } => Object::from_schema_object(name, r#type, schemas) .map_or(Model::Unresolved, Model::Object), OpenApiType { r#type: Some(_), .. } => Newtype::from_schema(name, r#type).map_or(Model::Unresolved, Model::Newtype), OpenApiType { one_of: Some(types), .. } => Enum::from_one_of(name, types).map_or(Model::Unresolved, Model::Enum), OpenApiType { all_of: Some(types), .. } => Object::from_all_of(name, types, schemas).map_or(Model::Unresolved, Model::Object), _ => Model::Unresolved, } } impl Model { pub fn codegen(&self, resolved: &ResolvedSchema) -> Option { match self { Self::Newtype(newtype) => newtype.codegen(), Self::Enum(r#enum) => r#enum.codegen(resolved), Self::Object(object) => object.codegen(resolved), Self::Unresolved => None, } } } #[cfg(test)] mod test { use super::*; use crate::openapi::schema::test::get_schema; #[test] fn resolve_newtypes() { let schema = get_schema(); let user_id_schema = schema.components.schemas.get("UserId").unwrap(); let user_id = resolve(user_id_schema, "UserId", &schema.components.schemas); assert_eq!( user_id, Model::Newtype(Newtype { name: "UserId".to_owned(), description: None, inner: newtype::NewtypeInner::I32, copy: true, ord: true }) ); let attack_code_schema = schema.components.schemas.get("AttackCode").unwrap(); let attack_code = resolve(attack_code_schema, "AttackCode", &schema.components.schemas); assert_eq!( attack_code, Model::Newtype(Newtype { name: "AttackCode".to_owned(), description: None, inner: newtype::NewtypeInner::Str, copy: false, ord: false }) ); } #[test] fn resolve_all() { let schema = get_schema(); let mut unresolved = vec![]; let total = schema.components.schemas.len(); for (name, desc) in &schema.components.schemas { if resolve(desc, name, &schema.components.schemas) == Model::Unresolved { unresolved.push(name); } } if !unresolved.is_empty() { panic!( "Failed to resolve {}/{} types. Could not resolve [{}]", unresolved.len(), total, unresolved .into_iter() .map(|u| format!("`{u}`")) .collect::>() .join(", ") ) } } }