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

Make Option non-required & add required attr #530

Merged
merged 1 commit into from
Mar 19, 2023
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
5 changes: 5 additions & 0 deletions utoipa-gen/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,11 @@ impl<'t> TypeTree<'t> {
pub fn is_object(&self) -> bool {
self.is("Object")
}

/// Check whether the [`TypeTree`]'s `generic_type` is [`GenericType::Option`]
pub fn is_option(&self) -> bool {
matches!(self.generic_type, Some(GenericType::Option))
}
}

#[cfg(not(feature = "debug"))]
Expand Down
59 changes: 58 additions & 1 deletion utoipa-gen/src/component/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ pub enum Feature {
Deprecated(Deprecated),
As(As),
AdditionalProperties(AdditionalProperites),
Required(Required),
}

impl Feature {
Expand Down Expand Up @@ -241,6 +242,10 @@ impl ToTokens for Feature {
Feature::As(_) => {
abort!(Span::call_site(), "As does not support `ToTokens`")
}
Feature::Required(required) => {
let name = <Required as Name>::get_name();
quote! { .#name(#required) }
}
};

tokens.extend(feature)
Expand Down Expand Up @@ -284,6 +289,7 @@ impl Display for Feature {
Feature::Deprecated(deprecated) => deprecated.fmt(f),
Feature::As(as_feature) => as_feature.fmt(f),
Feature::AdditionalProperties(additional_properties) => additional_properties.fmt(f),
Feature::Required(required) => required.fmt(f),
}
}
}
Expand Down Expand Up @@ -327,6 +333,7 @@ impl Validatable for Feature {
Feature::AdditionalProperties(additional_properites) => {
additional_properites.is_validatable()
}
Feature::Required(required) => required.is_validatable(),
}
}
}
Expand Down Expand Up @@ -377,7 +384,8 @@ is_validatable! {
Description => false,
Deprecated => false,
As => false,
AdditionalProperites => false
AdditionalProperites => false,
Required => false
}

#[derive(Clone)]
Expand Down Expand Up @@ -1417,6 +1425,55 @@ impl From<AdditionalProperites> for Feature {
}
}

#[derive(Clone)]
#[cfg_attr(feature = "debug", derive(Debug))]
pub struct Required(pub bool);

impl Required {
pub fn is_true(&self) -> bool {
self.0 == true
}
}

impl Parse for Required {
fn parse(input: ParseStream, _: Ident) -> syn::Result<Self>
where
Self: std::marker::Sized,
{
parse_utils::parse_bool_or_true(input).map(Self)
}
}

impl ToTokens for Required {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.0.to_tokens(tokens)
}
}

impl From<crate::Required> for Required {
fn from(value: crate::Required) -> Self {
if value == crate::Required::True {
Self(true)
} else {
Self(false)
}
}
}

impl From<bool> for Required {
fn from(value: bool) -> Self {
Self(value)
}
}

impl From<Required> for Feature {
fn from(value: Required) -> Self {
Self::Required(value)
}
}

name!(Required = "required");

pub trait Validator {
fn is_valid(&self) -> Result<(), &'static str>;
}
Expand Down
15 changes: 11 additions & 4 deletions utoipa-gen/src/component/into_params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ use crate::{

use super::{
features::{
impl_into_inner, impl_merge, parse_features, pop_feature, Feature, FeaturesExt, IntoInner,
Merge, ToTokensExt,
impl_into_inner, impl_merge, parse_features, pop_feature, pop_feature_as_inner, Feature,
FeaturesExt, IntoInner, Merge, ToTokensExt,
},
serde::{self, SerdeContainer},
ComponentSchema, TypeTree,
Expand Down Expand Up @@ -228,6 +228,7 @@ impl Parse for FieldFeatures {
Example,
Explode,
SchemaWith,
component::features::Required,
// param schema features
Inline,
Format,
Expand Down Expand Up @@ -399,8 +400,14 @@ impl ToTokens for Param<'_> {
.map(|value_type| value_type.as_type_tree())
.unwrap_or(type_tree);

let required: Required =
component::is_required(field_param_serde.as_ref(), self.serde_container).into();
let required = pop_feature_as_inner!(param_features => Feature::Required(_v))
.as_ref()
.map(super::features::Required::is_true)
.unwrap_or(false);

let non_required = (component.is_option() && !required)
|| !component::is_required(field_param_serde.as_ref(), self.serde_container);
let required: Required = (!non_required).into();

tokens.extend(quote! {
.required(#required)
Expand Down
107 changes: 71 additions & 36 deletions utoipa-gen/src/component/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,11 +258,18 @@ pub struct NamedStructSchema<'a> {
pub schema_as: Option<As>,
}

struct NamedStructFieldOptions<'a> {
property: Property,
rename_field_value: Option<Cow<'a, str>>,
required: Option<super::features::Required>,
is_option: bool,
}

impl NamedStructSchema<'_> {
fn field_as_schema_property<R>(
&self,
field: &Field,
yield_: impl FnOnce(Property, Option<Cow<'_, str>>) -> R,
yield_: impl FnOnce(NamedStructFieldOptions<'_>) -> R,
) -> R {
let type_tree = &mut TypeTree::from_type(&field.ty);

Expand Down Expand Up @@ -305,21 +312,26 @@ impl NamedStructSchema<'_> {
.map(|value_type| value_type.as_type_tree());
let comments = CommentAttributes::from_attributes(&field.attrs);
let with_schema = pop_feature!(field_features => Feature::SchemaWith(_));
let required = pop_feature_as_inner!(field_features => Feature::Required(_v));
let type_tree = override_type_tree.as_ref().unwrap_or(type_tree);
let is_option = type_tree.is_option();

yield_(
if let Some(with_schema) = with_schema {
yield_(NamedStructFieldOptions {
property: if let Some(with_schema) = with_schema {
Property::WithSchema(with_schema)
} else {
Property::Schema(ComponentSchema::new(super::ComponentSchemaProps {
type_tree: override_type_tree.as_ref().unwrap_or(type_tree),
type_tree,
features: field_features,
description: Some(&comments),
deprecated: deprecated.as_ref(),
object_name: self.struct_name.as_ref(),
}))
},
rename_field,
)
rename_field_value: rename_field,
required,
is_option,
})
}
}

Expand Down Expand Up @@ -348,37 +360,57 @@ impl ToTokens for NamedStructSchema<'_> {
field_name = &field_name[2..];
}

self.field_as_schema_property(field, |property, rename| {
let rename_to = field_rule
.as_ref()
.and_then(|field_rule| field_rule.rename.as_deref().map(Cow::Borrowed))
.or(rename);
let rename_all = container_rules
.as_ref()
.and_then(|container_rule| container_rule.rename_all.as_ref())
.or_else(|| {
self.rename_all
.as_ref()
.map(|rename_all| rename_all.as_rename_rule())
self.field_as_schema_property(
field,
|NamedStructFieldOptions {
property,
rename_field_value,
required,
is_option,
}| {
let rename_to = field_rule
.as_ref()
.and_then(|field_rule| {
field_rule.rename.as_deref().map(Cow::Borrowed)
})
.or(rename_field_value);
let rename_all = container_rules
.as_ref()
.and_then(|container_rule| container_rule.rename_all.as_ref())
.or_else(|| {
self.rename_all
.as_ref()
.map(|rename_all| rename_all.as_rename_rule())
});

let name =
super::rename::<FieldRename>(field_name, rename_to, rename_all)
.unwrap_or(Cow::Borrowed(field_name));

object_tokens.extend(quote! {
.property(#name, #property)
});

let name = super::rename::<FieldRename>(field_name, rename_to, rename_all)
.unwrap_or(Cow::Borrowed(field_name));

object_tokens.extend(quote! {
.property(#name, #property)
});

if let Property::Schema(_) = property {
if super::is_required(field_rule.as_ref(), container_rules.as_ref()) {
object_tokens.extend(quote! {
.required(#name)
})
if let Property::Schema(_) = property {
if (!is_option
&& super::is_required(
field_rule.as_ref(),
container_rules.as_ref(),
))
|| required
.as_ref()
.map(super::features::Required::is_true)
.unwrap_or(false)
{
object_tokens.extend(quote! {
.required(#name)
})
}
}
}

object_tokens
})
object_tokens
},
)
},
);

Expand All @@ -397,9 +429,12 @@ impl ToTokens for NamedStructSchema<'_> {
});

for field in flatten_fields {
self.field_as_schema_property(field, |schema_property, _| {
tokens.extend(quote! { .item(#schema_property) });
})
self.field_as_schema_property(
field,
|NamedStructFieldOptions { property, .. }| {
tokens.extend(quote! { .item(#property) });
},
)
}

tokens.extend(quote! {
Expand Down
7 changes: 4 additions & 3 deletions utoipa-gen/src/component/schema/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use crate::component::features::{
impl_into_inner, impl_merge, parse_features, AdditionalProperites, As, Default, Example,
ExclusiveMaximum, ExclusiveMinimum, Feature, Format, Inline, IntoInner, MaxItems, MaxLength,
MaxProperties, Maximum, Merge, MinItems, MinLength, MinProperties, Minimum, MultipleOf,
Nullable, Pattern, ReadOnly, Rename, RenameAll, SchemaWith, Title, ValueType, WriteOnly,
XmlAttr,
Nullable, Pattern, ReadOnly, Rename, RenameAll, Required, SchemaWith, Title, ValueType,
WriteOnly, XmlAttr,
};

#[cfg_attr(feature = "debug", derive(Debug))]
Expand Down Expand Up @@ -106,7 +106,8 @@ impl Parse for NamedFieldFeatures {
MaxItems,
MinItems,
SchemaWith,
AdditionalProperites
AdditionalProperites,
Required
)))
}
}
Expand Down
Loading