From 78d08a9037c3874e615b379fb14ed32e7f68f6b8 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 ad0258b5..63d9d531 100644 --- a/avro/src/types.rs +++ b/avro/src/types.rs @@ -1029,7 +1029,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)) @@ -3494,4 +3502,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 56e19e40f13daf7fd7b59d97846fc60812b11b6d 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 63d9d531..35026e90 100644 --- a/avro/src/types.rs +++ b/avro/src/types.rs @@ -3582,7 +3582,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 4372fac195e9278cc7f53f5a58e7423f2b1483ef 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 35026e90..57dcc526 100644 --- a/avro/src/types.rs +++ b/avro/src/types.rs @@ -3504,7 +3504,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", @@ -3535,7 +3535,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", @@ -3563,7 +3563,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 b8d7826c6155b4559f564ae83c5a799a6c878c26 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 57dcc526..d3a62f83 100644 --- a/avro/src/types.rs +++ b/avro/src/types.rs @@ -1037,7 +1037,7 @@ impl Value { bytes.resize(size, 0); Ok(Value::Fixed(size, bytes)) } - }, + } Value::Bytes(s) => { if s.len() == size { Ok(Value::Fixed(size, s)) @@ -3505,7 +3505,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", @@ -3517,11 +3518,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) @@ -3536,7 +3540,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", @@ -3548,23 +3553,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", @@ -3576,16 +3589,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 55b95183c8d9f977dd84b52a6c77120d94ca8c2f 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 d3a62f83..5b509217 100644 --- a/avro/src/types.rs +++ b/avro/src/types.rs @@ -3597,6 +3597,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!(