Skip to content

Commit

Permalink
Chore filter const generics (#1118)
Browse files Browse the repository at this point in the history
This commit will filter out const generics from being added to the
schema composing because the value is not used in anyway.

Perhaps in some future release the real support of const generics could
be experimented.

Fixes #1115
  • Loading branch information
juhaku authored Oct 12, 2024
1 parent 449b164 commit 7d10516
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 11 deletions.
1 change: 1 addition & 0 deletions utoipa-gen/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

### Fixed

* Chore filter const generics (https://github.com/juhaku/utoipa/pull/1118)
* Fix impl `ToSchema` for container types (https://github.com/juhaku/utoipa/pull/1107)
* Fix description on `inline` field (https://github.com/juhaku/utoipa/pull/1102)
* Fix `title` on unnamed struct and references (https://github.com/juhaku/utoipa/pull/1101)
Expand Down
58 changes: 47 additions & 11 deletions utoipa-gen/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1184,7 +1184,10 @@ impl ComponentSchema {
let mut object_schema_reference = SchemaReference::default();

if let Some(children) = &type_tree.children {
let children_name = Self::compose_name(children)?;
let children_name = Self::compose_name(
Self::filter_const_generics(children, container.generics),
container.generics,
)?;
name_tokens.extend(quote! { std::borrow::Cow::Owned(format!("{}_{}", < #rewritten_path as utoipa::ToSchema >::name(), #children_name)) });
} else {
name_tokens.extend(
Expand All @@ -1205,7 +1208,8 @@ impl ComponentSchema {
schema_references.extend(Self::compose_child_references(children)?);

let composed_generics =
Self::compose_generics(children)?.collect::<Array<_>>();
Self::compose_generics(children, container.generics)?
.collect::<Array<_>>();
quote_spanned! {type_path.span()=>
<#rewritten_path as utoipa::__dev::ComposeSchema>::compose(#composed_generics.to_vec())
}
Expand Down Expand Up @@ -1242,8 +1246,11 @@ impl ComponentSchema {
// only set schema references tokens for concrete non generic types
if index.is_none() {
let reference_tokens = if let Some(children) = &type_tree.children {
let composed_generics =
Self::compose_generics(children)?.collect::<Array<_>>();
let composed_generics = Self::compose_generics(
Self::filter_const_generics(children, container.generics),
container.generics,
)?
.collect::<Array<_>>();
quote! { <#rewritten_path as utoipa::__dev::ComposeSchema>::compose(#composed_generics.to_vec()) }
} else {
quote! { <#rewritten_path as utoipa::PartialSchema>::schema() }
Expand Down Expand Up @@ -1356,7 +1363,10 @@ impl ComponentSchema {
Ok(())
}

fn compose_name<'tr, I>(children: I) -> Result<TokenStream, Diagnostics>
fn compose_name<'tr, I>(
children: I,
generics: &'tr Generics,
) -> Result<TokenStream, Diagnostics>
where
I: IntoIterator<Item = &'tr TypeTree<'tr>>,
{
Expand All @@ -1365,12 +1375,12 @@ impl ComponentSchema {
.map(|type_tree| {
let name = type_tree
.path
.as_ref()
.as_deref()
.expect("Generic ValueType::Object must have path");
let rewritten_name = name.as_ref().rewrite_path()?;
let rewritten_name = name.rewrite_path()?;

if let Some(children) = &type_tree.children {
let children_name = Self::compose_name(children)?;
let children_name = Self::compose_name(Self::filter_const_generics(children, generics), generics)?;

Ok(quote! { std::borrow::Cow::Owned(format!("{}_{}", <#rewritten_name as utoipa::ToSchema>::name(), #children_name)) })
} else {
Expand All @@ -1384,6 +1394,7 @@ impl ComponentSchema {

fn compose_generics<'v, I: IntoIterator<Item = &'v TypeTree<'v>>>(
children: I,
generics: &'v Generics,
) -> Result<impl Iterator<Item = TokenStream> + 'v, Diagnostics>
where
<I as std::iter::IntoIterator>::IntoIter: 'v,
Expand All @@ -1395,7 +1406,7 @@ impl ComponentSchema {
.expect("inline TypeTree ValueType::Object must have child path if generic");
let rewritten_path = path.rewrite_path()?;
if let Some(children) = &child.children {
let items = Self::compose_generics(children)?.collect::<Array<_>>();
let items = Self::compose_generics(Self::filter_const_generics(children, generics), generics)?.collect::<Array<_>>();
Ok(quote! { <#rewritten_path as utoipa::__dev::ComposeSchema>::compose(#items.to_vec()) })
} else {
Ok(quote! { <#rewritten_path as utoipa::PartialSchema>::schema() })
Expand All @@ -1406,6 +1417,31 @@ impl ComponentSchema {
Ok(iter)
}

fn filter_const_generics<'v, I: IntoIterator<Item = &'v TypeTree<'v>>>(
children: I,
generics: &'v Generics,
) -> impl IntoIterator<Item = &'v TypeTree<'v>> + 'v
where
<I as std::iter::IntoIterator>::IntoIter: 'v,
{
children.into_iter().filter(|type_tree| {
let path = type_tree
.path
.as_deref()
.expect("child TypeTree must have a Path, did you call this on array or tuple?");
let is_const = path
.get_ident()
.map(|path_ident| {
generics.params.iter().any(
|param| matches!(param, GenericParam::Const(ty) if ty.ident == *path_ident),
)
})
.unwrap_or(false);

!is_const
})
}

fn compose_child_references<'a, I: IntoIterator<Item = &'a TypeTree<'a>> + 'a>(
children: I,
) -> Result<impl Iterator<Item = SchemaReference> + 'a, Diagnostics> {
Expand All @@ -1416,8 +1452,8 @@ impl ComponentSchema {
} else if type_tree.value_type == ValueType::Object {
let type_path = type_tree
.path
.as_ref()
.expect("Object TypePath must have type path, compose child references").as_ref();
.as_deref()
.expect("Object TypePath must have type path, compose child references");

let rewritten_path = type_path.rewrite_path()?;

Expand Down
32 changes: 32 additions & 0 deletions utoipa-gen/tests/schema_derive_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5854,3 +5854,35 @@ fn schema_manual_impl() {
})
)
}

#[test]
fn const_generic_test() {
#![allow(unused)]

#[derive(ToSchema)]
pub struct ArrayResponse<T: ToSchema, const N: usize> {
array: [T; N],
}

#[derive(ToSchema)]
struct CombinedResponse<T: ToSchema, const N: usize> {
pub array_response: ArrayResponse<T, N>,
}

use utoipa::PartialSchema;
let schema = <CombinedResponse<String, 1> as PartialSchema>::schema();
let value = serde_json::to_value(schema).expect("schema is JSON serializable");

assert_json_eq! {
value,
json!({
"properties": {
"array_response": {
"$ref": "#/components/schemas/ArrayResponse_String"
}
},
"required": ["array_response"],
"type": "object"
})
}
}

0 comments on commit 7d10516

Please sign in to comment.