This is an automated email from the ASF dual-hosted git repository. mgrigorov pushed a commit to branch avro-3965-default-len-bigger-than-size in repository https://gitbox.apache.org/repos/asf/avro.git
commit 6c05e0d9e1c7715773023c57775b747f0c718951 Author: Martin Tzvetanov Grigorov <[email protected]> AuthorDate: Tue May 14 14:38:23 2024 +0300 AVRO-3965: [Rust] Default values for fixed can be longer than size Return an error if the default value's length is not the same as the specified size of a Fixed schema Signed-off-by: Martin Tzvetanov Grigorov <[email protected]> --- lang/rust/avro/src/decode.rs | 4 +++ lang/rust/avro/src/encode.rs | 1 + lang/rust/avro/src/error.rs | 3 ++ lang/rust/avro/src/schema.rs | 51 ++++++++++++++++++++++++++++-- lang/rust/avro/src/schema_compatibility.rs | 2 ++ lang/rust/avro/src/schema_equality.rs | 2 ++ lang/rust/avro/src/types.rs | 5 +++ lang/rust/avro/src/writer.rs | 2 ++ 8 files changed, 67 insertions(+), 3 deletions(-) diff --git a/lang/rust/avro/src/decode.rs b/lang/rust/avro/src/decode.rs index 41c3f24ab..9c685e9a8 100644 --- a/lang/rust/avro/src/decode.rs +++ b/lang/rust/avro/src/decode.rs @@ -149,6 +149,7 @@ pub(crate) fn decode_internal<R: Read, S: Borrow<Schema>>( name: "uuid".into(), aliases: None, doc: None, + default: None, attributes: Default::default(), }), names, @@ -419,6 +420,7 @@ mod tests { doc: None, name: Name::new("decimal")?, aliases: None, + default: None, attributes: Default::default(), })); let schema = Schema::Decimal(DecimalSchema { @@ -448,6 +450,7 @@ mod tests { name: Name::new("decimal")?, aliases: None, doc: None, + default: None, attributes: Default::default(), })); let schema = Schema::Decimal(DecimalSchema { @@ -893,6 +896,7 @@ mod tests { name: "uuid".into(), aliases: None, doc: None, + default: None, attributes: Default::default(), }); let value = Value::Uuid(Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000")?); diff --git a/lang/rust/avro/src/encode.rs b/lang/rust/avro/src/encode.rs index a7d34a4f5..214673ec9 100644 --- a/lang/rust/avro/src/encode.rs +++ b/lang/rust/avro/src/encode.rs @@ -901,6 +901,7 @@ pub(crate) mod tests { name: "uuid".into(), aliases: None, doc: None, + default: None, attributes: Default::default(), }); let value = Value::Uuid(Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000")?); diff --git a/lang/rust/avro/src/error.rs b/lang/rust/avro/src/error.rs index e46a9975c..b436f403f 100644 --- a/lang/rust/avro/src/error.rs +++ b/lang/rust/avro/src/error.rs @@ -370,6 +370,9 @@ pub enum Error { #[error("Fixed schema has no `size`")] GetFixedSizeField, + #[error("Fixed schema's default value length ({0}) does not match its size ({1})")] + FixedDefaultLenSizeMismatch(usize, u64), + #[error("Failed to compress with flate")] DeflateCompress(#[source] std::io::Error), diff --git a/lang/rust/avro/src/schema.rs b/lang/rust/avro/src/schema.rs index e7f6d5e4b..a2e83ad6a 100644 --- a/lang/rust/avro/src/schema.rs +++ b/lang/rust/avro/src/schema.rs @@ -817,6 +817,8 @@ pub struct FixedSchema { pub doc: Documentation, /// The size of the fixed schema pub size: usize, + /// An optional default symbol used for compatibility + pub default: Option<String>, /// The custom attributes of the schema pub attributes: BTreeMap<String, Value>, } @@ -1832,6 +1834,18 @@ impl Parser { None => Err(Error::GetFixedSizeField), }?; + let default = complex.get("default").and_then(|v| match &v { + Value::String(ref default) => Some(default.clone()), + _ => None, + }); + + if default.is_some() { + let len = default.clone().unwrap().len(); + if len != size as usize { + return Err(Error::FixedDefaultLenSizeMismatch(len, size)); + } + } + let fully_qualified_name = Name::parse(complex, enclosing_namespace)?; let aliases = fix_aliases_namespace(complex.aliases(), &fully_qualified_name.namespace); @@ -1840,6 +1854,7 @@ impl Parser { aliases: aliases.clone(), doc, size: size as usize, + default, attributes: self.get_custom_attributes(complex, vec!["size"]), }); @@ -2080,6 +2095,7 @@ impl Serialize for Schema { aliases: None, doc: None, size: 12, + default: None, attributes: Default::default(), }); map.serialize_entry("type", &inner)?; @@ -3192,6 +3208,7 @@ mod tests { aliases: None, doc: None, size: 456, + default: None, attributes: Default::default(), }), order: RecordFieldOrder::Ascending, @@ -3211,6 +3228,7 @@ mod tests { aliases: None, doc: None, size: 456, + default: None, attributes: Default::default(), }), order: RecordFieldOrder::Ascending, @@ -3285,7 +3303,8 @@ mod tests { name: Name::new("test")?, aliases: None, doc: None, - size: 16usize, + size: 16_usize, + default: None, attributes: Default::default(), }); @@ -3304,7 +3323,8 @@ mod tests { name: Name::new("test")?, aliases: None, doc: Some(String::from("FixedSchema documentation")), - size: 16usize, + size: 16_usize, + default: None, attributes: Default::default(), }); @@ -6256,6 +6276,7 @@ mod tests { aliases: None, doc: None, size: 1, + default: None, attributes: attributes.clone(), }); let serialized = serde_json::to_string(&schema)?; @@ -6380,11 +6401,12 @@ mod tests { aliases: None, doc: None, size: 6, + default: None, attributes: BTreeMap::from([("logicalType".to_string(), "uuid".into())]), }) ); assert_logged( - r#"Ignoring uuid logical type for a Fixed schema because its size (6) is not 16! Schema: Fixed(FixedSchema { name: Name { name: "FixedUUID", namespace: None }, aliases: None, doc: None, size: 6, attributes: {"logicalType": String("uuid")} })"#, + r#"Ignoring uuid logical type for a Fixed schema because its size (6) is not 16! Schema: Fixed(FixedSchema { name: Name { name: "FixedUUID", namespace: None }, aliases: None, doc: None, size: 6, default: None, attributes: {"logicalType": String("uuid")} })"#, ); Ok(()) @@ -6524,6 +6546,7 @@ mod tests { aliases: None, doc: None, size: 16, + default: None, attributes: Default::default(), })), }); @@ -6708,4 +6731,26 @@ mod tests { Ok(()) } + + #[test] + fn avro_3965_fixed_schema_with_default_bigger_than_size() -> TestResult { + match Schema::parse_str( + r#"{ + "type": "fixed", + "name": "test", + "size": 1, + "default": "123456789" + }"#, + ) { + Ok(_schema) => panic!("Must fail!"), + Err(err) => { + assert_eq!( + err.to_string(), + "Fixed schema's default value length (9) does not match its size (1)" + ); + } + } + + Ok(()) + } } diff --git a/lang/rust/avro/src/schema_compatibility.rs b/lang/rust/avro/src/schema_compatibility.rs index 1c7fa8194..5df7c5201 100644 --- a/lang/rust/avro/src/schema_compatibility.rs +++ b/lang/rust/avro/src/schema_compatibility.rs @@ -393,6 +393,7 @@ impl SchemaCompatibility { aliases: _, doc: _w_doc, size: w_size, + default: _w_default, attributes: _, }) = writers_schema { @@ -401,6 +402,7 @@ impl SchemaCompatibility { aliases: _, doc: _r_doc, size: r_size, + default: _r_default, attributes: _, }) = readers_schema { diff --git a/lang/rust/avro/src/schema_equality.rs b/lang/rust/avro/src/schema_equality.rs index ae90c3f3f..c9eaa2a11 100644 --- a/lang/rust/avro/src/schema_equality.rs +++ b/lang/rust/avro/src/schema_equality.rs @@ -424,6 +424,7 @@ mod tests { name: Name::from("fixed"), doc: None, size: 10, + default: None, aliases: None, attributes: BTreeMap::new(), }); @@ -434,6 +435,7 @@ mod tests { name: Name::from("fixed"), doc: None, size: 10, + default: None, aliases: None, attributes: BTreeMap::new(), }); diff --git a/lang/rust/avro/src/types.rs b/lang/rust/avro/src/types.rs index 8bac843bb..47daab503 100644 --- a/lang/rust/avro/src/types.rs +++ b/lang/rust/avro/src/types.rs @@ -1367,6 +1367,7 @@ mod tests { name: Name::new("some_fixed").unwrap(), aliases: None, doc: None, + default: None, attributes: Default::default(), }); @@ -1722,6 +1723,7 @@ Field with name '"b"' is not a member of the map items"#, aliases: None, size: 20, doc: None, + default: None, attributes: Default::default(), })) })) @@ -3036,6 +3038,7 @@ Field with name '"b"' is not a member of the map items"#, aliases: None, doc: None, size: 3, + default: None, attributes: Default::default() }))?, Value::Fixed(3, vec![97, 98, 99]) @@ -3048,6 +3051,7 @@ Field with name '"b"' is not a member of the map items"#, aliases: None, doc: None, size: 3, + default: None, attributes: Default::default() })) .is_err(),); @@ -3059,6 +3063,7 @@ Field with name '"b"' is not a member of the map items"#, aliases: None, doc: None, size: 3, + default: None, attributes: Default::default() })) .is_err(),); diff --git a/lang/rust/avro/src/writer.rs b/lang/rust/avro/src/writer.rs index dc6fd55e8..90bb2da4c 100644 --- a/lang/rust/avro/src/writer.rs +++ b/lang/rust/avro/src/writer.rs @@ -790,6 +790,7 @@ mod tests { aliases: None, doc: None, size, + default: None, attributes: Default::default(), }); let value = vec![0u8; size]; @@ -830,6 +831,7 @@ mod tests { aliases: None, doc: None, size: 12, + default: None, attributes: Default::default(), }); let value = Value::Duration(Duration::new(
