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
3 changes: 3 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ jobs:
- run:
name: Cargo test (preserve proto field names)
command: cargo test --workspace --features preserve-proto-field-names
- run:
name: Cargo test (ignore unknown enum variants)
command: cargo test --workspace --features ignore-unknown-enum-variants
- cache_save

vendor:
Expand Down
31 changes: 27 additions & 4 deletions pbjson-build/src/generator/enumeration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub fn generate_enum<W: Write>(
descriptor: &EnumDescriptor,
writer: &mut W,
use_integers_for_enums: bool,
ignore_unknown_enum_variants: bool,
) -> Result<()> {
let rust_type = resolver.rust_type(path);

Expand Down Expand Up @@ -74,7 +75,13 @@ pub fn generate_enum<W: Write>(
// Generate Deserialize
write_deserialize_start(0, &rust_type, writer)?;
write_fields_array(writer, 2, variants.iter().map(|(name, _, _)| name.as_str()))?;
write_visitor(writer, 2, &rust_type, &variants)?;
write_visitor(
writer,
2,
&rust_type,
&variants,
ignore_unknown_enum_variants,
)?;

// Use deserialize_any to allow users to provide integers or strings
writeln!(
Expand All @@ -92,7 +99,23 @@ fn write_visitor<W: Write>(
indent: usize,
rust_type: &str,
variants: &[(String, i32, String)],
ignore_unknown_enum_variants: bool,
) -> Result<()> {
// These are what needs to be done for an unknown i32 or string value.
let (or_unknown_i32, unknown_string_return) = if ignore_unknown_enum_variants {
// If ignore_unknown_enum_variants is set, we will return the default for the enum.
(
format!(".or_else(|| Some({rust_type}::default()))"),
format!("Ok({rust_type}::default())"),
)
} else {
// If ignore_unknown_enum_variants is not set, we will return an Err.
(
"".into(),
"Err(serde::de::Error::unknown_variant(value, FIELDS))".into(),
)
};

// Protobuf supports deserialization of enumerations both from string and integer values
writeln!(
writer,
Expand All @@ -111,7 +134,7 @@ fn write_visitor<W: Write>(
{indent} {{
{indent} i32::try_from(v)
{indent} .ok()
{indent} .and_then(|x| x.try_into().ok())
{indent} .and_then(|x| x.try_into().ok(){or_unknown_i32})
{indent} .ok_or_else(|| {{
{indent} serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self)
{indent} }})
Expand All @@ -123,7 +146,7 @@ fn write_visitor<W: Write>(
{indent} {{
{indent} i32::try_from(v)
{indent} .ok()
{indent} .and_then(|x| x.try_into().ok())
{indent} .and_then(|x| x.try_into().ok(){or_unknown_i32})
{indent} .ok_or_else(|| {{
{indent} serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self)
{indent} }})
Expand Down Expand Up @@ -151,7 +174,7 @@ fn write_visitor<W: Write>(

writeln!(
writer,
"{indent}_ => Err(serde::de::Error::unknown_variant(value, FIELDS)),",
"{indent}_ => {unknown_string_return},",
indent = Indent(indent + 3)
)?;
writeln!(writer, "{}}}", Indent(indent + 2))?;
Expand Down
8 changes: 8 additions & 0 deletions pbjson-build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ pub struct Builder {
btree_map_paths: Vec<String>,
emit_fields: bool,
use_integers_for_enums: bool,
ignore_unknown_enum_variants: bool,
preserve_proto_field_names: bool,
}

Expand Down Expand Up @@ -193,6 +194,12 @@ impl Builder {
self
}

/// Ignore unknown enum variants, and instead return the enum default.
pub fn ignore_unknown_enum_variants(&mut self) -> &mut Self {
self.ignore_unknown_enum_variants = true;
self
}

/// Output fields with their original names as defined in their proto schemas, instead of
/// lowerCamelCase
pub fn preserve_proto_field_names(&mut self) -> &mut Self {
Expand Down Expand Up @@ -276,6 +283,7 @@ impl Builder {
descriptor,
writer,
self.use_integers_for_enums,
self.ignore_unknown_enum_variants,
)?,
Descriptor::Message(descriptor) => {
if let Some(message) = resolve_message(&self.descriptors, descriptor) {
Expand Down
1 change: 1 addition & 0 deletions pbjson-test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pbjson-types = { path = "../pbjson-types" }
serde = { version = "1.0", features = ["derive"] }

[features]
ignore-unknown-enum-variants = []
ignore-unknown-fields = []
btree = []
emit-fields = []
Expand Down
4 changes: 4 additions & 0 deletions pbjson-test/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ fn main() -> Result<()> {
.register_descriptors(&descriptor_set)?
.extern_path(".test.external", "crate");

if cfg!(feature = "ignore-unknown-enum-variants") {
builder.ignore_unknown_enum_variants();
}

if cfg!(feature = "ignore-unknown-fields") {
builder.ignore_unknown_fields();
}
Expand Down
27 changes: 27 additions & 0 deletions pbjson-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,33 @@ mod tests {
assert_eq!(empty, Empty {});
}

#[test]
#[cfg(feature = "ignore-unknown-enum-variants")]
fn test_ignore_unknown_enum_variant() {
// A known string still maps correctly.
let kitchen_sink =
serde_json::from_str::<KitchenSink>("{\n \"value\": \"VALUE_A\"\n}").unwrap();
assert!(matches!(kitchen_sink, KitchenSink { value: 45, .. }));

// A known integer still maps correctly.
let kitchen_sink = serde_json::from_str::<KitchenSink>("{\n \"value\": 63\n}").unwrap();
assert!(matches!(kitchen_sink, KitchenSink { value: 63, .. }));

// An unknown string maps to default.
let kitchen_sink =
serde_json::from_str::<KitchenSink>("{\n \"value\": \"VALUE_DOES_NOT_EXIST\"\n}")
.unwrap();
assert!(matches!(kitchen_sink, KitchenSink { value: 0, .. }));

// An unknown integer maps to default.
let kitchen_sink = serde_json::from_str::<KitchenSink>("{\n \"value\": 1337\n}").unwrap();
assert!(matches!(kitchen_sink, KitchenSink { value: 0, .. }));

// Numeric values that don't fit in an i32 should still error.
assert!(serde_json::from_str::<KitchenSink>("{\n \"value\": 5.6\n}").is_err());
assert!(serde_json::from_str::<KitchenSink>("{\n \"value\": 3000000000\n}").is_err());
}

#[test]
#[cfg(feature = "btree")]
fn test_btree() {
Expand Down