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.constraints.join(", ");
610
611 let mut schema = jzon::object::Object::new();
613 schema.insert(
614 "$comment",
615 JsonValue::String(format!(
616 "A constrained type which must keep the following true: [{}]",
617 constraints
618 )),
619 );
620 schema.insert(ref_keyword, JsonValue::String(self.data_type.clone()));
621
622 return Ok(schema);
623 }
624
625 pub fn schema_value(
633 &self,
634 value: &data_model::SerializationModel,
635 custom_types: &HashMap<String, data_model::DataType>,
636 ) -> Result<JsonValue, Error> {
637 return to_json(value, &self.data_type, custom_types);
638 }
639}
640
641fn to_json(
651 value: &data_model::SerializationModel,
652 data_type: &str,
653 custom_types: &HashMap<String, data_model::DataType>,
654) -> Result<JsonValue, Error> {
655 return match data_type {
656 "boolean" => match value {
657 data_model::SerializationModel::Value(value) => match value.as_str() {
658 "true" => Ok(JsonValue::Boolean(true)),
659 "false" => Ok(JsonValue::Boolean(false)),
660 _ => Err(Error {
661 location: "".to_string(),
662 error: ErrorCore::BoolConversion(value.clone()),
663 }),
664 },
665 _ => Err(Error {
666 location: "".to_string(),
667 error: ErrorCore::SerializationModel(value.clone(), "boolean".to_string()),
668 }),
669 },
670 "integer" => match value {
671 data_model::SerializationModel::Value(value) => match value.parse::<i64>() {
672 Ok(value) => Ok(JsonValue::Number(jzon::number::Number::from(value))),
673 Err(_) => Err(Error {
674 location: "".to_string(),
675 error: ErrorCore::IntegerConversion(value.clone()),
676 }),
677 },
678 _ => Err(Error {
679 location: "".to_string(),
680 error: ErrorCore::SerializationModel(value.clone(), "integer".to_string()),
681 }),
682 },
683 "number" => match value {
684 data_model::SerializationModel::Value(value) => match value.parse::<f64>() {
685 Ok(value) => Ok(JsonValue::Number(jzon::number::Number::from(value))),
686 Err(_) => Err(Error {
687 location: "".to_string(),
688 error: ErrorCore::FloatConversion(value.clone()),
689 }),
690 },
691 _ => Err(Error {
692 location: "".to_string(),
693 error: ErrorCore::SerializationModel(value.clone(), "number".to_string()),
694 }),
695 },
696 "string" => match value {
697 data_model::SerializationModel::Value(value) => Ok(JsonValue::String(value.clone())),
698 _ => Err(Error {
699 location: "".to_string(),
700 error: ErrorCore::SerializationModel(value.clone(), "string".to_string()),
701 }),
702 },
703 _ => {
704 if let Some(custom_type) = custom_types.get(data_type) {
705 custom_type.schema_value(value, custom_types)
706 } else {
707 Err(Error {
708 location: "".to_string(),
709 error: ErrorCore::UnknownType(data_type.to_string()),
710 })
711 }
712 }
713 };
714}
715
716fn is_schema_type(
730 name: &str,
731 custom_types: &HashMap<String, data_model::DataType>,
732 dependencies: &mut HashSet<String>,
733) -> Result<&'static str, Error> {
734 return Ok(if let Some(_) = custom_types.get(name) {
735 dependencies.insert(name.to_string());
736 "$ref"
737 } else {
738 if !vec!["boolean", "integer", "number", "string"].contains(&name) {
739 return Err(Error {
740 location: "".to_string(),
741 error: ErrorCore::UnknownType(name.to_string()),
742 });
743 }
744 "type"
745 });
746}
747
748#[derive(Debug, Clone)]
751pub struct Error {
752 pub location: String,
754 pub error: ErrorCore,
756}
757
758impl Error {
759 fn add_field(self, base: &str) -> Error {
765 let location = format!(".{}{}", base, self.location);
766
767 return Error {
768 location,
769 error: self.error,
770 };
771 }
772
773 fn add_element(self, index: usize) -> Error {
779 let location = format!("[{}]{}", index, self.location);
780
781 return Error {
782 location,
783 error: self.error,
784 };
785 }
786}
787
788impl fmt::Display for Error {
789 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
790 return write!(f, "{}: {}", self.location, self.error);
791 }
792}
793
794impl From<data_model::Error> for Error {
795 fn from(value: data_model::Error) -> Self {
796 return Error {
797 location: value.location.clone(),
798 error: ErrorCore::MacroError(value),
799 };
800 }
801}
802
803#[derive(thiserror::Error, Debug, Clone)]
805pub enum ErrorCore {
806 #[error("The type id {:?} does not exist in the model", .0)]
808 UnknownID(String),
809 #[error("The type id {:?} does not exist in the model or as a builtin type", .0)]
811 UnknownType(String),
812 #[error("The type id {:?} must refer to a struct when inheriting", .0)]
814 EnheritStruct(String),
815 #[error("The serialization model {:?} is incompatible with the type: {:}", .0, .1)]
817 SerializationModel(data_model::SerializationModel, String),
818 #[error("Unable to convert {:?} to a boolean", .0)]
820 BoolConversion(String),
821 #[error("Unable to convert {:?} to an integer", .0)]
823 IntegerConversion(String),
824 #[error("Unable to convert {:?} to a float", .0)]
826 FloatConversion(String),
827 #[error("Unable to convert {:?} to an enum", .0)]
829 EnumConversion(String),
830 #[error("Unable to convert {:?} to a typed enum", .0)]
832 TypedEnumConversion(HashMap<String, data_model::SerializationModel>),
833 #[error("Unable to convert {:?} to a typed enum because it did not have a single field", .0)]
835 TypedEnumLayout(HashMap<String, data_model::SerializationModel>),
836 #[error("Unable to convert {:?} to a variant with the following errors: {:?}", .0, .1)]
838 VariantConversion(data_model::SerializationModel, Vec<(String, Error)>),
839 #[error("Unable to convert {:?} to an struct because it is missing field {:}", .0, .1)]
841 StructConversionMissingField(HashMap<String, data_model::SerializationModel>, String),
842 #[error("Unable to convert {:?} to an struct because it has excess fields", .0)]
844 StructConversionExcessFields(HashMap<String, data_model::SerializationModel>),
845 #[error("An error occured when expanding macros: {:?}", .0)]
847 MacroError(data_model::Error),
848}