This is an automated email from the ASF dual-hosted git repository. kriskras99 pushed a commit to branch feat/less_options in repository https://gitbox.apache.org/repos/asf/avro-rs.git
commit 3e6c299522a3ed17179454ccc9785200daa81272 Author: default <[email protected]> AuthorDate: Sat Feb 28 09:39:13 2026 +0000 wip --- avro/src/error.rs | 7 ++ avro/src/schema/builders.rs | 56 ++++++----- avro/src/schema/mod.rs | 198 +++++++++++++++++++-------------------- avro/src/schema/name.rs | 4 +- avro/src/schema/parser.rs | 76 +++++++-------- avro/src/schema/record/schema.rs | 18 ++-- 6 files changed, 177 insertions(+), 182 deletions(-) diff --git a/avro/src/error.rs b/avro/src/error.rs index 12bee1e..0f97054 100644 --- a/avro/src/error.rs +++ b/avro/src/error.rs @@ -19,6 +19,7 @@ use crate::{ schema::{Name, Schema, SchemaKind, UnionSchema}, types::{Value, ValueKind}, }; +use std::convert::Infallible; use std::{error::Error as _, fmt}; /// Errors encountered by Avro. @@ -56,6 +57,12 @@ impl From<Details> for Error { } } +impl From<Infallible> for Error { + fn from(_value: Infallible) -> Self { + unreachable!() + } +} + impl serde::ser::Error for Error { fn custom<T: fmt::Display>(msg: T) -> Self { Self::new(<Details as serde::ser::Error>::custom(msg)) diff --git a/avro/src/schema/builders.rs b/avro/src/schema/builders.rs index dd16b3e..5400a91 100644 --- a/avro/src/schema/builders.rs +++ b/avro/src/schema/builders.rs @@ -20,7 +20,7 @@ use crate::schema::{ UnionSchema, }; use crate::types::Value; -use crate::{AvroResult, Schema}; +use crate::{AvroResult, Error, Schema}; use bon::bon; use serde_json::Value as JsonValue; use std::collections::{BTreeMap, HashMap}; @@ -33,9 +33,8 @@ impl Schema { pub fn map( #[builder(start_fn)] types: Schema, default: Option<HashMap<String, Value>>, - attributes: Option<BTreeMap<String, JsonValue>>, + #[builder(default)] attributes: BTreeMap<String, JsonValue>, ) -> Self { - let attributes = attributes.unwrap_or_default(); Schema::Map(MapSchema { types: Box::new(types), default, @@ -49,9 +48,8 @@ impl Schema { pub fn array( #[builder(start_fn)] items: Schema, default: Option<Vec<Value>>, - attributes: Option<BTreeMap<String, JsonValue>>, + #[builder(default)] attributes: BTreeMap<String, JsonValue>, ) -> Self { - let attributes = attributes.unwrap_or_default(); Schema::Array(ArraySchema { items: Box::new(items), default, @@ -65,12 +63,11 @@ impl Schema { pub fn r#enum( #[builder(start_fn)] name: Name, #[builder(start_fn)] symbols: Vec<impl Into<String>>, - aliases: Option<Vec<Alias>>, + #[builder(default)] aliases: Vec<Alias>, doc: Option<String>, default: Option<String>, - attributes: Option<BTreeMap<String, JsonValue>>, + #[builder(default)] attributes: BTreeMap<String, JsonValue>, ) -> Self { - let attributes = attributes.unwrap_or_default(); let symbols = symbols.into_iter().map(Into::into).collect(); Schema::Enum(EnumSchema { name, @@ -88,11 +85,10 @@ impl Schema { pub fn fixed( #[builder(start_fn)] name: Name, #[builder(start_fn)] size: usize, - aliases: Option<Vec<Alias>>, + #[builder(default)] aliases: Vec<Alias>, doc: Option<String>, - attributes: Option<BTreeMap<String, JsonValue>>, + #[builder(default)] attributes: BTreeMap<String, JsonValue>, ) -> Self { - let attributes = attributes.unwrap_or_default(); Schema::Fixed(FixedSchema { name, size, @@ -105,23 +101,25 @@ impl Schema { /// Returns a `Schema::Record` with the given name, size and optional /// aliases, doc and custom attributes. #[builder(finish_fn = build)] - pub fn record( - #[builder(start_fn)] name: Name, - fields: Option<Vec<RecordField>>, - aliases: Option<Vec<Alias>>, + pub fn record<N>( + #[builder(start_fn)] name: N, + #[builder(default)] fields: Vec<RecordField>, + #[builder(default)] aliases: Vec<Alias>, doc: Option<String>, - attributes: Option<BTreeMap<String, JsonValue>>, - ) -> Self { - let fields = fields.unwrap_or_default(); - let attributes = attributes.unwrap_or_default(); + #[builder(default)] attributes: BTreeMap<String, JsonValue>, + ) -> Result<Self, Error> + where + N: TryInto<Name>, + Error: From<<N as TryInto<Name>>::Error>, + { let record_schema = RecordSchema::builder() - .name(name) + .name(name.try_into()?) .fields(fields) .aliases(aliases) .doc(doc) .attributes(attributes) .build(); - Schema::Record(record_schema) + Ok(Schema::Record(record_schema)) } /// Returns a [`Schema::Union`] with the given variants. @@ -149,7 +147,7 @@ mod tests { if let Schema::Enum(enum_schema) = schema { assert_eq!(enum_schema.name, name); assert_eq!(enum_schema.symbols, symbols); - assert_eq!(enum_schema.aliases, None); + assert_eq!(enum_schema.aliases, Vec::new()); assert_eq!(enum_schema.doc, None); assert_eq!(enum_schema.default, None); assert_eq!(enum_schema.attributes, Default::default()); @@ -180,7 +178,7 @@ mod tests { if let Schema::Enum(enum_schema) = schema { assert_eq!(enum_schema.name, name); assert_eq!(enum_schema.symbols, symbols); - assert_eq!(enum_schema.aliases, Some(aliases)); + assert_eq!(enum_schema.aliases, aliases); assert_eq!(enum_schema.doc, Some(doc.into())); assert_eq!(enum_schema.default, Some(default.into())); assert_eq!(enum_schema.attributes, attributes); @@ -201,7 +199,7 @@ mod tests { if let Schema::Fixed(fixed_schema) = schema { assert_eq!(fixed_schema.name, name); assert_eq!(fixed_schema.size, size); - assert_eq!(fixed_schema.aliases, None); + assert_eq!(fixed_schema.aliases, Vec::new()); assert_eq!(fixed_schema.doc, None); assert_eq!(fixed_schema.attributes, Default::default()); } else { @@ -229,7 +227,7 @@ mod tests { if let Schema::Fixed(fixed_schema) = schema { assert_eq!(fixed_schema.name, name); assert_eq!(fixed_schema.size, size); - assert_eq!(fixed_schema.aliases, Some(aliases)); + assert_eq!(fixed_schema.aliases, aliases); assert_eq!(fixed_schema.doc, Some(doc.into())); assert_eq!(fixed_schema.attributes, attributes); } else { @@ -243,12 +241,12 @@ mod tests { fn avro_rs_472_record_builder_only_mandatory() -> TestResult { let name = Name::new("record_builder")?; - let schema = Schema::record(name.clone()).build(); + let schema = Schema::record(name.clone()).build()?; if let Schema::Record(record_schema) = schema { assert_eq!(record_schema.name, name); assert_eq!(record_schema.fields, vec![]); - assert_eq!(record_schema.aliases, None); + assert_eq!(record_schema.aliases, Vec::new()); assert_eq!(record_schema.doc, None); assert_eq!(record_schema.lookup, Default::default()); assert_eq!(record_schema.attributes, Default::default()); @@ -282,12 +280,12 @@ mod tests { .aliases(aliases.clone()) .doc(doc.into()) .attributes(attributes.clone()) - .build(); + .build()?; if let Schema::Record(fixed_schema) = schema { assert_eq!(fixed_schema.name, name); assert_eq!(fixed_schema.fields, fields); - assert_eq!(fixed_schema.aliases, Some(aliases)); + assert_eq!(fixed_schema.aliases, aliases); assert_eq!(fixed_schema.doc, Some(doc.into())); assert_eq!( fixed_schema.lookup, diff --git a/avro/src/schema/mod.rs b/avro/src/schema/mod.rs index a0f3077..abe34be 100644 --- a/avro/src/schema/mod.rs +++ b/avro/src/schema/mod.rs @@ -310,8 +310,8 @@ impl Debug for EnumSchema { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let mut debug = f.debug_struct("EnumSchema"); debug.field("name", &self.name); - if let Some(aliases) = &self.aliases { - debug.field("aliases", aliases); + if !self.aliases.is_empty() { + debug.field("aliases", &self.aliases); } if let Some(doc) = &self.doc { debug.field("doc", doc); @@ -323,7 +323,7 @@ impl Debug for EnumSchema { if !self.attributes.is_empty() { debug.field("attributes", &self.attributes); } - if self.aliases.is_none() + if self.aliases.is_empty() || self.doc.is_none() || self.default.is_none() || self.attributes.is_empty() @@ -357,8 +357,8 @@ impl Debug for FixedSchema { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let mut debug = f.debug_struct("FixedSchema"); debug.field("name", &self.name); - if let Some(aliases) = &self.aliases { - debug.field("aliases", aliases); + if !self.aliases.is_empty() { + debug.field("aliases", &self.aliases); } if let Some(doc) = &self.doc { debug.field("doc", doc); @@ -367,7 +367,7 @@ impl Debug for FixedSchema { if !self.attributes.is_empty() { debug.field("attributes", &self.attributes); } - if self.aliases.is_none() || self.doc.is_none() || !self.attributes.is_empty() { + if self.aliases.is_empty() || self.doc.is_none() || !self.attributes.is_empty() { debug.finish_non_exhaustive() } else { debug.finish() @@ -390,8 +390,8 @@ impl FixedSchema { } map.serialize_entry("size", &self.size)?; - if let Some(aliases) = self.aliases.as_ref() { - map.serialize_entry("aliases", aliases)?; + if !self.aliases.is_empty() { + map.serialize_entry("aliases", &self.aliases)?; } for attr in &self.attributes { @@ -410,7 +410,7 @@ impl FixedSchema { name: String::new(), namespace: None, }, - aliases: None, + aliases: Vec::new(), doc: None, size: self.size, attributes: Default::default(), @@ -700,7 +700,10 @@ impl Schema { } /// Returns the aliases of the schema if it has ones. - pub fn aliases(&self) -> Option<&Vec<Alias>> { + /// + /// # Returns + /// `None` if the schema type can't have aliases or if the aliases are empty. + pub fn aliases(&self) -> Option<&[Alias]> { match self { Schema::Record(RecordSchema { aliases, .. }) | Schema::Enum(EnumSchema { aliases, .. }) @@ -709,8 +712,15 @@ impl Schema { inner: InnerDecimalSchema::Fixed(FixedSchema { aliases, .. }), .. }) - | Schema::Uuid(UuidSchema::Fixed(FixedSchema { aliases, .. })) => aliases.as_ref(), - Schema::Duration(FixedSchema { aliases, .. }) => aliases.as_ref(), + | Schema::Uuid(UuidSchema::Fixed(FixedSchema { aliases, .. })) + | Schema::Duration(FixedSchema { aliases, .. }) => { + // TODO: Should we return an empty slice? + if aliases.is_empty() { + None + } else { + Some(aliases.as_slice()) + } + } _ => None, } } @@ -878,7 +888,7 @@ impl Serialize for Schema { if let Some(docstr) = doc { map.serialize_entry("doc", docstr)?; } - if let Some(aliases) = aliases { + if !aliases.is_empty() { map.serialize_entry("aliases", aliases)?; } map.serialize_entry("fields", fields)?; @@ -903,7 +913,7 @@ impl Serialize for Schema { map.serialize_entry("name", &name.name)?; map.serialize_entry("symbols", symbols)?; - if let Some(aliases) = aliases { + if !aliases.is_empty() { map.serialize_entry("aliases", aliases)?; } if let Some(default) = default { @@ -1209,10 +1219,7 @@ mod tests { #[test] fn test_union_schema() -> TestResult { let schema = Schema::parse_str(r#"["null", "int"]"#)?; - assert_eq!( - Schema::Union(UnionSchema::new(vec![Schema::Null, Schema::Int])?), - schema - ); + assert_eq!(Schema::union(vec![Schema::Null, Schema::Int])?, schema); Ok(()) } @@ -1287,24 +1294,21 @@ mod tests { .unwrap() .clone(); - let schema_c_expected = Schema::Record( - RecordSchema::builder() - .try_name("C")? - .fields(vec![ - RecordField::builder() - .name("field_one".to_string()) - .schema(Schema::Union(UnionSchema::new(vec![ - Schema::Ref { - name: Name::new("A")?, - }, - Schema::Ref { - name: Name::new("B")?, - }, - ])?)) - .build(), - ]) - .build(), - ); + let schema_c_expected = Schema::record("C") + .fields(vec![ + RecordField::builder() + .name("field_one".to_string()) + .schema(Schema::Union(UnionSchema::new(vec![ + Schema::Ref { + name: Name::new("A")?, + }, + Schema::Ref { + name: Name::new("B")?, + }, + ])?)) + .build(), + ]) + .build()?; assert_eq!(schema_c, schema_c_expected); Ok(()) @@ -1334,33 +1338,29 @@ mod tests { let (schema_c, schemata) = Schema::parse_str_with_list(schema_str_c, [schema_str_a, schema_str_b])?; - let schema_a_expected = Schema::Record(RecordSchema { - name: Name::new("A")?, - aliases: None, - doc: None, - fields: vec![ - RecordField::builder() - .name("field_one".to_string()) - .schema(Schema::Float) - .build(), - ], - lookup: BTreeMap::from_iter(vec![("field_one".to_string(), 0)]), - attributes: Default::default(), - }); + let schema_a_expected = Schema::Record( + RecordSchema::builder() + .name(Name::new("A")?) + .fields(vec![ + RecordField::builder() + .name("field_one".to_string()) + .schema(Schema::Float) + .build(), + ]) + .build(), + ); - let schema_b_expected = Schema::Record(RecordSchema { - name: Name::new("B")?, - aliases: None, - doc: None, - fields: vec![ - RecordField::builder() - .name("field_one".to_string()) - .schema(Schema::Float) - .build(), - ], - lookup: BTreeMap::from_iter(vec![("field_one".to_string(), 0)]), - attributes: Default::default(), - }); + let schema_b_expected = Schema::Record( + RecordSchema::builder() + .name(Name::new("B")?) + .fields(vec![ + RecordField::builder() + .name("field_one".to_string()) + .schema(Schema::Float) + .build(), + ]) + .build(), + ); let schema_c_expected = Schema::Union(UnionSchema::new(vec![ Schema::Ref { @@ -1498,25 +1498,23 @@ mod tests { .unwrap() .clone(); - let schema_option_a_expected = Schema::Record(RecordSchema { - name: Name::new("OptionA")?, - aliases: None, - doc: None, - fields: vec![ - RecordField::builder() - .name("field_one".to_string()) - .default(JsonValue::Null) - .schema(Schema::Union(UnionSchema::new(vec![ - Schema::Null, - Schema::Ref { - name: Name::new("A")?, - }, - ])?)) - .build(), - ], - lookup: BTreeMap::from_iter(vec![("field_one".to_string(), 0)]), - attributes: Default::default(), - }); + let schema_option_a_expected = Schema::Record( + RecordSchema::builder() + .name(Name::new("OptionA")?) + .fields(vec![ + RecordField::builder() + .name("field_one".to_string()) + .default(JsonValue::Null) + .schema(Schema::Union(UnionSchema::new(vec![ + Schema::Null, + Schema::Ref { + name: Name::new("A")?, + }, + ])?)) + .build(), + ]) + .build(), + ); assert_eq!(schema_option_a, schema_option_a_expected); @@ -1538,28 +1536,22 @@ mod tests { "#, )?; - let mut lookup = BTreeMap::new(); - lookup.insert("a".to_owned(), 0); - lookup.insert("b".to_owned(), 1); - - let expected = Schema::Record(RecordSchema { - name: Name::new("test")?, - aliases: None, - doc: None, - fields: vec![ - RecordField::builder() - .name("a".to_string()) - .default(JsonValue::Number(42i64.into())) - .schema(Schema::Long) - .build(), - RecordField::builder() - .name("b".to_string()) - .schema(Schema::String) - .build(), - ], - lookup, - attributes: Default::default(), - }); + let expected = Schema::Record( + RecordSchema::builder() + .name(Name::new("test")?) + .fields(vec![ + RecordField::builder() + .name("a".to_string()) + .default(JsonValue::Number(42i64.into())) + .schema(Schema::Long) + .build(), + RecordField::builder() + .name("b".to_string()) + .schema(Schema::String) + .build(), + ]) + .build(), + ); assert_eq!(parsed, expected); diff --git a/avro/src/schema/name.rs b/avro/src/schema/name.rs index b551584..f612a8e 100644 --- a/avro/src/schema/name.rs +++ b/avro/src/schema/name.rs @@ -45,13 +45,15 @@ pub struct Name { } /// Represents the aliases for Named Schema -pub type Aliases = Option<Vec<Alias>>; +pub type Aliases = Vec<Alias>; +pub type AliasesRef<'a> = &'a [Alias]; /// Represents Schema lookup within a schema env pub type Names = HashMap<Name, Schema>; /// Represents Schema lookup within a schema pub type NamesRef<'a> = HashMap<Name, &'a Schema>; /// Represents the namespace for Named Schema pub type Namespace = Option<String>; +pub type NamespaceRef<'a> = Option<&'a str>; impl Name { /// Create a new `Name`. diff --git a/avro/src/schema/parser.rs b/avro/src/schema/parser.rs index 6011d82..35a06cd 100644 --- a/avro/src/schema/parser.rs +++ b/avro/src/schema/parser.rs @@ -478,13 +478,11 @@ impl Parser { let namespace = &name.namespace; - if let Some(aliases) = aliases { - aliases.iter().for_each(|alias| { - let alias_fullname = alias.fully_qualified_name(namespace); - self.resolving_schemas - .insert(alias_fullname, resolving_schema.clone()); - }); - } + aliases.iter().for_each(|alias| { + let alias_fullname = alias.fully_qualified_name(namespace); + self.resolving_schemas + .insert(alias_fullname, resolving_schema.clone()); + }) } fn register_parsed_schema( @@ -501,13 +499,11 @@ impl Parser { let namespace = &fully_qualified_name.namespace; - if let Some(aliases) = aliases { - aliases.iter().for_each(|alias| { - let alias_fullname = alias.fully_qualified_name(namespace); - self.resolving_schemas.remove(&alias_fullname); - self.parsed_schemas.insert(alias_fullname, schema.clone()); - }); - } + aliases.iter().for_each(|alias| { + let alias_fullname = alias.fully_qualified_name(namespace); + self.resolving_schemas.remove(&alias_fullname); + self.parsed_schemas.insert(alias_fullname, schema.clone()); + }); } /// Returns already parsed schema or a schema that is currently being resolved. @@ -544,8 +540,10 @@ impl Parser { } let fully_qualified_name = Name::parse(complex, enclosing_namespace)?; - let aliases = - self.fix_aliases_namespace(complex.aliases(), &fully_qualified_name.namespace); + let aliases = complex + .aliases() + .map(|a| self.fix_aliases_namespace(a, &fully_qualified_name.namespace)) + .unwrap_or_default(); let mut lookup = BTreeMap::new(); @@ -618,8 +616,10 @@ impl Parser { } let fully_qualified_name = Name::parse(complex, enclosing_namespace)?; - let aliases = - self.fix_aliases_namespace(complex.aliases(), &fully_qualified_name.namespace); + let aliases = complex + .aliases() + .map(|a| self.fix_aliases_namespace(a, &fully_qualified_name.namespace)) + .unwrap_or_default(); let symbols: Vec<String> = symbols_opt .and_then(|v| v.as_array()) @@ -809,8 +809,10 @@ impl Parser { }?; let fully_qualified_name = Name::parse(complex, enclosing_namespace)?; - let aliases = - self.fix_aliases_namespace(complex.aliases(), &fully_qualified_name.namespace); + let aliases = complex + .aliases() + .map(|a| self.fix_aliases_namespace(a, &fully_qualified_name.namespace)) + .unwrap_or_default(); let schema = Schema::Fixed(FixedSchema { name: fully_qualified_name.clone(), @@ -830,27 +832,21 @@ impl Parser { // has aliases of "c" and "x.y", then the fully qualified names of its aliases are "a.c" // and "x.y". // https://avro.apache.org/docs/++version++/specification/#aliases - fn fix_aliases_namespace( - &self, - aliases: Option<Vec<String>>, - namespace: &Namespace, - ) -> Aliases { - aliases.map(|aliases| { - aliases - .iter() - .map(|alias| { - if alias.find('.').is_none() { - match namespace { - Some(ns) => format!("{ns}.{alias}"), - None => alias.clone(), - } - } else { - alias.clone() + fn fix_aliases_namespace(&self, aliases: Vec<String>, namespace: &Namespace) -> Aliases { + aliases + .iter() + .map(|alias| { + if alias.find('.').is_none() { + match namespace { + Some(ns) => format!("{ns}.{alias}"), + None => alias.clone(), } - }) - .map(|alias| Alias::new(alias.as_str()).unwrap()) - .collect() - }) + } else { + alias.clone() + } + }) + .map(|alias| Alias::new(alias.as_str()).unwrap()) + .collect() } fn get_schema_type_name(&self, name: Name, value: Value) -> Name { diff --git a/avro/src/schema/record/schema.rs b/avro/src/schema/record/schema.rs index 816a6bf..cbfdd77 100644 --- a/avro/src/schema/record/schema.rs +++ b/avro/src/schema/record/schema.rs @@ -47,8 +47,8 @@ impl Debug for RecordSchema { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let mut debug = f.debug_struct("RecordSchema"); debug.field("name", &self.name); - if let Some(aliases) = &self.aliases { - debug.field("default", aliases); + if !self.aliases.is_empty() { + debug.field("default", &self.aliases); } if let Some(doc) = &self.doc { debug.field("doc", doc); @@ -57,7 +57,7 @@ impl Debug for RecordSchema { if !self.attributes.is_empty() { debug.field("attributes", &self.attributes); } - if self.aliases.is_none() || self.doc.is_none() || self.attributes.is_empty() { + if self.aliases.is_empty() || self.doc.is_none() || self.attributes.is_empty() { debug.finish_non_exhaustive() } else { debug.finish() @@ -103,7 +103,7 @@ mod tests { let record_schema = RecordSchema::builder().name(name.clone()).build(); assert_eq!(record_schema.name, name); - assert_eq!(record_schema.aliases, None); + assert_eq!(record_schema.aliases, Vec::new()); assert_eq!(record_schema.doc, None); assert_eq!(record_schema.fields.len(), 0); assert_eq!(record_schema.lookup.len(), 0); @@ -118,11 +118,11 @@ mod tests { let record_schema = RecordSchema::builder() .name(name.clone()) - .aliases(Some(vec!["alias_1".try_into()?])) + .aliases(vec!["alias_1".try_into()?]) .build(); assert_eq!(record_schema.name, name); - assert_eq!(record_schema.aliases, Some(vec!["alias_1".try_into()?])); + assert_eq!(record_schema.aliases, vec!["alias_1".try_into()?]); assert_eq!(record_schema.doc, None); assert_eq!(record_schema.fields.len(), 0); assert_eq!(record_schema.lookup.len(), 0); @@ -141,7 +141,7 @@ mod tests { .build(); assert_eq!(record_schema.name, name); - assert_eq!(record_schema.aliases, None); + assert_eq!(record_schema.aliases, Vec::new()); assert_eq!(record_schema.doc, Some("some_doc".into())); assert_eq!(record_schema.fields.len(), 0); assert_eq!(record_schema.lookup.len(), 0); @@ -166,7 +166,7 @@ mod tests { .build(); assert_eq!(record_schema.name, name); - assert_eq!(record_schema.aliases, None); + assert_eq!(record_schema.aliases, Vec::new()); assert_eq!(record_schema.doc, None); assert_eq!(record_schema.fields.len(), 0); assert_eq!(record_schema.lookup.len(), 0); @@ -201,7 +201,7 @@ mod tests { .collect(); assert_eq!(record_schema.name, name); - assert_eq!(record_schema.aliases, None); + assert_eq!(record_schema.aliases, Vec::new()); assert_eq!(record_schema.doc, None); assert_eq!(record_schema.fields, fields); assert_eq!(record_schema.lookup, expected_lookup);
