1use crate::{data_model, DefaultType};
2use jzon::JsonValue;
3use std::{
4 collections::{HashMap, HashSet},
5 fmt,
6};
7
8impl data_model::DataModel {
9 pub fn export_schema(&self, id: &str, schema_id: &str) -> Result<JsonValue, Error> {
17 let data_types = HashMap::<String, data_model::DataType>::from_iter(
19 self.data_types
20 .iter()
21 .map(|data_type| (data_type.name.clone(), data_type.clone())),
22 );
23
24 let main_type = data_types.get(id).ok_or(Error {
26 location: "".to_string(),
27 error: ErrorCore::UnknownID(id.to_string()),
28 })?;
29
30 let mut defs = jzon::object::Object::new();
32 let mut dependencies = HashSet::new();
33 let mut implemented_types = HashSet::new();
34 dependencies.insert(main_type.name.clone());
35 while !dependencies.is_empty() {
36 let implement_type = if let Some(value) = dependencies
38 .iter()
39 .filter(|value| !implemented_types.contains(*value))
40 .next()
41 {
42 value.clone()
43 } else {
44 break;
45 };
46
47 let type_schema = data_types.get(&implement_type).unwrap().export_schema(
49 &data_types,
50 &mut dependencies,
51 &self.macros,
52 )?;
53
54 defs.insert(&implement_type, JsonValue::Object(type_schema));
56
57 implemented_types.insert(implement_type);
59 }
60
61 let schema = jzon::object! {
63 "$comment": format!("This schema is generated using Termite Data Model Generator to be compatible with a generated data model using version {}.{}", std::env!("CARGO_PKG_VERSION_MAJOR"), std::env!("CARGO_PKG_VERSION_MINOR")),
64 "$schema": "https://json-schema.org/draft/2020-12/schema",
65 "$id": schema_id.to_string(),
66 "$ref": id.to_string(),
67 "$defs": JsonValue::Object(defs),
68 };
69
70 return Ok(schema);
71 }
72}
73
74impl data_model::DataType {
75 pub fn export_schema(
85 &self,
86 custom_types: &HashMap<String, data_model::DataType>,
87 dependencies: &mut HashSet<String>,
88 macros: &HashMap<String, data_model::SerializationModel>,
89 ) -> Result<jzon::object::Object, Error> {
90 let mut schema = self
92 .data
93 .export_schema(custom_types, dependencies, macros)?;
94
95 schema.insert("$id", JsonValue::String(self.name.clone()));
97
98 if let Some(description) = &self.description {
100 schema.insert("description", JsonValue::String(description.clone()));
101 }
102
103 return Ok(schema);
104 }
105
106 pub fn schema_value(
114 &self,
115 value: &data_model::SerializationModel,
116 custom_types: &HashMap<String, data_model::DataType>,
117 ) -> Result<JsonValue, Error> {
118 return self.data.schema_value(value, custom_types);
119 }
120}
121
122impl data_model::DataTypeData {
123 pub fn export_schema(
133 &self,
134 custom_types: &HashMap<String, data_model::DataType>,
135 dependencies: &mut HashSet<String>,
136 macros: &HashMap<String, data_model::SerializationModel>,
137 ) -> Result<jzon::object::Object, Error> {
138 return match self {
139 data_model::DataTypeData::Struct(data) => {
140 data.export_schema(custom_types, dependencies, macros)
141 }
142 data_model::DataTypeData::Array(data) => data.export_schema(custom_types, dependencies),
143 data_model::DataTypeData::Variant(data) => {
144 data.export_schema(custom_types, dependencies)
145 }
146 data_model::DataTypeData::Enum(data) => data.export_schema(custom_types, dependencies),
147 data_model::DataTypeData::ConstrainedType(data) => {
148 data.export_schema(custom_types, dependencies)
149 }
150 };
151 }
152
153 pub fn schema_value(
161 &self,
162 value: &data_model::SerializationModel,
163 custom_types: &HashMap<String, data_model::DataType>,
164 ) -> Result<JsonValue, Error> {
165 return match self {
166 data_model::DataTypeData::Struct(data) => data.schema_value(value, custom_types),
167 data_model::DataTypeData::Array(data) => data.schema_value(value, custom_types),
168 data_model::DataTypeData::Variant(data) => data.schema_value(value, custom_types),
169 data_model::DataTypeData::Enum(data) => data.schema_value(value, custom_types),
170 data_model::DataTypeData::ConstrainedType(data) => {
171 data.schema_value(value, custom_types)
172 }
173 };
174 }
175}
176
177impl data_model::Struct {
178 pub fn export_schema(
188 &self,
189 custom_types: &HashMap<String, data_model::DataType>,
190 dependencies: &mut HashSet<String>,
191 macros: &HashMap<String, data_model::SerializationModel>,
192 ) -> Result<jzon::object::Object, Error> {
193 let mut required = vec![];
195
196 let mut properties = jzon::object::Object::new();
198
199 for field in self.fields.iter() {
201 let ref_keyword = is_schema_type(&field.data_type, custom_types, dependencies)
203 .or_else(|error| {
204 return Err(error.add_field(&field.name));
205 })?;
206
207 let mut field_schema = jzon::object::Object::new();
209 field_schema.insert(ref_keyword, JsonValue::String(field.data_type.clone()));
210 if let Some(description) = &field.description {
211 field_schema.insert("description", JsonValue::String(description.clone()));
212 }
213 match &field.default {
214 DefaultType::Optional => (),
215 DefaultType::Required => required.push(JsonValue::String(field.name.clone())),
216 DefaultType::Default(value) => {
217 field_schema.insert(
218 "default",
219 to_json(
220 &data_model::expand_macros(value, macros, &mut HashSet::new())?,
221 &field.data_type,
222 custom_types,
223 )?,
224 );
225 }
226 }
227
228 properties.insert(&field.name, JsonValue::Object(field_schema));
230 }
231
232 let mut schema = jzon::object::Object::new();
234 schema.insert("properties", JsonValue::Object(properties));
235 schema.insert("required", JsonValue::Array(required));
236 schema.insert("type", JsonValue::String("object".to_string()));
237 schema.insert(
238 "$comment",
239 JsonValue::String("A struct which contains a set of fields".to_string()),
240 );
241
242 if let Some(inherit) = &self.inherit {
244 match custom_types.get(inherit) {
245 Some(value) => match &value.data {
246 data_model::DataTypeData::Struct(_) => {
247 dependencies.insert(inherit.clone());
248 schema.insert("$ref", JsonValue::String(inherit.clone()));
249 }
250 _ => {
251 return Err(Error {
252 location: "".to_string(),
253 error: ErrorCore::EnheritStruct(inherit.clone()),
254 })
255 }
256 },
257 None => {
258 return Err(Error {
259 location: "".to_string(),
260 error: ErrorCore::UnknownID(inherit.clone()),
261 });
262 }
263 }
264 }
265
266 return Ok(schema);
267 }
268
269 pub fn schema_value(
277 &self,
278 value: &data_model::SerializationModel,
279 custom_types: &HashMap<String, data_model::DataType>,
280 ) -> Result<JsonValue, Error> {
281 return match value {
282 data_model::SerializationModel::Map(value) => {
283 let mut json_object = jzon::object::Object::new();
285 for field in self.fields.iter() {
286 if let Some(field_value) = value.get(&field.name) {
287 match to_json(field_value, &field.data_type, custom_types) {
288 Ok(value) => {
289 json_object.insert(&field.name, value);
290 }
291 Err(error) => return Err(error.add_field(&field.name)),
292 }
293 } else if let DefaultType::Required = field.default {
294 return Err(Error {
295 location: "".to_string(),
296 error: ErrorCore::StructConversionMissingField(
297 value.clone(),
298 field.name.clone(),
299 ),
300 });
301 }
302 }
303
304 if json_object.len() != value.len() {
306 Err(Error {
307 location: "".to_string(),
308 error: ErrorCore::StructConversionExcessFields(value.clone()),
309 })
310 } else {
311 Ok(JsonValue::Object(json_object))
312 }
313 }
314 _ => Err(Error {
315 location: "".to_string(),
316 error: ErrorCore::SerializationModel(value.clone(), "struct".to_string()),
317 }),
318 };
319 }
320}
321
322impl data_model::Array {
323 pub fn export_schema(
331 &self,
332 custom_types: &HashMap<String, data_model::DataType>,
333 dependencies: &mut HashSet<String>,
334 ) -> Result<jzon::object::Object, Error> {
335 let ref_keyword = is_schema_type(&self.data_type, custom_types, dependencies)?;
337
338 let mut element_schema = jzon::object::Object::new();
340 element_schema.insert(ref_keyword, JsonValue::String(self.data_type.clone()));
341
342 let mut schema = jzon::object::Object::new();
344 schema.insert(
345 "$comment",
346 JsonValue::String("An array of the same type elements".to_string()),
347 );
348 schema.insert("type", JsonValue::String("array".to_string()));
349 schema.insert("items", JsonValue::Object(element_schema));
350
351 return Ok(schema);
352 }
353
354 pub fn schema_value(
362 &self,
363 value: &data_model::SerializationModel,
364 custom_types: &HashMap<String, data_model::DataType>,
365 ) -> Result<JsonValue, Error> {
366 return match value {
367 data_model::SerializationModel::Array(values) => {
368 match values
369 .iter()
370 .enumerate()
371 .map(|(i, value)| {
372 to_json(value, &self.data_type, custom_types).map_err(|error| (i, error))
373 })
374 .collect::<Result<Vec<_>, _>>()
375 {
376 Ok(value) => Ok(JsonValue::Array(value)),
377 Err((i, error)) => Err(error.add_element(i)),
378 }
379 }
380 _ => Err(Error {
381 location: "".to_string(),
382 error: ErrorCore::SerializationModel(value.clone(), "array".to_string()),
383 }),
384 };
385 }
386}
387
388impl data_model::Variant {
389 pub fn export_schema(
397 &self,
398 custom_types: &HashMap<String, data_model::DataType>,
399 dependencies: &mut HashSet<String>,
400 ) -> Result<jzon::object::Object, Error> {
401 let variant_types = self
403 .data_types
404 .iter()
405 .map(|name| {
406 let ref_keyword = is_schema_type(name, custom_types, dependencies)?;
408
409 let mut schema = jzon::object::Object::new();
411 schema.insert(ref_keyword, JsonValue::String(name.clone()));
412
413 return Ok(JsonValue::Object(schema));
414 })
415 .collect::<Result<Vec<_>, Error>>()?;
416
417 let mut schema = jzon::object::Object::new();
419 schema.insert(
420 "$comment",
421 JsonValue::String(
422 "A variant which will convert an input to the first type it matches".to_string(),
423 ),
424 );
425 schema.insert("anyOf", JsonValue::Array(variant_types));
426
427 return Ok(schema);
428 }
429
430 pub fn schema_value(
438 &self,
439 value: &data_model::SerializationModel,
440 custom_types: &HashMap<String, data_model::DataType>,
441 ) -> Result<JsonValue, Error> {
442 let mut failures = Vec::new();
443 for data_type in self.data_types.iter() {
444 match to_json(value, data_type, custom_types) {
445 Ok(value) => return Ok(value),
446 Err(error) => failures.push((data_type.clone(), error)),
447 }
448 }
449
450 return Err(Error {
451 location: "".to_string(),
452 error: ErrorCore::VariantConversion(value.clone(), failures),
453 });
454 }
455}
456
457impl data_model::Enum {
458 pub fn export_schema(
466 &self,
467 custom_types: &HashMap<String, data_model::DataType>,
468 dependencies: &mut HashSet<String>,
469 ) -> Result<jzon::object::Object, Error> {
470 let enum_list = self
472 .types
473 .iter()
474 .map(|value| {
475 let mut schema = if let Some(data_type) = &value.data_type {
476 let ref_keyword = is_schema_type(data_type, custom_types, dependencies)?;
478
479 let mut internal_schema = jzon::object::Object::new();
481 internal_schema.insert(ref_keyword, JsonValue::String(data_type.clone()));
482
483 let mut properties = jzon::object::Object::new();
485 properties.insert(&value.name, JsonValue::Object(internal_schema));
486
487 let mut schema = jzon::object::Object::new();
489 schema.insert("type", JsonValue::String("object".to_string()));
490 schema.insert("additionalProperties", JsonValue::Boolean(false));
491 schema.insert("properties", JsonValue::Object(properties));
492 schema.insert(
493 "required",
494 JsonValue::Array(vec![JsonValue::String(value.name.clone())]),
495 );
496
497 schema
498 } else {
499 let mut schema = jzon::object::Object::new();
500 schema.insert("type", JsonValue::String("string".to_string()));
501 schema.insert("pattern", JsonValue::String(value.name.clone()));
502
503 schema
504 };
505
506 if let Some(description) = &value.description {
507 schema.insert("description", JsonValue::String(description.clone()));
508 }
509
510 return Ok(JsonValue::Object(schema));
511 })
512 .collect::<Result<Vec<_>, Error>>()?;
513
514 let mut schema = jzon::object::Object::new();
516 schema.insert(
517 "$comment",
518 JsonValue::String("A rust-like enum which can be typed".to_string()),
519 );
520 schema.insert("enum", JsonValue::Array(enum_list));
521
522 return Ok(schema);
523 }
524
525 pub fn schema_value(
533 &self,
534 value: &data_model::SerializationModel,
535 custom_types: &HashMap<String, data_model::DataType>,
536 ) -> Result<JsonValue, Error> {
537 return match value {
538 data_model::SerializationModel::Value(value) => {
539 if self
540 .types
541 .iter()
542 .filter(|enum_type| enum_type.data_type.is_none())
543 .any(|enum_type| &enum_type.name == value)
544 {
545 return Ok(JsonValue::String(value.clone()));
546 } else {
547 Err(Error {
548 location: "".to_string(),
549 error: ErrorCore::EnumConversion(value.clone()),
550 })
551 }
552 }
553 data_model::SerializationModel::Map(value) => {
554 if value.len() != 1 {
555 return Err(Error {
556 location: "".to_string(),
557 error: ErrorCore::TypedEnumLayout(value.clone()),
558 });
559 }
560
561 let (key, val) = value.iter().next().unwrap();
562 let enum_type = self
563 .types
564 .iter()
565 .filter(|enum_type| enum_type.data_type.is_some())
566 .find(|enum_type| &enum_type.name == key);
567 if let Some(enum_type) = enum_type {
568 let internal_type =
569 match to_json(val, enum_type.data_type.as_ref().unwrap(), custom_types) {
570 Ok(value) => value,
571 Err(error) => return Err(error.add_field(key)),
572 };
573
574 let mut json_object = jzon::object::Object::new();
575 json_object.insert(key, internal_type);
576 Ok(JsonValue::Object(json_object))
577 } else {
578 return Err(Error {
579 location: "".to_string(),
580 error: ErrorCore::TypedEnumConversion(value.clone()),
581 });
582 }
583 }
584 data_model::SerializationModel::Array(_) => Err(Error {
585 location: "".to_string(),
586 error: ErrorCore::SerializationModel(value.clone(), "enum".to_string()),
587 }),
588 };
589 }
590}
591
592impl data_model::ConstrainedType {
593 pub fn export_schema(
601 &self,
602 custom_types: &HashMap<String, data_model::DataType>,
603 dependencies: &mut HashSet<String>,
604 ) -> Result<jzon::object::Object, Error> {
605 let ref_keyword = is_schema_type(&self.data_type, custom_types, dependencies)?;
607
608 let constraints = self
610 .constraints
611 .iter()
612 .map(|constraint| constraint.export_schema())
613 .collect::<Vec<_>>()
614 .join(", ");
615
616 let mut schema = jzon::object::Object::new();
618 schema.insert(
619 "$comment",
620 JsonValue::String(format!(
621 "A constrained type which must keep the following true: [{}]",
622 constraints
623 )),
624 );
625 schema.insert(ref_keyword, JsonValue::String(self.data_type.clone()));
626
627 return Ok(schema);
628 }
629
630 pub fn schema_value(
638 &self,
639 value: &data_model::SerializationModel,
640 custom_types: &HashMap<String, data_model::DataType>,
641 ) -> Result<JsonValue, Error> {
642 return to_json(value, &self.data_type, custom_types);
643 }
644}
645
646impl data_model::Constraint {
647 pub fn export_schema(&self) -> String {
649 return match self {
650 Self::Arithmetic(value) => value.clone(),
651 Self::Function(value) => format!("{value}(x)"),
652 };
653 }
654}
655
656fn to_json(
666 value: &data_model::SerializationModel,
667 data_type: &str,
668 custom_types: &HashMap<String, data_model::DataType>,
669) -> Result<JsonValue, Error> {
670 return match data_type {
671 "boolean" => match value {
672 data_model::SerializationModel::Value(value) => match value.as_str() {
673 "true" => Ok(JsonValue::Boolean(true)),
674 "false" => Ok(JsonValue::Boolean(false)),
675 _ => Err(Error {
676 location: "".to_string(),
677 error: ErrorCore::BoolConversion(value.clone()),
678 }),
679 },
680 _ => Err(Error {
681 location: "".to_string(),
682 error: ErrorCore::SerializationModel(value.clone(), "boolean".to_string()),
683 }),
684 },
685 "integer" => match value {
686 data_model::SerializationModel::Value(value) => match value.parse::<i64>() {
687 Ok(value) => Ok(JsonValue::Number(jzon::number::Number::from(value))),
688 Err(_) => Err(Error {
689 location: "".to_string(),
690 error: ErrorCore::IntegerConversion(value.clone()),
691 }),
692 },
693 _ => Err(Error {
694 location: "".to_string(),
695 error: ErrorCore::SerializationModel(value.clone(), "integer".to_string()),
696 }),
697 },
698 "number" => match value {
699 data_model::SerializationModel::Value(value) => match value.parse::<f64>() {
700 Ok(value) => Ok(JsonValue::Number(jzon::number::Number::from(value))),
701 Err(_) => Err(Error {
702 location: "".to_string(),
703 error: ErrorCore::FloatConversion(value.clone()),
704 }),
705 },
706 _ => Err(Error {
707 location: "".to_string(),
708 error: ErrorCore::SerializationModel(value.clone(), "number".to_string()),
709 }),
710 },
711 "string" => match value {
712 data_model::SerializationModel::Value(value) => Ok(JsonValue::String(value.clone())),
713 _ => Err(Error {
714 location: "".to_string(),
715 error: ErrorCore::SerializationModel(value.clone(), "string".to_string()),
716 }),
717 },
718 _ => {
719 if let Some(custom_type) = custom_types.get(data_type) {
720 custom_type.schema_value(value, custom_types)
721 } else {
722 Err(Error {
723 location: "".to_string(),
724 error: ErrorCore::UnknownType(data_type.to_string()),
725 })
726 }
727 }
728 };
729}
730
731fn is_schema_type(
745 name: &str,
746 custom_types: &HashMap<String, data_model::DataType>,
747 dependencies: &mut HashSet<String>,
748) -> Result<&'static str, Error> {
749 return Ok(if let Some(_) = custom_types.get(name) {
750 dependencies.insert(name.to_string());
751 "$ref"
752 } else {
753 if !vec!["boolean", "integer", "number", "string"].contains(&name) {
754 return Err(Error {
755 location: "".to_string(),
756 error: ErrorCore::UnknownType(name.to_string()),
757 });
758 }
759 "type"
760 });
761}
762
763#[derive(Debug, Clone)]
766pub struct Error {
767 pub location: String,
769 pub error: ErrorCore,
771}
772
773impl Error {
774 fn add_field(self, base: &str) -> Error {
780 let location = format!(".{}{}", base, self.location);
781
782 return Error {
783 location,
784 error: self.error,
785 };
786 }
787
788 fn add_element(self, index: usize) -> Error {
794 let location = format!("[{}]{}", index, self.location);
795
796 return Error {
797 location,
798 error: self.error,
799 };
800 }
801}
802
803impl fmt::Display for Error {
804 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
805 return write!(f, "{}: {}", self.location, self.error);
806 }
807}
808
809impl From<data_model::Error> for Error {
810 fn from(value: data_model::Error) -> Self {
811 return Error {
812 location: value.location.clone(),
813 error: ErrorCore::MacroError(value),
814 };
815 }
816}
817
818#[derive(thiserror::Error, Debug, Clone)]
820pub enum ErrorCore {
821 #[error("The type id {:?} does not exist in the model", .0)]
823 UnknownID(String),
824 #[error("The type id {:?} does not exist in the model or as a builtin type", .0)]
826 UnknownType(String),
827 #[error("The type id {:?} must refer to a struct when inheriting", .0)]
829 EnheritStruct(String),
830 #[error("The serialization model {:?} is incompatible with the type: {:}", .0, .1)]
832 SerializationModel(data_model::SerializationModel, String),
833 #[error("Unable to convert {:?} to a boolean", .0)]
835 BoolConversion(String),
836 #[error("Unable to convert {:?} to an integer", .0)]
838 IntegerConversion(String),
839 #[error("Unable to convert {:?} to a float", .0)]
841 FloatConversion(String),
842 #[error("Unable to convert {:?} to an enum", .0)]
844 EnumConversion(String),
845 #[error("Unable to convert {:?} to a typed enum", .0)]
847 TypedEnumConversion(HashMap<String, data_model::SerializationModel>),
848 #[error("Unable to convert {:?} to a typed enum because it did not have a single field", .0)]
850 TypedEnumLayout(HashMap<String, data_model::SerializationModel>),
851 #[error("Unable to convert {:?} to a variant with the following errors: {:?}", .0, .1)]
853 VariantConversion(data_model::SerializationModel, Vec<(String, Error)>),
854 #[error("Unable to convert {:?} to an struct because it is missing field {:}", .0, .1)]
856 StructConversionMissingField(HashMap<String, data_model::SerializationModel>, String),
857 #[error("Unable to convert {:?} to an struct because it has excess fields", .0)]
859 StructConversionExcessFields(HashMap<String, data_model::SerializationModel>),
860 #[error("An error occured when expanding macros: {:?}", .0)]
862 MacroError(data_model::Error),
863}