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 fields
86 .iter()
87 .enumerate()
88 .map(|(i, field)| (field.name.clone(), i))
89 .collect()
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use crate::Schema;
96 use apache_avro_test_helper::TestResult;
97 use pretty_assertions::assert_eq;
98
99 #[test]
100 fn avro_rs_403_record_schema_builder_no_fields() -> TestResult {
101 let name = Name::new("TestRecord")?;
102
103 let record_schema = RecordSchema::builder().name(name.clone()).build();
104
105 assert_eq!(record_schema.name, name);
106 assert_eq!(record_schema.aliases, None);
107 assert_eq!(record_schema.doc, None);
108 assert_eq!(record_schema.fields.len(), 0);
109 assert_eq!(record_schema.lookup.len(), 0);
110 assert_eq!(record_schema.attributes.len(), 0);
111
112 Ok(())
113 }
114
115 #[test]
116 fn avro_rs_403_record_schema_builder_no_fields_with_aliases() -> TestResult {
117 let name = Name::new("TestRecord")?;
118
119 let record_schema = RecordSchema::builder()
120 .name(name.clone())
121 .aliases(Some(vec!["alias_1".try_into()?]))
122 .build();
123
124 assert_eq!(record_schema.name, name);
125 assert_eq!(record_schema.aliases, Some(vec!["alias_1".try_into()?]));
126 assert_eq!(record_schema.doc, None);
127 assert_eq!(record_schema.fields.len(), 0);
128 assert_eq!(record_schema.lookup.len(), 0);
129 assert_eq!(record_schema.attributes.len(), 0);
130
131 Ok(())
132 }
133
134 #[test]
135 fn avro_rs_403_record_schema_builder_no_fields_with_doc() -> TestResult {
136 let name = Name::new("TestRecord")?;
137
138 let record_schema = RecordSchema::builder()
139 .name(name.clone())
140 .doc(Some("some_doc".into()))
141 .build();
142
143 assert_eq!(record_schema.name, name);
144 assert_eq!(record_schema.aliases, None);
145 assert_eq!(record_schema.doc, Some("some_doc".into()));
146 assert_eq!(record_schema.fields.len(), 0);
147 assert_eq!(record_schema.lookup.len(), 0);
148 assert_eq!(record_schema.attributes.len(), 0);
149
150 Ok(())
151 }
152
153 #[test]
154 fn avro_rs_403_record_schema_builder_no_fields_with_attributes() -> TestResult {
155 let name = Name::new("TestRecord")?;
156 let attrs: BTreeMap<String, Value> = [
157 ("bool_key".into(), Value::Bool(true)),
158 ("key_2".into(), Value::String("value_2".into())),
159 ]
160 .into_iter()
161 .collect();
162
163 let record_schema = RecordSchema::builder()
164 .name(name.clone())
165 .attributes(attrs.clone())
166 .build();
167
168 assert_eq!(record_schema.name, name);
169 assert_eq!(record_schema.aliases, None);
170 assert_eq!(record_schema.doc, None);
171 assert_eq!(record_schema.fields.len(), 0);
172 assert_eq!(record_schema.lookup.len(), 0);
173 assert_eq!(record_schema.attributes, attrs);
174
175 Ok(())
176 }
177
178 #[test]
179 fn avro_rs_403_record_schema_builder_with_fields() -> TestResult {
180 let name = Name::new("TestRecord")?;
181 let fields = vec![
182 RecordField::builder()
183 .name("field1_null")
184 .schema(Schema::Null)
185 .build(),
186 RecordField::builder()
187 .name("field2_bool")
188 .schema(Schema::Boolean)
189 .build(),
190 ];
191
192 let record_schema = RecordSchema::builder()
193 .name(name.clone())
194 .fields(fields.clone())
195 .build();
196
197 let expected_lookup: BTreeMap<String, usize> =
198 [("field1_null".into(), 0), ("field2_bool".into(), 1)]
199 .iter()
200 .cloned()
201 .collect();
202
203 assert_eq!(record_schema.name, name);
204 assert_eq!(record_schema.aliases, None);
205 assert_eq!(record_schema.doc, None);
206 assert_eq!(record_schema.fields, fields);
207 assert_eq!(record_schema.lookup, expected_lookup);
208 assert_eq!(record_schema.attributes.len(), 0);
209
210 Ok(())
211 }
212
213 #[test]
214 fn avro_rs_419_name_into() -> TestResult {
215 let schema = RecordSchema::builder().try_name("str_slice")?.build();
216 assert_eq!(schema.name, "str_slice".try_into()?);
217
218 let schema = RecordSchema::builder()
219 .try_name("String".to_string())?
220 .build();
221 assert_eq!(schema.name, "String".try_into()?);
222
223 Ok(())
224 }
225}