1use std::any::TypeId;
22use std::collections::HashMap;
23
24use eure_document::Text;
25use indexmap::IndexMap;
26
27use crate::{
28 SchemaDocument, SchemaMetadata, SchemaNode, SchemaNodeContent, SchemaNodeId, TextSchema,
29};
30
31pub trait BuildSchema {
46 fn type_name() -> Option<&'static str> {
51 None
52 }
53
54 fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent;
59
60 fn schema_metadata() -> SchemaMetadata {
64 SchemaMetadata::default()
65 }
66}
67
68pub struct SchemaBuilder {
75 doc: SchemaDocument,
77 cache: HashMap<TypeId, SchemaNodeId>,
79}
80
81impl SchemaBuilder {
82 pub fn new() -> Self {
84 Self {
85 doc: SchemaDocument {
86 nodes: Vec::new(),
87 root: SchemaNodeId(0), types: Default::default(),
89 },
90 cache: HashMap::new(),
91 }
92 }
93
94 pub fn build<T: BuildSchema + 'static>(&mut self) -> SchemaNodeId {
102 let type_id = TypeId::of::<T>();
103
104 if let Some(&id) = self.cache.get(&type_id) {
106 return id;
107 }
108
109 let type_name = T::type_name();
111
112 if let Some(name) = type_name {
115 let content_id = self.reserve_node();
117
118 let content = T::build_schema(self);
120 let metadata = T::schema_metadata();
121 self.set_node(content_id, content, metadata);
122
123 if let Ok(ident) = name.parse::<eure_document::identifier::Identifier>() {
125 self.doc.types.insert(ident, content_id);
126 }
127
128 let ref_id = self.create_node(SchemaNodeContent::Reference(crate::TypeReference {
130 namespace: None,
131 name: name.parse().expect("valid type name"),
132 }));
133
134 self.cache.insert(type_id, ref_id);
136 ref_id
137 } else {
138 let id = self.reserve_node();
140 self.cache.insert(type_id, id);
141
142 let content = T::build_schema(self);
143 let metadata = T::schema_metadata();
144 self.set_node(id, content, metadata);
145
146 id
147 }
148 }
149
150 pub fn create_node(&mut self, content: SchemaNodeContent) -> SchemaNodeId {
155 let id = SchemaNodeId(self.doc.nodes.len());
156 self.doc.nodes.push(SchemaNode {
157 content,
158 metadata: SchemaMetadata::default(),
159 ext_types: Default::default(),
160 });
161 id
162 }
163
164 pub fn create_node_with_metadata(
166 &mut self,
167 content: SchemaNodeContent,
168 metadata: SchemaMetadata,
169 ) -> SchemaNodeId {
170 let id = SchemaNodeId(self.doc.nodes.len());
171 self.doc.nodes.push(SchemaNode {
172 content,
173 metadata,
174 ext_types: Default::default(),
175 });
176 id
177 }
178
179 fn reserve_node(&mut self) -> SchemaNodeId {
184 let id = SchemaNodeId(self.doc.nodes.len());
185 self.doc.nodes.push(SchemaNode {
186 content: SchemaNodeContent::Any, metadata: SchemaMetadata::default(),
188 ext_types: Default::default(),
189 });
190 id
191 }
192
193 fn set_node(&mut self, id: SchemaNodeId, content: SchemaNodeContent, metadata: SchemaMetadata) {
195 let node = &mut self.doc.nodes[id.0];
196 node.content = content;
197 node.metadata = metadata;
198 }
199
200 pub fn node_mut(&mut self, id: SchemaNodeId) -> &mut SchemaNode {
202 &mut self.doc.nodes[id.0]
203 }
204
205 pub fn register_type(&mut self, name: &str, id: SchemaNodeId) {
207 if let Ok(ident) = name.parse() {
208 self.doc.types.insert(ident, id);
209 }
210 }
211
212 pub fn finish(mut self, root: SchemaNodeId) -> SchemaDocument {
214 self.doc.root = root;
215 self.doc
216 }
217}
218
219impl Default for SchemaBuilder {
220 fn default() -> Self {
221 Self::new()
222 }
223}
224
225impl SchemaDocument {
226 pub fn of<T: BuildSchema + 'static>() -> SchemaDocument {
238 let mut builder = SchemaBuilder::new();
239 let root = builder.build::<T>();
240 builder.finish(root)
241 }
242}
243
244impl BuildSchema for String {
249 fn build_schema(_ctx: &mut SchemaBuilder) -> SchemaNodeContent {
250 SchemaNodeContent::Text(crate::TextSchema::default())
251 }
252}
253
254impl BuildSchema for &str {
255 fn build_schema(_ctx: &mut SchemaBuilder) -> SchemaNodeContent {
256 SchemaNodeContent::Text(crate::TextSchema::default())
257 }
258}
259
260impl BuildSchema for bool {
261 fn build_schema(_ctx: &mut SchemaBuilder) -> SchemaNodeContent {
262 SchemaNodeContent::Boolean
263 }
264}
265
266macro_rules! impl_build_schema_int {
267 ($($ty:ty),*) => {
268 $(
269 impl BuildSchema for $ty {
270 fn build_schema(_ctx: &mut SchemaBuilder) -> SchemaNodeContent {
271 SchemaNodeContent::Integer(crate::IntegerSchema::default())
272 }
273 }
274 )*
275 };
276}
277
278impl_build_schema_int!(
279 u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize
280);
281
282impl BuildSchema for f32 {
284 fn build_schema(_ctx: &mut SchemaBuilder) -> SchemaNodeContent {
285 SchemaNodeContent::Float(crate::FloatSchema {
286 precision: crate::FloatPrecision::F32,
287 ..Default::default()
288 })
289 }
290}
291
292impl BuildSchema for f64 {
293 fn build_schema(_ctx: &mut SchemaBuilder) -> SchemaNodeContent {
294 SchemaNodeContent::Float(crate::FloatSchema {
295 precision: crate::FloatPrecision::F64,
296 ..Default::default()
297 })
298 }
299}
300
301impl BuildSchema for () {
303 fn build_schema(_ctx: &mut SchemaBuilder) -> SchemaNodeContent {
304 SchemaNodeContent::Null
305 }
306}
307
308impl BuildSchema for Text {
309 fn build_schema(_ctx: &mut SchemaBuilder) -> SchemaNodeContent {
310 SchemaNodeContent::Text(TextSchema {
311 language: None,
312 min_length: None,
313 max_length: None,
314 pattern: None,
315 unknown_fields: IndexMap::new(),
316 })
317 }
318}
319
320impl<T: BuildSchema + 'static> BuildSchema for Option<T> {
326 fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
327 let some_schema = ctx.build::<T>();
328 let none_schema = ctx.create_node(SchemaNodeContent::Null);
329
330 SchemaNodeContent::Union(crate::UnionSchema {
331 variants: IndexMap::from([
332 ("some".to_string(), some_schema),
333 ("none".to_string(), none_schema),
334 ]),
335 unambiguous: Default::default(),
336 repr: eure_document::data_model::VariantRepr::default(),
337 deny_untagged: Default::default(),
338 })
339 }
340}
341
342impl<T: BuildSchema + 'static, E: BuildSchema + 'static> BuildSchema for Result<T, E> {
344 fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
345 let ok_schema = ctx.build::<T>();
346 let err_schema = ctx.build::<E>();
347
348 SchemaNodeContent::Union(crate::UnionSchema {
349 variants: IndexMap::from([
350 ("ok".to_string(), ok_schema),
351 ("err".to_string(), err_schema),
352 ]),
353 unambiguous: Default::default(),
354 repr: eure_document::data_model::VariantRepr::default(),
355 deny_untagged: Default::default(),
356 })
357 }
358}
359
360impl<T: BuildSchema + 'static> BuildSchema for Vec<T> {
362 fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
363 let item = ctx.build::<T>();
364 SchemaNodeContent::Array(crate::ArraySchema {
365 item,
366 min_length: None,
367 max_length: None,
368 unique: false,
369 contains: None,
370 binding_style: None,
371 })
372 }
373}
374
375impl<K: BuildSchema + 'static, V: BuildSchema + 'static> BuildSchema
377 for std::collections::HashMap<K, V>
378{
379 fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
380 let key = ctx.build::<K>();
381 let value = ctx.build::<V>();
382 SchemaNodeContent::Map(crate::MapSchema {
383 key,
384 value,
385 min_size: None,
386 max_size: None,
387 })
388 }
389}
390
391impl<K: BuildSchema + 'static, V: BuildSchema + 'static> BuildSchema
393 for std::collections::BTreeMap<K, V>
394{
395 fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
396 let key = ctx.build::<K>();
397 let value = ctx.build::<V>();
398 SchemaNodeContent::Map(crate::MapSchema {
399 key,
400 value,
401 min_size: None,
402 max_size: None,
403 })
404 }
405}
406
407impl<T: BuildSchema + 'static> BuildSchema for Box<T> {
409 fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
410 T::build_schema(ctx)
411 }
412}
413
414impl<T: BuildSchema + 'static> BuildSchema for std::rc::Rc<T> {
416 fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
417 T::build_schema(ctx)
418 }
419}
420
421impl<T: BuildSchema + 'static> BuildSchema for std::sync::Arc<T> {
423 fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
424 T::build_schema(ctx)
425 }
426}
427
428impl<A: BuildSchema + 'static> BuildSchema for (A,) {
430 fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
431 let elements = vec![ctx.build::<A>()];
432 SchemaNodeContent::Tuple(crate::TupleSchema {
433 elements,
434 binding_style: None,
435 })
436 }
437}
438
439impl<A: BuildSchema + 'static, B: BuildSchema + 'static> BuildSchema for (A, B) {
440 fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
441 let elements = vec![ctx.build::<A>(), ctx.build::<B>()];
442 SchemaNodeContent::Tuple(crate::TupleSchema {
443 elements,
444 binding_style: None,
445 })
446 }
447}
448
449impl<A: BuildSchema + 'static, B: BuildSchema + 'static, C: BuildSchema + 'static> BuildSchema
450 for (A, B, C)
451{
452 fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
453 let elements = vec![ctx.build::<A>(), ctx.build::<B>(), ctx.build::<C>()];
454 SchemaNodeContent::Tuple(crate::TupleSchema {
455 elements,
456 binding_style: None,
457 })
458 }
459}
460
461impl<
462 A: BuildSchema + 'static,
463 B: BuildSchema + 'static,
464 C: BuildSchema + 'static,
465 D: BuildSchema + 'static,
466> BuildSchema for (A, B, C, D)
467{
468 fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
469 let elements = vec![
470 ctx.build::<A>(),
471 ctx.build::<B>(),
472 ctx.build::<C>(),
473 ctx.build::<D>(),
474 ];
475 SchemaNodeContent::Tuple(crate::TupleSchema {
476 elements,
477 binding_style: None,
478 })
479 }
480}
481
482impl<
483 A: BuildSchema + 'static,
484 B: BuildSchema + 'static,
485 C: BuildSchema + 'static,
486 D: BuildSchema + 'static,
487 E: BuildSchema + 'static,
488> BuildSchema for (A, B, C, D, E)
489{
490 fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
491 let elements = vec![
492 ctx.build::<A>(),
493 ctx.build::<B>(),
494 ctx.build::<C>(),
495 ctx.build::<D>(),
496 ctx.build::<E>(),
497 ];
498 SchemaNodeContent::Tuple(crate::TupleSchema {
499 elements,
500 binding_style: None,
501 })
502 }
503}
504
505impl<
506 A: BuildSchema + 'static,
507 B: BuildSchema + 'static,
508 C: BuildSchema + 'static,
509 D: BuildSchema + 'static,
510 E: BuildSchema + 'static,
511 F: BuildSchema + 'static,
512> BuildSchema for (A, B, C, D, E, F)
513{
514 fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
515 let elements = vec![
516 ctx.build::<A>(),
517 ctx.build::<B>(),
518 ctx.build::<C>(),
519 ctx.build::<D>(),
520 ctx.build::<E>(),
521 ctx.build::<F>(),
522 ];
523 SchemaNodeContent::Tuple(crate::TupleSchema {
524 elements,
525 binding_style: None,
526 })
527 }
528}