Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance no_recursion rule to apply also containers #1144

Merged
merged 1 commit into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions utoipa-gen/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog - utoipa-gen

## Unreleased

### Changed

* Enhance no_recursion rule to apply also containers (https://github.com/juhaku/utoipa/pull/1144)

## 5.1.0 - Oct 16 2024

### Added
Expand Down
10 changes: 6 additions & 4 deletions utoipa-gen/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1188,10 +1188,12 @@ impl ComponentSchema {
let type_path = &**type_tree.path.as_ref().unwrap();
let rewritten_path = type_path.rewrite_path()?;
let nullable_item = nullable_one_of_item(nullable);
let mut object_schema_reference = SchemaReference::default();
object_schema_reference.no_recursion = features
.iter()
.any(|feature| matches!(feature, Feature::NoRecursion(_)));
let mut object_schema_reference = SchemaReference {
no_recursion: features
.iter()
.any(|feature| matches!(feature, Feature::NoRecursion(_))),
..SchemaReference::default()
};

if let Some(children) = &type_tree.children {
let children_name = Self::compose_name(
Expand Down
10 changes: 9 additions & 1 deletion utoipa-gen/src/component/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use self::{

use super::{
features::{
attributes::{As, Bound, Description, RenameAll},
attributes::{As, Bound, Description, NoRecursion, RenameAll},
parse_features, pop_feature, Feature, FeaturesExt, IntoInner, ToTokensExt,
},
serde::{self, SerdeContainer, SerdeValue},
Expand Down Expand Up @@ -515,6 +515,7 @@ impl NamedStructSchema {
features.push(Feature::Deprecated(true.into()));
}

let _ = pop_feature!(features => Feature::NoRecursion(_));
tokens.extend(features.to_token_stream()?);

let comments = CommentAttributes::from_attributes(root.attributes);
Expand Down Expand Up @@ -556,6 +557,13 @@ impl NamedStructSchema {
return Ok(None);
};

if features
.iter()
.any(|feature| matches!(feature, Feature::NoRecursion(_)))
{
field_features.push(Feature::NoRecursion(NoRecursion));
}

let schema_default = features.iter().any(|f| matches!(f, Feature::Default(_)));
let serde_default = container_rules.default;

Expand Down
14 changes: 11 additions & 3 deletions utoipa-gen/src/component/schema/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ use crate::{
component::{
features::{
attributes::{
Deprecated, Description, Discriminator, Example, Examples, Rename, RenameAll, Title,
Deprecated, Description, Discriminator, Example, Examples, NoRecursion, Rename,
RenameAll, Title,
},
parse_features, pop_feature, Feature, IntoInner, IsInline, ToTokensExt,
},
Expand Down Expand Up @@ -334,14 +335,20 @@ impl<'p> MixedEnum<'p> {

let mut items = variants
.into_iter()
.map(|(variant, variant_serde_rules, features)| {
.map(|(variant, variant_serde_rules, mut variant_features)| {
if features
.iter()
.any(|feature| matches!(feature, Feature::NoRecursion(_)))
{
variant_features.push(Feature::NoRecursion(NoRecursion));
}
MixedEnumContent::new(
variant,
root,
&container_rules,
rename_all.as_ref(),
variant_serde_rules,
features,
variant_features,
)
})
.collect::<Result<Vec<MixedEnumContent>, Diagnostics>>()?;
Expand All @@ -356,6 +363,7 @@ impl<'p> MixedEnum<'p> {
discriminator,
};

let _ = pop_feature!(features => Feature::NoRecursion(_));
let mut tokens = one_of_enum.to_token_stream();
tokens.extend(features.to_token_stream());

Expand Down
9 changes: 6 additions & 3 deletions utoipa-gen/src/component/schema/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ impl Parse for NamedFieldStructFeatures {
crate::component::features::attributes::Default,
Deprecated,
Description,
Bound
Bound,
NoRecursion
)))
}
}
Expand Down Expand Up @@ -103,7 +104,8 @@ impl Parse for MixedEnumFeatures {
As,
Deprecated,
Description,
Discriminator
Discriminator,
NoRecursion
)))
}
}
Expand Down Expand Up @@ -164,7 +166,8 @@ impl Parse for EnumNamedFieldVariantFeatures {
RenameAll,
Deprecated,
MaxProperties,
MinProperties
MinProperties,
NoRecursion
)))
}
}
Expand Down
14 changes: 13 additions & 1 deletion utoipa-gen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ static CONFIG: once_cell::sync::Lazy<utoipa_config::Config> =
/// contain. Value must be a number.
/// * `min_properties = ...` Can be used to define minimum number of properties this struct can
/// contain. Value must be a number.
///* `no_recursion` Is used to break from recursion in case of looping schema tree e.g. `Pet` ->
/// `Owner` -> `Pet`. _`no_recursion`_ attribute must be used within `Ower` type not to allow
/// recurring into `Pet`. Failing to do so will cause infinite loop and runtime **panic**. On
/// struct level the _`no_recursion`_ rule will be applied to all of its fields.
///
/// ## Named Fields Optional Configuration Options for `#[schema(...)]`
///
Expand Down Expand Up @@ -325,6 +329,10 @@ static CONFIG: once_cell::sync::Lazy<utoipa_config::Config> =
/// * `discriminator = ...` or `discriminator(...)` Can be used to define OpenAPI discriminator
/// field for enums with single unnamed _`ToSchema`_ reference field. See the [discriminator
/// syntax][derive@ToSchema#schemadiscriminator-syntax].
///* `no_recursion` Is used to break from recursion in case of looping schema tree e.g. `Pet` ->
/// `Owner` -> `Pet`. _`no_recursion`_ attribute must be used within `Ower` type not to allow
/// recurring into `Pet`. Failing to do so will cause infinite loop and runtime **panic**. On
/// enum level the _`no_recursion`_ rule will be applied to all of its variants.
///
/// ### `#[schema(discriminator)]` syntax
///
Expand All @@ -336,7 +344,7 @@ static CONFIG: once_cell::sync::Lazy<utoipa_config::Config> =
///
/// Can be literal string or expression e.g. [_`const`_][const] reference. It can be defined as
/// _`discriminator = "value"`_ where the assigned value is the
/// discriminator field that must exists in each variant referencing schema.
/// discriminator field that must exists in each variant referencing schema.
///
/// **Complex form `discriminator(...)`**
///
Expand Down Expand Up @@ -377,6 +385,10 @@ static CONFIG: once_cell::sync::Lazy<utoipa_config::Config> =
/// contain. Value must be a number.
/// * `min_properties = ...` Can be used to define minimum number of properties this struct can
/// contain. Value must be a number.
///* `no_recursion` Is used to break from recursion in case of looping schema tree e.g. `Pet` ->
/// `Owner` -> `Pet`. _`no_recursion`_ attribute must be used within `Ower` type not to allow
/// recurring into `Pet`. Failing to do so will cause infinite loop and runtime **panic**. On
/// named field variant level the _`no_recursion`_ rule will be applied to all of its fields.
///
/// ## Mixed Enum Unnamed Field Variant Optional Configuration Options for `#[serde(schema)]`
///
Expand Down
41 changes: 41 additions & 0 deletions utoipa-gen/tests/schema_derive_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5960,3 +5960,44 @@ fn test_recursion_compiles() {
.expect("OpenApi is JSON serializable");
println!("{json}")
}

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

#[derive(ToSchema)]
#[schema(no_recursion)]
pub struct Tree {
left: Box<Tree>,
right: Box<Tree>,
}

#[derive(ToSchema)]
#[schema(no_recursion)]
pub enum TreeRecursion {
Named { left: Box<TreeRecursion> },
Unnamed(Box<TreeRecursion>),
NoValue,
}

#[derive(ToSchema)]
pub enum Recursion {
#[schema(no_recursion)]
Named {
left: Box<Recursion>,
right: Box<Recursion>,
},
#[schema(no_recursion)]
Unnamed(Box<Recursion>),
NoValue,
}

#[derive(OpenApi)]
#[openapi(components(schemas(Recursion, Tree, TreeRecursion)))]
pub struct ApiDoc {}

let json = ApiDoc::openapi()
.to_pretty_json()
.expect("OpenApi is JSON serializable");
println!("{json}")
}
Loading