Repository: juhaku/utoipa (Rust)

Issue:
Derive Component doesn't support tuples?
```rust        
#[derive(Component)]
struct Bar {
    foo: (String, String)
}
```

or 

```rust        
#[derive(Component)]
struct Bar {
    foo: (String)
}
```


I get the following error message:

```text
unexpected type in component part get type path, expected one of: Path, Reference, Group
```
ToSchema breaks on Vec<(string, String)>
I got a struct which has a field of type `Vec<(String, String)>` like this:

```Rust
#[derive(ToSchema)]
struct Demo {
        options: Vec<(String, String)>
}
```

The compiler throws a confusing error message that it needs an argument:

```
error[E0061]: this function takes 1 argument but 0 arguments were supplied
    --> src/main.rs:3:10
     |
3    | #[derive(ToSchema)]
     |          ^^^^^^^^ an argument is missing
     |
note: associated function defined here
    --> /home/lutzg/.cargo/registry/src/github.com-1ecc6299db9ec823/utoipa-2.2.0/src/openapi/schema.rs:1040:12
     |
1040 |     pub fn items<I: Into<RefOr<Schema>>>(mut self, component: I) -> Self {
     |            ^^^^^
     = note: this error originates in the derive macro `ToSchema` (in Nightly builds, run with -Z macro-backtrace for more info)
help: provide the argument
     |
3    | #[derive(ToSchema(/* value */))]
     |                  +++++++++++++
```

I expect the same schema to be derived like that of a `HashMap<String, String>`.


Discussion: Yeah they are not supported. The reason is that there is no good way to serialize them as OpenAPI doc and the only way to represent tuples are to represent them as array of items with a specifc size. Also if tuple contains different types then we can only represent it as array of general object types thus not being helpful to the user. Here https://serde.rs/json.html tuples are serialized as array of types and OpenAPI cannot represent multitype arrays. 
The json schema generated for this struct is:

```json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Bar",
  "type": "object",
  "required": [
    "foo"
  ],
  "properties": {
    "foo": {
      "type": "array",
      "items": [
        {
          "type": "string"
        },
        {
          "type": "string"
        }
      ],
      "maxItems": 2,
      "minItems": 2
    }
  }
}
```

I guess the subset of json schema that OpenAPI 3.0 uses doesn't include `items`...

Okay from the spec:

> items - Value MUST be an object and not an array. Inline or referenced schema MUST be of a [Schema Object](https://swagger.io/specification/#schema-object) and not a standard JSON Schema. items MUST be present if the type is array.

Perhaps at least we could improve the error message?
Yeah, we could, and also what we can do also is to implement support for this behaviour similary already existin for UnnamedStruct in https://github.com/juhaku/utoipa/blob/master/utoipa-gen/src/schema/component.rs#L323.

So what it does it creates an object array with the size of the array which will be the amount of tuple items.  Adding this kind of behaviour to the component fields is not too big of a addition though. But I dont know whether it is useful for the users thus I haven't implemented it previously.

It's because Rust's type system is so flexible. So it makes it difficult to support all the type representations supported by the Rust in OpenAPI spec as it was not build for Rust originally. 
Related #330, #391
Native support for tuples is coming here: #541

Yeah. I am afraid that the tuple types are not fully supported at the moment thus yielding you this kind of cryptic error message. 

Actually when supported the output schema would be a array within array where inner array is only 2 items wide string array. So the representation would be something same as seen below according to: https://serde.rs/json.html
```json
[
  ["string", "string"],
]
```
#390 can be used as a workaround to manually implement the schema for the fields. It works similarly to the serde's `serialize_with` as it provides a function called `schema_with` what expects a function reference that implements the schema for underlying field.
Related #176 
Native support for tuples is coming here: #541



Fix (patch):
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<Deprecated> {
     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());
             }
         }
     }