diff --git a/utoipa-gen/src/component.rs b/utoipa-gen/src/component.rs index 43087750..4145d789 100644 --- a/utoipa-gen/src/component.rs +++ b/utoipa-gen/src/component.rs @@ -39,7 +39,12 @@ fn is_default(container_rules: &Option<&SerdeContainer>, field_rule: &Option<&Se /// or field attributes of struct. fn get_deprecated(attributes: &[Attribute]) -> Option { attributes.iter().find_map(|attribute| { - if attribute.path.get_ident().map(|ident| *ident == "deprecated").unwrap_or(false) { + if attribute + .path + .get_ident() + .map(|ident| *ident == "deprecated") + .unwrap_or(false) + { Some(Deprecated::True) } else { None @@ -741,14 +746,43 @@ impl<'c> ComponentSchema { } } } - // TODO support for tuple types ValueType::Tuple => { - // Detect unit type () - if type_tree.children.is_none() { - tokens.extend(quote! { - utoipa::openapi::schema::empty() + type_tree + .children + .as_ref() + .map(|children| { + let all_of = children.iter().fold( + quote! { utoipa::openapi::schema::AllOfBuilder::new() }, + |mut all_of, child| { + let features = if child.is_option() { + Some(vec![Feature::Nullable(Nullable::new())]) + } else { + None + }; + + let item = ComponentSchema::new(ComponentSchemaProps { + type_tree: child, + features, + description: None, + deprecated: None, + object_name, + }); + all_of.extend(quote!( .item(#item) )); + + all_of + }, + ); + quote! { + utoipa::openapi::schema::ArrayBuilder::new() + .items(#all_of) + #nullable + #description_stream + #deprecated_stream + } }) - }; + .unwrap_or_else(|| quote!(utoipa::openapi::schema::empty())) + .to_tokens(tokens); + tokens.extend(features.to_token_stream()); } } } diff --git a/utoipa-gen/tests/schema_derive_test.rs b/utoipa-gen/tests/schema_derive_test.rs index 939b20ae..6296bcdf 100644 --- a/utoipa-gen/tests/schema_derive_test.rs +++ b/utoipa-gen/tests/schema_derive_test.rs @@ -4232,3 +4232,84 @@ fn derive_schema_with_object_type_description() { }) ) } + +#[test] +fn derive_tuple_named_struct_field() { + #[derive(ToSchema)] + #[allow(unused)] + struct Person { + name: String, + } + + let value = api_doc! { + struct Post { + info: (String, i64, bool, Person) + } + }; +assert_json_eq!( + value, + json!({ + "properties": { + "info": { + "items": { + "allOf": [ + { + "type": "string" + }, + { + "type": "integer", + "format": "int64", + }, + { + "type": "boolean", + }, + { + "$ref": "#/components/schemas/Person" + } + ] + }, + "type": "array" + } + }, + "type": "object", + "required": ["info"] + }) + ) +} + +#[test] +fn derive_nullable_tuple() { + let value = api_doc! { + struct Post { + /// This is description + #[deprecated] + info: Option<(String, i64)> + } + }; + + assert_json_eq!( + value, + json!({ + "properties": { + "info": { + "items": { + "allOf": [ + { + "type": "string" + }, + { + "type": "integer", + "format": "int64", + }, + ] + }, + "type": "array", + "nullable": true, + "deprecated": true, + "description": "This is description", + } + }, + "type": "object", + }) + ) +}