diff --git a/serde/src/private/ser.rs b/serde/src/private/ser.rs index 7876542ee..50bcb251e 100644 --- a/serde/src/private/ser.rs +++ b/serde/src/private/ser.rs @@ -1370,3 +1370,16 @@ impl Serialize for AdjacentlyTaggedEnumVariant { serializer.serialize_unit_variant(self.enum_name, self.variant_index, self.variant_name) } } + +// Error when Serialize for a non_exhaustive remote enum encounters a variant +// that is not recognized. +pub struct CannotSerializeVariant(pub T); + +impl Display for CannotSerializeVariant +where + T: Debug, +{ + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "enum variant cannot be serialized: {:?}", self.0) + } +} diff --git a/serde_derive/src/internals/attr.rs b/serde_derive/src/internals/attr.rs index d5512348b..0b25c7c0d 100644 --- a/serde_derive/src/internals/attr.rs +++ b/serde_derive/src/internals/attr.rs @@ -221,6 +221,7 @@ pub struct Container { is_packed: bool, /// Error message generated when type can't be deserialized expecting: Option, + non_exhaustive: bool, } /// Styles of representing an enum. @@ -306,9 +307,12 @@ impl Container { let mut variant_identifier = BoolAttr::none(cx, VARIANT_IDENTIFIER); let mut serde_path = Attr::none(cx, CRATE); let mut expecting = Attr::none(cx, EXPECTING); + let mut non_exhaustive = false; for attr in &item.attrs { if attr.path() != SERDE { + non_exhaustive |= + matches!(&attr.meta, syn::Meta::Path(path) if path == NON_EXHAUSTIVE); continue; } @@ -587,6 +591,7 @@ impl Container { serde_path: serde_path.get(), is_packed, expecting: expecting.get(), + non_exhaustive, } } @@ -672,6 +677,10 @@ impl Container { pub fn expecting(&self) -> Option<&str> { self.expecting.as_ref().map(String::as_ref) } + + pub fn non_exhaustive(&self) -> bool { + self.non_exhaustive + } } fn decide_tag( diff --git a/serde_derive/src/internals/symbol.rs b/serde_derive/src/internals/symbol.rs index 68091fb85..572391a80 100644 --- a/serde_derive/src/internals/symbol.rs +++ b/serde_derive/src/internals/symbol.rs @@ -19,6 +19,7 @@ pub const FLATTEN: Symbol = Symbol("flatten"); pub const FROM: Symbol = Symbol("from"); pub const GETTER: Symbol = Symbol("getter"); pub const INTO: Symbol = Symbol("into"); +pub const NON_EXHAUSTIVE: Symbol = Symbol("non_exhaustive"); pub const OTHER: Symbol = Symbol("other"); pub const REMOTE: Symbol = Symbol("remote"); pub const RENAME: Symbol = Symbol("rename"); diff --git a/serde_derive/src/ser.rs b/serde_derive/src/ser.rs index dc02466f5..1f67f0430 100644 --- a/serde_derive/src/ser.rs +++ b/serde_derive/src/ser.rs @@ -401,7 +401,7 @@ fn serialize_enum(params: &Parameters, variants: &[Variant], cattrs: &attr::Cont let self_var = ¶ms.self_var; - let arms: Vec<_> = variants + let mut arms: Vec<_> = variants .iter() .enumerate() .map(|(variant_index, variant)| { @@ -409,6 +409,12 @@ fn serialize_enum(params: &Parameters, variants: &[Variant], cattrs: &attr::Cont }) .collect(); + if cattrs.non_exhaustive() { + arms.push(quote! { + unrecognized => _serde::__private::Err(_serde::ser::Error::custom(_serde::__private::ser::CannotSerializeVariant(unrecognized))), + }); + } + quote_expr! { match *#self_var { #(#arms)*