Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
=== Bug fixes
* fix untagged enum variant ordering: Integer now comes before Number to prevent unreachable deserialization (#991)
* generate `Default` impl for structs where all required fields have explicit defaults (#918)
* honour the JSON Schema `required` contract on serialization for fields whose Rust type has an intrinsic default (`Vec`, `HashMap`, `Option<T>`). Previously an empty/`None` required field was silently omitted because `skip_serializing_if` was applied; now `#[serde(default)]` is emitted alone, so deserialize stays lenient (`{}` parses) but serialize always renders the field. **Wire-format change**: payloads that previously omitted empty required fields will now render them as `[]`, `{}`, or `null`
* generate `TryFrom` instead of `From` for bounded integer newtypes, enforcing min/max constraints (#986)
* render integer `minimum`/`maximum` as integers (not floats) in doc comments (#843)
* handle special characters in enum variant names (`=`, `>`, `≥`, etc.) without panicking (#948)
Expand Down
2 changes: 1 addition & 1 deletion typify-impl/src/defaults.rs
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,7 @@ fn all_props<'a>(

if let Some(name) = maybe_name {
let required = match &property.state {
StructPropertyState::Required => true,
StructPropertyState::Required | StructPropertyState::RequiredWithDefault => true,
StructPropertyState::Optional | StructPropertyState::Default(_) => false,
};

Expand Down
5 changes: 4 additions & 1 deletion typify-impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1327,7 +1327,10 @@ impl<'a> TypeStruct<'a> {
.map(move |prop| TypeStructPropInfo {
name: prop.name.as_str(),
description: prop.description.as_deref(),
required: matches!(&prop.state, StructPropertyState::Required),
required: matches!(
&prop.state,
StructPropertyState::Required | StructPropertyState::RequiredWithDefault
),
type_id: prop.type_id.clone(),
})
}
Expand Down
27 changes: 24 additions & 3 deletions typify-impl/src/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,23 @@ impl TypeSpace {
let state = if required.contains(prop_name) {
// Even required fields may have an explicit default value
// specified in the schema. If so, we use it for the Default impl
// and builder pre-population (and also make serde more lenient).
has_default(
// and builder pre-population (and also make deserialization
// more lenient — but NOT serialization, since the schema
// still requires the field on the wire).
match has_default(
self,
&type_id,
metadata.as_ref().and_then(|m| m.default.as_ref()),
)
) {
// `Optional` here means an intrinsic-default type
// (Vec, Option, Map, …) with no explicit schema
// default. Promote to RequiredWithDefault so that
// `generate_serde_attr` emits `#[serde(default)]`
// (lenient deserialize) without `skip_serializing_if`
// (serialization always emits the required field).
StructPropertyState::Optional => StructPropertyState::RequiredWithDefault,
other => other,
}
} else {
// We can use serde's `default` and `skip_serializing_if`
// construction for options, arrays, and maps--i.e. properties that
Expand Down Expand Up @@ -342,6 +353,16 @@ pub(crate) fn generate_serde_attr(
}

(StructPropertyState::Required, _) => DefaultFunction::None,

// Required by the schema but with an intrinsic default for the
// Rust type. Emit `#[serde(default)]` so that deserializing a
// JSON object that lacks the field still succeeds (PR #918's
// intent), but DO NOT emit `skip_serializing_if`: the schema
// says the field is required on the wire.
(StructPropertyState::RequiredWithDefault, _) => {
serde_options.push(quote! { default });
DefaultFunction::Default
}
};

let serde = if serde_options.is_empty() {
Expand Down
6 changes: 6 additions & 0 deletions typify-impl/src/type_entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,12 @@ pub(crate) enum StructPropertyRename {
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum StructPropertyState {
Required,
/// Required by the schema, but with an intrinsic default value
/// (e.g. `Vec::new()`, `None`) — emit `#[serde(default)]` so
/// deserializing `{}` succeeds (PR #918), but DON'T emit
/// `skip_serializing_if`: the schema marks the field required, so
/// it must always be present on the wire.
RequiredWithDefault,
Optional,
Default(WrappedValue),
}
Expand Down
Loading
Loading