Skip to content

Commit

Permalink
feat: support deserializing explicit nulls (#73)
Browse files Browse the repository at this point in the history
  • Loading branch information
tustvold committed Sep 30, 2022
1 parent e2e073e commit 87f3788
Show file tree
Hide file tree
Showing 2 changed files with 180 additions and 96 deletions.
248 changes: 152 additions & 96 deletions pbjson-build/src/generator/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -753,151 +753,207 @@ fn write_deserialize_field<W: Write>(
json_name
)?;
writeln!(writer, "{}}}", Indent(indent + 1))?;
write!(writer, "{}{}__ = Some(", Indent(indent + 1), field_name)?;
write!(writer, "{}{}__ = ", Indent(indent + 1), field_name)?;

if let Some(one_of) = one_of {
write!(
writer,
"{}::{}(",
resolver.rust_type(&one_of.path),
field.rust_type_name()
)?;
}

match &field.field_type {
FieldType::Scalar(scalar) => {
write_encode_scalar_field(indent + 1, *scalar, field.field_modifier, writer)?;
}
FieldType::Enum(path) => match field.field_modifier {
FieldModifier::Repeated => {
write!(
writer,
"map.next_value::<Vec<{}>>()?.into_iter().map(|x| x as i32).collect()",
resolver.rust_type(path)
)?;
}
_ => {
match one_of {
Some(one_of) => match &field.field_type {
FieldType::Scalar(s) => match override_deserializer(*s) {
Some(deserializer) => {
write!(
writer,
"map.next_value::<::std::option::Option<{}>>()?.map(|x| {}::{}(x.0))",
deserializer,
resolver.rust_type(&one_of.path),
field.rust_type_name()
)?;
}
None => {
write!(
writer,
"map.next_value::<::std::option::Option<_>>()?.map({}::{})",
resolver.rust_type(&one_of.path),
field.rust_type_name()
)?;
}
},
FieldType::Enum(path) => {
write!(
writer,
"map.next_value::<{}>()? as i32",
resolver.rust_type(path)
"map.next_value::<::std::option::Option<{}>>()?.map(|x| {}::{}(x as i32))",
resolver.rust_type(path),
resolver.rust_type(&one_of.path),
field.rust_type_name()
)?;
}
FieldType::Message(_) => writeln!(
writer,
"map.next_value::<::std::option::Option<_>>()?.map({}::{})",
resolver.rust_type(&one_of.path),
field.rust_type_name()
)?,
FieldType::Map(_, _) => unreachable!("one of cannot contain map fields"),
},
FieldType::Map(key, value) => {
writeln!(writer)?;
match btree_map {
true => write!(
writer,
"{}map.next_value::<std::collections::BTreeMap<",
Indent(indent + 2),
)?,
false => write!(
writer,
"{}map.next_value::<std::collections::HashMap<",
Indent(indent + 2),
)?,
}

let map_k = match key {
ScalarType::Bytes | ScalarType::F32 | ScalarType::F64 => {
panic!("protobuf disallows maps with floating point or bytes keys")
None => match &field.field_type {
FieldType::Scalar(scalar) => {
write_encode_scalar_field(indent + 1, *scalar, field.field_modifier, writer)?;
}
FieldType::Enum(path) => match field.field_modifier {
FieldModifier::Optional => {
write!(
writer,
"map.next_value::<::std::option::Option<{}>>()?.map(|x| x as i32)",
resolver.rust_type(path)
)?;
}
_ if key.is_numeric() => {
FieldModifier::Repeated => {
write!(
writer,
"::pbjson::private::NumberDeserialize<{}>",
key.rust_type()
"Some(map.next_value::<Vec<{}>>()?.into_iter().map(|x| x as i32).collect())",
resolver.rust_type(path)
)?;
"k.0"
}
_ => {
write!(writer, "_")?;
"k"
}
};
write!(writer, ", ")?;
let map_v = match value.as_ref() {
FieldType::Scalar(scalar) if scalar.is_numeric() => {
write!(
writer,
"::pbjson::private::NumberDeserialize<{}>",
scalar.rust_type()
"Some(map.next_value::<{}>()? as i32)",
resolver.rust_type(path)
)?;
"v.0"
}
FieldType::Scalar(ScalarType::Bytes) => {
write!(writer, "::pbjson::private::BytesDeserialize<_>",)?;
"v.0"
}
FieldType::Enum(path) => {
write!(writer, "{}", resolver.rust_type(path))?;
"v as i32"
}
FieldType::Map(_, _) => panic!("protobuf disallows nested maps"),
_ => {
write!(writer, "_")?;
"v"
},
FieldType::Map(key, value) => {
write!(writer, "Some(")?;
writeln!(writer)?;
match btree_map {
true => write!(
writer,
"{}map.next_value::<std::collections::BTreeMap<",
Indent(indent + 2),
)?,
false => write!(
writer,
"{}map.next_value::<std::collections::HashMap<",
Indent(indent + 2),
)?,
}
};

writeln!(writer, ">>()?")?;
if map_k != "k" || map_v != "v" {
writeln!(
writer,
"{}.into_iter().map(|(k,v)| ({}, {})).collect()",
Indent(indent + 3),
map_k,
map_v,
)?;
let map_k = match key {
ScalarType::Bytes | ScalarType::F32 | ScalarType::F64 => {
panic!("protobuf disallows maps with floating point or bytes keys")
}
_ if key.is_numeric() => {
write!(
writer,
"::pbjson::private::NumberDeserialize<{}>",
key.rust_type()
)?;
"k.0"
}
_ => {
write!(writer, "_")?;
"k"
}
};
write!(writer, ", ")?;
let map_v = match value.as_ref() {
FieldType::Scalar(scalar) if scalar.is_numeric() => {
write!(
writer,
"::pbjson::private::NumberDeserialize<{}>",
scalar.rust_type()
)?;
"v.0"
}
FieldType::Scalar(ScalarType::Bytes) => {
write!(writer, "::pbjson::private::BytesDeserialize<_>",)?;
"v.0"
}
FieldType::Enum(path) => {
write!(writer, "{}", resolver.rust_type(path))?;
"v as i32"
}
FieldType::Map(_, _) => panic!("protobuf disallows nested maps"),
_ => {
write!(writer, "_")?;
"v"
}
};

writeln!(writer, ">>()?")?;
if map_k != "k" || map_v != "v" {
writeln!(
writer,
"{}.into_iter().map(|(k,v)| ({}, {})).collect()",
Indent(indent + 3),
map_k,
map_v,
)?;
}
write!(writer, "{})", Indent(indent + 1))?;
}
write!(writer, "{}", Indent(indent + 1))?;
}
_ => {
write!(writer, "map.next_value()?",)?;
}
};

if one_of.is_some() {
write!(writer, ")")?;
FieldType::Message(_) => {
write!(writer, "map.next_value()?")?;
}
},
}

writeln!(writer, ");")?;
writeln!(writer, ";")?;
writeln!(writer, "{}}}", Indent(indent))
}

fn override_deserializer(scalar: ScalarType) -> Option<&'static str> {
match scalar {
ScalarType::Bytes => Some("::pbjson::private::BytesDeserialize<_>"),
_ if scalar.is_numeric() => Some("::pbjson::private::NumberDeserialize<_>"),
_ => None,
}
}

fn write_encode_scalar_field<W: Write>(
indent: usize,
scalar: ScalarType,
field_modifier: FieldModifier,
writer: &mut W,
) -> Result<()> {
let deserializer = match scalar {
ScalarType::Bytes => "BytesDeserialize",
_ if scalar.is_numeric() => "NumberDeserialize",
_ => return write!(writer, "map.next_value()?",),
let deserializer = match override_deserializer(scalar) {
Some(deserializer) => deserializer,
None => {
return match field_modifier {
FieldModifier::Optional => {
write!(writer, "map.next_value()?")
}
_ => write!(writer, "Some(map.next_value()?)"),
}
}
};

writeln!(writer)?;

match field_modifier {
FieldModifier::Optional => {
writeln!(
writer,
"{}map.next_value::<::std::option::Option<{}>>()?.map(|x| x.0)",
Indent(indent + 1),
deserializer
)?;
}
FieldModifier::Repeated => {
writeln!(
writer,
"{}map.next_value::<Vec<::pbjson::private::{}<_>>>()?",
"{}Some(map.next_value::<Vec<{}>>()?",
Indent(indent + 1),
deserializer
)?;
writeln!(
writer,
"{}.into_iter().map(|x| x.0).collect()",
"{}.into_iter().map(|x| x.0).collect())",
Indent(indent + 2)
)?;
}
_ => {
writeln!(
writer,
"{}map.next_value::<::pbjson::private::{}<_>>()?.0",
"{}Some(map.next_value::<{}>()?.0)",
Indent(indent + 1),
deserializer
)?;
Expand Down
28 changes: 28 additions & 0 deletions pbjson-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,34 @@ mod tests {

decoded.string_value = None;
verify(&decoded, r#"{}"#);

// Test explicit null optional scalar
verify_decode(&decoded, r#"{"optionalU32":null}"#);
verify_decode(&decoded, r#"{"optionalU64":null}"#);
verify_decode(&decoded, r#"{"optionalString":null}"#);

// Test explicit null optional enum
verify_decode(&decoded, r#"{"optionalValue":null}"#);

// Test explicit null message
verify_decode(&decoded, r#"{"empty":null}"#);

// Test explicit null in oneof
verify_decode(&decoded, r#"{"oneOfI32":null}"#);
verify_decode(&decoded, r#"{"oneOfBool":null}"#);
verify_decode(&decoded, r#"{"oneOfValue":null}"#);
verify_decode(&decoded, r#"{"oneOfMessage":null}"#);

// Test explicit null value type
verify_decode(&decoded, r#"{"boolValue":null}"#);
verify_decode(&decoded, r#"{"bytesValue":null}"#);
verify_decode(&decoded, r#"{"doubleValue":null}"#);
verify_decode(&decoded, r#"{"floatValue":null}"#);
verify_decode(&decoded, r#"{"int32Value":null}"#);
verify_decode(&decoded, r#"{"int64Value":null}"#);
verify_decode(&decoded, r#"{"stringValue":null}"#);
verify_decode(&decoded, r#"{"uint32Value":null}"#);
verify_decode(&decoded, r#"{"uint64Value":null}"#);
}

#[test]
Expand Down

0 comments on commit 87f3788

Please sign in to comment.