Skip to content

Commit

Permalink
Make referenced schemas required (#1018)
Browse files Browse the repository at this point in the history
This will affect in all places where schema is being rendered. These are
`value_type = ..` `request_body`, `response_body = ...` to name few.
This aims to enforce `PartialSchema` implementation for every type
that is being used in OpenAPI spec generated by utoipa.

The `Schema` trait will be split to `PartialSchema` and `Schema` and
`Schema` will extend the `PartialSchema` trait. `PartialSchema` will
provide the actual schema and `Schema` will provide name and other data
related to the schema itself. This is useful since we already provide
`PartialSchema` implementation for many standard Rust types and not all
types need the full schema but only the schema definition. This makes
schema definition implementation easier by juts allowing users to
manually implement `PartialSchema` type for their type if needed. Still
as usual the implementation can be automatically derived with `ToSchema`
derive trait.

Fixes #500 Fixes #801
  • Loading branch information
juhaku authored Sep 3, 2024
1 parent 90ec7a6 commit b473b99
Show file tree
Hide file tree
Showing 16 changed files with 290 additions and 109 deletions.
84 changes: 69 additions & 15 deletions utoipa-gen/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,26 @@ impl<'t> TypeTree<'t> {
pub fn is_map(&self) -> bool {
matches!(self.generic_type, Some(GenericType::Map))
}

pub fn match_ident(&self, ident: &Ident) -> bool {
let Some(ref path) = self.path else {
return false;
};

let matches = path
.segments
.iter()
.last()
.map(|segment| &segment.ident == ident)
.unwrap_or_default();

matches
|| self
.children
.iter()
.flatten()
.any(|child| child.match_ident(ident))
}
}

impl PartialEq for TypeTree<'_> {
Expand Down Expand Up @@ -478,6 +498,7 @@ pub struct ComponentSchemaProps<'c> {
pub(crate) description: Option<&'c ComponentDescription<'c>>,
pub(crate) deprecated: Option<&'c Deprecated>,
pub object_name: &'c str,
pub is_generics_type_arg: bool,
}

#[cfg_attr(feature = "debug", derive(Debug))]
Expand Down Expand Up @@ -520,6 +541,7 @@ impl<'c> ComponentSchema {
description,
deprecated,
object_name,
is_generics_type_arg,
}: ComponentSchemaProps,
) -> Result<Self, Diagnostics> {
let mut tokens = TokenStream::new();
Expand All @@ -534,6 +556,7 @@ impl<'c> ComponentSchema {
object_name,
description,
deprecated_stream,
is_generics_type_arg,
)?,
Some(GenericType::Vec | GenericType::LinkedList | GenericType::Set) => {
ComponentSchema::vec_to_tokens(
Expand All @@ -543,6 +566,7 @@ impl<'c> ComponentSchema {
object_name,
description,
deprecated_stream,
is_generics_type_arg,
)?
}
#[cfg(feature = "smallvec")]
Expand All @@ -553,6 +577,7 @@ impl<'c> ComponentSchema {
object_name,
description,
deprecated_stream,
is_generics_type_arg,
)?,
Some(GenericType::Option) => {
// Add nullable feature if not already exists. Option is always nullable
Expand All @@ -575,6 +600,7 @@ impl<'c> ComponentSchema {
description,
deprecated,
object_name,
is_generics_type_arg,
})?
.to_tokens(&mut tokens)?;
}
Expand All @@ -591,6 +617,7 @@ impl<'c> ComponentSchema {
description,
deprecated,
object_name,
is_generics_type_arg,
})?
.to_tokens(&mut tokens)?;
}
Expand All @@ -608,6 +635,7 @@ impl<'c> ComponentSchema {
description,
deprecated,
object_name,
is_generics_type_arg,
})?
.to_tokens(&mut tokens)?;
}
Expand All @@ -618,6 +646,7 @@ impl<'c> ComponentSchema {
object_name,
description,
deprecated_stream,
is_generics_type_arg,
)?,
};

Expand Down Expand Up @@ -652,6 +681,7 @@ impl<'c> ComponentSchema {
object_name: &str,
description_stream: Option<&ComponentDescription<'_>>,
deprecated_stream: Option<TokenStream>,
is_generics_type_arg: bool,
) -> Result<(), Diagnostics> {
let example = features.pop_by(|feature| matches!(feature, Feature::Example(_)));
let additional_properties = pop_feature!(features => Feature::AdditionalProperties(_));
Expand Down Expand Up @@ -679,6 +709,7 @@ impl<'c> ComponentSchema {
description: None,
deprecated: None,
object_name,
is_generics_type_arg, // TODO check whether this is correct
})?;
let schema_tokens = as_tokens_or_diagnostics!(&schema_property);

Expand Down Expand Up @@ -709,6 +740,7 @@ impl<'c> ComponentSchema {
object_name: &str,
description_stream: Option<&ComponentDescription<'_>>,
deprecated_stream: Option<TokenStream>,
is_generics_type_arg: bool,
) -> Result<(), Diagnostics> {
let example = pop_feature!(features => Feature::Example(_));
let xml = features.extract_vec_xml_feature(type_tree)?;
Expand Down Expand Up @@ -747,6 +779,7 @@ impl<'c> ComponentSchema {
description: None,
deprecated: None,
object_name,
is_generics_type_arg,
})?;
let component_schema_tokens = as_tokens_or_diagnostics!(&component_schema);

Expand Down Expand Up @@ -810,6 +843,7 @@ impl<'c> ComponentSchema {
object_name: &str,
description_stream: Option<&ComponentDescription<'_>>,
deprecated_stream: Option<TokenStream>,
is_generics_type_arg: bool,
) -> Result<(), Diagnostics> {
let nullable_feat: Option<Nullable> =
pop_feature!(features => Feature::Nullable(_)).into_inner();
Expand Down Expand Up @@ -898,12 +932,12 @@ impl<'c> ComponentSchema {
quote_spanned! {type_path.span()=>
utoipa::openapi::schema::AllOfBuilder::new()
#nullable_item
.item(<#type_path as utoipa::ToSchema>::schema().1)
.item(<#type_path as utoipa::PartialSchema>::schema())
#default_tokens
}
} else {
quote_spanned! {type_path.span() =>
<#type_path as utoipa::ToSchema>::schema().1
<#type_path as utoipa::PartialSchema>::schema()
}
};

Expand All @@ -913,28 +947,45 @@ impl<'c> ComponentSchema {
if name == "Self" && !object_name.is_empty() {
name = Cow::Borrowed(object_name);
}

let default = pop_feature!(features => Feature::Default(_));
let default_tokens = as_tokens_or_diagnostics!(&default);

// TODO partial schema check cannot be performed for generic type, we
// need to know whether type is generic or not.
let check_type = if !is_generics_type_arg {
Some(
quote_spanned! {type_path.span()=> let _ = <#type_path as utoipa::PartialSchema>::schema;},
)
} else {
None
};

// TODO: refs support `summary` field but currently there is no such field
// on schemas more over there is no way to distinct the `summary` from
// `description` of the ref. Should we consider supporting the summary?
let schema = if default.is_some() || nullable {
quote! {
utoipa::openapi::schema::AllOfBuilder::new()
#nullable_item
.item(utoipa::openapi::schema::RefBuilder::new()
#description_stream
.ref_location_from_schema_name(#name)
)
#default_tokens
quote_spanned! {type_path.span()=>
{
#check_type

utoipa::openapi::schema::AllOfBuilder::new()
#nullable_item
.item(utoipa::openapi::schema::RefBuilder::new()
#description_stream
.ref_location_from_schema_name(#name)
)
#default_tokens
}
}
} else {
quote! {
utoipa::openapi::schema::RefBuilder::new()
#description_stream
.ref_location_from_schema_name(#name)
quote_spanned! {type_path.span()=>
{
#check_type

utoipa::openapi::schema::RefBuilder::new()
#description_stream
.ref_location_from_schema_name(#name)
}
}
};

Expand Down Expand Up @@ -962,6 +1013,7 @@ impl<'c> ComponentSchema {
description: None,
deprecated: None,
object_name,
is_generics_type_arg, // TODO check whether this is correct
}) {
Ok(child) => Ok(as_tokens_or_diagnostics!(&child)),
Err(diagnostics) => Err(diagnostics),
Expand Down Expand Up @@ -1024,6 +1076,7 @@ impl FlattenedMapSchema {
description,
deprecated,
object_name,
is_generics_type_arg,
}: ComponentSchemaProps,
) -> Result<Self, Diagnostics> {
let mut tokens = TokenStream::new();
Expand All @@ -1050,6 +1103,7 @@ impl FlattenedMapSchema {
description: None,
deprecated: None,
object_name,
is_generics_type_arg,
})?;
let schema_tokens = as_tokens_or_diagnostics!(&schema_property);

Expand Down
6 changes: 5 additions & 1 deletion utoipa-gen/src/component/into_params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use crate::{
FieldRename,
},
doc_comment::CommentAttributes,
Array, Diagnostics, OptionExt, Required, ToTokensDiagnostics,
Array, Diagnostics, GenericsExt, OptionExt, Required, ToTokensDiagnostics,
};

use super::{
Expand Down Expand Up @@ -147,6 +147,7 @@ impl ToTokensDiagnostics for IntoParams {
name,
},
serde_container: &serde_container,
generics: &self.generics
};

let mut param_tokens = TokenStream::new();
Expand Down Expand Up @@ -300,6 +301,8 @@ struct Param<'a> {
container_attributes: FieldParamContainerAttributes<'a>,
/// Either serde rename all rule or into_params rename all rule if provided.
serde_container: &'a SerdeContainer,
/// Container gnerics
generics: &'a Generics,
}

impl Param<'_> {
Expand Down Expand Up @@ -458,6 +461,7 @@ impl ToTokensDiagnostics for Param<'_> {
description: None,
deprecated: None,
object_name: "",
is_generics_type_arg: self.generics.any_match_type_tree(&component),
})?;
let schema_tokens = crate::as_tokens_or_diagnostics!(&schema);

Expand Down
Loading

0 comments on commit b473b99

Please sign in to comment.