diff --git a/pbjson-build/src/generator/message.rs b/pbjson-build/src/generator/message.rs index 33d1218..c7cb46e 100644 --- a/pbjson-build/src/generator/message.rs +++ b/pbjson-build/src/generator/message.rs @@ -358,7 +358,7 @@ fn write_serialize_scalar_variable( Indent(indent), field_name, variable.as_ref - ) + ); } }; @@ -611,9 +611,9 @@ fn write_deserialize_message( writeln!( writer, "{indent}{field}: {field}__.ok_or_else(|| serde::de::Error::missing_field(\"{json_name}\"))?,", - indent=Indent(indent + 3), - field= field.rust_field_name(), - json_name= field.json_name() + indent = Indent(indent + 3), + field = field.rust_field_name(), + json_name = field.json_name() )?; } FieldModifier::UseDefault | FieldModifier::Repeated => { @@ -967,9 +967,13 @@ fn write_deserialize_field( } write!(writer, "{})", Indent(indent + 1))?; } - FieldType::Message(_) => { - write!(writer, "map.next_value()?")?; - } + FieldType::Message(_) => match field.field_modifier { + FieldModifier::Repeated => { + // No explicit presence for repeated fields + write!(writer, "Some(map.next_value()?)")?; + } + _ => write!(writer, "map.next_value()?")?, + }, }, } writeln!(writer, ";")?; @@ -998,7 +1002,7 @@ fn write_encode_scalar_field( write!(writer, "map.next_value()?") } _ => write!(writer, "Some(map.next_value()?)"), - } + }; } }; diff --git a/pbjson-test/protos/syntax3.proto b/pbjson-test/protos/syntax3.proto index 6be284f..fec84a5 100644 --- a/pbjson-test/protos/syntax3.proto +++ b/pbjson-test/protos/syntax3.proto @@ -108,4 +108,7 @@ message KitchenSink { google.protobuf.StringValue string_value = 54; google.protobuf.UInt32Value uint32_value = 55; google.protobuf.UInt64Value uint64_value = 56; + + repeated google.protobuf.Int32Value repeated_int32_value = 57; + map map_int32_value = 58; } \ No newline at end of file diff --git a/pbjson-test/src/lib.rs b/pbjson-test/src/lib.rs index 5b98f54..a2f604a 100644 --- a/pbjson-test/src/lib.rs +++ b/pbjson-test/src/lib.rs @@ -28,14 +28,17 @@ pub mod test { include!(concat!(env!("OUT_DIR"), "/test.syntax3.rs")); include!(concat!(env!("OUT_DIR"), "/test.syntax3.serde.rs")); } + pub mod common { include!(concat!(env!("OUT_DIR"), "/test.common.rs")); include!(concat!(env!("OUT_DIR"), "/test.common.serde.rs")); } + pub mod duplicate_name { include!(concat!(env!("OUT_DIR"), "/test.duplicate_name.rs")); include!(concat!(env!("OUT_DIR"), "/test.duplicate_name.serde.rs")); } + pub mod escape { include!(concat!( env!("OUT_DIR"), @@ -80,6 +83,7 @@ mod tests { } } } + impl From<(&'static str, &'static str)> for EncodedStrings { fn from((expected, expected_preserved_proto): (&'static str, &'static str)) -> Self { EncodedStrings { @@ -132,6 +136,14 @@ mod tests { } } + fn verify_decode_err(encoded: &str, error: &str) { + let err = serde_json::from_str::(encoded) + .unwrap_err() + .to_string(); + + assert!(err.contains(error), "{}", err); + } + fn verify(decoded: &KitchenSink, expected: impl Into) { let expected = expected.into(); verify_encode(decoded, expected); @@ -690,6 +702,49 @@ mod tests { verify_decode(&decoded, r#"{"stringValue":null}"#); verify_decode(&decoded, r#"{"uint32Value":null}"#); verify_decode(&decoded, r#"{"uint64Value":null}"#); + + // Test primitives are not nullable + verify_decode_err(r#"{"i32":null}"#, "data did not match any variant"); + verify_decode_err(r#"{"u64":null}"#, "data did not match any variant"); + verify_decode_err(r#"{"value":null}"#, "invalid type: null"); + verify_decode_err(r#"{"bool":null}"#, "invalid type: null"); + verify_decode_err(r#"{"string":null}"#, "invalid type: null"); + + // Test lists are not nullable + verify_decode_err( + r#"{"repeatedI32":null}"#, + "invalid type: null, expected a sequence", + ); + verify_decode_err( + r#"{"repeatedI32":[null]}"#, + "data did not match any variant", + ); + verify_decode_err( + r#"{"repeatedInt32Value":null}"#, + "invalid type: null, expected a sequence", + ); + verify_decode_err( + r#"{"repeatedInt32Value":[null]}"#, + "data did not match any variant", + ); + + // Test maps are not nullable + verify_decode_err( + r#"{"stringDict":null}"#, + "invalid type: null, expected a map", + ); + verify_decode_err( + r#"{"stringDict": {"foo": null}}"#, + "invalid type: null, expected a string ", + ); + verify_decode_err( + r#"{"mapInt32Value":null}"#, + "invalid type: null, expected a map", + ); + verify_decode_err( + r#"{"mapInt32Value":{"foo": null}}"#, + "data did not match any variant", + ); } #[test]