diff --git a/utoipauto-core/src/discover.rs b/utoipauto-core/src/discover.rs index 081a592..d22fe6d 100644 --- a/utoipauto-core/src/discover.rs +++ b/utoipauto-core/src/discover.rs @@ -1,11 +1,11 @@ use std::vec; -use quote::ToTokens; -use syn::meta::ParseNestedMeta; -use syn::{punctuated::Punctuated, Attribute, Item, ItemFn, Meta, Token, Type}; - use crate::file_utils::{extract_module_name_from_path, parse_files}; use crate::token_utils::Parameters; +use quote::ToTokens; +use syn::meta::ParseNestedMeta; +use syn::token::Comma; +use syn::{punctuated::Punctuated, Attribute, GenericParam, Item, ItemFn, Meta, Token, Type}; /// Discover everything from a file, will explore folder recursively pub fn discover_from_file( @@ -85,26 +85,20 @@ fn parse_module_items( .into_iter() .map(|item| DiscoverType::Fn(build_path(module_path, &item))) .collect(), - syn::Item::Struct(s) => { - let is_generic = !s.generics.params.is_empty(); - parse_from_attr( - &s.attrs, - &build_path(module_path, &s.ident.to_string()), - is_generic, - imports.clone(), - params, - ) - } - syn::Item::Enum(e) => { - let is_generic = !e.generics.params.is_empty(); - parse_from_attr( - &e.attrs, - &build_path(module_path, &e.ident.to_string()), - is_generic, - imports.clone(), - params, - ) - } + syn::Item::Struct(s) => parse_from_attr( + &s.attrs, + &build_path(module_path, &s.ident.to_string()), + s.generics.params, + imports.clone(), + params, + ), + syn::Item::Enum(e) => parse_from_attr( + &e.attrs, + &build_path(module_path, &e.ident.to_string()), + e.generics.params, + imports.clone(), + params, + ), syn::Item::Impl(im) => parse_from_impl(&im, module_path, params), _ => vec![], }) @@ -118,11 +112,12 @@ fn parse_module_items( fn parse_from_attr( a: &Vec, name: &str, - is_generic: bool, + generic_params: Punctuated, imports: Vec, params: &Parameters, ) -> Vec { let mut out: Vec = vec![]; + let is_non_lifetime_generic = !generic_params.iter().all(|p| matches!(p, GenericParam::Lifetime(_))); for attr in a { let meta = &attr.meta; @@ -136,13 +131,13 @@ fn parse_from_attr( for nested_meta in nested { if nested_meta.path().segments.len() == 2 { if nested_meta.path().segments[0].ident == "utoipa" { - if nested_meta.path().segments[1].ident == "ToSchema" && !is_generic { + if nested_meta.path().segments[1].ident == "ToSchema" && !is_non_lifetime_generic { out.push(DiscoverType::Model(name.to_string())); - } else if nested_meta.path().segments[1].ident == "ToResponse" && !is_generic { + } else if nested_meta.path().segments[1].ident == "ToResponse" && !is_non_lifetime_generic { out.push(DiscoverType::Response(name.to_string())); } } - } else if nested_meta.path().is_ident(¶ms.schema_attribute_name) && !is_generic { + } else if nested_meta.path().is_ident(¶ms.schema_attribute_name) && !is_non_lifetime_generic { out.push(DiscoverType::Model(name.to_string())); } if nested_meta.path().is_ident(¶ms.response_attribute_name) { @@ -150,9 +145,14 @@ fn parse_from_attr( } } } - if is_generic && attr.path().is_ident("aliases") { + if is_non_lifetime_generic && attr.path().is_ident("aliases") { let _ = attr.parse_nested_meta(|meta| { - out.push(DiscoverType::Model(parse_generic_schema(meta, name, imports.clone()))); + out.push(DiscoverType::Model(parse_generic_schema( + meta, + name, + imports.clone(), + &generic_params, + ))); Ok(()) }); @@ -163,7 +163,12 @@ fn parse_from_attr( } #[cfg(not(feature = "generic_full_path"))] -fn parse_generic_schema(meta: ParseNestedMeta, name: &str, _imports: Vec) -> String { +fn parse_generic_schema( + meta: ParseNestedMeta, + name: &str, + _imports: Vec, + non_lifetime_generic_params: &Punctuated, +) -> String { let splited_types = split_type(meta); let mut nested_generics = Vec::new(); @@ -171,8 +176,14 @@ fn parse_generic_schema(meta: ParseNestedMeta, name: &str, _imports: Vec let parts: Vec<&str> = spl_type.split(',').collect(); let mut generics = Vec::new(); - for part in parts { - generics.push(part); + for (index, part) in parts.iter().enumerate() { + match non_lifetime_generic_params + .get(index) + .expect("Too few parameters provided to generic") + { + GenericParam::Lifetime(_) => (), + _ => generics.push(part.to_string()), + }; } nested_generics.push(generics.join(", ")); @@ -184,7 +195,12 @@ fn parse_generic_schema(meta: ParseNestedMeta, name: &str, _imports: Vec } #[cfg(feature = "generic_full_path")] -fn parse_generic_schema(meta: ParseNestedMeta, name: &str, imports: Vec) -> String { +fn parse_generic_schema( + meta: ParseNestedMeta, + name: &str, + imports: Vec, + non_lifetime_generic_params: &Punctuated, +) -> String { let splitted_types = split_type(meta); let mut nested_generics = Vec::new(); @@ -192,8 +208,15 @@ fn parse_generic_schema(meta: ParseNestedMeta, name: &str, imports: Vec) let parts: Vec<&str> = spl_type.split(",").collect(); let mut generics = Vec::new(); - for part in parts { - generics.push(process_one_generic(part, name, imports.clone())); + for (index, part) in parts.iter().enumerate() { + match non_lifetime_generic_params + .get(index) + .expect("Too few parameters provided to generic") + { + GenericParam::Lifetime(_) => (), + GenericParam::Type(_) => generics.push(process_one_generic(part, name, imports.clone())), + GenericParam::Const(_) => generics.push(part.to_string()), + }; } nested_generics.push(generics.join(", ")); diff --git a/utoipauto/tests/default_features/const_generics.rs b/utoipauto/tests/default_features/const_generics.rs new file mode 100644 index 0000000..bfdcdd0 --- /dev/null +++ b/utoipauto/tests/default_features/const_generics.rs @@ -0,0 +1,45 @@ +use utoipa::{OpenApi, ToSchema}; +use utoipauto_macro::utoipauto; + +#[derive(ToSchema)] +#[aliases(ConstGenericStructSchema0 = ConstGenericStructSchema<0>)] +pub struct ConstGenericStructSchema { + foo: [u16; N], +} + +#[derive(ToSchema)] +#[aliases(DoubleConstGenericStructSchema0 = DoubleConstGenericStructSchema<0, 0>)] +pub struct DoubleConstGenericStructSchema { + foo: [u16; N], + bar: [u16; N2], +} + +#[derive(ToSchema)] +#[aliases(ConstGenericEnumSchema0 = ConstGenericEnumSchema<0>)] +pub enum ConstGenericEnumSchema { + Foo([u16; N]), +} + +#[derive(ToSchema)] +#[aliases(DoubleConstGenericEnumSchema0 = DoubleConstGenericEnumSchema<0, 0>)] +pub enum DoubleConstGenericEnumSchema { + Foo([u16; N], [u16; N2]), +} + +/// Discover schema with const generics +#[utoipauto(paths = "./utoipauto/tests/default_features/const_generics.rs")] +#[derive(OpenApi)] +#[openapi(info(title = "Const Generic API", version = "1.0.0"))] +pub struct ConstGenericApiDocs {} + +#[test] +fn test_const_generics() { + assert_eq!( + ConstGenericApiDocs::openapi() + .components + .expect("no components") + .schemas + .len(), + 4 + ) +} diff --git a/utoipauto/tests/default_features/generics.rs b/utoipauto/tests/default_features/generics.rs index 73fcacb..c866e4f 100644 --- a/utoipauto/tests/default_features/generics.rs +++ b/utoipauto/tests/default_features/generics.rs @@ -1,35 +1,100 @@ -use utoipa::ToSchema; +use utoipa::OpenApi; +use utoipauto_macro::utoipauto; -#[derive(ToSchema)] -pub struct NonGenericSchema; +/// Combined Struct - type & const +#[derive(utoipa::ToSchema)] +#[aliases( + TypeGenericAndConstGenericStructSchemaU32 = TypeGenericAndConstGenericStructSchema, + TypeGenericAndConstGenericStructSchemaU64 = TypeGenericAndConstGenericStructSchema, +)] +pub struct TypeGenericAndConstGenericStructSchema { + foo: T, + bar: [u16; N], +} -#[derive(ToSchema)] -pub struct NonGenericSchema2; +/// Combined Struct - lifetime & type +#[derive(utoipa::ToSchema)] +#[aliases( + LifetimeAndTypeGenericGenericStructSchemaU32 = LifetimeAndTypeGenericGenericStructSchema<'a, core::primitive::u32>, + LifetimeAndTypeGenericGenericStructSchemaU64 = LifetimeAndTypeGenericGenericStructSchema<'a, core::primitive::u64>, +)] +pub struct LifetimeAndTypeGenericGenericStructSchema<'a, T> { + foo: &'a str, + bar: T, +} -#[derive(ToSchema)] -#[aliases(GenricModelSchema = GenericSchema < NonGenericSchema >)] -pub struct GenericSchema { - _data: T, +/// Combined Struct - lifetime & const +#[derive(utoipa::ToSchema)] +#[aliases(LifetimeAndConstGenericGenericStructSchema2 = LifetimeAndConstGenericGenericStructSchema<'a, 2>)] +pub struct LifetimeAndConstGenericGenericStructSchema<'a, const N: usize> { + foo: &'a str, + bar: [u16; N], } -#[derive(ToSchema)] -#[aliases(MultipleGenericModelSchema = MultipleGenericSchema < NonGenericSchema, NonGenericSchema2 >)] -pub struct MultipleGenericSchema { - _data: T, - _data2: U, +/// Combined Struct - lifetime & const & type +#[derive(utoipa::ToSchema)] +#[aliases( + LifetimeAndConstAndTypeGenericGenericStructSchema1 = LifetimeAndConstAndTypeGenericGenericStructSchema<'a, 5, 1, core::primitive::u32>, + LifetimeAndConstAndTypeGenericGenericStructSchema2 = LifetimeAndConstAndTypeGenericGenericStructSchema<'a, 6, 2, core::primitive::u64>, +)] +pub struct LifetimeAndConstAndTypeGenericGenericStructSchema<'a, const N: usize, const N2: usize, T> { + a: &'a str, + foo: [u16; N], + bar: [u16; N2], + t: T, } -#[derive(ToSchema)] +/// Combined Enum - type & const +#[derive(utoipa::ToSchema)] #[aliases( -MultipleAlaises1 = MultipleAliasesSchema < NonGenericSchema >, -MultipleAlaises2 = MultipleAliasesSchema < NonGenericSchema2 > + TypeGenericAndConstGenericEnumSchemaU32 = TypeGenericAndConstGenericEnumSchema, + TypeGenericAndConstGenericEnumSchemaU64 = TypeGenericAndConstGenericEnumSchema, )] -pub struct MultipleAliasesSchema { - _data: T, +pub enum TypeGenericAndConstGenericEnumSchema { + Foo(T, [u16; N]), } -#[derive(ToSchema)] -#[aliases(NestedGenericsSchema = NestedGenerics < GenericSchema < NonGenericSchema > >)] -pub struct NestedGenerics { - _data: T, +/// Combined Enum - lifetime & type +#[derive(utoipa::ToSchema)] +#[aliases( + LifetimeAndTypeGenericGenericEnumSchemaU32 = LifetimeAndTypeGenericGenericEnumSchema<'a, core::primitive::u32>, + LifetimeAndTypeGenericGenericEnumSchemaU64 = LifetimeAndTypeGenericGenericEnumSchema<'a, core::primitive::u64>, +)] +pub enum LifetimeAndTypeGenericGenericEnumSchema<'a, T> { + Foo(&'a str, T), +} + +/// Combined Enum - lifetime & const +#[derive(utoipa::ToSchema)] +#[aliases(LifetimeAndConstGenericGenericEnumSchema2 = LifetimeAndConstGenericGenericEnumSchema<'a, 2>)] +pub enum LifetimeAndConstGenericGenericEnumSchema<'a, const N: usize> { + Foo(&'a str, [u16; N]), +} + +/// Combined Enum - lifetime & const & type +#[derive(utoipa::ToSchema)] +#[aliases( + LifetimeAndConstAndTypeGenericGenericEnumSchema1 = LifetimeAndConstAndTypeGenericGenericEnumSchema<'a, 5, 1, core::primitive::u32>, + LifetimeAndConstAndTypeGenericGenericEnumSchema2 = LifetimeAndConstAndTypeGenericGenericEnumSchema<'a, 6, 2, core::primitive::u64>, +)] +pub enum LifetimeAndConstAndTypeGenericGenericEnumSchema<'a, const N: usize, const N2: usize, T> { + Foo(&'a str, [u16; N], [u16; N2], T), +} + +/// Discover schema with generics +#[utoipauto(paths = "./utoipauto/tests/default_features/generics.rs")] +#[derive(OpenApi)] +#[openapi(info(title = "Const Generic API", version = "1.0.0"))] +pub struct GenericsApiDocs {} + +#[test] +fn test_generics() { + assert_eq!( + GenericsApiDocs::openapi() + .components + .expect("no components") + .schemas + .len(), + 14 + ) } diff --git a/utoipauto/tests/default_features/lifetime_generics.rs b/utoipauto/tests/default_features/lifetime_generics.rs new file mode 100644 index 0000000..8e4c6fc --- /dev/null +++ b/utoipauto/tests/default_features/lifetime_generics.rs @@ -0,0 +1,41 @@ +use utoipa::{OpenApi, ToSchema}; +use utoipauto_macro::utoipauto; + +#[derive(ToSchema)] +pub struct LifetimeStructSchema<'a> { + foo: &'a str, +} + +#[derive(ToSchema)] +pub struct DoubleLifetimeStructSchema<'a, 'b> { + foo: &'a str, + bar: &'b str, +} + +#[derive(ToSchema)] +pub enum LifetimeEnumSchema<'a> { + Foo(&'a str), +} + +#[derive(ToSchema)] +pub enum DoubleLifetimeEnumSchema<'a, 'b> { + Foo(&'a str, &'b str), +} + +/// Discover schema with lifetime generics +#[utoipauto(paths = "./utoipauto/tests/default_features/lifetime_generics.rs")] +#[derive(OpenApi)] +#[openapi(info(title = "Lifetimes API", version = "1.0.0"))] +pub struct LifetimesApiDocs {} + +#[test] +fn test_lifetimes() { + assert_eq!( + LifetimesApiDocs::openapi() + .components + .expect("no components") + .schemas + .len(), + 4 + ) +} diff --git a/utoipauto/tests/default_features/mod.rs b/utoipauto/tests/default_features/mod.rs index 1572d02..3502641 100644 --- a/utoipauto/tests/default_features/mod.rs +++ b/utoipauto/tests/default_features/mod.rs @@ -1,4 +1,7 @@ +pub mod const_generics; pub mod controllers; pub mod generics; +pub mod lifetime_generics; pub mod models; pub mod test; +pub mod type_generics; diff --git a/utoipauto/tests/default_features/test.rs b/utoipauto/tests/default_features/test.rs index 6ec8651..9ba0427 100644 --- a/utoipauto/tests/default_features/test.rs +++ b/utoipauto/tests/default_features/test.rs @@ -3,9 +3,9 @@ use utoipa::OpenApi; use utoipauto::utoipauto; use crate::default_features::controllers; -use crate::default_features::generics::GenericSchema; -use crate::default_features::generics::NonGenericSchema; -use crate::default_features::generics::NonGenericSchema2; +use crate::default_features::type_generics::GenericSchema; +use crate::default_features::type_generics::NonGenericSchema; +use crate::default_features::type_generics::NonGenericSchema2; // Discover from multiple controllers #[utoipauto( diff --git a/utoipauto/tests/default_features/type_generics.rs b/utoipauto/tests/default_features/type_generics.rs new file mode 100644 index 0000000..73fcacb --- /dev/null +++ b/utoipauto/tests/default_features/type_generics.rs @@ -0,0 +1,35 @@ +use utoipa::ToSchema; + +#[derive(ToSchema)] +pub struct NonGenericSchema; + +#[derive(ToSchema)] +pub struct NonGenericSchema2; + +#[derive(ToSchema)] +#[aliases(GenricModelSchema = GenericSchema < NonGenericSchema >)] +pub struct GenericSchema { + _data: T, +} + +#[derive(ToSchema)] +#[aliases(MultipleGenericModelSchema = MultipleGenericSchema < NonGenericSchema, NonGenericSchema2 >)] +pub struct MultipleGenericSchema { + _data: T, + _data2: U, +} + +#[derive(ToSchema)] +#[aliases( +MultipleAlaises1 = MultipleAliasesSchema < NonGenericSchema >, +MultipleAlaises2 = MultipleAliasesSchema < NonGenericSchema2 > +)] +pub struct MultipleAliasesSchema { + _data: T, +} + +#[derive(ToSchema)] +#[aliases(NestedGenericsSchema = NestedGenerics < GenericSchema < NonGenericSchema > >)] +pub struct NestedGenerics { + _data: T, +} diff --git a/utoipauto/tests/generic_full_path/const_generics.rs b/utoipauto/tests/generic_full_path/const_generics.rs new file mode 100644 index 0000000..513768c --- /dev/null +++ b/utoipauto/tests/generic_full_path/const_generics.rs @@ -0,0 +1,13 @@ +use utoipa::ToSchema; + +#[derive(ToSchema)] +#[aliases(ConstGenericStructSchema0 = ConstGenericStructSchema<0>)] +pub struct ConstGenericStructSchema { + foo: [u16; N], +} + +#[derive(ToSchema)] +#[aliases(ConstGenericEnumSchema0 = ConstGenericEnumSchema<0>)] +pub enum ConstGenericEnumSchema { + Foo([u16; N]), +} diff --git a/utoipauto/tests/generic_full_path/mod.rs b/utoipauto/tests/generic_full_path/mod.rs index 9c5f3fc..7082e2e 100644 --- a/utoipauto/tests/generic_full_path/mod.rs +++ b/utoipauto/tests/generic_full_path/mod.rs @@ -1,3 +1,4 @@ +mod const_generics; mod even_more_schemas; mod more_schemas; mod partial_imports; diff --git a/utoipauto/tests/generic_full_path/test.rs b/utoipauto/tests/generic_full_path/test.rs index 2273464..6272852 100644 --- a/utoipauto/tests/generic_full_path/test.rs +++ b/utoipauto/tests/generic_full_path/test.rs @@ -6,3 +6,21 @@ use utoipauto_macro::utoipauto; #[derive(OpenApi)] #[openapi(info(title = "Percentage API", version = "1.0.0"))] pub struct CrateApiDocs; + +/// Discover schema with const generics +#[utoipauto(paths = "./utoipauto/tests/generic_full_path/const_generics.rs")] +#[derive(OpenApi)] +#[openapi(info(title = "Const Generic API", version = "1.0.0"))] +pub struct ConstGenericApiDocs {} + +#[test] +fn test_const_generics() { + assert_eq!( + ConstGenericApiDocs::openapi() + .components + .expect("no components") + .schemas + .len(), + 2 + ) +}