Repository: juhaku/utoipa
Pull request #12 (2022-01-19 01:31:10)

Refactor component derive
* Add some missing functionalities like support for deprecated and nested Vecs
* Refactor and get rid of some unnecessities from the code


Patch:
diff --git a/src/openapi/mod.rs b/src/openapi/mod.rs
index 6ef4016..5f41130 100644
--- a/src/openapi/mod.rs
+++ b/src/openapi/mod.rs
@@ -13,7 +13,9 @@ pub use self::{
     licence::Licence,
     path::{PathItem, PathItemType, Paths},
     response::{Response, Responses},
-    schema::{Array, Component, ComponentFormat, ComponentType, Object, Property, Ref, Schema},
+    schema::{
+        Array, Component, ComponentFormat, ComponentType, Object, Property, Ref, Schema, ToArray,
+    },
     security::Security,
     server::Server,
     tag::Tag,
diff --git a/src/openapi/schema.rs b/src/openapi/schema.rs
index c4a9728..48ba273 100644
--- a/src/openapi/schema.rs
+++ b/src/openapi/schema.rs
@@ -1,6 +1,9 @@
 use std::collections::HashMap;
 
 use serde::{Deserialize, Serialize};
+use serde_json::Value;
+
+use super::Deprecated;
 
 #[non_exhaustive]
 #[derive(Serialize, Deserialize, Default)]
@@ -57,13 +60,16 @@ pub struct Property {
     description: Option<String>,
 
     #[serde(skip_serializing_if = "Option::is_none")]
-    default: Option<String>,
+    default: Option<Value>,
 
     #[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
     enum_values: Option<Vec<String>>,
 
     #[serde(skip_serializing_if = "Option::is_none")]
     example: Option<String>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    deprecated: Option<Deprecated>,
 }
 
 impl Property {
@@ -86,8 +92,8 @@ impl Property {
         self
     }
 
-    pub fn with_default<S: AsRef<str>>(mut self, default: S) -> Self {
-        self.default = Some(default.as_ref().to_string());
+    pub fn with_default(mut self, default: Value) -> Self {
+        self.default = Some(default);
 
         self
     }
@@ -108,6 +114,12 @@ impl Property {
 
         self
     }
+
+    pub fn with_deprecated(mut self, deprecated: Deprecated) -> Self {
+        self.deprecated = Some(deprecated);
+
+        self
+    }
 }
 
 impl From<Property> for Component {
@@ -136,6 +148,12 @@ pub struct Object {
 
     #[serde(skip_serializing_if = "Option::is_none")]
     description: Option<String>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    deprecated: Option<Deprecated>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    example: Option<Value>,
 }
 
 impl Object {
@@ -167,6 +185,18 @@ impl Object {
 
         self
     }
+
+    pub fn with_deprecated(mut self, deprecated: Deprecated) -> Self {
+        self.deprecated = Some(deprecated);
+
+        self
+    }
+
+    pub fn with_example(mut self, example: Value) -> Self {
+        self.example = Some(example);
+
+        self
+    }
 }
 
 impl From<Object> for Component {
@@ -237,6 +267,8 @@ impl From<Array> for Component {
     }
 }
 
+impl ToArray for Array {}
+
 pub trait ToArray
 where
     Component: From<Self>,
@@ -313,7 +345,7 @@ impl Serialize for ComponentFormat {
 
 #[cfg(test)]
 mod tests {
-    use serde_json::Value;
+    use serde_json::{json, Value};
 
     use super::*;
     use crate::openapi::*;
@@ -331,7 +363,7 @@ mod tests {
                             Property::new(ComponentType::Integer)
                                 .with_format(ComponentFormat::Int32)
                                 .with_description("Id of credential")
-                                .with_default("1"),
+                                .with_default(json!(1)),
                         )
                         .with_property(
                             "name",
@@ -341,7 +373,7 @@ mod tests {
                         .with_property(
                             "status",
                             Property::new(ComponentType::String)
-                                .with_default("Active")
+                                .with_default(json!("Active"))
                                 .with_description("Credential status")
                                 .with_enum_values(&["Active", "NotActive", "Locked", "Expired"]),
                         )
@@ -381,7 +413,7 @@ mod tests {
                 .get("id")
                 .unwrap_or(&serde_json::value::Value::Null)
                 .to_string(),
-            r#"{"default":"1","description":"Id of credential","format":"int32","type":"integer"}"#,
+            r#"{"default":1,"description":"Id of credential","format":"int32","type":"integer"}"#,
             "components.schemas.Credential.properties.id did not match"
         );
         assert_eq!(
@@ -417,6 +449,19 @@ mod tests {
         Ok(())
     }
 
+    #[test]
+    fn derive_object_with_example() {
+        let expected = r#"{"type":"object","example":{"age":20,"name":"bob the cat"}}"#;
+        let json_value = Object::new().with_example(json!({"age": 20, "name": "bob the cat"}));
+
+        let value_string = serde_json::to_string(&json_value).unwrap();
+        assert_eq!(
+            value_string, expected,
+            "value string != expected string, {} != {}",
+            value_string, expected
+        );
+    }
+
     fn get_json_path<'a>(value: &'a Value, path: &str) -> &'a Value {
         path.split('.').into_iter().fold(value, |acc, fragment| {
             acc.get(fragment).unwrap_or(&serde_json::value::Value::Null)
diff --git a/utoipa-gen/src/component.rs b/utoipa-gen/src/component.rs
index fd5e9f7..79a6cc4 100644
--- a/utoipa-gen/src/component.rs
+++ b/utoipa-gen/src/component.rs
@@ -1,4 +1,4 @@
-use std::{ops::Deref, rc::Rc};
+use std::rc::Rc;
 
 use proc_macro2::{Ident, TokenStream as TokenStream2};
 use proc_macro_error::{abort, abort_call_site, emit_error};
@@ -11,12 +11,12 @@ use syn::{
 use crate::{
     component_type::{ComponentFormat, ComponentType},
     doc_comment::CommentAttributes,
-    ValueArray,
+    Deprecated, ValueArray,
 };
 
-use self::attribute::{AttributeType, ComponentAttribute};
+use self::attr::{ComponentAttr, Enum, NamedField, UnnamedFieldStruct};
 
-mod attribute;
+mod attr;
 
 pub struct Component<'a> {
     ident: &'a Ident,
@@ -114,81 +114,111 @@ impl<'a> ComponentVariant<'a> {
         field_type: &FieldType,
     ) {
         match field_type {
-            FieldType::Named => self.named_fields_struct_to_tokens(fields, tokens),
-            FieldType::Unnamed => self.unnamed_fields_struct_to_tokens(fields, tokens),
+            FieldType::Named => self.named_fields_struct_to_tokens(fields, attributes, tokens),
+            FieldType::Unnamed => self.unnamed_fields_struct_to_tokens(fields, attributes, tokens),
+        }
+
+        if let Some(deprecated) = get_deprecated(attributes) {
+            tokens.extend(quote! {
+                .with_deprecated(#deprecated)
+            })
         }
 
         self.append_description(attributes, tokens);
     }
 
-    fn named_fields_struct_to_tokens(&self, fields: &[Field], tokens: &mut TokenStream2) {
+    fn named_fields_struct_to_tokens(
+        &self,
+        fields: &[Field],
+        attributes: &[Attribute],
+        tokens: &mut TokenStream2,
+    ) {
         tokens.extend(quote! { utoipa::openapi::Object::new() });
 
         fields.iter().for_each(|field| {
             let field_name = &*field.ident.as_ref().unwrap().to_string();
 
             let component_part = &ComponentPart::from_type(&field.ty);
-            let component = Into::<ComponentPartRef<'_, ComponentPart<'_>>>::into(component_part)
-                .collect::<ComponentPropertyType>();
 
-            let property = create_property_stream(
-                &component,
-                CommentAttributes::from_attributes(&field.attrs),
-                attribute::parse_component_attribute(&field.attrs),
+            let deprecated = get_deprecated(&field.attrs);
+            let attrs = attr::parse_component_attr::<ComponentAttr<NamedField>>(&field.attrs);
+            let comments = CommentAttributes::from_attributes(&field.attrs);
+            let component = ComponentProperty::new(
+                component_part,
+                Some(&comments),
+                attrs.as_ref(),
+                deprecated.as_ref(),
             );
+
             tokens.extend(quote! {
-                .with_property(#field_name, #property)
+                .with_property(#field_name, #component)
             });
 
-            if !component.option {
+            if !component.is_option() {
                 tokens.extend(quote! {
                     .with_required(#field_name)
                 })
             }
         });
+
+        if let Some(deprecated) = get_deprecated(attributes) {
+            tokens.extend(quote! { .with_deprecated(#deprecated) });
+        }
+
+        let attrs = attr::parse_component_attr::<ComponentAttr<attr::Struct>>(attributes);
+        if let Some(attrs) = attrs {
+            tokens.extend(attrs.to_token_stream());
+        }
     }
 
-    fn unnamed_fields_struct_to_tokens(&self, fields: &[Field], tokens: &mut TokenStream2) {
+    fn unnamed_fields_struct_to_tokens(
+        &self,
+        fields: &[Field],
+        attributes: &[Attribute],
+        tokens: &mut TokenStream2,
+    ) {
         let fields_len = fields.len();
         let first_field = fields.first().unwrap();
         let first_part = &ComponentPart::from_type(&first_field.ty);
-        let first_component = Into::<ComponentPartRef<'_, ComponentPart<'_>>>::into(first_part)
-            .collect::<ComponentPropertyType>();
-
-        let all_fields_are_same = fields.iter().skip(1).all(|field| {
-            let component_part = &ComponentPart::from_type(&field.ty);
-            let component = Into::<ComponentPartRef<'_, ComponentPart<'_>>>::into(component_part)
-                .collect::<ComponentPropertyType>();
 
-            first_component == component
-        });
+        let all_fields_are_same = fields_len == 1
+            || fields.iter().skip(1).all(|field| {
+                let component_part = &ComponentPart::from_type(&field.ty);
 
-        // If Struct is single value struct such as Point(i64) create a Property component based on type
-        if fields_len == 1 {
-            let component =
-                create_property_stream(&first_component, CommentAttributes::empty(), None);
+                first_part == component_part
+            });
 
-            tokens.extend(quote! { #component });
+        let attrs = attr::parse_component_attr::<ComponentAttr<UnnamedFieldStruct>>(attributes);
+        let deprecated = get_deprecated(attributes);
+        if all_fields_are_same {
+            tokens.extend(
+                ComponentProperty::new(first_part, None, attrs.as_ref(), deprecated.as_ref())
+                    .to_token_stream(),
+            );
+            if fields_len > 1 {
+                tokens.extend(quote! { .to_array() })
+            }
         } else {
-            let component = if all_fields_are_same {
-                // When all fields are same we can represent the struct as typed array
-                create_property_stream(&first_component, CommentAttributes::empty(), None)
-            } else {
-                // Struct that has multiple unnamed fields is serialized to array by default with serde.
-                // See: https://serde.rs/json.html
-                // Typically OpenAPI does not support multi type arrays thus we simply consider the case
-                // as generic object array
-                quote! {
-                    utoipa::openapi::Object::new()
-                }
-            };
-
+            // Struct that has multiple unnamed fields is serialized to array by default with serde.
+            // See: https://serde.rs/json.html
+            // Typically OpenAPI does not support multi type arrays thus we simply consider the case
+            // as generic object array
             tokens.extend(quote! {
-                utoipa::openapi::Array::new(
-                    #component
-                )
+                utoipa::openapi::Object::new()
             });
-        }
+
+            if let Some(deprecated) = deprecated {
+                tokens.extend(quote! { .with_deprecated(#deprecated) });
+            }
+
+            if let Some(attrs) = attrs {
+                tokens.extend(attrs.to_token_stream())
+            }
+
+            tokens.extend(quote! {
+                .to_array()
+            })
+        };
     }
 
     fn warn_unsupported_enum_variants(&self, variants: &[Variant]) {
@@ -217,14 +247,14 @@ impl<'a> ComponentVariant<'a> {
                 .with_enum_values(#enum_values)
         });
 
-        if let Some(enum_attributes) = attribute::parse_component_attribute(attributes) {
-            append_attributes(
-                tokens,
-                enum_attributes
-                    .into_iter()
-                    .filter(|attribute| !matches!(attribute, AttributeType::Format(..))),
-            )
-        };
+        let attrs = attr::parse_component_attr::<ComponentAttr<Enum>>(attributes);
+        if let Some(attributes) = attrs {
+            tokens.extend(attributes.to_token_stream());
+        }
+
+        if let Some(deprecated) = get_deprecated(attributes) {
+            tokens.extend(quote! { .with_deprecated(#deprecated) });
+        }
 
         self.append_description(attributes, tokens);
     }
@@ -238,129 +268,17 @@ impl<'a> ComponentVariant<'a> {
     }
 }
 
-fn append_attributes<I: Iterator<Item = AttributeType>>(
-    token_stream: &mut TokenStream2,
-    component_attribute: I,
-) {
-    component_attribute
-        .into_iter()
-        .map(|attribute_type| match attribute_type {
-            AttributeType::Default(..) => quote! {
-                .with_default(#attribute_type)
-            },
-            AttributeType::Example(..) => quote! {
-                .with_example(#attribute_type)
-            },
-            AttributeType::Format(..) => quote! {
-                .with_format(#attribute_type)
-            },
-        })
-        .for_each(|stream| token_stream.extend(stream))
-}
-
-fn create_property_stream(
-    component: &ComponentPropertyType,
-    comment_attributes: CommentAttributes,
-    component_attribute: Option<ComponentAttribute>,
-) -> TokenStream2 {
-    let mut property = match component {
-        ComponentPropertyType {
-            generic_type: None,
-            value_type:
-                Some(TypeTuple(
-                    value_type @ ValueType::Primitive | value_type @ ValueType::Object,
-                    ident,
-                )),
-            ..
-        } => create_simple_property(value_type, ident, &comment_attributes),
-        ComponentPropertyType {
-            generic_type: Some(generic_type_tuple),
-            value_type: Some(value_type_tuple),
-            ..
-        } => create_complex_property(generic_type_tuple, value_type_tuple, &comment_attributes),
-        _ => unreachable!(), // will never occur, there are only complex generic types or simple types with or without generics
-    };
-
-    if let Some(component_attribute) = component_attribute {
-        append_attributes(&mut property, component_attribute.into_iter())
-    }
-
-    property
-}
-
-fn create_simple_property(
-    value_type: &ValueType,
-    ident: &Ident,
-    comment_attributes: &CommentAttributes,
-) -> TokenStream2 {
-    match value_type {
-        ValueType::Primitive => {
-            let component_type = ComponentType(ident);
-
-            let mut property = quote! {
-                utoipa::openapi::Property::new(
-                    #component_type
-                )
-            };
-
-            if let Some(comment) = comment_attributes.0.first() {
-                property.extend(quote! {
-                    .with_description(#comment)
-                })
-            }
-
-            let format = ComponentFormat(ident);
-            if format.is_known_format() {
-                property.extend(quote! {
-                    .with_format(#format)
-                })
-            }
-
-            property
-        }
-        ValueType::Object => {
-            let object_name = &*ident.to_string();
-
-            quote! {
-                utoipa::openapi::Ref::from_component_name(#object_name)
-            }
-        }
-    }
-}
-
-fn create_complex_property(
-    generic_type_tuple: &TypeTuple<GenericType>,
-    value_type_tuple: &TypeTuple<ValueType>,
-    comment_attributes: &CommentAttributes,
-) -> TokenStream2 {
-    match generic_type_tuple.0 {
-        GenericType::Map => {
-            let mut property = quote! {
-                utoipa::openapi::Object::new()
-            };
-
-            if let Some(comment) = comment_attributes.0.first() {
-                property.extend(quote! {
-                    .with_description(#comment)
-                })
-            }
-
-            property
-        }
-        GenericType::Vec => {
-            let property =
-                create_simple_property(&value_type_tuple.0, value_type_tuple.1, comment_attributes);
-
-            quote! {
-                utoipa::openapi::Array::new(
-                    #property
-                )
-            }
+fn get_deprecated(attributes: &[Attribute]) -> Option<Deprecated> {
+    attributes.iter().find_map(|attribute| {
+        if *attribute.path.get_ident().unwrap() == "deprecated" {
+            Some(Deprecated::True)
+        } else {
+            None
         }
-        _ => unreachable!(), //  we do not have option type here
-    }
+    })
 }
 
+#[derive(PartialEq)]
 #[cfg_attr(feature = "debug", derive(Debug))]
 /// Linked list of implementing types of a field in a struct.
 struct ComponentPart<'a> {
@@ -456,45 +374,6 @@ impl<'a> ComponentPart<'a> {
     }
 }
 
-struct ComponentPartRef<'a, T> {
-    _inner: Option<&'a T>,
-}
-
-impl<'a> Deref for ComponentPartRef<'a, ComponentPart<'a>> {
-    type Target = ComponentPart<'a>;
-
-    fn deref(&self) -> &Self::Target {
-        self._inner.unwrap() // we can unwrap since it must have value
-    }
-}
-
-impl<'a> From<&'a ComponentPart<'a>> for ComponentPartRef<'a, ComponentPart<'a>> {
-    fn from(component_type: &'a ComponentPart<'_>) -> Self {
-        Self {
-            _inner: Some(component_type),
-        }
-    }
-}
-
-impl<'a> Iterator for ComponentPartRef<'a, ComponentPart<'a>> {
-    type Item = ComponentPartRef<'a, ComponentPart<'a>>;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        let current = self._inner;
-        let next = current.and_then(|current| current.child.as_ref());
-
-        if let Some(component) = next {
-            self._inner = Some(component.as_ref());
-        } else {
-            self._inner = None
-        }
-
-        current.map(|component_type| ComponentPartRef {
-            _inner: Some(component_type),
-        })
-    }
-}
-
 #[cfg_attr(feature = "debug", derive(Debug))]
 #[derive(Clone, Copy, PartialEq)]
 enum ValueType {
@@ -514,37 +393,114 @@ enum GenericType {
 #[derive(PartialEq)]
 struct TypeTuple<'a, T>(T, &'a Ident);
 
-#[cfg_attr(feature = "debug", derive(Debug))]
-#[derive(Default, PartialEq)]
-struct ComponentPropertyType<'a> {
-    option: bool,
-    generic_type: Option<TypeTuple<'a, GenericType>>,
-    value_type: Option<TypeTuple<'a, ValueType>>,
+struct ComponentProperty<'a, T> {
+    component_part: &'a ComponentPart<'a>,
+    comments: Option<&'a CommentAttributes>,
+    attrs: Option<&'a ComponentAttr<T>>,
+    deprecated: Option<&'a Deprecated>,
 }
 
-impl<'a> FromIterator<ComponentPartRef<'a, ComponentPart<'a>>> for ComponentPropertyType<'a> {
-    fn from_iter<T: IntoIterator<Item = ComponentPartRef<'a, ComponentPart<'a>>>>(iter: T) -> Self {
-        let components_iter = iter.into_iter();
-        components_iter.fold(Self::default(), |mut component, item| {
-            match item.generic_type {
-                Some(GenericType::Option) => component.option = true,
-                Some(generic_type @ GenericType::Map | generic_type @ GenericType::Vec) => {
-                    component.generic_type = Some(TypeTuple(generic_type, item.ident))
-                }
-                None => (),
-            }
+impl<'a, T: Sized + ToTokens> ComponentProperty<'a, T> {
+    fn new(
+        component_part: &'a ComponentPart<'a>,
+        comments: Option<&'a CommentAttributes>,
+        attrs: Option<&'a ComponentAttr<T>>,
+        deprecated: Option<&'a Deprecated>,
+    ) -> Self {
+        Self {
+            component_part,
+            comments,
+            attrs,
+            deprecated,
+        }
+    }
 
-            // we are only interested of final concrete value type
-            match item.value_type {
-                value_type @ ValueType::Object | value_type @ ValueType::Primitive
-                    if item.generic_type == None =>
+    /// Check wheter property is required or not
+    fn is_option(&self) -> bool {
+        matches!(self.component_part.generic_type, Some(GenericType::Option))
+    }
+}
+
+impl<T> ToTokens for ComponentProperty<'_, T>
+where
+    T: Sized + quote::ToTokens,
+{
+    fn to_tokens(&self, tokens: &mut TokenStream2) {
+        match self.component_part.generic_type {
+            Some(GenericType::Map) => {
+                // Maps are treated just as generic objects without types. There is no Map type in OpenAPI spec.
+                tokens.extend(quote! {
+                    utoipa::openapi::Object::new()
+                });
+
+                if let Some(description) = self.comments.and_then(|attributes| attributes.0.first())
                 {
-                    component.value_type = Some(TypeTuple(value_type, item.ident))
+                    tokens.extend(quote! {
+                        .with_description(#description)
+                    })
                 }
-                _ => (),
             }
+            Some(GenericType::Vec) => {
+                let component_property = ComponentProperty::new(
+                    self.component_part.child.as_ref().unwrap(),
+                    self.comments,
+                    self.attrs,
+                    self.deprecated,
+                );
 
-            component
-        })
+                tokens.extend(quote! {
+                    #component_property.to_array()
+                });
+            }
+            Some(GenericType::Option) => {
+                let component_property = ComponentProperty::new(
+                    self.component_part.child.as_ref().unwrap(),
+                    self.comments,
+                    self.attrs,
+                    self.deprecated,
+                );
+
+                tokens.extend(component_property.into_token_stream())
+            }
+            None => match self.component_part.value_type {
+                ValueType::Primitive => {
+                    let component_type = ComponentType(self.component_part.ident);
+
+                    tokens.extend(quote! {
+                        utoipa::openapi::Property::new(#component_type)
+                    });
+
+                    let format = ComponentFormat(self.component_part.ident);
+                    if format.is_known_format() {
+                        tokens.extend(quote! {
+                            .with_format(#format)
+                        })
+                    }
+
+                    if let Some(description) =
+                        self.comments.and_then(|attributes| attributes.0.first())
+                    {
+                        tokens.extend(quote! {
+                            .with_description(#description)
+                        })
+                    }
+
+                    if let Some(deprecated) = self.deprecated {
+                        tokens.extend(quote! { .with_deprecated(#deprecated) });
+                    }
+
+                    if let Some(attributes) = self.attrs {
+                        tokens.extend(attributes.to_token_stream())
+                    }
+                }
+                ValueType::Object => {
+                    let name = &*self.component_part.ident.to_string();
+
+                    tokens.extend(quote! {
+                        utoipa::openapi::Ref::from_component_name(#name)
+                    })
+                }
+            },
+        }
     }
 }
diff --git a/utoipa-gen/src/component/attr.rs b/utoipa-gen/src/component/attr.rs
new file mode 100644
index 0000000..971dd75
--- /dev/null
+++ b/utoipa-gen/src/component/attr.rs
@@ -0,0 +1,355 @@
+use proc_macro2::{Group, Ident, TokenStream};
+use proc_macro_error::{abort, ResultExt};
+use quote::{quote, quote_spanned, ToTokens};
+use syn::{
+    parse::{Parse, ParseBuffer},
+    Attribute, Error, ExprPath, Lit, Token,
+};
+
+use crate::parse_utils;
+
+#[cfg_attr(feature = "debug", derive(Debug))]
+pub struct ComponentAttr<T>
+where
+    T: Sized,
+{
+    inner: T,
+}
+
+impl<T> AsRef<T> for ComponentAttr<T>
+where
+    T: Sized,
+{
+    fn as_ref(&self) -> &T {
+        &self.inner
+    }
+}
+
+#[derive(Default)]
+#[cfg_attr(feature = "debug", derive(Debug))]
+pub struct Enum {
+    default: Option<TokenStream>,
+    example: Option<String>,
+}
+
+#[cfg_attr(feature = "debug", derive(Debug))]
+pub struct Struct {
+    example: Option<TokenStream>,
+}
+
+#[derive(Default)]
+#[cfg_attr(feature = "debug", derive(Debug))]
+pub struct UnnamedFieldStruct {
+    default: Option<TokenStream>,
+    example: Option<String>,
+}
+
+#[derive(Default)]
+#[cfg_attr(feature = "debug", derive(Debug))]
+pub struct NamedField {
+    example: Option<String>,
+    format: Option<ExprPath>,
+    default: Option<TokenStream>,
+}
+
+impl Parse for ComponentAttr<Enum> {
+    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+        let mut enum_attr = Enum::default();
+
+        loop {
+            let ident = input
+                .parse::<Ident>()
+                .expect_or_abort("Unparseable ComponentAttr<Enum>, expected Ident");
+            let name = &*ident.to_string();
+
+            match name {
+                "default" => {
+                    enum_attr.default = Some(parse_utils::parse_next(input, || {
+                        parse_default_as_token_stream(input, name)
+                    }))
+                }
+                "example" => {
+                    enum_attr.example = Some(parse_utils::parse_next(input, || {
+                        parse_lit_as_string(input, name, "unparseable example, expected Literal")
+                    }))
+                }
+                _ => {
+                    return Err(Error::new(
+                        ident.span(),
+                        format!("unexpected attribute: {}, expected: default, example", name),
+                    ))
+                }
+            }
+
+            if input.peek(Token![,]) {
+                input.parse::<Token![,]>().unwrap();
+            }
+
+            if input.is_empty() {
+                break;
+            }
+        }
+        Ok(Self { inner: enum_attr })
+    }
+}
+
+impl Parse for ComponentAttr<Struct> {
+    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+        let ident = input
+            .parse::<Ident>()
+            .expect_or_abort("Unparseable ComponentAttr<Struct>, expected Ident");
+        let name = &*ident.to_string();
+
+        match name {
+            "example" => {
+                let tokens = parse_utils::parse_next(input, || {
+                    if input.peek(syn::Ident) && input.peek2(Token![!]) {
+                        input.parse::<Ident>().unwrap();
+                        input.parse::<Token![!]>().unwrap();
+
+                        Ok(input.parse::<Group>().expect_or_abort(
+                            "unparseable example, expected Parenthesized Group e.g. json!(...)",
+                        ))
+                    } else {
+                        Err(Error::new(
+                            ident.span(),
+                            "unexpected example, expected json!(...)",
+                        ))
+                    }
+                })?;
+
+                Ok(Self {
+                    inner: Struct {
+                        example: Some(tokens.stream()),
+                    },
+                })
+            }
+            _ => Err(Error::new(
+                ident.span(),
+                format!("unexpected attribute: {}, expected: example", name),
+            )),
+        }
+    }
+}
+
+impl Parse for ComponentAttr<UnnamedFieldStruct> {
+    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+        let mut unnamed_struct = UnnamedFieldStruct::default();
+
+        loop {
+            let attribute = input
+                .parse::<Ident>()
+                .expect_or_abort("Unparseable ComponentAttr<UnnamedFieldStruct>, expected Ident");
+            let name = &*attribute.to_string();
+
+            match name {
+                "default" => {
+                    unnamed_struct.default = Some(parse_utils::parse_next(input, || {
+                        parse_default_as_token_stream(input, name)
+                    }))
+                }
+                "example" => {
+                    unnamed_struct.example = Some(parse_utils::parse_next(input, || {
+                        parse_lit_as_string(input, name, "unparseable example, expected Literal")
+                    }))
+                }
+                _ => {
+                    return Err(Error::new(
+                        attribute.span(),
+                        format!("unexpected attribute: {}, expected default, example", name),
+                    ))
+                }
+            }
+
+            if input.peek(Token![,]) {
+                input.parse::<Token![,]>().unwrap();
+            }
+
+            if input.is_empty() {
+                break;
+            }
+        }
+
+        Ok(Self {
+            inner: unnamed_struct,
+        })
+    }
+}
+
+impl Parse for ComponentAttr<NamedField> {
+    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+        let mut field = NamedField::default();
+
+        loop {
+            let ident = input
+                .parse::<Ident>()
+                .expect_or_abort("Unparseable ComponentAttr<NamedField>, expected Ident");
+            let name = &*ident.to_string();
+
+            match name {
+                "example" => {
+                    field.example = Some(parse_utils::parse_next(input, || {
+                        parse_lit_as_string(input, name, "unparseable example, expected Literal")
+                    }));
+                }
+                "format" => {
+                    let format = parse_utils::parse_next(input, || {
+                        input.parse::<ExprPath>().expect_or_abort(
+                            "unparseable format expected ExprPath e.g. ComponentFormat::String",
+                        )
+                    });
+
+                    if format.path.segments.first().unwrap().ident != "utoipa" {
+                        let appended_path: ExprPath = syn::parse_quote!(utoipa::openapi::#format);
+                        field.format = Some(appended_path);
+                    } else {
+                        field.format = Some(format);
+                    }
+                }
+                "default" => {
+                    field.default = Some(parse_utils::parse_next(input, || {
+                        parse_default_as_token_stream(input, name)
+                    }))
+                }
+                _ => {
+                    return Err(Error::new(
+                        ident.span(),
+                        format!(
+                        "unexpected attribute: {}, expected: deprecated, example, format, default",
+                        name
+                    ),
+                    ))
+                }
+            }
+
+            if input.peek(Token![,]) {
+                input.parse::<Token![,]>().unwrap();
+            }
+            if input.is_empty() {
+                break;
+            }
+        }
+
+        Ok(Self { inner: field })
+    }
+}
+
+fn parse_lit_as_string(input: &ParseBuffer, field: &str, error_msg: &str) -> String {
+    let lit = &input.parse::<Lit>().expect_or_abort(error_msg);
+    match lit {
+        Lit::Bool(bool) => bool.value().to_string(),
+        Lit::Byte(byte) => byte.value().to_string(),
+        Lit::ByteStr(byte_str) => String::from_utf8(byte_str.value()).unwrap_or_else(|_| {
+            abort!(
+                input.span(),
+                format!("Unparseable utf8 content in: {}", &field)
+            )
+        }),
+        Lit::Char(char) => char.value().to_string(),
+        Lit::Float(float) => float.base10_digits().to_string(),
+        Lit::Int(int) => int.base10_digits().to_string(),
+        Lit::Str(str) => str.value(),
+        Lit::Verbatim(_) => {
+            abort!(
+                input.span(),
+                format!("Unparseable literal in field: {}", &field)
+            )
+        }
+    }
+}
+
+fn parse_default_as_token_stream(input: &ParseBuffer, name: &str) -> TokenStream {
+    if input.peek(Lit) {
+        let literal = parse_lit_as_string(input, name, "unparseable default, expected Literal");
+        quote_spanned! {input.span()=>
+            #literal
+        }
+    } else {
+        let method = input
+            .parse::<ExprPath>()
+            .expect_or_abort("unparseable default, expected Literal, or ExprPath");
+        quote_spanned! {input.span()=>
+            #method()
+        }
+    }
+}
+
+pub fn parse_component_attr<T: Sized + Parse>(attributes: &[Attribute]) -> Option<T> {
+    attributes
+        .iter()
+        .find(|attribute| attribute.path.get_ident().unwrap() == "component")
+        .map(|attribute| attribute.parse_args::<T>().unwrap_or_abort())
+}
+
+impl<T> ToTokens for ComponentAttr<T>
+where
+    T: quote::ToTokens,
+{
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        tokens.extend(self.inner.to_token_stream())
+    }
+}
+
+impl ToTokens for Enum {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        if let Some(ref default) = self.default {
+            tokens.extend(quote! {
+                .with_default(serde_json::json!(#default))
+            })
+        }
+
+        if let Some(ref example) = self.example {
+            tokens.extend(quote! {
+                .with_example(#example)
+            })
+        }
+    }
+}
+
+impl ToTokens for Struct {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        if let Some(ref example) = self.example {
+            tokens.extend(quote! {
+                .with_example(serde_json::json!(#example))
+            })
+        }
+    }
+}
+
+impl ToTokens for UnnamedFieldStruct {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        if let Some(ref default) = self.default {
+            tokens.extend(quote! {
+                .with_default(serde_json::json!(#default))
+            })
+        }
+
+        if let Some(ref example) = self.example {
+            tokens.extend(quote! {
+                .with_example(#example)
+            })
+        }
+    }
+}
+
+impl ToTokens for NamedField {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        if let Some(ref default) = self.default {
+            tokens.extend(quote! {
+                .with_default(serde_json::json!(#default))
+            })
+        }
+
+        if let Some(ref format) = self.format {
+            tokens.extend(quote! {
+                .with_format(#format)
+            })
+        }
+
+        if let Some(ref example) = self.example {
+            tokens.extend(quote! {
+                .with_example(#example)
+            })
+        }
+    }
+}
diff --git a/utoipa-gen/src/component/attribute.rs b/utoipa-gen/src/component/attribute.rs
deleted file mode 100644
index 13b0c16..0000000
--- a/utoipa-gen/src/component/attribute.rs
+++ /dev/null
@@ -1,159 +0,0 @@
-use std::{slice::Iter, vec::IntoIter};
-
-use proc_macro2::{Ident, Span};
-use proc_macro_error::{abort, emit_warning, OptionExt, ResultExt};
-use quote::{quote_spanned, ToTokens};
-use syn::{parse::Parse, Attribute, ExprPath, Lit, Token};
-
-use crate::doc_comment::CommentAttributes;
-
-const COMPONENT_ATTRIBUTE_TYPE: &str = "component";
-
-#[cfg_attr(feature = "debug", derive(Debug))]
-/// AttributeType is parsed representation of `#[component(...)]` attribute values of Component derive.
-pub(crate) enum AttributeType {
-    Default(String, Span),
-    Format(String, Span),
-    Example(String, Span),
-}
-
-impl ToTokens for AttributeType {
-    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
-        match self {
-            Self::Default(value, span) => {
-                if value.contains("::") {
-                    let method = syn::parse_str::<ExprPath>(value)
-                        .map(|method| {
-                            quote_spanned! {*span=>
-                                #method().to_string()
-                            }
-                        })
-                        .unwrap_or_else(|_| {
-                            quote_spanned! {*span=>
-                                #value
-                            }
-                        });
-
-                    tokens.extend(method);
-                } else {
-                    tokens.extend(quote_spanned! {*span=>
-                        #value
-                    })
-                }
-            }
-            Self::Example(value, span) => tokens.extend(quote_spanned! {*span=>
-                #value
-            }),
-            Self::Format(value, span) => {
-                let path = syn::parse_str::<ExprPath>(&format!("utoipa::openapi::{}", value))
-                    .expect_or_abort(&format!("parse path failed: {}", value));
-
-                tokens.extend(quote_spanned! {*span=>
-                    #path
-                })
-            }
-        }
-    }
-}
-
-#[cfg_attr(feature = "debug", derive(Debug))]
-/// Wrapper struct for containing parsed [`enum@AttributeType`]s. It implements custom parser
-/// to parse `#[component(...)]` attribute content of Component derive macro.
-pub(crate) struct ComponentAttribute(pub(crate) Vec<AttributeType>);
-
-impl Parse for ComponentAttribute {
-    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
-        let mut attribute = ComponentAttribute(vec![]);
-        loop {
-            let ident = input
-                .parse::<Ident>()
-                .expect_or_abort("Expected Ident as first item in the TokenStream");
-            let name = &*ident.to_string();
-            input.parse::<Token![=]>().unwrap_or_abort();
-
-            let lookahead = input.lookahead1();
-            if lookahead.peek(Lit) {
-                let lit = &input.parse::<Lit>().unwrap_or_abort();
-                let lit_value = match lit {
-                    Lit::Bool(bool) => bool.value().to_string(),
-                    Lit::Byte(byte) => byte.value().to_string(),
-                    Lit::ByteStr(byte_str) => {
-                        String::from_utf8(byte_str.value()).unwrap_or_else(|_| {
-                            abort!(
-                                input.span(),
-                                format!("Unparseable utf8 content in: {}", name)
-                            )
-                        })
-                    }
-                    Lit::Char(char) => char.value().to_string(),
-                    Lit::Float(float) => float.base10_digits().to_string(),
-                    Lit::Int(int) => int.base10_digits().to_string(),
-                    Lit::Str(str) => str.value(),
-                    Lit::Verbatim(_) => {
-                        abort!(
-                            input.span(),
-                            format!("Unparseable literal in field: {}", name)
-                        )
-                    }
-                };
-
-                match name {
-                    "default" => attribute
-                        .0
-                        .push(AttributeType::Default(lit_value, lit.span())),
-                    "format" => attribute
-                        .0
-                        .push(AttributeType::Format(lit_value, lit.span())),
-                    "example" => attribute
-                        .0
-                        .push(AttributeType::Example(lit_value, lit.span())),
-                    _ => emit_warning!(
-                        input.span(),
-                        format!("Unsupported attribute field: {}", name)
-                    ),
-                };
-            } else {
-                return Err(lookahead.error());
-            }
-
-            if input.peek(Token![,]) {
-                input.parse::<Token![,]>().unwrap_or_abort();
-            }
-            if input.is_empty() {
-                break;
-            }
-        }
-
-        Ok(attribute)
-    }
-}
-
-impl IntoIterator for ComponentAttribute {
-    type Item = AttributeType;
-
-    type IntoIter = std::vec::IntoIter<Self::Item>;
-
-    fn into_iter(self) -> Self::IntoIter {
-        self.0.into_iter()
-    }
-}
-
-fn is_component_attribute(attribute: &&Attribute) -> bool {
-    *attribute
-        .path
-        .get_ident()
-        .expect_or_abort("Expected component attribute with one path segment")
-        == COMPONENT_ATTRIBUTE_TYPE
-}
-
-/// Parses [`struct@ComponentAttribute`] from given syn::Attributes using only first matching attribute.
-pub(crate) fn parse_component_attribute(attributes: &[Attribute]) -> Option<ComponentAttribute> {
-    attributes
-        .iter()
-        .find(is_component_attribute)
-        .map(|component_attribute| {
-            component_attribute
-                .parse_args::<ComponentAttribute>()
-                .unwrap_or_abort()
-        })
-}
diff --git a/utoipa-gen/src/doc_comment.rs b/utoipa-gen/src/doc_comment.rs
index d7ac994..79bb1c8 100644
--- a/utoipa-gen/src/doc_comment.rs
+++ b/utoipa-gen/src/doc_comment.rs
@@ -8,9 +8,6 @@ const DOC_ATTRIBUTE_TYPE: &str = "doc";
 pub(crate) struct CommentAttributes(pub(crate) Vec<String>);
 
 impl CommentAttributes {
-    pub(crate) fn empty() -> Self {
-        Self { 0: vec![] }
-    }
     /// Creates new [`CommentAttributes`] instance from [`Attribute`] slice filtering out all
     /// other attributes which are not `doc` comments
     pub(crate) fn from_attributes(attributes: &[Attribute]) -> Self {
diff --git a/utoipa-gen/src/lib.rs b/utoipa-gen/src/lib.rs
index de5de27..1ae83aa 100644
--- a/utoipa-gen/src/lib.rs
+++ b/utoipa-gen/src/lib.rs
@@ -40,6 +40,7 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
     let component = Component::new(data, &attrs, &ident);
 
     quote! {
+        use utoipa::openapi::ToArray;
         #component
     }
     .into()
@@ -78,7 +79,7 @@ pub fn path(attr: TokenStream, item: TokenStream) -> TokenStream {
         }));
 
     quote! {
-        use utoipa::openapi::schema::ToArray;
+        use utoipa::openapi::ToArray;
         #path
         #ast_fn
     }
diff --git a/utoipa-gen/src/openapi.rs b/utoipa-gen/src/openapi.rs
index 3e9b5d0..f5bd6fb 100644
--- a/utoipa-gen/src/openapi.rs
+++ b/utoipa-gen/src/openapi.rs
@@ -134,7 +134,7 @@ impl ToTokens for OpenApi {
         let path_items = impl_paths(&attributes.handlers, tokens);
 
         tokens.extend(quote! {
-            use utoipa::openapi::schema::ToArray;
+            use utoipa::openapi::ToArray;
             impl utoipa::OpenApi for #ident {
                 fn openapi() -> utoipa::openapi::OpenApi {
                     utoipa::openapi::OpenApi::new(#info, #path_items)


Test patch:
diff --git a/tests/component_derive_test.rs b/tests/component_derive_test.rs
index 11014dc..460348f 100644
--- a/tests/component_derive_test.rs
+++ b/tests/component_derive_test.rs
@@ -1,4 +1,4 @@
-use std::collections::HashMap;
+use std::{collections::HashMap, vec};
 
 use serde_json::Value;
 use utoipa::{Component, OpenApi};
@@ -77,7 +77,7 @@ fn derive_enum_with_defaults_success() {
 #[test]
 fn derive_enum_with_with_custom_default_fn_success() {
     let mode = api_doc! {
-        #[component(default = "crate::mode_custom_default_fn")]
+        #[component(default = mode_custom_default_fn)]
         enum Mode {
             Mode1,
             Mode2
@@ -119,11 +119,12 @@ fn derive_struct_with_defaults_success() {
 fn derive_struct_with_custom_properties_success() {
     let book = api_doc! {
         struct Book {
+            #[component(default = String::default)]
             name: String,
             #[component(
-                default = "testhash"
+                default = "testhash",
                 example = "base64 text",
-                format = "ComponentFormat::Byte"
+                format = ComponentFormat::Byte,
             )]
             hash: String,
         }
@@ -132,6 +133,7 @@ fn derive_struct_with_custom_properties_success() {
     assert_value! {book=>
         "type" = r#""object""#, "Book type"
         "properties.name.type" = r#""string""#, "Book name type"
+        "properties.name.default" = r#""""#, "Book name default"
         "properties.hash.type" = r#""string""#, "Book hash type"
         "properties.hash.format" = r#""byte""#, "Book hash format"
         "properties.hash.example" = r#""base64 text""#, "Book hash example"
@@ -306,7 +308,6 @@ fn derive_struct_unnamed_field_vec_type_success() {
 }
 
 #[test]
-#[ignore = "not supported yet!!!"]
 fn derive_struct_nested_vec_success() {
     let vecs = api_doc! {
         struct VecTest {
@@ -314,10 +315,95 @@ fn derive_struct_nested_vec_success() {
         }
     };
 
-    println!("{:#?}", vecs);
-    // assert_value! {point=>
-    //     "type" = r#""array""#, "Wrapper type"
-    //     "items.type" = r#""integer""#, "Wrapper items type"
-    //     "items.format" = r#""int32""#, "Wrapper items format"
-    // }
+    assert_value! {vecs=>
+        "properties.vecs.type" = r#""array""#, "Vecs property type"
+        "properties.vecs.items.type" = r#""array""#, "Vecs property items type"
+        "properties.vecs.items.items.type" = r#""string""#, "Vecs property items item type"
+        "type" = r#""object""#, "Property type"
+        "required.[0]" = r#""vecs""#, "Required properties"
+    }
+    common::assert_json_array_len(vecs.get("required").unwrap(), 1);
+}
+
+#[test]
+fn derive_struct_with_example() {
+    let pet = api_doc! {
+        #[component(example = json!({"name": "bob the cat", "age": 8}))]
+        struct Pet {
+            name: String,
+            age: i32
+        }
+    };
+
+    assert_value! {pet=>
+        "example.name" = r#""bob the cat""#, "Pet example name"
+        "example.age" = r#"8"#, "Pet example age"
+    }
+}
+
+#[test]
+fn derive_struct_with_deprecated() {
+    #[allow(deprecated)]
+    let pet = api_doc! {
+        #[deprecated]
+        struct Pet {
+            name: String,
+            #[deprecated]
+            age: i32
+        }
+    };
+
+    assert_value! {pet=>
+        "deprecated" = r#"true"#, "Pet deprecated"
+        "properties.name.type" = r#""string""#, "Pet properties name type"
+        "properties.name.deprecated" = r#"null"#, "Pet properties name deprecated"
+        "properties.age.type" = r#""integer""#, "Pet properties age type"
+        "properties.age.deprecated" = r#"true"#, "Pet properties age deprecated"
+        "example" = r#"null"#, "Pet example"
+    }
+}
+
+#[test]
+fn derive_unnamed_struct_deprecated_success() {
+    #[allow(deprecated)]
+    let pet_age = api_doc! {
+        #[deprecated]
+        #[component(example = 8)]
+        struct PetAge(u64);
+    };
+
+    assert_value! {pet_age=>
+        "deprecated" = r#"true"#, "PetAge deprecated"
+        "example" = r#""8""#, "PetAge example"
+    }
+}
+
+#[test]
+fn derive_unnamed_struct_example_json_array_success() {
+    let pet_age = api_doc! {
+        #[component(example = "0", default = u64::default)]
+        struct PetAge(u64, u64);
+    };
+
+    assert_value! {pet_age=>
+        "items.example" = r#""0""#, "PetAge example"
+        "items.default" = r#"0"#, "PetAge default"
+    }
+}
+
+#[test]
+fn derive_enum_with_deprecated() {
+    #[allow(deprecated)]
+    let mode = api_doc! {
+        #[deprecated]
+        enum Mode {
+            Mode1, Mode2
+        }
+    };
+
+    assert_value! {mode=>
+        "enum" = r#"["Mode1","Mode2"]"#, "Mode enum variants"
+        "type" = r#""string""#, "Mode type"
+        "deprecated" = r#"true"#, "Mode deprecated"
+    };
 }
diff --git a/tests/utoipa_gen_test.rs b/tests/utoipa_gen_test.rs
index 9ad0040..025a0fe 100644
--- a/tests/utoipa_gen_test.rs
+++ b/tests/utoipa_gen_test.rs
@@ -6,6 +6,7 @@ use serde_json::json;
 use utoipa::{Component, OpenApi};
 
 #[derive(Deserialize, Serialize, Component)]
+#[component(example = json!({"name": "bob the cat", "id": 1}))]
 struct Pet {
     id: u64,
     name: String,