1use crate::schema::{
19 Alias, ArraySchema, EnumSchema, FixedSchema, MapSchema, Name, RecordField, RecordSchema,
20 UnionSchema,
21};
22use crate::types::Value;
23use crate::{AvroResult, Schema};
24use bon::bon;
25use serde_json::Value as JsonValue;
26use std::collections::{BTreeMap, HashMap};
27
28#[bon]
29impl Schema {
30 #[builder(finish_fn = build)]
33 pub fn map(
34 #[builder(start_fn)] types: Schema,
35 default: Option<HashMap<String, Value>>,
36 attributes: Option<BTreeMap<String, JsonValue>>,
37 ) -> Self {
38 let attributes = attributes.unwrap_or_default();
39 Schema::Map(MapSchema {
40 types: Box::new(types),
41 default,
42 attributes,
43 })
44 }
45
46 #[builder(finish_fn = build)]
49 pub fn array(
50 #[builder(start_fn)] items: Schema,
51 default: Option<Vec<Value>>,
52 attributes: Option<BTreeMap<String, JsonValue>>,
53 ) -> Self {
54 let attributes = attributes.unwrap_or_default();
55 Schema::Array(ArraySchema {
56 items: Box::new(items),
57 default,
58 attributes,
59 })
60 }
61
62 #[builder(finish_fn = build)]
65 pub fn r#enum(
66 #[builder(start_fn)] name: Name,
67 #[builder(start_fn)] symbols: Vec<impl Into<String>>,
68 aliases: Option<Vec<Alias>>,
69 doc: Option<String>,
70 default: Option<String>,
71 attributes: Option<BTreeMap<String, JsonValue>>,
72 ) -> Self {
73 let attributes = attributes.unwrap_or_default();
74 let symbols = symbols.into_iter().map(Into::into).collect();
75 Schema::Enum(EnumSchema {
76 name,
77 symbols,
78 aliases,
79 doc,
80 default,
81 attributes,
82 })
83 }
84
85 #[builder(finish_fn = build)]
88 pub fn fixed(
89 #[builder(start_fn)] name: Name,
90 #[builder(start_fn)] size: usize,
91 aliases: Option<Vec<Alias>>,
92 doc: Option<String>,
93 attributes: Option<BTreeMap<String, JsonValue>>,
94 ) -> Self {
95 let attributes = attributes.unwrap_or_default();
96 Schema::Fixed(FixedSchema {
97 name,
98 size,
99 aliases,
100 doc,
101 attributes,
102 })
103 }
104
105 #[builder(finish_fn = build)]
108 pub fn record(
109 #[builder(start_fn)] name: Name,
110 fields: Option<Vec<RecordField>>,
111 aliases: Option<Vec<Alias>>,
112 doc: Option<String>,
113 attributes: Option<BTreeMap<String, JsonValue>>,
114 ) -> Self {
115 let fields = fields.unwrap_or_default();
116 let attributes = attributes.unwrap_or_default();
117 let record_schema = RecordSchema::builder()
118 .name(name)
119 .fields(fields)
120 .aliases(aliases)
121 .doc(doc)
122 .attributes(attributes)
123 .build();
124 Schema::Record(record_schema)
125 }
126
127 pub fn union(schemas: Vec<Schema>) -> AvroResult<Schema> {
133 UnionSchema::new(schemas).map(Schema::Union)
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140 use apache_avro_test_helper::TestResult;
141
142 #[test]
143 fn avro_rs_472_enum_builder_only_mandatory() -> TestResult {
144 let name = Name::new("enum_builder")?;
145 let symbols = vec!["A", "B", "C", "D", "E"];
146
147 let schema = Schema::r#enum(name.clone(), symbols.clone()).build();
148
149 if let Schema::Enum(enum_schema) = schema {
150 assert_eq!(enum_schema.name, name);
151 assert_eq!(enum_schema.symbols, symbols);
152 assert_eq!(enum_schema.aliases, None);
153 assert_eq!(enum_schema.doc, None);
154 assert_eq!(enum_schema.default, None);
155 assert_eq!(enum_schema.attributes, Default::default());
156 } else {
157 panic!("Expected a Schema::Enum, got: {schema}");
158 }
159
160 Ok(())
161 }
162
163 #[test]
164 fn avro_rs_472_enum_builder_with_optionals() -> TestResult {
165 let name = Name::new("enum_builder")?;
166 let symbols = vec!["A", "B", "C", "D", "E"];
167 let aliases = vec![Alias::new("alias")?];
168 let doc = "docu";
169 let default = "default value";
170 let attributes =
171 BTreeMap::from_iter([("key".to_string(), JsonValue::String("value".into()))]);
172
173 let schema = Schema::r#enum(name.clone(), symbols.clone())
174 .aliases(aliases.clone())
175 .doc(doc.into())
176 .default(default.into())
177 .attributes(attributes.clone())
178 .build();
179
180 if let Schema::Enum(enum_schema) = schema {
181 assert_eq!(enum_schema.name, name);
182 assert_eq!(enum_schema.symbols, symbols);
183 assert_eq!(enum_schema.aliases, Some(aliases));
184 assert_eq!(enum_schema.doc, Some(doc.into()));
185 assert_eq!(enum_schema.default, Some(default.into()));
186 assert_eq!(enum_schema.attributes, attributes);
187 } else {
188 panic!("Expected a Schema::Enum, got: {schema}");
189 }
190
191 Ok(())
192 }
193
194 #[test]
195 fn avro_rs_472_fixed_builder_only_mandatory() -> TestResult {
196 let name = Name::new("fixed_builder")?;
197 let size = 123;
198
199 let schema = Schema::fixed(name.clone(), size).build();
200
201 if let Schema::Fixed(fixed_schema) = schema {
202 assert_eq!(fixed_schema.name, name);
203 assert_eq!(fixed_schema.size, size);
204 assert_eq!(fixed_schema.aliases, None);
205 assert_eq!(fixed_schema.doc, None);
206 assert_eq!(fixed_schema.attributes, Default::default());
207 } else {
208 panic!("Expected a Schema::Fixed, got: {schema}");
209 }
210
211 Ok(())
212 }
213
214 #[test]
215 fn avro_rs_472_fixed_builder_with_optionals() -> TestResult {
216 let name = Name::new("fixed_builder")?;
217 let size = 234;
218 let aliases = vec![Alias::new("alias")?];
219 let doc = "docu";
220 let attributes =
221 BTreeMap::from_iter([("key".to_string(), JsonValue::String("value".into()))]);
222
223 let schema = Schema::fixed(name.clone(), size)
224 .aliases(aliases.clone())
225 .doc(doc.into())
226 .attributes(attributes.clone())
227 .build();
228
229 if let Schema::Fixed(fixed_schema) = schema {
230 assert_eq!(fixed_schema.name, name);
231 assert_eq!(fixed_schema.size, size);
232 assert_eq!(fixed_schema.aliases, Some(aliases));
233 assert_eq!(fixed_schema.doc, Some(doc.into()));
234 assert_eq!(fixed_schema.attributes, attributes);
235 } else {
236 panic!("Expected a Schema::Fixed, got: {schema}");
237 }
238
239 Ok(())
240 }
241
242 #[test]
243 fn avro_rs_472_record_builder_only_mandatory() -> TestResult {
244 let name = Name::new("record_builder")?;
245
246 let schema = Schema::record(name.clone()).build();
247
248 if let Schema::Record(record_schema) = schema {
249 assert_eq!(record_schema.name, name);
250 assert_eq!(record_schema.fields, vec![]);
251 assert_eq!(record_schema.aliases, None);
252 assert_eq!(record_schema.doc, None);
253 assert_eq!(record_schema.lookup, Default::default());
254 assert_eq!(record_schema.attributes, Default::default());
255 } else {
256 panic!("Expected a Schema::Record, got: {schema}");
257 }
258
259 Ok(())
260 }
261
262 #[test]
263 fn avro_rs_472_record_builder_with_optionals() -> TestResult {
264 let name = Name::new("record_builder")?;
265 let fields = vec![
266 RecordField::builder()
267 .name("f1")
268 .schema(Schema::Boolean)
269 .build(),
270 RecordField::builder()
271 .name("f2")
272 .schema(Schema::Int)
273 .build(),
274 ];
275 let aliases = vec![Alias::new("alias")?];
276 let doc = "docu";
277 let attributes =
278 BTreeMap::from_iter([("key".to_string(), JsonValue::String("value".into()))]);
279
280 let schema = Schema::record(name.clone())
281 .fields(fields.clone())
282 .aliases(aliases.clone())
283 .doc(doc.into())
284 .attributes(attributes.clone())
285 .build();
286
287 if let Schema::Record(fixed_schema) = schema {
288 assert_eq!(fixed_schema.name, name);
289 assert_eq!(fixed_schema.fields, fields);
290 assert_eq!(fixed_schema.aliases, Some(aliases));
291 assert_eq!(fixed_schema.doc, Some(doc.into()));
292 assert_eq!(
293 fixed_schema.lookup,
294 BTreeMap::from_iter([("f1".into(), 0), ("f2".into(), 1)])
295 );
296 assert_eq!(fixed_schema.attributes, attributes);
297 } else {
298 panic!("Expected a Schema::Record, got: {schema}");
299 }
300
301 Ok(())
302 }
303
304 #[test]
305 fn avro_rs_472_union_builder() -> TestResult {
306 let variants = vec![Schema::Null, Schema::Boolean, Schema::Int];
307
308 let schema = Schema::union(variants.clone())?;
309
310 if let Schema::Union(union_schema) = schema {
311 assert_eq!(union_schema.variants(), variants);
312 } else {
313 panic!("Expected a Schema::Union, got: {schema}");
314 }
315
316 Ok(())
317 }
318}