Skip to content

Commit

Permalink
Feature serde default for ToSchema (#275)
Browse files Browse the repository at this point in the history
Add support for serde default attribute for ToSchema trait. default attribute
can be used in struct level or field level on a struct having named fields.
Add docs about `default` attribute to `ToSchema` derive trait.
  • Loading branch information
juhaku authored Sep 9, 2022
1 parent b5c3327 commit 3ee19c6
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 4 deletions.
10 changes: 8 additions & 2 deletions utoipa-gen/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ pub mod serde {
pub struct SerdeValue {
pub skip: Option<bool>,
pub rename: Option<String>,
pub default: Option<bool>,
}

impl SerdeValue {
Expand All @@ -310,6 +311,7 @@ pub mod serde {
value.rename = Some(literal)
};
}
TokenTree::Ident(ident) if ident == "default" => value.default = Some(true),
_ => (),
}

Expand All @@ -328,11 +330,12 @@ pub mod serde {
pub struct SerdeContainer {
pub rename_all: Option<RenameRule>,
pub tag: Option<String>,
pub default: Option<bool>,
}

impl SerdeContainer {
/// Parse a single serde attribute, currently `rename_all = ...` and `tag = ...` attributes
/// are supported.
/// Parse a single serde attribute, currently `rename_all = ...`, `tag = ...` and
/// `defaut = ...` attributes are supported.
fn parse_attribute(&mut self, ident: Ident, next: Cursor) -> syn::Result<()> {
match ident.to_string().as_str() {
"rename_all" => {
Expand All @@ -349,6 +352,9 @@ pub mod serde {
self.tag = Some(literal)
}
}
"default" => {
self.default = Some(true);
}
_ => {}
}
Ok(())
Expand Down
14 changes: 13 additions & 1 deletion utoipa-gen/src/component/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ impl ToTokens for NamedStructSchema<'_> {
.property(#name, #schema_property)
});

if !schema_property.is_option() {
if !schema_property.is_option() && !is_default(&container_rules, &field_rule) {
tokens.extend(quote! {
.required(#name)
})
Expand All @@ -283,6 +283,18 @@ impl ToTokens for NamedStructSchema<'_> {
}
}

#[inline]
fn is_default(container_rules: &Option<SerdeContainer>, field_rule: &Option<SerdeValue>) -> bool {
*container_rules
.as_ref()
.and_then(|rule| rule.default.as_ref())
.unwrap_or(&false)
|| *field_rule
.as_ref()
.and_then(|rule| rule.default.as_ref())
.unwrap_or(&false)
}

#[cfg_attr(feature = "debug", derive(Debug))]
struct UnnamedStructSchema<'a> {
fields: &'a Punctuated<Field, Comma>,
Expand Down
15 changes: 14 additions & 1 deletion utoipa-gen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,15 @@ use ext::ArgumentResolver;
///
/// # Partial `#[serde(...)]` attributes support
///
/// ToSchema derive has partial support for [serde attributes](https://serde.rs/attributes.html). These supported attributes will reflect to the
/// ToSchema derive has partial support for [serde attributes]. These supported attributes will reflect to the
/// generated OpenAPI doc. For example if _`#[serde(skip)]`_ is defined the attribute will not show up in the OpenAPI spec at all since it will not never
/// be serialized anyway. Similarly the _`rename`_ and _`rename_all`_ will reflect to the generated OpenAPI doc.
///
/// * `rename_all = "..."` Supported in container level.
/// * `rename = "..."` Supported **only** in field or variant level.
/// * `skip = "..."` Supported **only** in field or variant level.
/// * `tag = "..."` Supported in container level.
/// * `default` Supported in container level and field level according to [serde attributes].
///
/// Other _`serde`_ attributes works as is but does not have any effect on the generated OpenAPI doc.
///
Expand Down Expand Up @@ -168,6 +169,17 @@ use ext::ArgumentResolver;
/// }
/// ```
///
/// Add serde `default` attribute for MyValue struct. Similarly `default` could be added to
/// individual fields as well. If `default` is given the field's affected will be treated
/// as optional.
/// ```rust
/// #[derive(utoipa::ToSchema, serde::Deserialize, Default)]
/// #[serde(default)]
/// struct MyValue {
/// field: String
/// }
/// ```
///
/// # Generic schemas with aliases
///
/// Schemas can also be generic which allows reusing types. This enables certain behaviour patters
Expand Down Expand Up @@ -366,6 +378,7 @@ use ext::ArgumentResolver;
/// [xml]: openapi/xml/struct.Xml.html
/// [into_params]: derive.IntoParams.html
/// [primitive]: https://doc.rust-lang.org/std/primitive/index.html
/// [serde attributes]: https://serde.rs/attributes.html
pub fn derive_to_schema(input: TokenStream) -> TokenStream {
let DeriveInput {
attrs,
Expand Down
46 changes: 46 additions & 0 deletions utoipa-gen/tests/schema_derive_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1805,3 +1805,49 @@ fn derive_component_with_smallvec_feature() {
})
)
}

#[test]
fn derive_schema_with_default_field() {
let value = api_doc! {
#[derive(serde::Deserialize)]
struct MyValue {
#[serde(default)]
field: String
}
};

assert_json_eq!(
value,
json!({
"properties": {
"field": {
"type": "string"
}
},
"type": "object"
})
)
}

#[test]
fn derive_schema_with_default_struct() {
let value = api_doc! {
#[derive(serde::Deserialize, Default)]
#[serde(default)]
struct MyValue {
field: String
}
};

assert_json_eq!(
value,
json!({
"properties": {
"field": {
"type": "string"
}
},
"type": "object"
})
)
}

0 comments on commit 3ee19c6

Please sign in to comment.