diff --git a/src/lib.rs b/src/lib.rs index de1e8a2..e599daa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -104,6 +104,56 @@ impl syn::parse::Parse for MetadataAttr { } } +struct MetaAttr { + into: Option, + skip: bool, + optional: bool, +} + +/// Parses Meta information a field by going through it's attributes +fn parse_field_attr(attrs: &[syn::Attribute]) -> syn::Result { + let mut meta_attr = MetaAttr { + into: None, + skip: false, + optional: false, + }; + + for attr in attrs { + if !attr.path().is_ident("meta") { + continue; + } + + attr.parse_nested_meta(|meta| { + if meta.path.is_ident("into") { + meta.input.parse::()?; + let ty: syn::Type = meta.input.parse()?; + meta_attr.into = Some(ty); + return Ok(()); + } + + if meta.path.is_ident("skip") { + meta_attr.skip = true; + return Ok(()); + } + + if meta.path.is_ident("optional") { + meta_attr.optional = true; + return Ok(()); + } + Err(meta.error("Expected #[meta(into = Type)], #[meta(skip)] or #[meta(optional)]")) + })?; + } + + if (meta_attr.into.is_some() || meta_attr.optional) && meta_attr.skip { + return Err(s_err( + proc_macro2::Span::call_site(), + "skip allows no further arguments", + )); + } + + Ok(meta_attr) +} + /// Generates a TypeScript API client and axum compatible router. /// /// ## Parameters @@ -171,6 +221,7 @@ fn generate_inner(input: TokenStream) -> syn::Result { .iter() .flat_map(|f| f.to_routes()) .collect(); + let mut fn_infos = HashMap::new(); let mut type_infos = HashMap::new(); @@ -256,7 +307,8 @@ fn generate_inner(input: TokenStream) -> syn::Result { let metadata_attr = attr .parse_args::() .unwrap_or(MetadataAttr { custom: vec![] }); - let enum_type = generate_enum(item_enum.clone(), metadata_attr)?; + let enum_type = + TypeInfo::from_enum_tokens(item_enum.clone(), metadata_attr)?; if !type_infos.contains_key(&enum_type.name) { type_infos .insert(enum_type.name.clone(), TypeCategory::Enum(enum_type)); @@ -270,7 +322,8 @@ fn generate_inner(input: TokenStream) -> syn::Result { let metadata_attr = attr .parse_args::() .unwrap_or(MetadataAttr { custom: vec![] }); - let struct_type = generate_struct(item_struct.clone(), metadata_attr)?; + let struct_type = + TypeInfo::from_struct_tokens(item_struct.clone(), metadata_attr)?; if !type_infos.contains_key(&struct_type.name) { type_infos.insert( struct_type.name.clone(), @@ -286,7 +339,8 @@ fn generate_inner(input: TokenStream) -> syn::Result { let metadata_attr = attr .parse_args::() .unwrap_or(MetadataAttr { custom: vec![] }); - let type_type = generate_type(item_type.clone(), metadata_attr)?; + let type_type = + TypeInfo::from_type_tokens(item_type.clone(), metadata_attr)?; if !type_infos.contains_key(&type_type.name) { type_infos .insert(type_type.name.clone(), TypeCategory::Type(type_type)); @@ -300,7 +354,8 @@ fn generate_inner(input: TokenStream) -> syn::Result { let metadata_attr = attr .parse_args::() .unwrap_or(MetadataAttr { custom: vec![] }); - let fn_info = generate_function(item_fn.clone(), metadata_attr)?; + let fn_info = + FnInfo::from_function_tokens(item_fn.clone(), metadata_attr)?; if !fn_infos.contains_key(&fn_info.name) { fn_infos.insert(fn_info.name.clone(), fn_info); } @@ -446,6 +501,7 @@ impl Parse for GenerateArgs { } } +/// Syntax in `routers` field. #[derive(Clone)] struct MethodRouter { url: LitStr, @@ -555,6 +611,68 @@ struct FnInfo { } impl FnInfo { + /// Generates function info from token input + fn from_function_tokens( + item_fn: syn::ItemFn, + metadata_attr: MetadataAttr, + ) -> syn::Result { + let fn_name_ident = item_fn.sig.ident.clone(); + let name = fn_name_ident.to_string(); + let docs = get_docs(&item_fn.attrs); + + let mut dependencies = Vec::new(); + + let params = item_fn + .sig + .inputs + .iter() + .filter_map(|param| match param { + syn::FnArg::Typed(syn::PatType { pat, ty, .. }) => { + let pat = pat.to_token_stream().to_string(); + if let Some(rust_type) = RustType::from_tokens(ty, &metadata_attr.custom) { + process_rust_type(&rust_type, &mut dependencies, &[]); + Some(Ok((pat, rust_type))) + } else { + None + } + } + syn::FnArg::Receiver(_) => { + Some(Err(s_err(param.span(), "Receiver parameter not allowed"))) + } + }) + .collect::>>()?; + + let response = match &item_fn.sig.output { + syn::ReturnType::Type(_, ty) => { + if let Some(rust_type) = RustType::from_tokens(ty, &metadata_attr.custom) { + process_rust_type(&rust_type, &mut dependencies, &[]); + rust_type + } else { + return Err(s_err(ty.span(), "Unsupported return type")); + } + } + syn::ReturnType::Default => RustType::BuiltIn("()".to_string()), + }; + + let params_info: Vec = params + .iter() + .map(|(pat, ty)| Field { + name: pat.clone(), + ty: ty.clone(), + docs: vec![], + optional: false, + }) + .collect(); + + Ok(FnInfo { + name, + params: params_info, + response, + types: dependencies, + docs, + }) + } + fn generate_ts_function( &self, route: &Route, @@ -728,335 +846,180 @@ impl TypeCategory { } } -fn generate_struct( - item_struct: syn::ItemStruct, - metadata_attr: MetadataAttr, -) -> syn::Result { - let struct_name_ident = item_struct.ident.clone(); - let struct_name = struct_name_ident.to_string(); - let generics: Vec = item_struct - .generics - .type_params() - .map(|type_param| type_param.ident.to_string()) - .collect(); - let docs = get_docs(&item_struct.attrs); +/// Type information. +#[derive(Clone, Debug)] +struct TypeInfo { + name: String, + generics: Vec, + fields: Vec, + dependencies: Vec, + docs: Vec, +} - let mut dependencies: Vec = Vec::new(); +impl TypeInfo { + /// Generates type info of structs from token input + fn from_struct_tokens( + item_struct: syn::ItemStruct, + metadata_attr: MetadataAttr, + ) -> syn::Result { + let struct_name_ident = item_struct.ident.clone(); + let struct_name = struct_name_ident.to_string(); + let generics: Vec = item_struct + .generics + .type_params() + .map(|type_param| type_param.ident.to_string()) + .collect(); + let docs = get_docs(&item_struct.attrs); + + let mut dependencies: Vec = Vec::new(); + + let item_struct_fields = item_struct.fields.clone(); + + let fields = item_struct_fields + .iter() + .filter_map(|field| { + let ident = match field.ident.clone() { + Some(ident) => ident.to_string(), + None => { + return Some(Err(s_err( + field.span(), + "Unnamed fields like `self` are not supported", + ))) + } + }; + + let docs = get_docs(&field.attrs); + + let meta_attr = match parse_field_attr(&field.attrs) { + Ok(meta_attr) => meta_attr, + Err(_) => { + return Some(Err(s_err( + field.span(), + format!( + "An error occurred when parsing field attributes on struct '{}'", + struct_name + ), + ))) + } + }; - let item_struct_fields = item_struct.fields.clone(); + let MetaAttr { + into, + skip, + optional, + } = meta_attr; - let fields = item_struct_fields - .iter() - .filter_map(|field| { - let ident = match field.ident.clone() { - Some(ident) => ident.to_string(), - None => { - return Some(Err(s_err( - field.span(), - "Unnamed fields like `self` are not supported", - ))) - } - }; + let field_ty = if let Some(conv_fn) = into.clone() { + conv_fn + } else { + field.ty.clone() + }; - let docs = get_docs(&field.attrs); - - let meta_attr = match parse_field_attr(&field.attrs) { - Ok(meta_attr) => meta_attr, - Err(_) => { - return Some(Err(s_err( - field.span(), - format!( - "An error occurred when parsing field attributes on struct '{}'", - struct_name - ), - ))) + if skip { + return None; } - }; - - let MetaAttr { - into, - skip, - optional, - } = meta_attr; - let field_ty = if let Some(conv_fn) = into.clone() { - conv_fn - } else { - field.ty.clone() - }; - - if skip { - return None; - } + if let Some(ty) = RustType::from_tokens(&field_ty, &metadata_attr.custom) { + process_rust_type(&ty, &mut dependencies, &generics); + Some(Ok(Field { + name: ident, + ty, + docs, + optional, + })) + } else { + Some(Err(s_err(field.span(), "Unsupported Rust Type"))) + } + }) + .collect::>>()?; - if let Some(ty) = to_rust_type(&field_ty, &metadata_attr.custom) { - process_rust_type(&ty, &mut dependencies, &generics); - Some(Ok(Field { - name: ident, - ty, - docs, - optional, - })) - } else { - Some(Err(s_err(field.span(), "Unsupported Rust Type"))) - } + Ok(Self { + name: struct_name, + generics, + fields, + dependencies, + docs, }) - .collect::>>()?; - - Ok(TypeInfo { - name: struct_name, - generics, - fields, - dependencies, - docs, - }) -} - -struct MetaAttr { - into: Option, - skip: bool, - optional: bool, -} - -fn parse_field_attr(attrs: &[syn::Attribute]) -> syn::Result { - let mut meta_attr = MetaAttr { - into: None, - skip: false, - optional: false, - }; - - for attr in attrs { - if !attr.path().is_ident("meta") { - continue; - } - - attr.parse_nested_meta(|meta| { - if meta.path.is_ident("into") { - meta.input.parse::()?; - let ty: syn::Type = meta.input.parse()?; - meta_attr.into = Some(ty); - return Ok(()); - } - - if meta.path.is_ident("skip") { - meta_attr.skip = true; - return Ok(()); - } - - if meta.path.is_ident("optional") { - meta_attr.optional = true; - return Ok(()); - } - Err(meta.error("Expected #[meta(into = Type)], #[meta(skip)] or #[meta(optional)]")) - })?; - } - - if (meta_attr.into.is_some() || meta_attr.optional) && meta_attr.skip { - return Err(s_err( - proc_macro2::Span::call_site(), - "skip allows no further arguments", - )); } - Ok(meta_attr) -} - -fn generate_enum(item_enum: syn::ItemEnum, _: MetadataAttr) -> syn::Result { - if !item_enum.generics.params.is_empty() { - return Err(s_err( - item_enum.generics.span(), - "Generics and Lifetimes not supported for enums", - )); - } - - let enum_name_ident = item_enum.ident.clone(); - let enum_name = enum_name_ident.to_string(); - let docs = get_docs(&item_enum.attrs); - - let fields = item_enum - .variants - .iter() - .map(|variant| { - if !variant.fields.is_empty() { - return Err(s_err( - variant.fields.span(), - "Enums with values are not supported", - )); - } - let ident = variant.ident.to_string(); - let docs = get_docs(&variant.attrs); - Ok(Field { - name: ident, - ty: RustType::None, - docs, - optional: false, - }) - }) - .collect::>>()?; - - Ok(TypeInfo { - name: enum_name, - generics: vec![], - fields, - dependencies: vec![], - docs, - }) -} - -fn generate_type(item_type: syn::ItemType, metadata_attr: MetadataAttr) -> syn::Result { - let type_name_ident = item_type.ident.clone(); - let type_name = type_name_ident.to_string(); - let generics: Vec = item_type - .generics - .type_params() - .map(|type_param| type_param.ident.to_string()) - .collect(); - let docs = get_docs(&item_type.attrs); - - let mut dependencies: Vec = Vec::new(); - - let ty = to_rust_type(&item_type.ty, &metadata_attr.custom) - .ok_or_else(|| s_err(item_type.ty.span(), "Unsupported type"))?; - - process_rust_type(&ty, &mut dependencies, &generics); - - Ok(TypeInfo { - name: type_name, - generics, - fields: vec![Field { - name: String::new(), - ty, - docs: vec![], - optional: false, - }], - dependencies, - docs, - }) -} - -fn generate_function(item_fn: syn::ItemFn, metadata_attr: MetadataAttr) -> syn::Result { - let fn_name_ident = item_fn.sig.ident.clone(); - let name = fn_name_ident.to_string(); - let docs = get_docs(&item_fn.attrs); + /// Generates type info of enums from token input + fn from_enum_tokens(item_enum: syn::ItemEnum, _: MetadataAttr) -> syn::Result { + if !item_enum.generics.params.is_empty() { + return Err(s_err( + item_enum.generics.span(), + "Generics and Lifetimes not supported for enums", + )); + } - let mut dependencies = Vec::new(); + let enum_name_ident = item_enum.ident.clone(); + let enum_name = enum_name_ident.to_string(); + let docs = get_docs(&item_enum.attrs); - let params = item_fn - .sig - .inputs - .iter() - .filter_map(|param| match param { - syn::FnArg::Typed(syn::PatType { pat, ty, .. }) => { - let pat = pat.to_token_stream().to_string(); - if let Some(rust_type) = to_rust_type(ty, &metadata_attr.custom) { - process_rust_type(&rust_type, &mut dependencies, &[]); - Some(Ok((pat, rust_type))) - } else { - None + let fields = item_enum + .variants + .iter() + .map(|variant| { + if !variant.fields.is_empty() { + return Err(s_err( + variant.fields.span(), + "Enums with values are not supported", + )); } - } - syn::FnArg::Receiver(_) => { - Some(Err(s_err(param.span(), "Receiver parameter not allowed"))) - } - }) - .collect::>>()?; - - let response = match &item_fn.sig.output { - syn::ReturnType::Type(_, ty) => { - if let Some(rust_type) = to_rust_type(ty, &metadata_attr.custom) { - process_rust_type(&rust_type, &mut dependencies, &[]); - rust_type - } else { - return Err(s_err(ty.span(), "Unsupported return type")); - } - } - syn::ReturnType::Default => RustType::BuiltIn("()".to_string()), - }; + let ident = variant.ident.to_string(); + let docs = get_docs(&variant.attrs); + Ok(Field { + name: ident, + ty: RustType::None, + docs, + optional: false, + }) + }) + .collect::>>()?; - let params_info: Vec = params - .iter() - .map(|(pat, ty)| Field { - name: pat.clone(), - ty: ty.clone(), - docs: vec![], - optional: false, + Ok(Self { + name: enum_name, + generics: vec![], + fields, + dependencies: vec![], + docs, }) - .collect(); - - Ok(FnInfo { - name, - params: params_info, - response, - types: dependencies, - docs, - }) -} - -fn process_rust_type(rust_type: &RustType, dependencies: &mut Vec, generics: &[String]) { - match rust_type { - RustType::Custom(inner_ty) => { - if !dependencies.contains(inner_ty) && !generics.contains(inner_ty) { - dependencies.push(inner_ty.clone()); - } - } - RustType::CustomGeneric(outer_ty, inner_tys) => { - if !dependencies.contains(outer_ty) && !generics.contains(outer_ty) { - dependencies.push(outer_ty.clone()); - } - for inner_ty in inner_tys { - process_rust_type(inner_ty, dependencies, generics); - } - } - RustType::Tuple(inner_tys) => { - for inner_ty in inner_tys { - process_rust_type(inner_ty, dependencies, generics); - } - } - RustType::Generic(_, inner_tys) => { - for inner_ty in inner_tys { - process_rust_type(inner_ty, dependencies, generics); - } - } - _ => {} } -} -fn get_docs(attrs: &[syn::Attribute]) -> Vec { - attrs - .iter() - .filter_map(|attr| { - if attr.path().is_ident("doc") { - Some( - syn::parse::( - attr.meta - .require_name_value() - .ok()? - .value - .to_token_stream() - .into(), - ) - .ok()? - .value(), - ) - } else { - None - } + /// Generates type info of types from token input + fn from_type_tokens( + item_type: syn::ItemType, + metadata_attr: MetadataAttr, + ) -> syn::Result { + let type_name_ident = item_type.ident.clone(); + let type_name = type_name_ident.to_string(); + let generics: Vec = item_type + .generics + .type_params() + .map(|type_param| type_param.ident.to_string()) + .collect(); + let docs = get_docs(&item_type.attrs); + + let mut dependencies: Vec = Vec::new(); + + let ty = RustType::from_tokens(&item_type.ty, &metadata_attr.custom) + .ok_or_else(|| s_err(item_type.ty.span(), "Unsupported type"))?; + + process_rust_type(&ty, &mut dependencies, &generics); + + Ok(Self { + name: type_name, + generics, + fields: vec![Field { + name: String::new(), + ty, + docs: vec![], + optional: false, + }], + dependencies, + docs, }) - .map(|s| s.trim().to_string()) - .collect() -} - -/// Type information. -#[derive(Clone, Debug)] -struct TypeInfo { - name: String, - generics: Vec, - fields: Vec, - dependencies: Vec, - docs: Vec, -} + } -impl TypeInfo { fn generate_interface( &self, interfaces: &BTreeMap, @@ -1149,6 +1112,62 @@ fn generate_docstring(docs: &[String], ident: &str) -> String { docstring } +/// Fills the dependencies for `Custom` and `CustomGeneric` types +fn process_rust_type(rust_type: &RustType, dependencies: &mut Vec, generics: &[String]) { + match rust_type { + RustType::Custom(inner_ty) => { + if !dependencies.contains(inner_ty) && !generics.contains(inner_ty) { + dependencies.push(inner_ty.clone()); + } + } + RustType::CustomGeneric(outer_ty, inner_tys) => { + if !dependencies.contains(outer_ty) && !generics.contains(outer_ty) { + dependencies.push(outer_ty.clone()); + } + for inner_ty in inner_tys { + process_rust_type(inner_ty, dependencies, generics); + } + } + RustType::Tuple(inner_tys) => { + for inner_ty in inner_tys { + process_rust_type(inner_ty, dependencies, generics); + } + } + RustType::Generic(_, inner_tys) => { + for inner_ty in inner_tys { + process_rust_type(inner_ty, dependencies, generics); + } + } + _ => {} + } +} + +/// Gets docs by collecting the `LitStr` of `doc` attributes +fn get_docs(attrs: &[syn::Attribute]) -> Vec { + attrs + .iter() + .filter_map(|attr| { + if attr.path().is_ident("doc") { + Some( + syn::parse::( + attr.meta + .require_name_value() + .ok()? + .value + .to_token_stream() + .into(), + ) + .ok()? + .value(), + ) + } else { + None + } + }) + .map(|s| s.trim().to_string()) + .collect() +} + /// Field information. #[derive(Clone, Debug)] struct Field { @@ -1158,6 +1177,7 @@ struct Field { optional: bool, } +/// Type wrapper for `axum`'s types. #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug)] enum ApiType { Unknown(String), @@ -1181,6 +1201,7 @@ impl ApiType { } } +/// Contains all TypeScript code. struct ParsedTypeScript<'a> { prefix: String, basic_functions: String, @@ -1211,6 +1232,7 @@ impl<'a> ParsedTypeScript<'a> { } } + /// Creates the `ParsedTypeScript` struct with a few default stuff fn filled(prefix: &'a str) -> ParsedTypeScript { let prefix = format!("const PREFIX = '{}';\n", prefix); let basic_functions = @@ -1236,6 +1258,7 @@ impl<'a> ParsedTypeScript<'a> { ParsedTypeScript::new(prefix, basic_functions, namespace_start, namespace_end) } + /// Adds the query parser to the `basic_functions` fn fill_query_parser(&mut self) { self.basic_functions += r#" function query_str(params: Record): string { @@ -1251,8 +1274,8 @@ impl<'a> ParsedTypeScript<'a> { "#; } + /// Write the parsed type script to the `path` fn write_to_file>(&self, path: P) -> std::io::Result<()> { - // todo: errors let mut file = fs::File::create(path)?; file.write_all(self.prefix.as_bytes())?; @@ -1292,6 +1315,7 @@ impl<'a> ParsedTypeScript<'a> { } } +/// The parsed Rust Type. #[derive(Debug, PartialEq, Clone)] enum RustType { BuiltIn(String), @@ -1303,6 +1327,7 @@ enum RustType { } impl RustType { + /// Unwrap a Rust Type into the Type String fn unwrap(&self) -> String { match self { Self::BuiltIn(ty) => ty.to_string(), @@ -1311,6 +1336,8 @@ impl RustType { _ => String::new(), } } + + /// Generate an api type if needed, the knowledge of already generated types is therefore necessary fn to_api_type<'a>( &self, generics: &[String], @@ -1455,6 +1482,7 @@ impl RustType { )) } + /// Joins generics into a string for TypeScript syntax together fn join_generic( tys: &[RustType], generics: &[String], @@ -1471,97 +1499,96 @@ impl RustType { .collect::>() .join(", ")) } -} -const RUST_TYPES: &[&str] = &[ - "bool", "char", "str", "u8", "u16", "u32", "u64", "u128", "i8", "i16", "i32", "i64", "i128", - "usize", "isize", "f16", "f32", "f64", "f128", "String", -]; + const RUST_TYPES: &'static [&'static str] = &[ + "bool", "char", "str", "u8", "u16", "u32", "u64", "u128", "i8", "i16", "i32", "i64", + "i128", "usize", "isize", "f16", "f32", "f64", "f128", "String", + ]; -const SKIP_TYPES: &[&str] = &["State", "Headers", "Bytes", "Request", "Extension"]; + const SKIP_TYPES: &'static [&'static str] = + &["State", "Headers", "Bytes", "Request", "Extension"]; -const BUILTIN_GENERICS: &[&str] = &[ - "Query", "HashMap", "Path", "Vec", "Json", "Option", "Result", -]; + const BUILTIN_GENERICS: &'static [&'static str] = &[ + "Query", "HashMap", "Path", "Vec", "Json", "Option", "Result", + ]; -fn is_builtin_type(ident: &syn::Ident) -> bool { - RUST_TYPES.contains(&ident.to_string().as_str()) -} + fn is_builtin_type(ident: &syn::Ident) -> bool { + Self::RUST_TYPES.contains(&ident.to_string().as_str()) + } -fn is_skip_type(ident: &syn::Ident) -> bool { - SKIP_TYPES.contains(&ident.to_string().as_str()) -} + fn is_skip_type(ident: &syn::Ident) -> bool { + Self::SKIP_TYPES.contains(&ident.to_string().as_str()) + } -fn is_builtin_generic(ident: &syn::Ident) -> bool { - BUILTIN_GENERICS.contains(&ident.to_string().as_str()) -} + fn is_builtin_generic(ident: &syn::Ident) -> bool { + Self::BUILTIN_GENERICS.contains(&ident.to_string().as_str()) + } -fn is_custom(ident: &syn::Ident, custom: &[String]) -> bool { - custom.contains(&ident.to_string()) -} + fn is_custom(ident: &syn::Ident, custom: &[String]) -> bool { + custom.contains(&ident.to_string()) + } -fn to_rust_type(ty: &syn::Type, custom: &[String]) -> Option { - match ty { - syn::Type::Path(type_path) => { - let segment = type_path.path.segments.last().unwrap(); - let ident = &segment.ident; + /// Returns an optional Rust Type, if `None` the type needs to be skipped + fn from_tokens(ty: &syn::Type, custom: &[String]) -> Option { + match ty { + syn::Type::Path(type_path) => { + let segment = type_path.path.segments.last().unwrap(); + let ident = &segment.ident; - if is_builtin_type(ident) && !is_custom(ident, custom) { - Some(RustType::BuiltIn(ident.to_string())) - } else if is_skip_type(ident) && !is_custom(ident, custom) { - None - } else if is_builtin_generic(ident) && !is_custom(ident, custom) { - if let syn::PathArguments::AngleBracketed(args) = &segment.arguments { - let inner_types: Vec = args - .args - .iter() - .filter_map(|arg| { - if let syn::GenericArgument::Type(inner_ty) = arg { - to_rust_type(inner_ty, custom) - } else { - None - } - }) - .collect(); - Some(RustType::Generic(ident.to_string(), inner_types)) + if Self::is_builtin_type(ident) && !Self::is_custom(ident, custom) { + Some(RustType::BuiltIn(ident.to_string())) + } else if Self::is_skip_type(ident) && !Self::is_custom(ident, custom) { + None + } else if Self::is_builtin_generic(ident) && !Self::is_custom(ident, custom) { + if let syn::PathArguments::AngleBracketed(args) = &segment.arguments { + let inner_types = Self::extract_inner_types(args, custom); + Some(RustType::Generic(ident.to_string(), inner_types)) + } else { + Some(RustType::Generic(ident.to_string(), vec![])) + } + } else if let syn::PathArguments::AngleBracketed(args) = &segment.arguments { + let inner_types = Self::extract_inner_types(args, custom); + Some(RustType::CustomGeneric(ident.to_string(), inner_types)) } else { - Some(RustType::Generic(ident.to_string(), vec![])) + Some(RustType::Custom(ident.to_string())) } - } else if let syn::PathArguments::AngleBracketed(args) = &segment.arguments { - let inner_types: Vec = args - .args + } + syn::Type::Reference(syn::TypeReference { elem, .. }) + | syn::Type::Paren(syn::TypeParen { elem, .. }) + | syn::Type::Group(syn::TypeGroup { elem, .. }) => Self::from_tokens(elem, custom), + + syn::Type::Tuple(type_tuple) => { + if type_tuple.elems.is_empty() { + return Some(RustType::BuiltIn(String::from("()"))); + } + let inner_types: Vec = type_tuple + .elems .iter() - .filter_map(|arg| { - if let syn::GenericArgument::Type(inner_ty) = arg { - to_rust_type(inner_ty, custom) - } else { - None - } - }) + .filter_map(|t| Self::from_tokens(t, custom)) .collect(); - Some(RustType::CustomGeneric(ident.to_string(), inner_types)) - } else { - Some(RustType::Custom(ident.to_string())) + Some(RustType::Tuple(inner_types)) } + syn::Type::Slice(syn::TypeSlice { elem, .. }) + | syn::Type::Array(syn::TypeArray { elem, .. }) => Self::from_tokens(elem, custom) + .map(|inner| RustType::Generic("Vec".to_string(), vec![inner])), + _ => None, } - syn::Type::Reference(syn::TypeReference { elem, .. }) - | syn::Type::Paren(syn::TypeParen { elem, .. }) - | syn::Type::Group(syn::TypeGroup { elem, .. }) => to_rust_type(elem, custom), + } - syn::Type::Tuple(type_tuple) => { - if type_tuple.elems.is_empty() { - return Some(RustType::BuiltIn(String::from("()"))); - } - let inner_types: Vec = type_tuple - .elems - .iter() - .filter_map(|t| to_rust_type(t, custom)) - .collect(); - Some(RustType::Tuple(inner_types)) - } - syn::Type::Slice(syn::TypeSlice { elem, .. }) - | syn::Type::Array(syn::TypeArray { elem, .. }) => to_rust_type(elem, custom) - .map(|inner| RustType::Generic("Vec".to_string(), vec![inner])), - _ => None, + /// Recursively get inner types + fn extract_inner_types( + args: &syn::AngleBracketedGenericArguments, + custom: &[String], + ) -> Vec { + args.args + .iter() + .filter_map(|arg| { + if let syn::GenericArgument::Type(inner_ty) = arg { + Self::from_tokens(inner_ty, custom) + } else { + None + } + }) + .collect() } }