Skip to content

Commit

Permalink
oapi: Support serde deny_unknown_fields (#558)
Browse files Browse the repository at this point in the history
* oapi: Support serde deny_unknown_fields

* fix ci
  • Loading branch information
chrislearn authored Dec 13, 2023
1 parent ba7f745 commit a443aca
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 5 deletions.
23 changes: 19 additions & 4 deletions crates/oapi-macros/src/schema/struct_schemas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ impl ToTokens for NamedStructSchema<'_> {
})
.collect();

if !flatten_fields.is_empty() {
let all_of = if !flatten_fields.is_empty() {
let mut flattened_tokens = TokenStream::new();
let mut flattened_map_field = None;

Expand Down Expand Up @@ -228,16 +228,31 @@ impl ToTokens for NamedStructSchema<'_> {
}

if flattened_tokens.is_empty() {
tokens.extend(object_tokens)
tokens.extend(object_tokens);
false
} else {
tokens.extend(quote! {
utoipa::openapi::AllOfBuilder::new()
#flattened_tokens
.item(#object_tokens)
})
});

true
}
} else {
tokens.extend(object_tokens)
tokens.extend(object_tokens);
false
};

if !all_of
&& container_rules
.as_ref()
.and_then(|container_rule| Some(container_rule.deny_unknown_fields))
.unwrap_or(false)
{
tokens.extend(quote! {
.additional_properties(Some(utoipa::openapi::schema::AdditionalProperties::FreeForm(false)))
});
}

if let Some(deprecated) = crate::get_deprecated(self.attributes) {
Expand Down
44 changes: 43 additions & 1 deletion crates/oapi-macros/src/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ impl SerdeValue {
/// The [Serde Enum representation](https://serde.rs/enum-representations.html) being used
/// The default case (when no serde attributes are present) is `ExternallyTagged`.
#[derive(Default, Clone, Debug)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub(crate) enum SerdeEnumRepr {
#[default]
ExternallyTagged,
Expand All @@ -87,10 +88,12 @@ pub(crate) enum SerdeEnumRepr {

/// Attributes defined within a `#[serde(...)]` container attribute.
#[derive(Default, Debug)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub(crate) struct SerdeContainer {
pub(crate) rename_all: Option<RenameRule>,
pub(crate) enum_repr: SerdeEnumRepr,
pub(crate) is_default: bool,
pub(crate) deny_unknown_fields: bool,
}

impl SerdeContainer {
Expand Down Expand Up @@ -151,6 +154,9 @@ impl SerdeContainer {
"default" => {
self.is_default = true;
}
"deny_unknown_fields" => {
self.deny_unknown_fields = true;
}
_ => {}
}
Ok(())
Expand Down Expand Up @@ -214,6 +220,9 @@ pub(crate) fn parse_container(attributes: &[Attribute]) -> Option<SerdeContainer
if value.is_default {
acc.is_default = value.is_default;
}
if value.deny_unknown_fields {
acc.deny_unknown_fields = value.deny_unknown_fields;
}
match value.enum_repr {
SerdeEnumRepr::ExternallyTagged => {}
SerdeEnumRepr::Untagged
Expand All @@ -232,6 +241,7 @@ pub(crate) fn parse_container(attributes: &[Attribute]) -> Option<SerdeContainer
}

#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub(crate) enum RenameRule {
Lower,
Upper,
Expand Down Expand Up @@ -345,7 +355,8 @@ impl FromStr for RenameRule {

#[cfg(test)]
mod tests {
use super::{RenameRule, RENAME_RULE_NAME_MAPPING};
use super::{parse_container, RenameRule, SerdeContainer, RENAME_RULE_NAME_MAPPING};
use syn::{parse_quote, Attribute};

macro_rules! test_rename_rule {
( $($case:expr=> $value:literal = $expected:literal)* ) => {
Expand All @@ -359,6 +370,37 @@ mod tests {
};
}

#[test]
fn test_serde_parse_container() {
let default_attribute_1: syn::Attribute = parse_quote! {
#[serde(default)]
};
let default_attribute_2: syn::Attribute = parse_quote! {
#[serde(default)]
};
let deny_unknown_fields_attribute: syn::Attribute = parse_quote! {
#[serde(deny_unknown_fields)]
};
let unsupported_attribute: syn::Attribute = parse_quote! {
#[serde(expecting = "...")]
};
let attributes: &[Attribute] = &[
default_attribute_1,
default_attribute_2,
deny_unknown_fields_attribute,
unsupported_attribute,
];

let expected = SerdeContainer {
is_default: true,
deny_unknown_fields: true,
..Default::default()
};

let result = parse_container(attributes).unwrap();
assert_eq!(expected, result);
}

macro_rules! test_rename_variant_rule {
( $($case:expr=> $value:literal = $expected:literal)* ) => {
#[test]
Expand Down
1 change: 1 addition & 0 deletions crates/oapi/docs/derive_to_schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ be serialized anyway. Similarly the _`rename`_ and _`rename_all`_ will reflect t
* `untagged` Supported at the container level. Allows [untagged
enum representation](https://serde.rs/enum-representations.html#untagged).
* `default` Supported at the container level and field level according to [serde attributes].
* `deny_unknown_fields` Supported at the container level.
* `flatten` Supported at the field level.

Other _`serde`_ attributes works as is but does not have any effect on the generated OpenAPI doc.
Expand Down

0 comments on commit a443aca

Please sign in to comment.