Skip to content

Commit

Permalink
Merge pull request #42 from Calum4/Fix/Lifetimes
Browse files Browse the repository at this point in the history
feat: discover schema with lifetimes and const generics
  • Loading branch information
DenuxPlays authored Sep 17, 2024
2 parents eb04cba + 8510db8 commit b7d8525
Show file tree
Hide file tree
Showing 10 changed files with 306 additions and 62 deletions.
95 changes: 59 additions & 36 deletions utoipauto-core/src/discover.rs
Original file line number Diff line number Diff line change
@@ -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(
Expand Down Expand Up @@ -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![],
})
Expand All @@ -118,11 +112,12 @@ fn parse_module_items(
fn parse_from_attr(
a: &Vec<Attribute>,
name: &str,
is_generic: bool,
generic_params: Punctuated<GenericParam, Comma>,
imports: Vec<String>,
params: &Parameters,
) -> Vec<DiscoverType> {
let mut out: Vec<DiscoverType> = vec![];
let is_non_lifetime_generic = !generic_params.iter().all(|p| matches!(p, GenericParam::Lifetime(_)));

for attr in a {
let meta = &attr.meta;
Expand All @@ -136,23 +131,28 @@ 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(&params.schema_attribute_name) && !is_generic {
} else if nested_meta.path().is_ident(&params.schema_attribute_name) && !is_non_lifetime_generic {
out.push(DiscoverType::Model(name.to_string()));
}
if nested_meta.path().is_ident(&params.response_attribute_name) {
out.push(DiscoverType::Response(name.to_string()));
}
}
}
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(())
});
Expand All @@ -163,16 +163,27 @@ fn parse_from_attr(
}

#[cfg(not(feature = "generic_full_path"))]
fn parse_generic_schema(meta: ParseNestedMeta, name: &str, _imports: Vec<String>) -> String {
fn parse_generic_schema(
meta: ParseNestedMeta,
name: &str,
_imports: Vec<String>,
non_lifetime_generic_params: &Punctuated<GenericParam, Comma>,
) -> String {
let splited_types = split_type(meta);
let mut nested_generics = Vec::new();

for spl_type in splited_types {
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(", "));
Expand All @@ -184,16 +195,28 @@ fn parse_generic_schema(meta: ParseNestedMeta, name: &str, _imports: Vec<String>
}

#[cfg(feature = "generic_full_path")]
fn parse_generic_schema(meta: ParseNestedMeta, name: &str, imports: Vec<String>) -> String {
fn parse_generic_schema(
meta: ParseNestedMeta,
name: &str,
imports: Vec<String>,
non_lifetime_generic_params: &Punctuated<GenericParam, Comma>,
) -> String {
let splitted_types = split_type(meta);
let mut nested_generics = Vec::new();

for spl_type in splitted_types {
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(", "));
Expand Down
45 changes: 45 additions & 0 deletions utoipauto/tests/default_features/const_generics.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use utoipa::{OpenApi, ToSchema};
use utoipauto_macro::utoipauto;

#[derive(ToSchema)]
#[aliases(ConstGenericStructSchema0 = ConstGenericStructSchema<0>)]
pub struct ConstGenericStructSchema<const N: usize> {
foo: [u16; N],
}

#[derive(ToSchema)]
#[aliases(DoubleConstGenericStructSchema0 = DoubleConstGenericStructSchema<0, 0>)]
pub struct DoubleConstGenericStructSchema<const N: usize, const N2: usize> {
foo: [u16; N],
bar: [u16; N2],
}

#[derive(ToSchema)]
#[aliases(ConstGenericEnumSchema0 = ConstGenericEnumSchema<0>)]
pub enum ConstGenericEnumSchema<const N: usize> {
Foo([u16; N]),
}

#[derive(ToSchema)]
#[aliases(DoubleConstGenericEnumSchema0 = DoubleConstGenericEnumSchema<0, 0>)]
pub enum DoubleConstGenericEnumSchema<const N: usize, const N2: usize> {
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
)
}
111 changes: 88 additions & 23 deletions utoipauto/tests/default_features/generics.rs
Original file line number Diff line number Diff line change
@@ -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<core::primitive::u32, 1>,
TypeGenericAndConstGenericStructSchemaU64 = TypeGenericAndConstGenericStructSchema<core::primitive::u64, 2>,
)]
pub struct TypeGenericAndConstGenericStructSchema<T, const N: usize> {
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<T> {
_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<T, U> {
_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<core::primitive::u32, 1>,
TypeGenericAndConstGenericEnumSchemaU64 = TypeGenericAndConstGenericEnumSchema<core::primitive::u64, 2>,
)]
pub struct MultipleAliasesSchema<T> {
_data: T,
pub enum TypeGenericAndConstGenericEnumSchema<T, const N: usize> {
Foo(T, [u16; N]),
}

#[derive(ToSchema)]
#[aliases(NestedGenericsSchema = NestedGenerics < GenericSchema < NonGenericSchema > >)]
pub struct NestedGenerics<T> {
_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
)
}
41 changes: 41 additions & 0 deletions utoipauto/tests/default_features/lifetime_generics.rs
Original file line number Diff line number Diff line change
@@ -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
)
}
3 changes: 3 additions & 0 deletions utoipauto/tests/default_features/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Loading

0 comments on commit b7d8525

Please sign in to comment.