Skip to content

Commit

Permalink
for read-only properties, can use default to specify a function f…
Browse files Browse the repository at this point in the history
…or creating a default value. #647
  • Loading branch information
sunli829 committed Sep 7, 2023
1 parent 27a77b7 commit e978f7d
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 71 deletions.
112 changes: 41 additions & 71 deletions poem-openapi-derive/src/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,32 +144,47 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {

fields.push(field_ident);

let create_default_value = match (&field.default, &args.default) {
// field default
(Some(default_value), _) => Some(match default_value {
DefaultValue::Default => quote!(<#field_ty as ::std::default::Default>::default()),
DefaultValue::Function(func_name) => quote!(#func_name()),
}),
// object default
(_, Some(default_value)) => Some(match default_value {
DefaultValue::Default => {
quote!(<Self as ::std::default::Default>::default().#field_ident)
}
DefaultValue::Function(func_name) => quote!({
let default_obj: Self = #func_name();
default_obj.#field_ident
}),
}),
// no default
_ => None,
};

if read_only {
let create_default_value = create_default_value
.clone()
.unwrap_or_else(|| quote! { ::std::default::Default::default() });
deserialize_fields.push(quote! {
#[allow(non_snake_case)]
let #field_ident: #field_ty = {
if obj.contains_key(#field_name) {
return Err(#crate_name::types::ParseError::custom(format!("properties `{}` is read only.", #field_name)));
}
::std::default::Default::default()
#create_default_value
};
});
} else if !*field.flatten {
match (&field.default, &args.default) {
// field default
(Some(default_value), _) => {
let default_value = match default_value {
DefaultValue::Default => {
quote!(<#field_ty as ::std::default::Default>::default())
}
DefaultValue::Function(func_name) => quote!(#func_name()),
};

match &create_default_value {
Some(create_default_value) => {
deserialize_fields.push(quote! {
#[allow(non_snake_case)]
let #field_ident: #field_ty = {
match obj.remove(#field_name) {
::std::option::Option::Some(#crate_name::__private::serde_json::Value::Null) | ::std::option::Option::None => #default_value,
::std::option::Option::Some(#crate_name::__private::serde_json::Value::Null) | ::std::option::Option::None => #create_default_value,
value => {
let value = #crate_name::types::ParseFromJSON::parse_from_json(value).map_err(#crate_name::types::ParseError::propagate)?;
#validators_checker
Expand All @@ -179,45 +194,16 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
};
});
}
// object default
(_, Some(default_value)) => {
let default_value = match default_value {
DefaultValue::Default => {
quote!(<Self as ::std::default::Default>::default().#field_ident)
}
DefaultValue::Function(func_name) => quote!({
let default_obj: Self = #func_name();
default_obj.#field_ident
}),
None => deserialize_fields.push(quote! {
#[allow(non_snake_case)]
let #field_ident: #field_ty = {
let value = #crate_name::types::ParseFromJSON::parse_from_json(obj.remove(#field_name))
.map_err(#crate_name::types::ParseError::propagate)?;
#validators_checker
value
};

deserialize_fields.push(quote! {
#[allow(non_snake_case)]
let #field_ident: #field_ty = {
match obj.remove(#field_name) {
::std::option::Option::Some(#crate_name::__private::serde_json::Value::Null) | ::std::option::Option::None => #default_value,
value => {
let value = #crate_name::types::ParseFromJSON::parse_from_json(value).map_err(#crate_name::types::ParseError::propagate)?;
#validators_checker
value
}
}
};
});
}
// no default
_ => {
deserialize_fields.push(quote! {
#[allow(non_snake_case)]
let #field_ident: #field_ty = {
let value = #crate_name::types::ParseFromJSON::parse_from_json(obj.remove(#field_name))
.map_err(#crate_name::types::ParseError::propagate)?;
#validators_checker
value
};
});
}
};
}),
}
} else {
if args.deny_unknown_fields {
return Err(Error::new(
Expand Down Expand Up @@ -269,27 +255,11 @@ pub(crate) fn generate(args: DeriveInput) -> GeneratorResult<TokenStream> {
});
}

let field_meta_default = match (&field.default, &args.default) {
(Some(default_value), _) => match default_value {
DefaultValue::Default => {
quote!(#crate_name::types::ToJSON::to_json(&<#field_ty as ::std::default::Default>::default()))
}
DefaultValue::Function(func_name) => {
quote!(#crate_name::types::ToJSON::to_json(&#func_name()))
}
},
(_, Some(default_value)) => match default_value {
DefaultValue::Default => {
quote!(#crate_name::types::ToJSON::to_json(&<Self as ::std::default::Default>::default().#field_ident))
}
DefaultValue::Function(func_name) => {
quote!(#crate_name::types::ToJSON::to_json(&{
let default_object: Self = #func_name();
default_object.#field_ident
}))
}
},
(None, None) => quote!(::std::option::Option::None),
let field_meta_default = match &create_default_value {
Some(create_default_value) if !read_only => {
quote!(#crate_name::types::ToJSON::to_json(&#create_default_value))
}
_ => quote!(::std::option::Option::None),
};

if !*field.flatten {
Expand Down
16 changes: 16 additions & 0 deletions poem-openapi/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

# [3.0.6] 2023-09-07

- for `read-only` properties, can use `default` to specify a function for creating a default value. [#647](https://github.com/poem-web/poem/issues/647)

```rust
fn default_offset_datetime() -> OffsetDateTime {
OffsetDateTime::now_utc()
}

#[derive(Debug, Object, PartialEq)]
struct Obj {
#[oai(read_only, default = "default_offset_datetime")]
time: OffsetDateTime,
}
```

# [3.0.5] 2023-09-06

- fixes [#648](https://github.com/poem-web/poem/issues/648)
Expand Down
26 changes: 26 additions & 0 deletions poem-openapi/tests/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use poem_openapi::{
Enum, NewType, Object, OpenApi,
};
use serde_json::json;
use time::OffsetDateTime;

fn get_meta<T: Type>() -> MetaSchema {
let mut registry = Registry::new();
Expand Down Expand Up @@ -389,6 +390,31 @@ fn read_only() {
);
}

#[test]
fn read_only_with_default() {
fn default_offset_datetime() -> OffsetDateTime {
OffsetDateTime::from_unix_timestamp(1694045893).unwrap()
}

#[derive(Debug, Object, PartialEq)]
struct Obj {
#[oai(read_only, default = "default_offset_datetime")]
time: OffsetDateTime,
}

let meta = get_meta::<Obj>();
assert_eq!(meta.properties[0].0, "time");
assert!(meta.properties[0].1.unwrap_inline().read_only);
assert!(meta.properties[0].1.unwrap_inline().default.is_none());

assert_eq!(
Obj::parse_from_json(Some(serde_json::json!({}))).unwrap(),
Obj {
time: OffsetDateTime::from_unix_timestamp(1694045893).unwrap()
}
);
}

#[test]
fn write_only() {
#[derive(Debug, Object, PartialEq)]
Expand Down

0 comments on commit e978f7d

Please sign in to comment.