1use crate::{
2 HashMap, HashSet, Schema, SchemaDynamicArray, SchemaEnum, SchemaEnumSymbol, SchemaFingerprint,
3 SchemaMap, SchemaNamedType, SchemaRecord, SchemaRecordField, SchemaStaticArray,
4};
5use std::fmt::Formatter;
6use std::hash::{Hash, Hasher};
7use std::path::PathBuf;
8use uuid::Uuid;
9
10#[derive(Debug)]
11pub enum SchemaDefValidationError {
12 DuplicateFieldName(String, String),
13 ReferencedNamedTypeNotFound(String, String),
14 InvalidMapKeyType(String, String),
16 InvalidAssetRefInnerType(String, String),
18}
19
20impl std::fmt::Display for SchemaDefValidationError {
21 fn fmt(
22 &self,
23 f: &mut Formatter<'_>,
24 ) -> std::fmt::Result {
25 match self {
26 SchemaDefValidationError::DuplicateFieldName(schema_name, duplicate_field_name) => {
27 write!(
28 f,
29 "Schema {} has a duplicate field {}",
30 schema_name, duplicate_field_name
31 )
32 }
33 SchemaDefValidationError::ReferencedNamedTypeNotFound(
34 schema_name,
35 referenced_named_type_not_found,
36 ) => write!(
37 f,
38 "Schema {} references a type {} that wasn't found",
39 schema_name, referenced_named_type_not_found
40 ),
41 SchemaDefValidationError::InvalidMapKeyType(schema_name, invalid_map_key_type) => {
42 write!(
43 f,
44 "Schema {} has map with key of type {}, but this type cannot be used as a key",
45 schema_name, invalid_map_key_type
46 )
47 }
48 SchemaDefValidationError::InvalidAssetRefInnerType(
49 schema_name,
50 invalid_asset_ref_inner_type,
51 ) => write!(
52 f,
53 "Schema {} references an AssetRef that references {} but it is not a record",
54 schema_name, invalid_asset_ref_inner_type
55 ),
56 }
57 }
58}
59
60pub type SchemaDefValidationResult<T> = Result<T, SchemaDefValidationError>;
61
62#[derive(Debug)]
63pub enum SchemaDefParserError {
64 Str(&'static str),
65 String(String),
66 ValidationError(SchemaDefValidationError),
67}
68
69impl From<SchemaDefValidationError> for SchemaDefParserError {
70 fn from(validation_error: SchemaDefValidationError) -> Self {
71 SchemaDefParserError::ValidationError(validation_error)
72 }
73}
74
75pub type SchemaDefParserResult<T> = Result<T, SchemaDefParserError>;
76
77#[derive(Debug)]
78pub struct SchemaDefStaticArray {
79 pub(super) item_type: Box<SchemaDefType>,
80 pub(super) length: usize,
81}
82
83impl SchemaDefStaticArray {
84 fn apply_type_aliases(
85 &mut self,
86 aliases: &HashMap<String, String>,
87 ) {
88 self.item_type.apply_type_aliases(aliases);
89 }
90
91 fn collect_all_related_types(
92 &self,
93 types: &mut HashSet<String>,
94 ) {
95 self.item_type.collect_all_related_types(types);
96 }
97
98 fn partial_hash<T: Hasher>(
99 &self,
100 hasher: &mut T,
101 ) {
102 self.item_type.partial_hash(hasher);
103 self.length.hash(hasher);
104 }
105
106 fn to_schema(
107 &self,
108 named_types: &HashMap<String, SchemaDefNamedType>,
109 fingerprints: &HashMap<String, SchemaFingerprint>,
110 ) -> SchemaStaticArray {
111 SchemaStaticArray::new(
112 Box::new(self.item_type.to_schema(named_types, fingerprints)),
113 self.length,
114 )
115 }
116}
117
118#[derive(Debug)]
119pub struct SchemaDefDynamicArray {
120 pub(super) item_type: Box<SchemaDefType>,
121}
122
123impl SchemaDefDynamicArray {
124 pub fn new(item_type: Box<SchemaDefType>) -> Self {
125 SchemaDefDynamicArray { item_type }
126 }
127
128 fn apply_type_aliases(
129 &mut self,
130 aliases: &HashMap<String, String>,
131 ) {
132 self.item_type.apply_type_aliases(aliases);
133 }
134
135 fn collect_all_related_types(
136 &self,
137 types: &mut HashSet<String>,
138 ) {
139 self.item_type.collect_all_related_types(types);
140 }
141
142 fn partial_hash<T: Hasher>(
143 &self,
144 hasher: &mut T,
145 ) {
146 self.item_type.partial_hash(hasher);
147 }
148
149 fn to_schema(
150 &self,
151 named_types: &HashMap<String, SchemaDefNamedType>,
152 fingerprints: &HashMap<String, SchemaFingerprint>,
153 ) -> SchemaDynamicArray {
154 SchemaDynamicArray::new(Box::new(
155 self.item_type.to_schema(named_types, fingerprints),
156 ))
157 }
158}
159
160#[derive(Debug)]
161pub struct SchemaDefMap {
162 pub(super) key_type: Box<SchemaDefType>,
163 pub(super) value_type: Box<SchemaDefType>,
164}
165
166impl SchemaDefMap {
167 fn apply_type_aliases(
168 &mut self,
169 aliases: &HashMap<String, String>,
170 ) {
171 self.key_type.apply_type_aliases(aliases);
172 self.value_type.apply_type_aliases(aliases);
173 }
174
175 fn collect_all_related_types(
176 &self,
177 types: &mut HashSet<String>,
178 ) {
179 self.key_type.collect_all_related_types(types);
180 self.value_type.collect_all_related_types(types);
181 }
182
183 fn partial_hash<T: Hasher>(
184 &self,
185 hasher: &mut T,
186 ) {
187 self.key_type.partial_hash(hasher);
188 self.value_type.partial_hash(hasher);
189 }
190
191 fn to_schema(
192 &self,
193 named_types: &HashMap<String, SchemaDefNamedType>,
194 fingerprints: &HashMap<String, SchemaFingerprint>,
195 ) -> SchemaMap {
196 SchemaMap::new(
197 Box::new(self.key_type.to_schema(named_types, fingerprints)),
198 Box::new(self.value_type.to_schema(named_types, fingerprints)),
199 )
200 }
201}
202
203#[derive(Default, Debug, Clone)]
224pub struct SchemaDefRecordFieldMarkup {
225 pub display_name: Option<String>,
227 pub description: Option<String>,
229
230 pub category: Option<String>,
232
233 pub clamp_min: Option<f64>,
235 pub clamp_max: Option<f64>,
236
237 pub ui_min: Option<f64>,
240 pub ui_max: Option<f64>,
241}
242
243impl SchemaDefRecordFieldMarkup {
244 pub fn clamp_min(&self) -> f64 {
245 self.clamp_min.unwrap_or(f64::MIN)
246 }
247
248 pub fn clamp_max(&self) -> f64 {
249 self.clamp_max.unwrap_or(f64::MAX)
250 }
251
252 pub fn ui_min(&self) -> f64 {
253 self.clamp_min
255 .unwrap_or(f64::MIN)
256 .max(self.ui_min.unwrap_or(f64::MIN))
257 }
258
259 pub fn ui_max(&self) -> f64 {
260 self.clamp_max
262 .unwrap_or(f64::MAX)
263 .min(self.ui_max.unwrap_or(f64::MAX))
264 }
265
266 pub fn has_min_bound(&self) -> bool {
267 self.ui_min.is_some() || self.clamp_min.is_some()
268 }
269
270 pub fn has_max_bound(&self) -> bool {
271 self.ui_max.is_some() || self.clamp_max.is_some()
272 }
273}
274
275#[derive(Debug)]
276pub struct SchemaDefRecordField {
277 pub(super) field_name: String,
278 pub(super) field_uuid: Uuid,
279 pub(super) aliases: Vec<String>,
280 pub(super) field_type: SchemaDefType,
281 pub(super) markup: SchemaDefRecordFieldMarkup,
282}
283
284impl SchemaDefRecordField {
285 pub fn new(
286 field_name: String,
287 field_uuid: Uuid,
288 aliases: Vec<String>,
289 field_type: SchemaDefType,
290 markup: SchemaDefRecordFieldMarkup,
291 ) -> SchemaDefValidationResult<Self> {
292 Ok(SchemaDefRecordField {
293 field_name,
294 field_uuid,
295 aliases,
296 field_type,
297 markup,
298 })
299 }
300
301 fn apply_type_aliases(
302 &mut self,
303 aliases: &HashMap<String, String>,
304 ) {
305 self.field_type.apply_type_aliases(aliases);
306 }
307
308 fn collect_all_related_types(
309 &self,
310 types: &mut HashSet<String>,
311 ) {
312 self.field_type.collect_all_related_types(types);
313 }
314
315 fn partial_hash<T: Hasher>(
316 &self,
317 hasher: &mut T,
318 ) {
319 self.field_name.hash(hasher);
321 self.field_type.partial_hash(hasher);
322 }
323
324 fn to_schema(
325 &self,
326 named_types: &HashMap<String, SchemaDefNamedType>,
327 fingerprints: &HashMap<String, SchemaFingerprint>,
328 ) -> SchemaRecordField {
329 SchemaRecordField::new(
330 self.field_name.clone(),
331 self.field_uuid,
332 self.aliases.clone().into_boxed_slice(),
333 self.field_type.to_schema(named_types, fingerprints),
334 self.markup.clone(),
335 )
336 }
337}
338
339#[derive(Default, Debug, Clone)]
340pub struct SchemaDefRecordMarkup {
341 pub display_name: Option<String>,
343
344 pub default_thumbnail: Option<PathBuf>,
345
346 pub tags: HashSet<String>,
348 }
350
351#[derive(Debug)]
353pub struct SchemaDefRecord {
354 pub(super) type_name: String,
355 pub(super) type_uuid: Uuid,
356 pub(super) aliases: Vec<String>,
357 pub(super) fields: Vec<SchemaDefRecordField>,
358 pub(super) markup: SchemaDefRecordMarkup,
359}
360
361impl SchemaDefRecord {
362 pub fn new(
363 type_name: String,
364 type_uuid: Uuid,
365 aliases: Vec<String>,
366 fields: Vec<SchemaDefRecordField>,
367 markup: SchemaDefRecordMarkup,
368 ) -> SchemaDefValidationResult<Self> {
369 for i in 0..fields.len() {
371 for j in 0..i {
372 if fields[i].field_name == fields[j].field_name {
373 Err(SchemaDefValidationError::DuplicateFieldName(
374 type_name.clone(),
375 fields[i].field_name.to_string(),
376 ))?;
377 }
378 }
379 }
380
381 Ok(SchemaDefRecord {
382 type_name,
383 type_uuid,
384 aliases,
385 fields,
386 markup,
387 })
388 }
389
390 pub(crate) fn fields(&self) -> &Vec<SchemaDefRecordField> {
391 &self.fields
392 }
393
394 fn apply_type_aliases(
395 &mut self,
396 aliases: &HashMap<String, String>,
397 ) {
398 for field in &mut self.fields {
399 field.apply_type_aliases(aliases);
400 }
401 }
402
403 fn collect_all_related_types(
404 &self,
405 types: &mut HashSet<String>,
406 ) {
407 types.insert(self.type_name.clone());
408 for field in &self.fields {
409 field.collect_all_related_types(types);
410 }
411 }
412
413 fn partial_hash<T: Hasher>(
414 &self,
415 hasher: &mut T,
416 ) {
417 self.type_name.hash(hasher);
419
420 let mut sorted_fields: Vec<_> = self.fields.iter().collect();
422 sorted_fields.sort_by_key(|x| &x.field_name);
423
424 for field in sorted_fields {
425 field.partial_hash(hasher);
427 }
428 }
429
430 fn to_schema(
431 &self,
432 named_types: &HashMap<String, SchemaDefNamedType>,
433 fingerprints: &HashMap<String, SchemaFingerprint>,
434 ) -> SchemaRecord {
435 let fingerprint = *fingerprints.get(&self.type_name).unwrap();
436
437 let mut fields = Vec::with_capacity(self.fields.len());
438 for field in &self.fields {
439 fields.push(field.to_schema(named_types, fingerprints));
440 }
441
442 SchemaRecord::new(
443 self.type_name.clone(),
444 self.type_uuid,
445 fingerprint,
446 self.aliases.clone().into_boxed_slice(),
447 fields,
448 self.markup.clone(),
449 )
450 }
451}
452
453#[derive(Debug)]
454pub struct SchemaDefEnumSymbol {
455 pub(super) symbol_name: String,
456 pub(super) symbol_uuid: Uuid,
457 pub(super) aliases: Vec<String>,
458}
459
460impl SchemaDefEnumSymbol {
461 pub fn new(
462 symbol_name: String,
463 symbol_uuid: Uuid,
464 aliases: Vec<String>,
465 ) -> SchemaDefValidationResult<Self> {
466 Ok(SchemaDefEnumSymbol {
467 symbol_name,
468 symbol_uuid,
469 aliases,
470 })
471 }
472
473 fn partial_hash<T: Hasher>(
474 &self,
475 hasher: &mut T,
476 ) {
477 self.symbol_name.hash(hasher);
479 }
480
481 fn to_schema(&self) -> SchemaEnumSymbol {
482 SchemaEnumSymbol::new(
483 self.symbol_name.clone(),
484 self.symbol_uuid,
485 self.aliases.clone().into_boxed_slice(),
486 )
487 }
488}
489
490#[derive(Debug)]
492pub struct SchemaDefEnum {
493 pub(super) type_name: String,
494 pub(super) type_uuid: Uuid,
495 pub(super) aliases: Vec<String>,
496 pub(super) symbols: Vec<SchemaDefEnumSymbol>,
497}
498
499impl SchemaDefEnum {
500 pub fn new(
501 type_name: String,
502 type_uuid: Uuid,
503 aliases: Vec<String>,
504 symbols: Vec<SchemaDefEnumSymbol>,
505 ) -> SchemaDefValidationResult<Self> {
506 Ok(SchemaDefEnum {
507 type_name,
508 type_uuid,
509 aliases,
510 symbols,
511 })
512 }
513
514 fn apply_type_aliases(
515 &mut self,
516 _aliases: &HashMap<String, String>,
517 ) {
518 }
519
520 fn collect_all_related_types(
521 &self,
522 types: &mut HashSet<String>,
523 ) {
524 types.insert(self.type_name.clone());
525 }
526
527 fn partial_hash<T: Hasher>(
528 &self,
529 hasher: &mut T,
530 ) {
531 self.type_name.hash(hasher);
533
534 let mut sorted_symbols: Vec<_> = self.symbols.iter().collect();
536 sorted_symbols.sort_by(|a, b| a.symbol_name.cmp(&b.symbol_name));
537
538 for symbol in sorted_symbols {
539 symbol.partial_hash(hasher);
540 }
541 }
542
543 fn to_schema(
544 &self,
545 named_types: &HashMap<String, SchemaFingerprint>,
546 ) -> SchemaEnum {
547 let fingerprint = *named_types.get(&self.type_name).unwrap();
548
549 let mut symbols = Vec::with_capacity(self.symbols.len());
550 for symbol in &self.symbols {
551 symbols.push(symbol.to_schema());
552 }
553
554 SchemaEnum::new(
555 self.type_name.clone(),
556 self.type_uuid,
557 fingerprint,
558 self.aliases.clone().into_boxed_slice(),
559 symbols.into_boxed_slice(),
560 )
561 }
562}
563
564#[derive(Debug)]
565pub enum SchemaDefType {
566 Nullable(Box<SchemaDefType>),
567 Boolean,
568 I32,
569 I64,
570 U32,
571 U64,
572 F32,
573 F64,
574 Bytes,
575 String,
576 StaticArray(SchemaDefStaticArray),
577 DynamicArray(SchemaDefDynamicArray),
578 Map(SchemaDefMap),
579 AssetRef(String), NamedType(String), }
582
583impl SchemaDefType {
584 fn apply_type_aliases(
585 &mut self,
586 aliases: &HashMap<String, String>,
587 ) {
588 match self {
589 SchemaDefType::Nullable(x) => x.apply_type_aliases(aliases),
590 SchemaDefType::Boolean => {}
591 SchemaDefType::I32 => {}
592 SchemaDefType::I64 => {}
593 SchemaDefType::U32 => {}
594 SchemaDefType::U64 => {}
595 SchemaDefType::F32 => {}
596 SchemaDefType::F64 => {}
597 SchemaDefType::Bytes => {}
598 SchemaDefType::String => {}
599 SchemaDefType::StaticArray(x) => x.apply_type_aliases(aliases),
600 SchemaDefType::DynamicArray(x) => x.apply_type_aliases(aliases),
601 SchemaDefType::Map(x) => x.apply_type_aliases(aliases),
602 SchemaDefType::AssetRef(x) => {
603 let alias = aliases.get(x);
604 if let Some(alias) = alias {
605 *x = alias.clone();
606 }
607 }
608 SchemaDefType::NamedType(x) => {
609 let alias = aliases.get(x);
610 if let Some(alias) = alias {
611 *x = alias.clone();
612 }
613 }
614 }
615 }
616
617 fn collect_all_related_types(
618 &self,
619 types: &mut HashSet<String>,
620 ) {
621 match self {
622 SchemaDefType::Nullable(x) => x.collect_all_related_types(types),
623 SchemaDefType::Boolean => {}
624 SchemaDefType::I32 => {}
625 SchemaDefType::I64 => {}
626 SchemaDefType::U32 => {}
627 SchemaDefType::U64 => {}
628 SchemaDefType::F32 => {}
629 SchemaDefType::F64 => {}
630 SchemaDefType::Bytes => {}
631 SchemaDefType::String => {}
632 SchemaDefType::StaticArray(x) => x.collect_all_related_types(types),
633 SchemaDefType::DynamicArray(x) => x.collect_all_related_types(types),
634 SchemaDefType::Map(x) => x.collect_all_related_types(types),
635 SchemaDefType::AssetRef(x) => {
636 types.insert(x.clone());
637 }
638 SchemaDefType::NamedType(x) => {
639 types.insert(x.clone());
640 }
641 }
642 }
643
644 fn partial_hash<T: Hasher>(
645 &self,
646 hasher: &mut T,
647 ) {
648 match self {
650 SchemaDefType::Nullable(x) => {
651 "Nullable".hash(hasher);
652 x.partial_hash(hasher);
653 }
654 SchemaDefType::Boolean => "Boolean".hash(hasher),
655 SchemaDefType::I32 => "I32".hash(hasher),
656 SchemaDefType::I64 => "I64".hash(hasher),
657 SchemaDefType::U32 => "U32".hash(hasher),
658 SchemaDefType::U64 => "U64".hash(hasher),
659 SchemaDefType::F32 => "F32".hash(hasher),
660 SchemaDefType::F64 => "F64".hash(hasher),
661 SchemaDefType::Bytes => "Bytes".hash(hasher),
662 SchemaDefType::String => "String".hash(hasher),
663 SchemaDefType::StaticArray(x) => {
664 "StaticArray".hash(hasher);
665 x.partial_hash(hasher);
666 }
667 SchemaDefType::DynamicArray(x) => {
668 "DynamicArray".hash(hasher);
669 x.partial_hash(hasher);
670 }
671 SchemaDefType::Map(x) => {
672 "Map".hash(hasher);
673 x.partial_hash(hasher);
674 }
675 SchemaDefType::AssetRef(x) => {
676 "AssetRef".hash(hasher);
677 x.hash(hasher);
678 }
679 SchemaDefType::NamedType(x) => {
680 "NamedType".hash(hasher);
681 x.hash(hasher);
682 }
683 }
684 }
685
686 fn to_schema(
687 &self,
688 named_types: &HashMap<String, SchemaDefNamedType>,
689 fingerprints: &HashMap<String, SchemaFingerprint>,
690 ) -> Schema {
691 match self {
692 SchemaDefType::Nullable(x) => {
693 Schema::Nullable(Box::new(x.to_schema(named_types, fingerprints)))
694 }
695 SchemaDefType::Boolean => Schema::Boolean,
696 SchemaDefType::I32 => Schema::I32,
697 SchemaDefType::I64 => Schema::I64,
698 SchemaDefType::U32 => Schema::U32,
699 SchemaDefType::U64 => Schema::U64,
700 SchemaDefType::F32 => Schema::F32,
701 SchemaDefType::F64 => Schema::F64,
702 SchemaDefType::Bytes => Schema::Bytes,
703 SchemaDefType::String => Schema::String,
704 SchemaDefType::StaticArray(x) => {
705 Schema::StaticArray(x.to_schema(named_types, fingerprints))
706 }
707 SchemaDefType::DynamicArray(x) => {
708 Schema::DynamicArray(x.to_schema(named_types, fingerprints))
709 }
710 SchemaDefType::Map(x) => Schema::Map(x.to_schema(named_types, fingerprints)),
711 SchemaDefType::AssetRef(x) => Schema::AssetRef(*fingerprints.get(x).unwrap()),
712 SchemaDefType::NamedType(x) => {
713 let named_type = named_types.get(x).unwrap();
714 match named_type {
715 SchemaDefNamedType::Record(_) => Schema::Record(*fingerprints.get(x).unwrap()),
716 SchemaDefNamedType::Enum(_) => Schema::Enum(*fingerprints.get(x).unwrap()),
717 }
718 }
719 }
720 }
721}
722
723pub enum SchemaDefNamedType {
724 Record(SchemaDefRecord),
725 Enum(SchemaDefEnum),
726}
727
728impl SchemaDefNamedType {
729 pub(super) fn type_name(&self) -> &str {
730 match self {
731 SchemaDefNamedType::Record(x) => &x.type_name,
732 SchemaDefNamedType::Enum(x) => &x.type_name,
733 }
734 }
735
736 pub(super) fn type_uuid(&self) -> Uuid {
737 match self {
738 SchemaDefNamedType::Record(x) => x.type_uuid,
739 SchemaDefNamedType::Enum(x) => x.type_uuid,
740 }
741 }
742
743 pub(super) fn aliases(&self) -> &[String] {
744 match self {
745 SchemaDefNamedType::Record(x) => &x.aliases,
746 SchemaDefNamedType::Enum(x) => &x.aliases,
747 }
748 }
749
750 pub(super) fn apply_type_aliases(
751 &mut self,
752 aliases: &HashMap<String, String>,
753 ) {
754 match self {
755 SchemaDefNamedType::Record(x) => x.apply_type_aliases(aliases),
756 SchemaDefNamedType::Enum(x) => x.apply_type_aliases(aliases),
757 }
758 }
759
760 pub(super) fn collect_all_related_types(
761 &self,
762 types: &mut HashSet<String>,
763 ) {
764 match self {
765 SchemaDefNamedType::Record(x) => x.collect_all_related_types(types),
766 SchemaDefNamedType::Enum(x) => x.collect_all_related_types(types),
767 }
768 }
769
770 pub(super) fn partial_hash<T: Hasher>(
771 &self,
772 hasher: &mut T,
773 ) {
774 match self {
775 SchemaDefNamedType::Record(x) => {
776 "record".hash(hasher);
778 x.partial_hash(hasher);
779 }
780 SchemaDefNamedType::Enum(x) => {
781 "enum".hash(hasher);
782 x.partial_hash(hasher);
783 }
784 }
785 }
786
787 pub(super) fn to_schema(
788 &self,
789 named_types: &HashMap<String, SchemaDefNamedType>,
790 fingerprints: &HashMap<String, SchemaFingerprint>,
791 ) -> SchemaNamedType {
792 match self {
793 SchemaDefNamedType::Record(x) => {
794 SchemaNamedType::Record(x.to_schema(named_types, fingerprints))
795 }
796 SchemaDefNamedType::Enum(x) => SchemaNamedType::Enum(x.to_schema(fingerprints)),
797 }
798 }
799}