1use crate::Schema;
19use crate::schema::{FixedSchema, Name, Names, Namespace, UnionSchema, UuidSchema};
20use serde_json::Map;
21use std::borrow::Cow;
22use std::collections::HashMap;
23
24pub trait AvroSchema {
29 fn get_schema() -> Schema;
30}
31
32pub trait AvroSchemaComponent {
85 fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema;
86}
87
88impl<T> AvroSchema for T
89where
90 T: AvroSchemaComponent + ?Sized,
91{
92 fn get_schema() -> Schema {
93 T::get_schema_in_ctxt(&mut HashMap::default(), &None)
94 }
95}
96
97macro_rules! impl_schema (
98 ($type:ty, $variant_constructor:expr) => (
99 impl AvroSchemaComponent for $type {
100 fn get_schema_in_ctxt(_: &mut Names, _: &Namespace) -> Schema {
101 $variant_constructor
102 }
103 }
104 );
105);
106
107impl_schema!(bool, Schema::Boolean);
108impl_schema!(i8, Schema::Int);
109impl_schema!(i16, Schema::Int);
110impl_schema!(i32, Schema::Int);
111impl_schema!(i64, Schema::Long);
112impl_schema!(u8, Schema::Int);
113impl_schema!(u16, Schema::Int);
114impl_schema!(u32, Schema::Long);
115impl_schema!(f32, Schema::Float);
116impl_schema!(f64, Schema::Double);
117impl_schema!(String, Schema::String);
118impl_schema!(str, Schema::String);
119impl_schema!(char, Schema::String);
120
121impl<T> AvroSchemaComponent for &T
122where
123 T: AvroSchemaComponent + ?Sized,
124{
125 fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
126 T::get_schema_in_ctxt(named_schemas, enclosing_namespace)
127 }
128}
129
130impl<T> AvroSchemaComponent for &mut T
131where
132 T: AvroSchemaComponent + ?Sized,
133{
134 fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
135 T::get_schema_in_ctxt(named_schemas, enclosing_namespace)
136 }
137}
138
139impl<T> AvroSchemaComponent for [T]
140where
141 T: AvroSchemaComponent,
142{
143 fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
144 Schema::array(T::get_schema_in_ctxt(named_schemas, enclosing_namespace))
145 }
146}
147
148impl<const N: usize, T> AvroSchemaComponent for [T; N]
149where
150 T: AvroSchemaComponent,
151{
152 fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
153 Schema::array(T::get_schema_in_ctxt(named_schemas, enclosing_namespace))
154 }
155}
156
157impl<T> AvroSchemaComponent for Vec<T>
158where
159 T: AvroSchemaComponent,
160{
161 fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
162 Schema::array(T::get_schema_in_ctxt(named_schemas, enclosing_namespace))
163 }
164}
165
166impl<T> AvroSchemaComponent for Option<T>
167where
168 T: AvroSchemaComponent,
169{
170 fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
171 let variants = vec![
172 Schema::Null,
173 T::get_schema_in_ctxt(named_schemas, enclosing_namespace),
174 ];
175
176 Schema::Union(
177 UnionSchema::new(variants).expect("Option<T> must produce a valid (non-nested) union"),
178 )
179 }
180}
181
182impl<T> AvroSchemaComponent for Map<String, T>
183where
184 T: AvroSchemaComponent,
185{
186 fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
187 Schema::map(T::get_schema_in_ctxt(named_schemas, enclosing_namespace))
188 }
189}
190
191impl<T> AvroSchemaComponent for HashMap<String, T>
192where
193 T: AvroSchemaComponent,
194{
195 fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
196 Schema::map(T::get_schema_in_ctxt(named_schemas, enclosing_namespace))
197 }
198}
199
200impl<T> AvroSchemaComponent for Box<T>
201where
202 T: AvroSchemaComponent,
203{
204 fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
205 T::get_schema_in_ctxt(named_schemas, enclosing_namespace)
206 }
207}
208
209impl<T> AvroSchemaComponent for std::sync::Mutex<T>
210where
211 T: AvroSchemaComponent,
212{
213 fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
214 T::get_schema_in_ctxt(named_schemas, enclosing_namespace)
215 }
216}
217
218impl<T> AvroSchemaComponent for Cow<'_, T>
219where
220 T: AvroSchemaComponent + Clone,
221{
222 fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
223 T::get_schema_in_ctxt(named_schemas, enclosing_namespace)
224 }
225}
226
227impl AvroSchemaComponent for core::time::Duration {
228 #[expect(clippy::map_entry, reason = "We don't use the value from the map")]
232 fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
233 let name = Name::new("duration")
234 .expect("Name is valid")
235 .fully_qualified_name(enclosing_namespace);
236 if named_schemas.contains_key(&name) {
237 Schema::Ref { name }
238 } else {
239 let schema = Schema::Duration(FixedSchema {
240 name: name.clone(),
241 aliases: None,
242 doc: None,
243 size: 12,
244 default: None,
245 attributes: Default::default(),
246 });
247 named_schemas.insert(name, schema.clone());
248 schema
249 }
250 }
251}
252
253impl AvroSchemaComponent for uuid::Uuid {
254 #[expect(clippy::map_entry, reason = "We don't use the value from the map")]
258 fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
259 let name = Name::new("uuid")
260 .expect("Name is valid")
261 .fully_qualified_name(enclosing_namespace);
262 if named_schemas.contains_key(&name) {
263 Schema::Ref { name }
264 } else {
265 let schema = Schema::Uuid(UuidSchema::Fixed(FixedSchema {
266 name: name.clone(),
267 aliases: None,
268 doc: None,
269 size: 16,
270 default: None,
271 attributes: Default::default(),
272 }));
273 named_schemas.insert(name, schema.clone());
274 schema
275 }
276 }
277}
278
279impl AvroSchemaComponent for u64 {
280 #[expect(clippy::map_entry, reason = "We don't use the value from the map")]
282 fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
283 let name = Name::new("u64")
284 .expect("Name is valid")
285 .fully_qualified_name(enclosing_namespace);
286 if named_schemas.contains_key(&name) {
287 Schema::Ref { name }
288 } else {
289 let schema = Schema::Fixed(FixedSchema {
290 name: name.clone(),
291 aliases: None,
292 doc: None,
293 size: 8,
294 default: None,
295 attributes: Default::default(),
296 });
297 named_schemas.insert(name, schema.clone());
298 schema
299 }
300 }
301}
302
303impl AvroSchemaComponent for u128 {
304 #[expect(clippy::map_entry, reason = "We don't use the value from the map")]
306 fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
307 let name = Name::new("u128")
308 .expect("Name is valid")
309 .fully_qualified_name(enclosing_namespace);
310 if named_schemas.contains_key(&name) {
311 Schema::Ref { name }
312 } else {
313 let schema = Schema::Fixed(FixedSchema {
314 name: name.clone(),
315 aliases: None,
316 doc: None,
317 size: 16,
318 default: None,
319 attributes: Default::default(),
320 });
321 named_schemas.insert(name, schema.clone());
322 schema
323 }
324 }
325}
326
327impl AvroSchemaComponent for i128 {
328 #[expect(clippy::map_entry, reason = "We don't use the value from the map")]
330 fn get_schema_in_ctxt(named_schemas: &mut Names, enclosing_namespace: &Namespace) -> Schema {
331 let name = Name::new("i128")
332 .expect("Name is valid")
333 .fully_qualified_name(enclosing_namespace);
334 if named_schemas.contains_key(&name) {
335 Schema::Ref { name }
336 } else {
337 let schema = Schema::Fixed(FixedSchema {
338 name: name.clone(),
339 aliases: None,
340 doc: None,
341 size: 16,
342 default: None,
343 attributes: Default::default(),
344 });
345 named_schemas.insert(name, schema.clone());
346 schema
347 }
348 }
349}
350
351#[cfg(test)]
352mod tests {
353 use crate::schema::{FixedSchema, Name};
354 use crate::{AvroSchema, Schema};
355 use apache_avro_test_helper::TestResult;
356
357 #[test]
358 fn avro_rs_401_str() -> TestResult {
359 let schema = str::get_schema();
360 assert_eq!(schema, Schema::String);
361
362 Ok(())
363 }
364
365 #[test]
366 fn avro_rs_401_references() -> TestResult {
367 let schema_ref = <&str>::get_schema();
368 let schema_ref_mut = <&mut str>::get_schema();
369
370 assert_eq!(schema_ref, Schema::String);
371 assert_eq!(schema_ref_mut, Schema::String);
372
373 Ok(())
374 }
375
376 #[test]
377 fn avro_rs_401_slice() -> TestResult {
378 let schema = <[u8]>::get_schema();
379 assert_eq!(schema, Schema::array(Schema::Int));
380
381 Ok(())
382 }
383
384 #[test]
385 fn avro_rs_401_array() -> TestResult {
386 let schema = <[u8; 55]>::get_schema();
387 assert_eq!(schema, Schema::array(Schema::Int));
388
389 Ok(())
390 }
391
392 #[test]
393 fn avro_rs_401_option_ref_slice_array() -> TestResult {
394 let schema = <Option<&[[u8; 55]]>>::get_schema();
395 assert_eq!(
396 schema,
397 Schema::union(vec![
398 Schema::Null,
399 Schema::array(Schema::array(Schema::Int))
400 ])?
401 );
402
403 Ok(())
404 }
405
406 #[test]
407 fn avro_rs_414_char() -> TestResult {
408 let schema = char::get_schema();
409 assert_eq!(schema, Schema::String);
410
411 Ok(())
412 }
413
414 #[test]
415 fn avro_rs_414_u64() -> TestResult {
416 let schema = u64::get_schema();
417 assert_eq!(
418 schema,
419 Schema::Fixed(FixedSchema {
420 name: Name::new("u64")?,
421 aliases: None,
422 doc: None,
423 size: 8,
424 default: None,
425 attributes: Default::default(),
426 })
427 );
428
429 Ok(())
430 }
431
432 #[test]
433 fn avro_rs_414_i128() -> TestResult {
434 let schema = i128::get_schema();
435 assert_eq!(
436 schema,
437 Schema::Fixed(FixedSchema {
438 name: Name::new("i128")?,
439 aliases: None,
440 doc: None,
441 size: 16,
442 default: None,
443 attributes: Default::default(),
444 })
445 );
446
447 Ok(())
448 }
449
450 #[test]
451 fn avro_rs_414_u128() -> TestResult {
452 let schema = u128::get_schema();
453 assert_eq!(
454 schema,
455 Schema::Fixed(FixedSchema {
456 name: Name::new("u128")?,
457 aliases: None,
458 doc: None,
459 size: 16,
460 default: None,
461 attributes: Default::default(),
462 })
463 );
464
465 Ok(())
466 }
467}