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