serde_avro_derive/
lib.rs

1//! Bring automatic Avro Schema generation to [`serde_avro_fast`]
2//!
3//! See the [`#[derive(BuildSchema)]`](derive@BuildSchema) documentation for
4//! more information
5
6pub use serde_avro_fast;
7
8pub use serde_avro_derive_macros::*;
9
10use std::{any::TypeId, collections::HashMap};
11
12use serde_avro_fast::schema::*;
13
14/// We can automatically build a schema for this type (can be `derive`d)
15///
16/// This trait can be derived using
17/// [`#[derive(BuildSchema)]`](derive@BuildSchema)
18pub trait BuildSchema {
19	/// Build a [`Schema`] for this type
20	fn schema() -> Result<Schema, SchemaError> {
21		Self::schema_mut().try_into()
22	}
23	/// Build a [`SchemaMut`] for this type
24	fn schema_mut() -> SchemaMut {
25		let mut builder = SchemaBuilder::default();
26		Self::append_schema(&mut builder);
27		SchemaMut::from_nodes(builder.nodes)
28	}
29
30	/// Largely internal method to build the schema. Registers the schema within
31	/// the builder.
32	///
33	/// This does not check if this type already exists in the builder, so it
34	/// should never be called directly (instead, use
35	/// [`SchemaBuilder::find_or_build`])
36	///
37	/// The [`SchemaNode`] for this type should be put at the current end of the
38	/// `nodes` array, and its non-already-built dependencies should be put
39	/// after in the array.
40	fn append_schema(builder: &mut SchemaBuilder);
41
42	/// Largely internal type used by
43	/// [`#[derive(BuildSchema)]`](derive@BuildSchema)
44	///
45	/// The TypeId of this type will be used to lookup whether the
46	/// [`SchemaNode`] for this type has already been built in the
47	/// [`SchemaBuilder`].
48	///
49	/// This indirection is required to allow non-static types to implement
50	/// [`BuildSchema`], and also enables using the same node for types that we
51	/// know map to the same schema.
52	type TypeLookup: std::any::Any;
53}
54
55/// Largely internal type used by [`#[derive(BuildSchema)]`](derive@BuildSchema)
56///
57/// You should typically not use this directly
58#[derive(Default)]
59#[non_exhaustive]
60pub struct SchemaBuilder {
61	/// The current set of nodes that have been built
62	///
63	/// These will be the nodes of the resulting schema.
64	///
65	/// The first node of the array is the root of the schema.
66	pub nodes: Vec<SchemaNode>,
67	/// This map maintains the lookup from the TypeId of a type to the index of
68	/// the node in `nodes` that represents the schema for that type.
69	///
70	/// This allows not registering several nodes for the same type if it is
71	/// referenced multiple times. This results in a smaller, more efficient
72	/// schema (also avoids infinite loops for cyclic types).
73	///
74	/// Note that it is important for serialization of the Schema to JSON that
75	/// the same named schema node is used for the same type, so this map is
76	/// necessary. (This is used for de-duplication of named types.)
77	pub already_built_types: HashMap<TypeId, SchemaKey>,
78}
79
80impl SchemaBuilder {
81	/// Reserve a slot in the `nodes` array
82	///
83	/// After building the `SchemaNode`, it should be put at the corresponding
84	/// position in `nodes`.
85	pub fn reserve(&mut self) -> usize {
86		let idx = self.nodes.len();
87		self.nodes.push(RegularType::Null.into());
88		idx
89	}
90
91	/// If the schema for this type (generic parameter) has already been built
92	/// and inserted in the [`nodes`](SchemaBuilder::nodes), return the
93	/// [`SchemaKey`] for it.
94	///
95	/// Otherwise, build the relevant [`SchemaNode`]s, insert them in
96	/// [`nodes`](SchemaBuilder::nodes), and return the [`SchemaKey`] for the
97	/// newly built schema.
98	pub fn find_or_build<T: BuildSchema + ?Sized>(&mut self) -> SchemaKey {
99		match self
100			.already_built_types
101			.entry(TypeId::of::<T::TypeLookup>())
102		{
103			std::collections::hash_map::Entry::Occupied(entry) => *entry.get(),
104			std::collections::hash_map::Entry::Vacant(entry) => {
105				let schema_key = SchemaKey::from_idx(self.nodes.len());
106				entry.insert(schema_key);
107				T::append_schema(self);
108				assert!(
109					self.nodes.len() > schema_key.idx(),
110					"append_schema should always insert at least a node \
111					(and its dependencies below itself)"
112				);
113				schema_key
114			}
115		}
116	}
117
118	/// Insert a new [`SchemaNode`] corresponding to the schema for the type `T`
119	/// into [`nodes`](SchemaBuilder::nodes), regardless of whether it has
120	/// already been built.
121	///
122	/// This is only useful if using a type as a base for the definition of
123	/// another type, but patching it afterwards (e.g. adding a logical type).
124	/// Otherwise, use [`find_or_build`](SchemaBuilder::find_or_build) to avoid
125	/// duplicate nodes.
126	pub fn build_duplicate<T: BuildSchema + ?Sized>(&mut self) -> SchemaKey {
127		let schema_key = SchemaKey::from_idx(self.nodes.len());
128		T::append_schema(self);
129		assert!(
130			self.nodes.len() > schema_key.idx(),
131			"append_schema should always insert at least a node \
132				(and its dependencies below itself)"
133		);
134		schema_key
135	}
136
137	/// Register a new node for this logical type, where the regular type
138	/// specified with `T` is annotated with the logical type specified as the
139	/// `logical_type` argument.
140	///
141	/// `name_override` specifies how to override the name if the underlying
142	/// node ends up generating a named node
143	pub fn build_logical_type(
144		&mut self,
145		logical_type: LogicalType,
146		inner_type_duplicate: impl FnOnce(&mut Self) -> SchemaKey,
147		name_override: impl FnOnce() -> String,
148	) -> SchemaKey {
149		let inner_type_duplicate_key = inner_type_duplicate(self);
150		let node = &mut self.nodes[inner_type_duplicate_key.idx()];
151		node.logical_type = Some(logical_type);
152
153		if let Some(name) = node.type_.name_mut() {
154			*name = Name::from_fully_qualified_name(name_override());
155		}
156
157		inner_type_duplicate_key
158	}
159}
160
161macro_rules! impl_primitive {
162	($($ty:ty, $variant:ident;)+) => {
163		$(
164			impl BuildSchema for $ty {
165				fn append_schema(builder: &mut SchemaBuilder) {
166					builder.nodes.push(RegularType::$variant.into());
167				}
168				type TypeLookup = Self;
169			}
170		)*
171	};
172}
173impl_primitive!(
174	(), Null;
175	bool, Boolean;
176	i32, Int;
177	i64, Long;
178	f32, Float;
179	f64, Double;
180	String, String;
181	Vec<u8>, Bytes;
182);
183
184macro_rules! impl_forward {
185	($($ty:ty, $to:ty;)+) => {
186		$(
187			impl BuildSchema for $ty {
188				fn append_schema(builder: &mut SchemaBuilder) {
189					<$to as BuildSchema>::append_schema(builder)
190				}
191				type TypeLookup = <$to as BuildSchema>::TypeLookup;
192			}
193		)*
194	};
195}
196impl_forward! {
197	str, String;
198	[u8], Vec<u8>;
199	u16, i32;
200	u32, i64;
201	u64, i64;
202	i8, i32;
203	i16, i32;
204	usize, i64;
205}
206
207macro_rules! impl_ptr {
208	($($($ty_path:ident)::+,)+) => {
209		$(
210			impl<T: BuildSchema + ?Sized> BuildSchema for $($ty_path)::+<T> {
211				fn append_schema(builder: &mut SchemaBuilder) {
212					<T as BuildSchema>::append_schema(builder)
213				}
214				type TypeLookup = T::TypeLookup;
215			}
216		)*
217	};
218}
219impl_ptr! {
220	Box,
221	std::sync::Arc,
222	std::rc::Rc,
223	std::cell::RefCell,
224	std::cell::Cell,
225}
226impl<T: BuildSchema + ?Sized> BuildSchema for &'_ T {
227	fn append_schema(builder: &mut SchemaBuilder) {
228		<T as BuildSchema>::append_schema(builder)
229	}
230	type TypeLookup = T::TypeLookup;
231}
232impl<T: BuildSchema + ?Sized> BuildSchema for &'_ mut T {
233	fn append_schema(builder: &mut SchemaBuilder) {
234		<T as BuildSchema>::append_schema(builder)
235	}
236	type TypeLookup = T::TypeLookup;
237}
238
239impl<T: BuildSchema> BuildSchema for Vec<T> {
240	fn append_schema(builder: &mut SchemaBuilder) {
241		let reserved_schema_key = builder.reserve();
242		let new_node = Array::new(builder.find_or_build::<T>()).into();
243		builder.nodes[reserved_schema_key] = new_node;
244	}
245
246	type TypeLookup = Vec<T::TypeLookup>;
247}
248
249impl<T: BuildSchema> BuildSchema for [T] {
250	fn append_schema(builder: &mut SchemaBuilder) {
251		<Vec<T> as BuildSchema>::append_schema(builder)
252	}
253	type TypeLookup = <Vec<T> as BuildSchema>::TypeLookup;
254}
255
256impl<T: BuildSchema> BuildSchema for Option<T> {
257	fn append_schema(builder: &mut SchemaBuilder) {
258		let reserved_schema_key = builder.reserve();
259		let new_node = Union::new(vec![
260			builder.find_or_build::<()>(),
261			builder.find_or_build::<T>(),
262		])
263		.into();
264		builder.nodes[reserved_schema_key] = new_node;
265	}
266
267	type TypeLookup = Option<T::TypeLookup>;
268}
269
270impl<const N: usize> BuildSchema for [u8; N] {
271	fn append_schema(builder: &mut SchemaBuilder) {
272		builder.nodes.push(
273			Fixed::new(
274				Name::from_fully_qualified_name(format!("u8_array_{}", N)),
275				N,
276			)
277			.into(),
278		);
279	}
280	type TypeLookup = Self;
281}
282
283impl<S: std::ops::Deref<Target = str>, V: BuildSchema> BuildSchema for HashMap<S, V> {
284	fn append_schema(builder: &mut SchemaBuilder) {
285		let reserved_schema_key = builder.reserve();
286		let new_node = Map::new(builder.find_or_build::<V>()).into();
287		builder.nodes[reserved_schema_key] = new_node;
288	}
289	type TypeLookup = HashMap<String, V::TypeLookup>;
290}
291impl<S: std::ops::Deref<Target = str>, V: BuildSchema> BuildSchema
292	for std::collections::BTreeMap<S, V>
293{
294	fn append_schema(builder: &mut SchemaBuilder) {
295		<HashMap<String, V> as BuildSchema>::append_schema(builder)
296	}
297	type TypeLookup = <HashMap<String, V> as BuildSchema>::TypeLookup;
298}
299
300#[doc(hidden)]
301/// Used by the [`BuildSchema!`] derive macro to generate a unique name for a
302/// struct when it's generic
303pub fn hash_type_id(struct_name: &mut String, type_id: TypeId) {
304	use std::{
305		fmt::Write,
306		hash::{Hash as _, Hasher as _},
307	};
308	#[allow(deprecated)] // I actually want to not change hasher
309	let mut hasher = std::hash::SipHasher::new();
310	type_id.hash(&mut hasher);
311	write!(struct_name, "_{:016x?}", hasher.finish()).unwrap();
312}
313
314#[doc(hidden)]
315pub enum LazyNamespace {
316	Pending(fn() -> String),
317	Computed(String),
318}
319impl LazyNamespace {
320	pub fn new(f: fn() -> String) -> Self {
321		Self::Pending(f)
322	}
323	pub fn get(&mut self) -> &str {
324		match self {
325			Self::Pending(f) => {
326				let n = f();
327				*self = Self::Computed(n);
328				match self {
329					Self::Computed(n) => n,
330					_ => unreachable!(),
331				}
332			}
333			Self::Computed(n) => n,
334		}
335	}
336}