use crate::schema::{Aliases, Documentation, Name, RecordField};
use serde_json::Value;
use std::collections::BTreeMap;
use std::fmt::{Debug, Formatter};
#[derive(bon::Builder, Clone)]
pub struct RecordSchema {
pub name: Name,
#[builder(default)]
pub aliases: Aliases,
#[builder(default)]
pub doc: Documentation,
#[builder(default)]
pub fields: Vec<RecordField>,
#[builder(skip = calculate_lookup_table(&fields))]
pub lookup: BTreeMap<String, usize>,
#[builder(default)]
pub attributes: BTreeMap<String, Value>,
}
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 let Some(doc) = &self.doc {
debug.field("doc", doc);
}
debug.field("fields", &self.fields);
if !self.attributes.is_empty() {
debug.field("attributes", &self.attributes);
}
if self.aliases.is_none() || self.doc.is_none() || self.attributes.is_empty() {
debug.finish_non_exhaustive()
} else {
debug.finish()
}
}
}
impl<S: record_schema_builder::State> RecordSchemaBuilder<S> {
pub fn try_name<T>(
self,
name: T,
) -> Result<RecordSchemaBuilder<record_schema_builder::SetName<S>>, <T as TryInto<Name>>::Error>
where
<S as record_schema_builder::State>::Name: record_schema_builder::IsUnset,
T: TryInto<Name>,
{
let name = name.try_into()?;
Ok(self.name(name))
}
}
fn calculate_lookup_table(fields: &[RecordField]) -> BTreeMap<String, usize> {
let map: BTreeMap<_, _> = fields
.iter()
.enumerate()
.map(|(i, field)| (field.name.clone(), i))
.collect();
assert_eq!(map.len(), fields.len(), "Duplicate field names found");
map
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Schema;
use apache_avro_test_helper::TestResult;
use pretty_assertions::assert_eq;
#[test]
fn avro_rs_403_record_schema_builder_no_fields() -> TestResult {
let name = Name::new("TestRecord")?;
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.doc, None);
assert_eq!(record_schema.fields.len(), 0);
assert_eq!(record_schema.lookup.len(), 0);
assert_eq!(record_schema.attributes.len(), 0);
Ok(())
}
#[test]
fn avro_rs_403_record_schema_builder_no_fields_with_aliases() -> TestResult {
let name = Name::new("TestRecord")?;
let record_schema = RecordSchema::builder()
.name(name.clone())
.aliases(Some(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.doc, None);
assert_eq!(record_schema.fields.len(), 0);
assert_eq!(record_schema.lookup.len(), 0);
assert_eq!(record_schema.attributes.len(), 0);
Ok(())
}
#[test]
fn avro_rs_403_record_schema_builder_no_fields_with_doc() -> TestResult {
let name = Name::new("TestRecord")?;
let record_schema = RecordSchema::builder()
.name(name.clone())
.doc(Some("some_doc".into()))
.build();
assert_eq!(record_schema.name, name);
assert_eq!(record_schema.aliases, None);
assert_eq!(record_schema.doc, Some("some_doc".into()));
assert_eq!(record_schema.fields.len(), 0);
assert_eq!(record_schema.lookup.len(), 0);
assert_eq!(record_schema.attributes.len(), 0);
Ok(())
}
#[test]
fn avro_rs_403_record_schema_builder_no_fields_with_attributes() -> TestResult {
let name = Name::new("TestRecord")?;
let attrs: BTreeMap<String, Value> = [
("bool_key".into(), Value::Bool(true)),
("key_2".into(), Value::String("value_2".into())),
]
.into_iter()
.collect();
let record_schema = RecordSchema::builder()
.name(name.clone())
.attributes(attrs.clone())
.build();
assert_eq!(record_schema.name, name);
assert_eq!(record_schema.aliases, None);
assert_eq!(record_schema.doc, None);
assert_eq!(record_schema.fields.len(), 0);
assert_eq!(record_schema.lookup.len(), 0);
assert_eq!(record_schema.attributes, attrs);
Ok(())
}
#[test]
fn avro_rs_403_record_schema_builder_with_fields() -> TestResult {
let name = Name::new("TestRecord")?;
let fields = vec![
RecordField::builder()
.name("field1_null")
.schema(Schema::Null)
.build(),
RecordField::builder()
.name("field2_bool")
.schema(Schema::Boolean)
.build(),
];
let record_schema = RecordSchema::builder()
.name(name.clone())
.fields(fields.clone())
.build();
let expected_lookup: BTreeMap<String, usize> =
[("field1_null".into(), 0), ("field2_bool".into(), 1)]
.iter()
.cloned()
.collect();
assert_eq!(record_schema.name, name);
assert_eq!(record_schema.aliases, None);
assert_eq!(record_schema.doc, None);
assert_eq!(record_schema.fields, fields);
assert_eq!(record_schema.lookup, expected_lookup);
assert_eq!(record_schema.attributes.len(), 0);
Ok(())
}
#[test]
fn avro_rs_419_name_into() -> TestResult {
let schema = RecordSchema::builder().try_name("str_slice")?.build();
assert_eq!(schema.name, "str_slice".try_into()?);
let schema = RecordSchema::builder()
.try_name("String".to_string())?
.build();
assert_eq!(schema.name, "String".try_into()?);
Ok(())
}
}