apache_avro/schema/record/
schema.rs1use crate::schema::{Aliases, Documentation, Name, RecordField};
19use serde_json::Value;
20use std::collections::BTreeMap;
21use std::fmt::{Debug, Formatter};
22
23#[derive(bon::Builder, Clone)]
25pub struct RecordSchema {
26 pub name: Name,
28 #[builder(default)]
30 pub aliases: Aliases,
31 #[builder(default)]
33 pub doc: Documentation,
34 #[builder(default)]
36 pub fields: Vec<RecordField>,
37 #[builder(skip = calculate_lookup_table(&fields))]
40 pub lookup: BTreeMap<String, usize>,
41 #[builder(default)]
43 pub attributes: BTreeMap<String, Value>,
44}
45
46impl Debug for RecordSchema {
47 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
48 let mut debug = f.debug_struct("RecordSchema");
49 debug.field("name", &self.name);
50 if let Some(aliases) = &self.aliases {
51 debug.field("default", aliases);
52 }
53 if let Some(doc) = &self.doc {
54 debug.field("doc", doc);
55 }
56 debug.field("fields", &self.fields);
57 if !self.attributes.is_empty() {
58 debug.field("attributes", &self.attributes);
59 }
60 if self.aliases.is_none() || self.doc.is_none() || self.attributes.is_empty() {
61 debug.finish_non_exhaustive()
62 } else {
63 debug.finish()
64 }
65 }
66}
67
68impl<S: record_schema_builder::State> RecordSchemaBuilder<S> {
69 pub fn try_name<T>(
71 self,
72 name: T,
73 ) -> Result<RecordSchemaBuilder<record_schema_builder::SetName<S>>, <T as TryInto<Name>>::Error>
74 where
75 <S as record_schema_builder::State>::Name: record_schema_builder::IsUnset,
76 T: TryInto<Name>,
77 {
78 let name = name.try_into()?;
79 Ok(self.name(name))
80 }
81}
82
83fn calculate_lookup_table(fields: &[RecordField]) -> BTreeMap<String, usize> {
85 let map: BTreeMap<_, _> = fields
86 .iter()
87 .enumerate()
88 .map(|(i, field)| (field.name.clone(), i))
89 .collect();
90 assert_eq!(map.len(), fields.len(), "Duplicate field names found");
91 map
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97 use crate::Schema;
98 use apache_avro_test_helper::TestResult;
99 use pretty_assertions::assert_eq;
100
101 #[test]
102 fn avro_rs_403_record_schema_builder_no_fields() -> TestResult {
103 let name = Name::new("TestRecord")?;
104
105 let record_schema = RecordSchema::builder().name(name.clone()).build();
106
107 assert_eq!(record_schema.name, name);
108 assert_eq!(record_schema.aliases, None);
109 assert_eq!(record_schema.doc, None);
110 assert_eq!(record_schema.fields.len(), 0);
111 assert_eq!(record_schema.lookup.len(), 0);
112 assert_eq!(record_schema.attributes.len(), 0);
113
114 Ok(())
115 }
116
117 #[test]
118 fn avro_rs_403_record_schema_builder_no_fields_with_aliases() -> TestResult {
119 let name = Name::new("TestRecord")?;
120
121 let record_schema = RecordSchema::builder()
122 .name(name.clone())
123 .aliases(Some(vec!["alias_1".try_into()?]))
124 .build();
125
126 assert_eq!(record_schema.name, name);
127 assert_eq!(record_schema.aliases, Some(vec!["alias_1".try_into()?]));
128 assert_eq!(record_schema.doc, None);
129 assert_eq!(record_schema.fields.len(), 0);
130 assert_eq!(record_schema.lookup.len(), 0);
131 assert_eq!(record_schema.attributes.len(), 0);
132
133 Ok(())
134 }
135
136 #[test]
137 fn avro_rs_403_record_schema_builder_no_fields_with_doc() -> TestResult {
138 let name = Name::new("TestRecord")?;
139
140 let record_schema = RecordSchema::builder()
141 .name(name.clone())
142 .doc(Some("some_doc".into()))
143 .build();
144
145 assert_eq!(record_schema.name, name);
146 assert_eq!(record_schema.aliases, None);
147 assert_eq!(record_schema.doc, Some("some_doc".into()));
148 assert_eq!(record_schema.fields.len(), 0);
149 assert_eq!(record_schema.lookup.len(), 0);
150 assert_eq!(record_schema.attributes.len(), 0);
151
152 Ok(())
153 }
154
155 #[test]
156 fn avro_rs_403_record_schema_builder_no_fields_with_attributes() -> TestResult {
157 let name = Name::new("TestRecord")?;
158 let attrs: BTreeMap<String, Value> = [
159 ("bool_key".into(), Value::Bool(true)),
160 ("key_2".into(), Value::String("value_2".into())),
161 ]
162 .into_iter()
163 .collect();
164
165 let record_schema = RecordSchema::builder()
166 .name(name.clone())
167 .attributes(attrs.clone())
168 .build();
169
170 assert_eq!(record_schema.name, name);
171 assert_eq!(record_schema.aliases, None);
172 assert_eq!(record_schema.doc, None);
173 assert_eq!(record_schema.fields.len(), 0);
174 assert_eq!(record_schema.lookup.len(), 0);
175 assert_eq!(record_schema.attributes, attrs);
176
177 Ok(())
178 }
179
180 #[test]
181 fn avro_rs_403_record_schema_builder_with_fields() -> TestResult {
182 let name = Name::new("TestRecord")?;
183 let fields = vec![
184 RecordField::builder()
185 .name("field1_null")
186 .schema(Schema::Null)
187 .build(),
188 RecordField::builder()
189 .name("field2_bool")
190 .schema(Schema::Boolean)
191 .build(),
192 ];
193
194 let record_schema = RecordSchema::builder()
195 .name(name.clone())
196 .fields(fields.clone())
197 .build();
198
199 let expected_lookup: BTreeMap<String, usize> =
200 [("field1_null".into(), 0), ("field2_bool".into(), 1)]
201 .iter()
202 .cloned()
203 .collect();
204
205 assert_eq!(record_schema.name, name);
206 assert_eq!(record_schema.aliases, None);
207 assert_eq!(record_schema.doc, None);
208 assert_eq!(record_schema.fields, fields);
209 assert_eq!(record_schema.lookup, expected_lookup);
210 assert_eq!(record_schema.attributes.len(), 0);
211
212 Ok(())
213 }
214
215 #[test]
216 fn avro_rs_419_name_into() -> TestResult {
217 let schema = RecordSchema::builder().try_name("str_slice")?.build();
218 assert_eq!(schema.name, "str_slice".try_into()?);
219
220 let schema = RecordSchema::builder()
221 .try_name("String".to_string())?
222 .build();
223 assert_eq!(schema.name, "String".try_into()?);
224
225 Ok(())
226 }
227}