From 61046d109f9e0254e430fb7e3d6fc2f651141502 Mon Sep 17 00:00:00 2001 From: Juha Kukkonen Date: Mon, 20 Mar 2023 01:15:38 +0200 Subject: [PATCH] Make `Option` non-required & add `required` attr (#530) Make `Option` fields non-required by default. This is the way it used to be originally but was changed due hopes for better client code generation. However since it caused issues with client side generation the behaviour is now changed back on this commit. The nullability behaviour will still stay to same, so `Option` will still be considered nullable. These rules are explained in the docs for greater detail. This commit also adds `required` attribute that can be used to declare a field of `ToSchema` and `IntoParams` as required in order to change the default required status of a field / parameter. Since `Option` is by default non-required, this would enforce the `name` parameter to be required. ```rust #[derive(IntoParams)] #[into_params(parameter_in = Query)] struct Params { #[param(required)] name: Option, } ``` --- utoipa-gen/src/component.rs | 5 + utoipa-gen/src/component/features.rs | 59 +++++++++- utoipa-gen/src/component/into_params.rs | 15 ++- utoipa-gen/src/component/schema.rs | 107 ++++++++++++------ utoipa-gen/src/component/schema/features.rs | 7 +- utoipa-gen/src/lib.rs | 67 +++++++---- utoipa-gen/src/path/parameter.rs | 27 +++-- utoipa-gen/src/path/request_body.rs | 5 +- utoipa-gen/tests/path_derive.rs | 80 +++++++++++-- utoipa-gen/tests/path_derive_actix.rs | 4 +- utoipa-gen/tests/path_derive_axum_test.rs | 2 +- utoipa-gen/tests/path_derive_rocket.rs | 2 +- .../tests/path_parameter_derive_test.rs | 6 +- utoipa-gen/tests/request_body_derive_test.rs | 4 +- utoipa-gen/tests/schema_derive_test.rs | 37 +++--- 15 files changed, 306 insertions(+), 121 deletions(-) diff --git a/utoipa-gen/src/component.rs b/utoipa-gen/src/component.rs index ee201bc6..99e3ae54 100644 --- a/utoipa-gen/src/component.rs +++ b/utoipa-gen/src/component.rs @@ -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"))] diff --git a/utoipa-gen/src/component/features.rs b/utoipa-gen/src/component/features.rs index f9da2981..6d4f0a7f 100644 --- a/utoipa-gen/src/component/features.rs +++ b/utoipa-gen/src/component/features.rs @@ -118,6 +118,7 @@ pub enum Feature { Deprecated(Deprecated), As(As), AdditionalProperties(AdditionalProperites), + Required(Required), } impl Feature { @@ -241,6 +242,10 @@ impl ToTokens for Feature { Feature::As(_) => { abort!(Span::call_site(), "As does not support `ToTokens`") } + Feature::Required(required) => { + let name = ::get_name(); + quote! { .#name(#required) } + } }; tokens.extend(feature) @@ -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), } } } @@ -327,6 +333,7 @@ impl Validatable for Feature { Feature::AdditionalProperties(additional_properites) => { additional_properites.is_validatable() } + Feature::Required(required) => required.is_validatable(), } } } @@ -377,7 +384,8 @@ is_validatable! { Description => false, Deprecated => false, As => false, - AdditionalProperites => false + AdditionalProperites => false, + Required => false } #[derive(Clone)] @@ -1417,6 +1425,55 @@ impl From 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 + 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 for Required { + fn from(value: crate::Required) -> Self { + if value == crate::Required::True { + Self(true) + } else { + Self(false) + } + } +} + +impl From for Required { + fn from(value: bool) -> Self { + Self(value) + } +} + +impl From for Feature { + fn from(value: Required) -> Self { + Self::Required(value) + } +} + +name!(Required = "required"); + pub trait Validator { fn is_valid(&self) -> Result<(), &'static str>; } diff --git a/utoipa-gen/src/component/into_params.rs b/utoipa-gen/src/component/into_params.rs index a20e6366..301b7b81 100644 --- a/utoipa-gen/src/component/into_params.rs +++ b/utoipa-gen/src/component/into_params.rs @@ -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, @@ -228,6 +228,7 @@ impl Parse for FieldFeatures { Example, Explode, SchemaWith, + component::features::Required, // param schema features Inline, Format, @@ -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) diff --git a/utoipa-gen/src/component/schema.rs b/utoipa-gen/src/component/schema.rs index 6d2fa933..16707190 100644 --- a/utoipa-gen/src/component/schema.rs +++ b/utoipa-gen/src/component/schema.rs @@ -258,11 +258,18 @@ pub struct NamedStructSchema<'a> { pub schema_as: Option, } +struct NamedStructFieldOptions<'a> { + property: Property, + rename_field_value: Option>, + required: Option, + is_option: bool, +} + impl NamedStructSchema<'_> { fn field_as_schema_property( &self, field: &Field, - yield_: impl FnOnce(Property, Option>) -> R, + yield_: impl FnOnce(NamedStructFieldOptions<'_>) -> R, ) -> R { let type_tree = &mut TypeTree::from_type(&field.ty); @@ -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, + }) } } @@ -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::(field_name, rename_to, rename_all) + .unwrap_or(Cow::Borrowed(field_name)); + + object_tokens.extend(quote! { + .property(#name, #property) }); - let name = super::rename::(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 + }, + ) }, ); @@ -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! { diff --git a/utoipa-gen/src/component/schema/features.rs b/utoipa-gen/src/component/schema/features.rs index 94aab0da..117e4e2d 100644 --- a/utoipa-gen/src/component/schema/features.rs +++ b/utoipa-gen/src/component/schema/features.rs @@ -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))] @@ -106,7 +106,8 @@ impl Parse for NamedFieldFeatures { MaxItems, MinItems, SchemaWith, - AdditionalProperites + AdditionalProperites, + Required ))) } } diff --git a/utoipa-gen/src/lib.rs b/utoipa-gen/src/lib.rs index 92f472ce..7cb07461 100644 --- a/utoipa-gen/src/lib.rs +++ b/utoipa-gen/src/lib.rs @@ -36,7 +36,10 @@ mod security_requirement; use crate::path::{Path, PathAttr}; -use self::path::response::derive::{IntoResponses, ToResponse}; +use self::{ + component::features, + path::response::derive::{IntoResponses, ToResponse}, +}; #[proc_macro_error] #[proc_macro_derive(ToSchema, attributes(schema, aliases))] @@ -138,6 +141,8 @@ use self::path::response::derive::{IntoResponses, ToResponse}; /// _`Object`_ will be rendered as generic OpenAPI object _(`type: object`)_. /// * `inline` If the type of this field implements [`ToSchema`][to_schema], then the schema definition /// will be inlined. **warning:** Don't use this for recursive data types! +/// * `required = ...` Can be used to enforce required status for the field. [See +/// rules][derive@ToSchema#field-nullability-and-required-rules] /// * `nullable` Defines property is nullable (note this is different to non-required). /// * `rename = ...` Supports same syntax as _serde_ _`rename`_ attribute. Will rename field /// accordingly. If both _serde_ `rename` and _schema_ _`rename`_ are defined __serde__ will take @@ -166,7 +171,8 @@ use self::path::response::derive::{IntoResponses, ToResponse}; /// #### Field nullability and required rules /// /// Field is considered _`required`_ if -/// * it does not have _`skip_serializing_if`_ property +/// * it is not `Option` field +/// * and it does not have _`skip_serializing_if`_ property /// * and it does not have _`serde_with`_ _[`double_option`](https://docs.rs/serde_with/latest/serde_with/rust/double_option/index.html)_ /// * and it does not have default value provided with serde _`default`_ /// attribute @@ -194,7 +200,8 @@ use self::path::response::derive::{IntoResponses, ToResponse}; /// * `rename = "..."` Supported **only** at the field or variant level. /// * `skip = "..."` Supported **only** at the field or variant level. /// * `skip_serializing = "..."` Supported **only** at the field or variant level. -/// * `with = ...` Supported **only at field level!** +/// * `skip_serializing_if = "..."` Supported **only** at the field level. +/// * `with = ...` Supported **only at field level.** /// * `tag = "..."` Supported at the container level. `tag` attribute works as a [discriminator field][discriminator] for an enum. /// * `content = "..."` Supported at the container level, allows [adjacently-tagged enums](https://serde.rs/enum-representations.html#adjacently-tagged). /// This attribute requires that a `tag` is present, otherwise serde will trigger a compile-time @@ -1586,6 +1593,24 @@ pub fn openapi(input: TokenStream) -> TokenStream { /// [`IntoParams::into_params()`](trait.IntoParams.html#tymethod.into_params). /// * `rename_all = ...` Can be provided to alternatively to the serde's `rename_all` attribute. Effectively provides same functionality. /// +/// Use `names` to define name for single unnamed argument. +/// ```rust +/// # use utoipa::IntoParams; +/// # +/// #[derive(IntoParams)] +/// #[into_params(names("id"))] +/// struct Id(u64); +/// ``` +/// +/// Use `names` to define names for multiple unnamed arguments. +/// ```rust +/// # use utoipa::IntoParams; +/// # +/// #[derive(IntoParams)] +/// #[into_params(names("id", "name"))] +/// struct IdAndName(u64, String); +/// ``` +/// /// # IntoParams Field Attributes for `#[param(...)]` /// /// The following attributes are available for use in the `#[param(...)]` on struct fields: @@ -1623,6 +1648,9 @@ pub fn openapi(input: TokenStream) -> TokenStream { /// /// * `nullable` Defines property is nullable (note this is different to non-required). /// +/// * `required = ...` Can be used to enforce required status for the parameter. [See +/// rules][derive@IntoParams#field-nullability-and-required-rules] +/// /// * `rename = ...` Can be provided to alternatively to the serde's `rename` attribute. Effectively provides same functionality. /// /// * `multiple_of = ...` Can be used to define multiplier for a value. Value is considered valid @@ -1657,34 +1685,21 @@ pub fn openapi(input: TokenStream) -> TokenStream { /// Free form type enables use of arbitrary types within map values. /// Supports formats _`additional_properties`_ and _`additional_properties = true`_. /// -/// **Note!** `#[into_params(...)]` is only supported on unnamed struct types to declare names for the arguments. -/// -/// Use `names` to define name for single unnamed argument. -/// ```rust -/// # use utoipa::IntoParams; -/// # -/// #[derive(IntoParams)] -/// #[into_params(names("id"))] -/// struct Id(u64); -/// ``` +/// #### Field nullability and required rules /// -/// Use `names` to define names for multiple unnamed arguments. -/// ```rust -/// # use utoipa::IntoParams; -/// # -/// #[derive(IntoParams)] -/// #[into_params(names("id", "name"))] -/// struct IdAndName(u64, String); -/// ``` +/// Same rules for nullability and required status apply for _`IntoParams`_ field attributes as for +/// _`ToSchema`_ field attributes. [See the rules][`derive@ToSchema#field-nullability-and-required-rules`]. /// /// # Partial `#[serde(...)]` attributes support /// /// IntoParams derive has partial support for [serde attributes]. These supported attributes will reflect to the -/// generated OpenAPI doc. The following attributes are currently supported : +/// generated OpenAPI doc. The following attributes are currently supported: /// /// * `rename_all = "..."` Supported at the container level. /// * `rename = "..."` Supported **only** at the field level. /// * `default` Supported at the container level and field level according to [serde attributes]. +/// * `skip_serializing_if = "..."` Supported **only** at the field level. +/// * `with = ...` Supported **only** at field level. /// /// Other _`serde`_ attributes will impact the serialization but will not be reflected on the generated OpenAPI doc. /// @@ -2386,6 +2401,7 @@ impl ToTokens for Deprecated { } } +#[derive(PartialEq, Eq)] #[cfg_attr(feature = "debug", derive(Debug))] enum Required { True, @@ -2402,6 +2418,13 @@ impl From for Required { } } +impl From for Required { + fn from(value: features::Required) -> Self { + let features::Required(required) = value; + crate::Required::from(required) + } +} + impl ToTokens for Required { fn to_tokens(&self, tokens: &mut TokenStream2) { tokens.extend(match self { diff --git a/utoipa-gen/src/path/parameter.rs b/utoipa-gen/src/path/parameter.rs index b7be0270..1a01674a 100644 --- a/utoipa-gen/src/path/parameter.rs +++ b/utoipa-gen/src/path/parameter.rs @@ -121,18 +121,23 @@ impl ToTokens for ParameterSchema<'_> { feature = "rocket_extras", feature = "axum_extras" ))] - ParameterType::External(type_tree) => to_tokens( - ComponentSchema::new(component::ComponentSchemaProps { - type_tree, - features: Some(self.features.clone()), - description: None, - deprecated: None, - object_name: "", - }), - Required::True, - ), + ParameterType::External(type_tree) => { + let required: Required = (!type_tree.is_option()).into(); + + to_tokens( + ComponentSchema::new(component::ComponentSchemaProps { + type_tree, + features: Some(self.features.clone()), + description: None, + deprecated: None, + object_name: "", + }), + required, + ) + } ParameterType::Parsed(inline_type) => { let type_tree = inline_type.as_type_tree(); + let required: Required = (!type_tree.is_option()).into(); let mut schema_features = Vec::::new(); schema_features.clone_from(&self.features); schema_features.push(Feature::Inline(inline_type.is_inline.into())); @@ -145,7 +150,7 @@ impl ToTokens for ParameterSchema<'_> { deprecated: None, object_name: "", }), - Required::True, + required, ) } } diff --git a/utoipa-gen/src/path/request_body.rs b/utoipa-gen/src/path/request_body.rs index c70e755e..ccf71a0a 100644 --- a/utoipa-gen/src/path/request_body.rs +++ b/utoipa-gen/src/path/request_body.rs @@ -6,7 +6,7 @@ use syn::{parenthesized, parse::Parse, token::Paren, Error, Token}; use crate::component::features::Inline; use crate::component::ComponentSchema; -use crate::{parse_utils, AnyValue, Array}; +use crate::{parse_utils, AnyValue, Array, Required}; use super::example::Example; use super::{PathType, PathTypeTree}; @@ -184,6 +184,7 @@ impl ToTokens for RequestBodyAttr<'_> { } PathType::MediaType(body_type) => { let type_tree = body_type.as_type_tree(); + let required: Required = (!type_tree.is_option()).into(); let content_type = self .content_type .as_deref() @@ -191,7 +192,7 @@ impl ToTokens for RequestBodyAttr<'_> { tokens.extend(quote! { utoipa::openapi::request_body::RequestBodyBuilder::new() .content(#content_type, #content.build()) - .required(Some(utoipa::openapi::Required::True)) + .required(Some(#required)) }); } PathType::InlineSchema(_, _) => { diff --git a/utoipa-gen/tests/path_derive.rs b/utoipa-gen/tests/path_derive.rs index 83df9aea..212e589c 100644 --- a/utoipa-gen/tests/path_derive.rs +++ b/utoipa-gen/tests/path_derive.rs @@ -241,7 +241,7 @@ fn derive_path_with_extra_attributes_without_nested_module() { "parameters.[1].description" = r#""Datetime since foo is updated""#, "Parameter 1 description" "parameters.[1].in" = r#""query""#, "Parameter 1 in" "parameters.[1].name" = r#""since""#, "Parameter 1 name" - "parameters.[1].required" = r#"true"#, "Parameter 1 required" + "parameters.[1].required" = r#"false"#, "Parameter 1 required" "parameters.[1].schema.allOf.[0].format" = r#"null"#, "Parameter 1 schema format" "parameters.[1].schema.allOf.[0].type" = r#"null"#, "Parameter 1 schema type" "parameters.[1].schema.allOf.nullable" = r#"null"#, "Parameter 1 schema type" @@ -337,7 +337,7 @@ fn derive_path_with_parameter_schema() { "description": "Datetime since foo is updated", "in": "query", "name": "since", - "required": true, + "required": false, "schema": { "allOf": [ { @@ -408,7 +408,7 @@ fn derive_path_with_parameter_inline_schema() { "description": "Datetime since foo is updated", "in": "query", "name": "since", - "required": true, + "required": false, "schema": { "allOf": [ { @@ -720,7 +720,7 @@ fn derive_required_path_params() { { "in": "query", "name": "vec_option", - "required": true, + "required": false, "schema": { "nullable": true, "items": { @@ -732,7 +732,7 @@ fn derive_required_path_params() { { "in": "query", "name": "string_option", - "required": true, + "required": false, "schema": { "nullable": true, "type": "string" @@ -791,7 +791,7 @@ fn derive_path_params_with_serde_and_custom_rename() { { "in": "query", "name": "vecDefault", - "required": true, + "required": false, "schema": { "type": "array", "nullable": true, @@ -856,7 +856,7 @@ fn derive_path_params_custom_rename_all() { { "in": "query", "name": "vecDefault", - "required": true, + "required": false, "schema": { "type": "array", "nullable": true, @@ -886,7 +886,7 @@ fn derive_path_params_custom_rename_all_serde_will_override() { { "in": "query", "name": "VEC_DEFAULT", - "required": true, + "required": false, "schema": { "type": "array", "nullable": true, @@ -1021,7 +1021,7 @@ fn derive_path_params_intoparams() { "example": "2020-04-12T10:23:00Z", "in": "query", "name": "since", - "required": true, + "required": false, "schema": { "nullable": true, "type": "string" @@ -1055,7 +1055,7 @@ fn derive_path_params_intoparams() { "description": "An optional Foo item inline.", "in": "query", "name": "foo_inline_option", - "required": true, + "required": false, "schema": { "allOf": [ { @@ -1199,7 +1199,7 @@ fn derive_path_params_into_params_with_value_type() { { "in": "query", "name": "value3", - "required": true, + "required": false, "schema": { "nullable": true, "type": "string" @@ -1208,7 +1208,7 @@ fn derive_path_params_into_params_with_value_type() { { "in": "query", "name": "value4", - "required": true, + "required": false, "schema": { "nullable": true, "type": "object" @@ -1561,3 +1561,59 @@ fn derive_path_with_into_params_custom_schema() { ]) ) } + +#[test] +fn derive_into_params_required() { + #[derive(IntoParams)] + #[into_params(parameter_in = Query)] + #[allow(unused)] + struct Params { + name: String, + name2: Option, + #[param(required)] + name3: Option, + } + + #[utoipa::path(get, path = "/params", params(Params))] + #[allow(unused)] + fn get_params() {} + let operation = test_api_fn_doc! { + get_params, + operation: get, + path: "/params" + }; + + let value = operation.pointer("/parameters"); + + assert_json_eq!( + value, + json!([ + { + "in": "query", + "name": "name", + "required": true, + "schema": { + "type": "string", + }, + }, + { + "in": "query", + "name": "name2", + "required": false, + "schema": { + "type": "string", + "nullable": true, + }, + }, + { + "in": "query", + "name": "name3", + "required": true, + "schema": { + "type": "string", + "nullable": true, + }, + }, + ]) + ) +} diff --git a/utoipa-gen/tests/path_derive_actix.rs b/utoipa-gen/tests/path_derive_actix.rs index 023ab24b..e54db19d 100644 --- a/utoipa-gen/tests/path_derive_actix.rs +++ b/utoipa-gen/tests/path_derive_actix.rs @@ -501,7 +501,7 @@ fn derive_path_with_struct_variables_with_into_params() { "[2].in" = r#""query""#, "Parameter in" "[2].name" = r#""age""#, "Parameter name" "[2].description" = r#""Age filter for user""#, "Parameter description" - "[2].required" = r#"true"#, "Parameter required" + "[2].required" = r#"false"#, "Parameter required" "[2].deprecated" = r#"true"#, "Parameter deprecated" "[2].schema.type" = r#""array""#, "Parameter schema type" "[2].schema.items.type" = r#""string""#, "Parameter items schema type" @@ -699,7 +699,7 @@ fn derive_into_params_with_custom_attributes() { "[2].in" = r#""query""#, "Parameter in" "[2].name" = r#""age""#, "Parameter name" "[2].description" = r#""Age filter for user""#, "Parameter description" - "[2].required" = r#"true"#, "Parameter required" + "[2].required" = r#"false"#, "Parameter required" "[2].deprecated" = r#"null"#, "Parameter deprecated" "[2].style" = r#""form""#, "Parameter style" "[2].example" = r#"["10"]"#, "Parameter example" diff --git a/utoipa-gen/tests/path_derive_axum_test.rs b/utoipa-gen/tests/path_derive_axum_test.rs index bffc5bd1..279b2782 100644 --- a/utoipa-gen/tests/path_derive_axum_test.rs +++ b/utoipa-gen/tests/path_derive_axum_test.rs @@ -81,7 +81,7 @@ fn derive_path_params_into_params_axum() { "description": "Age filter for user", "in": "query", "name": "age", - "required": true, + "required": false, "schema": { "items": { "type": "string", diff --git a/utoipa-gen/tests/path_derive_rocket.rs b/utoipa-gen/tests/path_derive_rocket.rs index 90b324b9..2675fde8 100644 --- a/utoipa-gen/tests/path_derive_rocket.rs +++ b/utoipa-gen/tests/path_derive_rocket.rs @@ -125,7 +125,7 @@ fn resolve_get_with_optional_query_args() { { "in": "query", "name": "colors", - "required": true, + "required": false, "schema": { "items": { "type": "string", diff --git a/utoipa-gen/tests/path_parameter_derive_test.rs b/utoipa-gen/tests/path_parameter_derive_test.rs index 6945f76f..e694fc6d 100644 --- a/utoipa-gen/tests/path_parameter_derive_test.rs +++ b/utoipa-gen/tests/path_parameter_derive_test.rs @@ -192,7 +192,7 @@ fn derive_parameters_with_all_types() { "[2].in" = r#""query""#, "Parameter in" "[2].name" = r#""numbers""#, "Parameter name" "[2].description" = r#""Foo numbers list""#, "Parameter description" - "[2].required" = r#"true"#, "Parameter required" + "[2].required" = r#"false"#, "Parameter required" "[2].deprecated" = r#"null"#, "Parameter deprecated" "[2].schema.type" = r#""array""#, "Parameter schema type" "[2].schema.format" = r#"null"#, "Parameter schema format" @@ -284,7 +284,7 @@ fn derive_params_with_params_ext() { "[0].in" = r#""query""#, "Parameter in" "[0].name" = r#""value""#, "Parameter name" "[0].description" = r#""Foo value description""#, "Parameter description" - "[0].required" = r#"true"#, "Parameter required" + "[0].required" = r#"false"#, "Parameter required" "[0].deprecated" = r#"true"#, "Parameter deprecated" "[0].schema.type" = r#""array""#, "Parameter schema type" "[0].schema.items.type" = r#""string""#, "Parameter schema items type" @@ -326,7 +326,7 @@ fn derive_path_params_with_parameter_type_args() { { "in": "query", "name": "value", - "required": true, + "required": false, "deprecated": true, "description": "Foo value description", "schema": { diff --git a/utoipa-gen/tests/request_body_derive_test.rs b/utoipa-gen/tests/request_body_derive_test.rs index d01a0b2d..a7cb982c 100644 --- a/utoipa-gen/tests/request_body_derive_test.rs +++ b/utoipa-gen/tests/request_body_derive_test.rs @@ -100,7 +100,7 @@ fn derive_request_body_option_array_success() { }, } }, - "required": true, + "required": false, }) ); } @@ -382,7 +382,7 @@ fn derive_request_body_complex_required_explicit_false_success() { } }, "description": "Create new Foo", - "required": true, + "required": false, }) ); } diff --git a/utoipa-gen/tests/schema_derive_test.rs b/utoipa-gen/tests/schema_derive_test.rs index a46261ac..5589f579 100644 --- a/utoipa-gen/tests/schema_derive_test.rs +++ b/utoipa-gen/tests/schema_derive_test.rs @@ -282,10 +282,6 @@ fn derive_struct_with_optional_properties() { }, "required": [ "id", - "enabled", - "books", - "metadata", - "optional_book" ], "type": "object" }) @@ -737,8 +733,6 @@ fn derive_struct_with_inline() { }, "required": [ "foo1", - "foo2", - "foo3", "foo4", ], "type": "object" @@ -1161,7 +1155,6 @@ fn derive_complex_enum() { }, "required": [ "id", - "names" ], }, }, @@ -1291,7 +1284,6 @@ fn derive_complex_enum_serde_rename_all() { }, "required": [ "id", - "names" ], }, }, @@ -1362,7 +1354,6 @@ fn derive_complex_enum_serde_rename_variant() { }, "required": [ "renamed_id", - "renamed_names" ], }, }, @@ -1714,7 +1705,6 @@ fn derive_complex_enum_serde_tag() { }, "required": [ "id", - "names", "tag", ], }, @@ -2380,7 +2370,9 @@ fn derive_struct_with_nullable_and_required() { #[schema(nullable)] edit_history: Vec, #[serde(skip_serializing_if = "Vec::is_empty")] - friends: Vec> + friends: Vec>, + #[schema(required)] + updated: Option, } }; @@ -2415,9 +2407,18 @@ fn derive_struct_with_nullable_and_required() { "type": "string", "nullable": true, }, + }, + "updated": { + "type": "string", + "nullable": true, } }, - "required": ["phone", "email", "name", "edit_history"], + "required": [ + "email", + "name", + "edit_history", + "updated", + ], "type": "object" }) ) @@ -2514,7 +2515,7 @@ fn derive_struct_xml_with_optional_vec() { } } }, - "required": ["id", "links"], + "required": ["id"], "type": "object", "xml": { "name": "user" @@ -2910,7 +2911,7 @@ fn derive_parse_serde_complex_enum() { "oneOf.[1].properties.namedFields.properties.id.type" = r#""string""#, "Named fields id type" "oneOf.[1].properties.namedFields.properties.nameList.type" = r#""array""#, "Named fields nameList type" "oneOf.[1].properties.namedFields.properties.nameList.items.type" = r#""string""#, "Named fields nameList items type" - "oneOf.[1].properties.namedFields.required" = r#"["id","nameList"]"#, "Named fields required" + "oneOf.[1].properties.namedFields.required" = r#"["id"]"#, "Named fields required" "oneOf.[2].properties.unnamedFields.$ref" = r###""#/components/schemas/Foo""###, "Unnamed fields ref" } @@ -3053,7 +3054,7 @@ fn derive_component_with_primitive_aliases() { } #[test] -fn derive_component_with_into_params_value_type() { +fn derive_component_with_to_schema_value_type() { #[derive(ToSchema)] struct Foo { #[allow(unused)] @@ -3134,8 +3135,6 @@ fn derive_component_with_into_params_value_type() { "another_id", "value1", "value2", - "value3", - "value4", "value5", "value6", ], @@ -3894,8 +3893,6 @@ fn derive_schema_with_generics_and_lifetimes() { "required": [ "total", "data", - "next", - "prev" ], "type": "object" } @@ -3926,8 +3923,6 @@ fn derive_schema_with_generics_and_lifetimes() { "required": [ "total", "data", - "next", - "prev" ], "type": "object" }