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
266impl BuildSchema for i8 {
268 fn build_schema(_ctx: &mut SchemaBuilder) -> SchemaNodeContent {
269 SchemaNodeContent::Integer(crate::IntegerSchema::default())
270 }
271}
272
273impl BuildSchema for i16 {
274 fn build_schema(_ctx: &mut SchemaBuilder) -> SchemaNodeContent {
275 SchemaNodeContent::Integer(crate::IntegerSchema::default())
276 }
277}
278
279impl BuildSchema for i32 {
280 fn build_schema(_ctx: &mut SchemaBuilder) -> SchemaNodeContent {
281 SchemaNodeContent::Integer(crate::IntegerSchema::default())
282 }
283}
284
285impl BuildSchema for i64 {
286 fn build_schema(_ctx: &mut SchemaBuilder) -> SchemaNodeContent {
287 SchemaNodeContent::Integer(crate::IntegerSchema::default())
288 }
289}
290
291impl BuildSchema for i128 {
292 fn build_schema(_ctx: &mut SchemaBuilder) -> SchemaNodeContent {
293 SchemaNodeContent::Integer(crate::IntegerSchema::default())
294 }
295}
296
297impl BuildSchema for isize {
298 fn build_schema(_ctx: &mut SchemaBuilder) -> SchemaNodeContent {
299 SchemaNodeContent::Integer(crate::IntegerSchema::default())
300 }
301}
302
303impl BuildSchema for u8 {
305 fn build_schema(_ctx: &mut SchemaBuilder) -> SchemaNodeContent {
306 SchemaNodeContent::Integer(crate::IntegerSchema::default())
307 }
308}
309
310impl BuildSchema for u16 {
311 fn build_schema(_ctx: &mut SchemaBuilder) -> SchemaNodeContent {
312 SchemaNodeContent::Integer(crate::IntegerSchema::default())
313 }
314}
315
316impl BuildSchema for u32 {
317 fn build_schema(_ctx: &mut SchemaBuilder) -> SchemaNodeContent {
318 SchemaNodeContent::Integer(crate::IntegerSchema::default())
319 }
320}
321
322impl BuildSchema for u64 {
323 fn build_schema(_ctx: &mut SchemaBuilder) -> SchemaNodeContent {
324 SchemaNodeContent::Integer(crate::IntegerSchema::default())
325 }
326}
327
328impl BuildSchema for u128 {
329 fn build_schema(_ctx: &mut SchemaBuilder) -> SchemaNodeContent {
330 SchemaNodeContent::Integer(crate::IntegerSchema::default())
331 }
332}
333
334impl BuildSchema for usize {
335 fn build_schema(_ctx: &mut SchemaBuilder) -> SchemaNodeContent {
336 SchemaNodeContent::Integer(crate::IntegerSchema::default())
337 }
338}
339
340impl BuildSchema for f32 {
342 fn build_schema(_ctx: &mut SchemaBuilder) -> SchemaNodeContent {
343 SchemaNodeContent::Float(crate::FloatSchema {
344 precision: crate::FloatPrecision::F32,
345 ..Default::default()
346 })
347 }
348}
349
350impl BuildSchema for f64 {
351 fn build_schema(_ctx: &mut SchemaBuilder) -> SchemaNodeContent {
352 SchemaNodeContent::Float(crate::FloatSchema {
353 precision: crate::FloatPrecision::F64,
354 ..Default::default()
355 })
356 }
357}
358
359impl BuildSchema for () {
361 fn build_schema(_ctx: &mut SchemaBuilder) -> SchemaNodeContent {
362 SchemaNodeContent::Null
363 }
364}
365
366impl BuildSchema for Text {
367 fn build_schema(_ctx: &mut SchemaBuilder) -> SchemaNodeContent {
368 SchemaNodeContent::Text(TextSchema {
369 language: None,
370 min_length: None,
371 max_length: None,
372 pattern: None,
373 unknown_fields: IndexMap::new(),
374 })
375 }
376}
377
378impl<T: BuildSchema + 'static> BuildSchema for Option<T> {
384 fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
385 let some_schema = ctx.build::<T>();
386 let none_schema = ctx.create_node(SchemaNodeContent::Null);
387
388 SchemaNodeContent::Union(crate::UnionSchema {
389 variants: IndexMap::from([
390 ("some".to_string(), some_schema),
391 ("none".to_string(), none_schema),
392 ]),
393 unambiguous: Default::default(),
394 repr: eure_document::data_model::VariantRepr::default(),
395 deny_untagged: Default::default(),
396 })
397 }
398}
399
400impl<T: BuildSchema + 'static, E: BuildSchema + 'static> BuildSchema for Result<T, E> {
402 fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
403 let ok_schema = ctx.build::<T>();
404 let err_schema = ctx.build::<E>();
405
406 SchemaNodeContent::Union(crate::UnionSchema {
407 variants: IndexMap::from([
408 ("ok".to_string(), ok_schema),
409 ("err".to_string(), err_schema),
410 ]),
411 unambiguous: Default::default(),
412 repr: eure_document::data_model::VariantRepr::default(),
413 deny_untagged: Default::default(),
414 })
415 }
416}
417
418impl<T: BuildSchema + 'static> BuildSchema for Vec<T> {
420 fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
421 let item = ctx.build::<T>();
422 SchemaNodeContent::Array(crate::ArraySchema {
423 item,
424 min_length: None,
425 max_length: None,
426 unique: false,
427 contains: None,
428 binding_style: None,
429 })
430 }
431}
432
433impl<K: BuildSchema + 'static, V: BuildSchema + 'static> BuildSchema
435 for std::collections::HashMap<K, V>
436{
437 fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
438 let key = ctx.build::<K>();
439 let value = ctx.build::<V>();
440 SchemaNodeContent::Map(crate::MapSchema {
441 key,
442 value,
443 min_size: None,
444 max_size: None,
445 })
446 }
447}
448
449impl<K: BuildSchema + 'static, V: BuildSchema + 'static> BuildSchema
451 for std::collections::BTreeMap<K, V>
452{
453 fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
454 let key = ctx.build::<K>();
455 let value = ctx.build::<V>();
456 SchemaNodeContent::Map(crate::MapSchema {
457 key,
458 value,
459 min_size: None,
460 max_size: None,
461 })
462 }
463}
464
465impl<T: BuildSchema + 'static> BuildSchema for Box<T> {
467 fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
468 T::build_schema(ctx)
469 }
470}
471
472impl<T: BuildSchema + 'static> BuildSchema for std::rc::Rc<T> {
474 fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
475 T::build_schema(ctx)
476 }
477}
478
479impl<T: BuildSchema + 'static> BuildSchema for std::sync::Arc<T> {
481 fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
482 T::build_schema(ctx)
483 }
484}
485
486impl<A: BuildSchema + 'static> BuildSchema for (A,) {
488 fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
489 let elements = vec![ctx.build::<A>()];
490 SchemaNodeContent::Tuple(crate::TupleSchema {
491 elements,
492 binding_style: None,
493 })
494 }
495}
496
497impl<A: BuildSchema + 'static, B: BuildSchema + 'static> BuildSchema for (A, B) {
498 fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
499 let elements = vec![ctx.build::<A>(), ctx.build::<B>()];
500 SchemaNodeContent::Tuple(crate::TupleSchema {
501 elements,
502 binding_style: None,
503 })
504 }
505}
506
507impl<A: BuildSchema + 'static, B: BuildSchema + 'static, C: BuildSchema + 'static> BuildSchema
508 for (A, B, C)
509{
510 fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
511 let elements = vec![ctx.build::<A>(), ctx.build::<B>(), ctx.build::<C>()];
512 SchemaNodeContent::Tuple(crate::TupleSchema {
513 elements,
514 binding_style: None,
515 })
516 }
517}
518
519impl<
520 A: BuildSchema + 'static,
521 B: BuildSchema + 'static,
522 C: BuildSchema + 'static,
523 D: BuildSchema + 'static,
524> BuildSchema for (A, B, C, D)
525{
526 fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
527 let elements = vec![
528 ctx.build::<A>(),
529 ctx.build::<B>(),
530 ctx.build::<C>(),
531 ctx.build::<D>(),
532 ];
533 SchemaNodeContent::Tuple(crate::TupleSchema {
534 elements,
535 binding_style: None,
536 })
537 }
538}
539
540impl<
541 A: BuildSchema + 'static,
542 B: BuildSchema + 'static,
543 C: BuildSchema + 'static,
544 D: BuildSchema + 'static,
545 E: BuildSchema + 'static,
546> BuildSchema for (A, B, C, D, E)
547{
548 fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
549 let elements = vec![
550 ctx.build::<A>(),
551 ctx.build::<B>(),
552 ctx.build::<C>(),
553 ctx.build::<D>(),
554 ctx.build::<E>(),
555 ];
556 SchemaNodeContent::Tuple(crate::TupleSchema {
557 elements,
558 binding_style: None,
559 })
560 }
561}
562
563impl<
564 A: BuildSchema + 'static,
565 B: BuildSchema + 'static,
566 C: BuildSchema + 'static,
567 D: BuildSchema + 'static,
568 E: BuildSchema + 'static,
569 F: BuildSchema + 'static,
570> BuildSchema for (A, B, C, D, E, F)
571{
572 fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
573 let elements = vec![
574 ctx.build::<A>(),
575 ctx.build::<B>(),
576 ctx.build::<C>(),
577 ctx.build::<D>(),
578 ctx.build::<E>(),
579 ctx.build::<F>(),
580 ];
581 SchemaNodeContent::Tuple(crate::TupleSchema {
582 elements,
583 binding_style: None,
584 })
585 }
586}