From 0b5d1c620e9436b34dba30d95d68d071b1d8bf8f Mon Sep 17 00:00:00 2001 From: Kit Danison-Fieldhouse Date: Mon, 4 May 2026 14:06:17 -0500 Subject: [PATCH 1/5] Fix: String to Fixed resolution will obey the schema's size constraints adding padding if needed --- avro/src/types.rs | 98 ++++++++++++++++++++++++++++++++++++++++++++++- avro/tests/io.rs | 2 +- 2 files changed, 98 insertions(+), 2 deletions(-) diff --git a/avro/src/types.rs b/avro/src/types.rs index c2e0b3a3..857e68a0 100644 --- a/avro/src/types.rs +++ b/avro/src/types.rs @@ -1110,7 +1110,15 @@ impl Value { Err(Details::CompareFixedSizes { size, n }.into()) } } - Value::String(s) => Ok(Value::Fixed(s.len(), s.into_bytes())), + Value::String(s) => { + if s.len() > size { + Err(Details::CompareFixedSizes { size, n: s.len() }.into()) + } else { + let mut bytes = s.into_bytes(); + bytes.resize(size, 0); + Ok(Value::Fixed(size, bytes)) + } + }, Value::Bytes(s) => { if s.len() == size { Ok(Value::Fixed(size, s)) @@ -3643,4 +3651,92 @@ Field with name '"b"' is not a member of the map items"#, "JSON number 18446744073709551615 could not be converted into an Avro value as it's too large" ); } + + #[test] + fn avro_rs_test_biggger_string_to_smaller_fixed_resolution() -> TestResult{ + let schema = Schema::parse_str(r#" + { + "name": "test", + "type": "record", + "fields": [{ + "name": "test_field", + "type": { + "name": "myFixed", + "type": "fixed", + "size": 2 + } + }] + }"#)?; + + let long_string = "This string is too long and won't fit!!".to_string(); + let value = Value::Record(vec![("test_field".to_string(), + Value::String(long_string.clone()))]); + + assert_eq!( + value.resolve(&schema) + .expect_err("Expected resolution to fail since string is too large for the given fixed schema size") + .into_details() + .to_string(), + Details::CompareFixedSizes { size: 2, n: long_string.len() }.to_string() + ); + + Ok(()) + } + + #[test] + fn avro_rs_test_smaller_string_to_bigger_fixed_resolution() -> TestResult{ + let schema = Schema::parse_str(r#" + { + "name": "test", + "type": "record", + "fields": [{ + "name": "test_field", + "type": { + "name": "myFixed", + "type": "fixed", + "size": 6 + } + }] + }"#)?; + + let mut value = Value::Record(vec![("test_field".to_string(), + Value::String("abc".to_string()))]); + + value = value.resolve(&schema)?; + + assert_eq!(value, + Value::Record(vec![("test_field".to_string() , Value::Fixed(6, vec![97, 98, 99, 0 ,0 , 0]))]) + ); + + Ok(()) + } + + #[test] + fn avro_rs_test_smaller_string_to_bigger_fixed_resolution_idempotence() -> TestResult{ + let schema = Schema::parse_str(r#" + { + "name": "test", + "type": "record", + "fields": [{ + "name": "test_field", + "type": { + "name": "myFixed", + "type": "fixed", + "size": 6 + } + }] + }"#)?; + + let mut value = Value::Record(vec![("test_field".to_string(), + Value::String("abc".to_string()))]); + + value = value.resolve(&schema)?; + value = value.resolve(&schema)?; + + assert_eq!(value, + Value::Record(vec![("test_field".to_string() , Value::Fixed(6, vec![97, 98, 99, 0 ,0 , 0]))]) + ); + + Ok(()) + } } diff --git a/avro/tests/io.rs b/avro/tests/io.rs index 2998a2cb..bd7e3cb9 100644 --- a/avro/tests/io.rs +++ b/avro/tests/io.rs @@ -127,7 +127,7 @@ fn default_value_examples() -> &'static Vec<(&'static str, &'static str, Value)> ( r#"{"type": "fixed", "name": "F", "size": 2}"#, r#""a""#, - Value::Fixed(1, vec![97]), + Value::Fixed(2, vec![97, 0]), ), // ASCII 'a' => one byte ( r#"{"type": "fixed", "name": "F", "size": 2}"#, From c5b7d2d2e7216b0091d9dce033563ebeb2951e86 Mon Sep 17 00:00:00 2001 From: Martin Grigorov Date: Tue, 5 May 2026 08:54:48 +0300 Subject: [PATCH 2/5] No need to resolve twice --- avro/src/types.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/avro/src/types.rs b/avro/src/types.rs index 857e68a0..75922d43 100644 --- a/avro/src/types.rs +++ b/avro/src/types.rs @@ -3731,7 +3731,6 @@ Field with name '"b"' is not a member of the map items"#, Value::String("abc".to_string()))]); value = value.resolve(&schema)?; - value = value.resolve(&schema)?; assert_eq!(value, Value::Record(vec![("test_field".to_string() , Value::Fixed(6, vec![97, 98, 99, 0 ,0 , 0]))]) From 0714633ff3109709898e6d6cbd9561aa5462c4a7 Mon Sep 17 00:00:00 2001 From: Martin Grigorov Date: Tue, 5 May 2026 08:55:20 +0300 Subject: [PATCH 3/5] Improve test method names Co-authored-by: Martin Grigorov --- avro/src/types.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/avro/src/types.rs b/avro/src/types.rs index 75922d43..445d8e5a 100644 --- a/avro/src/types.rs +++ b/avro/src/types.rs @@ -3653,7 +3653,7 @@ Field with name '"b"' is not a member of the map items"#, } #[test] - fn avro_rs_test_biggger_string_to_smaller_fixed_resolution() -> TestResult{ + fn avro_rs_542_test_bigger_string_to_smaller_fixed_resolution() -> TestResult { let schema = Schema::parse_str(r#" { "name": "test", @@ -3684,7 +3684,7 @@ Field with name '"b"' is not a member of the map items"#, } #[test] - fn avro_rs_test_smaller_string_to_bigger_fixed_resolution() -> TestResult{ + fn avro_rs_542_test_smaller_string_to_bigger_fixed_resolution() -> TestResult { let schema = Schema::parse_str(r#" { "name": "test", @@ -3712,7 +3712,7 @@ Field with name '"b"' is not a member of the map items"#, } #[test] - fn avro_rs_test_smaller_string_to_bigger_fixed_resolution_idempotence() -> TestResult{ + fn avro_rs_542_test_smaller_string_to_bigger_fixed_resolution_idempotence() -> TestResult { let schema = Schema::parse_str(r#" { "name": "test", From 57a1f044514f8a75bf0ff7711e66a6761ba4ea98 Mon Sep 17 00:00:00 2001 From: Martin Tzvetanov Grigorov Date: Tue, 5 May 2026 08:56:00 +0300 Subject: [PATCH 4/5] fmt --- avro/src/types.rs | 58 +++++++++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/avro/src/types.rs b/avro/src/types.rs index 445d8e5a..3c861367 100644 --- a/avro/src/types.rs +++ b/avro/src/types.rs @@ -1118,7 +1118,7 @@ impl Value { bytes.resize(size, 0); Ok(Value::Fixed(size, bytes)) } - }, + } Value::Bytes(s) => { if s.len() == size { Ok(Value::Fixed(size, s)) @@ -3654,7 +3654,8 @@ Field with name '"b"' is not a member of the map items"#, #[test] fn avro_rs_542_test_bigger_string_to_smaller_fixed_resolution() -> TestResult { - let schema = Schema::parse_str(r#" + let schema = Schema::parse_str( + r#" { "name": "test", "type": "record", @@ -3666,11 +3667,14 @@ Field with name '"b"' is not a member of the map items"#, "size": 2 } }] - }"#)?; + }"#, + )?; let long_string = "This string is too long and won't fit!!".to_string(); - let value = Value::Record(vec![("test_field".to_string(), - Value::String(long_string.clone()))]); + let value = Value::Record(vec![( + "test_field".to_string(), + Value::String(long_string.clone()), + )]); assert_eq!( value.resolve(&schema) @@ -3685,7 +3689,8 @@ Field with name '"b"' is not a member of the map items"#, #[test] fn avro_rs_542_test_smaller_string_to_bigger_fixed_resolution() -> TestResult { - let schema = Schema::parse_str(r#" + let schema = Schema::parse_str( + r#" { "name": "test", "type": "record", @@ -3697,23 +3702,31 @@ Field with name '"b"' is not a member of the map items"#, "size": 6 } }] - }"#)?; + }"#, + )?; - let mut value = Value::Record(vec![("test_field".to_string(), - Value::String("abc".to_string()))]); + let mut value = Value::Record(vec![( + "test_field".to_string(), + Value::String("abc".to_string()), + )]); value = value.resolve(&schema)?; - assert_eq!(value, - Value::Record(vec![("test_field".to_string() , Value::Fixed(6, vec![97, 98, 99, 0 ,0 , 0]))]) - ); + assert_eq!( + value, + Value::Record(vec![( + "test_field".to_string(), + Value::Fixed(6, vec![97, 98, 99, 0, 0, 0]) + )]) + ); Ok(()) } #[test] fn avro_rs_542_test_smaller_string_to_bigger_fixed_resolution_idempotence() -> TestResult { - let schema = Schema::parse_str(r#" + let schema = Schema::parse_str( + r#" { "name": "test", "type": "record", @@ -3725,16 +3738,23 @@ Field with name '"b"' is not a member of the map items"#, "size": 6 } }] - }"#)?; + }"#, + )?; - let mut value = Value::Record(vec![("test_field".to_string(), - Value::String("abc".to_string()))]); + let mut value = Value::Record(vec![( + "test_field".to_string(), + Value::String("abc".to_string()), + )]); value = value.resolve(&schema)?; - assert_eq!(value, - Value::Record(vec![("test_field".to_string() , Value::Fixed(6, vec![97, 98, 99, 0 ,0 , 0]))]) - ); + assert_eq!( + value, + Value::Record(vec![( + "test_field".to_string(), + Value::Fixed(6, vec![97, 98, 99, 0, 0, 0]) + )]) + ); Ok(()) } From 4aabe2a544527c7469de5aa8a9bf8cb41183e490 Mon Sep 17 00:00:00 2001 From: Martin Grigorov Date: Tue, 5 May 2026 09:12:17 +0300 Subject: [PATCH 5/5] Restore idempotence in test --- avro/src/types.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/avro/src/types.rs b/avro/src/types.rs index 3c861367..117138b7 100644 --- a/avro/src/types.rs +++ b/avro/src/types.rs @@ -3746,6 +3746,7 @@ Field with name '"b"' is not a member of the map items"#, Value::String("abc".to_string()), )]); + value = value.resolve(&schema)?; value = value.resolve(&schema)?; assert_eq!(