Skip to content

Commit 430704f

Browse files
committed
feat: add ignore_unknown_enum_variants
feat: add ignore_unknown_enum_variants feature Enabling this feature adjust deserialization to ignore unknown enum variants rather than Err out. This permits similar behavior to binary protobuf deserialization and greater backwards/forward compat. of the serialized messages.
1 parent 9b1f8a6 commit 430704f

File tree

6 files changed

+70
-4
lines changed

6 files changed

+70
-4
lines changed

.circleci/config.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,9 @@ jobs:
142142
- run:
143143
name: Cargo test (preserve proto field names)
144144
command: cargo test --workspace --features preserve-proto-field-names
145+
- run:
146+
name: Cargo test (ignore unknown enum variants)
147+
command: cargo test --workspace --features ignore-unknown-enum-variants
145148
- cache_save
146149

147150
vendor:

pbjson-build/src/generator/enumeration.rs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub fn generate_enum<W: Write>(
2020
descriptor: &EnumDescriptor,
2121
writer: &mut W,
2222
use_integers_for_enums: bool,
23+
ignore_unknown_enum_variants: bool,
2324
) -> Result<()> {
2425
let rust_type = resolver.rust_type(path);
2526

@@ -74,7 +75,13 @@ pub fn generate_enum<W: Write>(
7475
// Generate Deserialize
7576
write_deserialize_start(0, &rust_type, writer)?;
7677
write_fields_array(writer, 2, variants.iter().map(|(name, _, _)| name.as_str()))?;
77-
write_visitor(writer, 2, &rust_type, &variants)?;
78+
write_visitor(
79+
writer,
80+
2,
81+
&rust_type,
82+
&variants,
83+
ignore_unknown_enum_variants,
84+
)?;
7885

7986
// Use deserialize_any to allow users to provide integers or strings
8087
writeln!(
@@ -92,7 +99,23 @@ fn write_visitor<W: Write>(
9299
indent: usize,
93100
rust_type: &str,
94101
variants: &[(String, i32, String)],
102+
ignore_unknown_enum_variants: bool,
95103
) -> Result<()> {
104+
// These are what needs to be done for an unknown i32 or string value.
105+
let (or_unknown_i32, unknown_string_return) = if ignore_unknown_enum_variants {
106+
// If ignore_unknown_enum_variants is set, we will return the default for the enum.
107+
(
108+
format!(".or_else(|| Some({rust_type}::default()))"),
109+
format!("Ok({rust_type}::default())"),
110+
)
111+
} else {
112+
// If ignore_unknown_enum_variants is not set, we will return an Err.
113+
(
114+
"".into(),
115+
"Err(serde::de::Error::unknown_variant(value, FIELDS))".into(),
116+
)
117+
};
118+
96119
// Protobuf supports deserialization of enumerations both from string and integer values
97120
writeln!(
98121
writer,
@@ -111,7 +134,7 @@ fn write_visitor<W: Write>(
111134
{indent} {{
112135
{indent} i32::try_from(v)
113136
{indent} .ok()
114-
{indent} .and_then(|x| x.try_into().ok())
137+
{indent} .and_then(|x| x.try_into().ok(){or_unknown_i32})
115138
{indent} .ok_or_else(|| {{
116139
{indent} serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self)
117140
{indent} }})
@@ -123,7 +146,7 @@ fn write_visitor<W: Write>(
123146
{indent} {{
124147
{indent} i32::try_from(v)
125148
{indent} .ok()
126-
{indent} .and_then(|x| x.try_into().ok())
149+
{indent} .and_then(|x| x.try_into().ok(){or_unknown_i32})
127150
{indent} .ok_or_else(|| {{
128151
{indent} serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self)
129152
{indent} }})
@@ -151,7 +174,7 @@ fn write_visitor<W: Write>(
151174

152175
writeln!(
153176
writer,
154-
"{indent}_ => Err(serde::de::Error::unknown_variant(value, FIELDS)),",
177+
"{indent}_ => {unknown_string_return},",
155178
indent = Indent(indent + 3)
156179
)?;
157180
writeln!(writer, "{}}}", Indent(indent + 2))?;

pbjson-build/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ pub struct Builder {
107107
btree_map_paths: Vec<String>,
108108
emit_fields: bool,
109109
use_integers_for_enums: bool,
110+
ignore_unknown_enum_variants: bool,
110111
preserve_proto_field_names: bool,
111112
}
112113

@@ -193,6 +194,12 @@ impl Builder {
193194
self
194195
}
195196

197+
/// Ignore unknown enum variants, and instead return the enum default.
198+
pub fn ignore_unknown_enum_variants(&mut self) -> &mut Self {
199+
self.ignore_unknown_enum_variants = true;
200+
self
201+
}
202+
196203
/// Output fields with their original names as defined in their proto schemas, instead of
197204
/// lowerCamelCase
198205
pub fn preserve_proto_field_names(&mut self) -> &mut Self {
@@ -276,6 +283,7 @@ impl Builder {
276283
descriptor,
277284
writer,
278285
self.use_integers_for_enums,
286+
self.ignore_unknown_enum_variants,
279287
)?,
280288
Descriptor::Message(descriptor) => {
281289
if let Some(message) = resolve_message(&self.descriptors, descriptor) {

pbjson-test/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pbjson-types = { path = "../pbjson-types" }
1313
serde = { version = "1.0", features = ["derive"] }
1414

1515
[features]
16+
ignore-unknown-enum-variants = []
1617
ignore-unknown-fields = []
1718
btree = []
1819
emit-fields = []

pbjson-test/build.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ fn main() -> Result<()> {
4444
.register_descriptors(&descriptor_set)?
4545
.extern_path(".test.external", "crate");
4646

47+
if cfg!(feature = "ignore-unknown-enum-variants") {
48+
builder.ignore_unknown_enum_variants();
49+
}
50+
4751
if cfg!(feature = "ignore-unknown-fields") {
4852
builder.ignore_unknown_fields();
4953
}

pbjson-test/src/lib.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,33 @@ mod tests {
188188
assert_eq!(empty, Empty {});
189189
}
190190

191+
#[test]
192+
#[cfg(feature = "ignore-unknown-enum-variants")]
193+
fn test_ignore_unknown_enum_variant() {
194+
// A known string still maps correctly.
195+
let kitchen_sink =
196+
serde_json::from_str::<KitchenSink>("{\n \"value\": \"VALUE_A\"\n}").unwrap();
197+
assert!(matches!(kitchen_sink, KitchenSink { value: 45, .. }));
198+
199+
// A known integer still maps correctly.
200+
let kitchen_sink = serde_json::from_str::<KitchenSink>("{\n \"value\": 63\n}").unwrap();
201+
assert!(matches!(kitchen_sink, KitchenSink { value: 63, .. }));
202+
203+
// An unknown string maps to default.
204+
let kitchen_sink =
205+
serde_json::from_str::<KitchenSink>("{\n \"value\": \"VALUE_DOES_NOT_EXIST\"\n}")
206+
.unwrap();
207+
assert!(matches!(kitchen_sink, KitchenSink { value: 0, .. }));
208+
209+
// An unknown integer maps to default.
210+
let kitchen_sink = serde_json::from_str::<KitchenSink>("{\n \"value\": 1337\n}").unwrap();
211+
assert!(matches!(kitchen_sink, KitchenSink { value: 0, .. }));
212+
213+
// Numeric values that don't fit in an i32 should still error.
214+
assert!(serde_json::from_str::<KitchenSink>("{\n \"value\": 5.6\n}").is_err());
215+
assert!(serde_json::from_str::<KitchenSink>("{\n \"value\": 3000000000\n}").is_err());
216+
}
217+
191218
#[test]
192219
#[cfg(feature = "btree")]
193220
fn test_btree() {

0 commit comments

Comments
 (0)