From 3deb73c840fe2b966cda0094f8114f47cf6b131e Mon Sep 17 00:00:00 2001 From: Kousuke Saruta Date: Thu, 10 Aug 2023 16:28:57 +0900 Subject: [PATCH] AVRO-3827: [Rust] Disallow duplicate field names (#2433) * AVRO-3827: [Rust] Disallow duplicate field names * Reuse BTreeMap. * Use the returned Option. --- lang/rust/avro/src/error.rs | 3 ++ lang/rust/avro/src/schema.rs | 71 +++++++++++++++++++++++++++++++++++- 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/lang/rust/avro/src/error.rs b/lang/rust/avro/src/error.rs index 5b8b39ce3b5..8eb0525f1a1 100644 --- a/lang/rust/avro/src/error.rs +++ b/lang/rust/avro/src/error.rs @@ -310,6 +310,9 @@ pub enum Error { #[error("Invalid field name {0}")] FieldName(String), + #[error("Duplicate field name {0}")] + FieldNameDuplicate(String), + #[error("Invalid schema name {0}. It must match the regex '{1}'")] InvalidSchemaName(String, &'static str), diff --git a/lang/rust/avro/src/schema.rs b/lang/rust/avro/src/schema.rs index 6185bb67201..53f567e727f 100644 --- a/lang/rust/avro/src/schema.rs +++ b/lang/rust/avro/src/schema.rs @@ -1441,7 +1441,9 @@ impl Parser { })?; for field in &fields { - lookup.insert(field.name.clone(), field.position); + if let Some(_old) = lookup.insert(field.name.clone(), field.position) { + return Err(Error::FieldNameDuplicate(field.name.clone())); + } if let Some(ref field_aliases) = field.aliases { for alias in field_aliases { @@ -5064,4 +5066,71 @@ mod tests { other => Err(format!("Expected Error::FieldName, got {other:?}").into()), } } + + #[test] + fn test_avro_3827_disallow_duplicate_field_names() -> TestResult { + let schema_str = r#" + { + "name": "my_schema", + "type": "record", + "fields": [ + { + "name": "f1", + "type": { + "name": "a", + "type": "record", + "fields": [] + } + }, { + "name": "f1", + "type": { + "name": "b", + "type": "record", + "fields": [] + } + } + ] + } + "#; + + match Schema::parse_str(schema_str) { + Err(Error::FieldNameDuplicate(_)) => (), + other => { + return Err(format!("Expected Error::FieldNameDuplicate, got {other:?}").into()) + } + }; + + let schema_str = r#" + { + "name": "my_schema", + "type": "record", + "fields": [ + { + "name": "f1", + "type": { + "name": "a", + "type": "record", + "fields": [ + { + "name": "f1", + "type": { + "name": "b", + "type": "record", + "fields": [] + } + } + ] + } + } + ] + } + "#; + + let expected = r#"{"name":"my_schema","type":"record","fields":[{"name":"f1","type":{"name":"a","type":"record","fields":[{"name":"f1","type":{"name":"b","type":"record","fields":[]}}]}}]}"#; + let schema = Schema::parse_str(schema_str)?; + let canonical_form = schema.canonical_form(); + assert_eq!(canonical_form, expected); + + Ok(()) + } }