diff --git a/lang/rust/avro/src/decode.rs b/lang/rust/avro/src/decode.rs index 41c3f24ab27..9c685e9a84c 100644 --- a/lang/rust/avro/src/decode.rs +++ b/lang/rust/avro/src/decode.rs @@ -149,6 +149,7 @@ pub(crate) fn decode_internal>( 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 a7d34a4f504..214673ec9d4 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 e46a9975c3a..b436f403fe3 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 e7f6d5e4b5b..a2e83ad6a50 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, /// The custom attributes of the schema pub attributes: BTreeMap, } @@ -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 1c7fa819480..5df7c520145 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 ae90c3f3fef..c9eaa2a1173 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 8bac843bb9c..47daab503b9 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 dc6fd55e83d..90bb2da4c52 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(