termite_dmg/
data_model.rs

1use serde::{Deserialize, Serialize};
2use std::{
3    collections::{HashMap, HashSet},
4    fmt,
5};
6
7/// An entire data model
8#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
9#[serde(default)]
10pub struct DataModel {
11    /// List of the the data types to implement
12    pub data_types: Vec<DataType>,
13    /// List of all header data used to include external packages
14    pub headers: HashMap<String, String>,
15    /// List of all footer data
16    pub footers: HashMap<String, String>,
17    /// The nested namespace to put the data model into
18    pub namespace: Vec<String>,
19    /// A set of replacement macros to use for default values
20    pub macros: HashMap<String, SerializationModel>,
21}
22
23impl DataModel {
24    /// Exports the data model to a yaml string
25    pub fn export_yaml(&self) -> Result<String, serde_yaml::Error> {
26        return serde_yaml::to_string(self);
27    }
28
29    /// Exports the data model to a json string
30    pub fn export_json(&self) -> Result<String, serde_json::Error> {
31        return serde_json::to_string(self);
32    }
33
34    /// Imports a data model from a yaml string
35    pub fn import_yaml(mode: &str) -> Result<DataModel, serde_yaml::Error> {
36        return serde_yaml::from_value(sanitize_yaml(serde_yaml::from_str(mode)?));
37    }
38
39    /// Imports a data model from a json string
40    pub fn import_json(mode: &str) -> Result<DataModel, serde_json::Error> {
41        return serde_json::from_value(sanitize_json(serde_json::from_str(mode)?));
42    }
43}
44
45fn sanitize_yaml(value: serde_yaml::Value) -> serde_yaml::Value {
46    match value {
47        serde_yaml::Value::Bool(value) => {
48            if value {
49                serde_yaml::Value::String("true".to_string())
50            } else {
51                serde_yaml::Value::String("false".to_string())
52            }
53        }
54        serde_yaml::Value::Mapping(value) => serde_yaml::Value::Mapping(
55            value
56                .into_iter()
57                .map(|(k, v)| (k, sanitize_yaml(v)))
58                .collect(),
59        ),
60        serde_yaml::Value::Number(value) => serde_yaml::Value::String(value.to_string()),
61        serde_yaml::Value::Sequence(value) => {
62            serde_yaml::Value::Sequence(value.into_iter().map(sanitize_yaml).collect())
63        }
64        serde_yaml::Value::Tagged(value) => {
65            serde_yaml::Value::Tagged(Box::new(serde_yaml::value::TaggedValue {
66                tag: value.tag,
67                value: sanitize_yaml(value.value),
68            }))
69        }
70        _ => value,
71    }
72}
73
74fn sanitize_json(value: serde_json::Value) -> serde_json::Value {
75    match value {
76        serde_json::Value::Bool(value) => {
77            if value {
78                serde_json::Value::String("true".to_string())
79            } else {
80                serde_json::Value::String("false".to_string())
81            }
82        }
83        serde_json::Value::Object(value) => serde_json::Value::Object(
84            value
85                .into_iter()
86                .map(|(k, v)| (k, sanitize_json(v)))
87                .collect(),
88        ),
89        serde_json::Value::Number(value) => serde_json::Value::String(value.to_string()),
90        serde_json::Value::Array(value) => {
91            serde_json::Value::Array(value.into_iter().map(sanitize_json).collect())
92        }
93        _ => value,
94    }
95}
96
97/// Any data type (struct, variant, ect.)
98#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
99pub struct DataType {
100    /// The name of the type
101    pub name: String,
102    /// The description of the type
103    pub description: Option<String>,
104    /// The type specific data
105    pub data: DataTypeData,
106}
107
108/// Supplies the type specific information for a data type
109#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
110pub enum DataTypeData {
111    /// Describes a struct
112    Struct(Struct),
113    /// Describes an array
114    Array(Array),
115    /// Describes a variant
116    Variant(Variant),
117    /// Describes an enum
118    Enum(Enum),
119    /// Describes a constrained type
120    ConstrainedType(ConstrainedType),
121}
122
123/// A struct which has a number of fields
124///
125/// It will automatically add a termite::Node::Map field called extra_fields
126/// which holds all fields which were not captured when parsing
127#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
128pub struct Struct {
129    /// A list of all the fields of the struct
130    pub fields: Vec<StructField>,
131    /// The name of a different Struct this Struct builds onto, used in Schema
132    /// generation
133    pub inherit: Option<String>,
134}
135
136/// The data for a single field in a struct
137#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
138pub struct StructField {
139    /// The name of the field
140    pub name: String,
141    /// The description of the field
142    pub description: Option<String>,
143    /// What type the field is, without Option<>
144    pub data_type: String,
145    /// A default value if it it not required
146    pub default: DefaultType,
147}
148
149/// An array of values of the same data type
150#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
151pub struct Array {
152    /// The data type for all elements
153    pub data_type: String,
154}
155
156/// A variant which can be any of a number of different types, when parsing it
157/// will attempt to parse all types from the start until it is successful
158#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
159pub struct Variant {
160    /// The list of data types the variant can be
161    pub data_types: Vec<String>,
162}
163
164/// An enum, includes a number of enum values
165#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
166pub struct Enum {
167    /// All the possible enum values
168    pub types: Vec<EnumType>,
169}
170
171/// An enum value, describes a specific enum type
172#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
173pub struct EnumType {
174    /// The name of this enum type
175    pub name: String,
176    /// The description describing this enum type
177    pub description: Option<String>,
178    /// The type this enum type is wrapping, may be omitted for an empty type
179    pub data_type: Option<String>,
180}
181
182/// A constrained type, wraps any other type and adds constraints onto them
183#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
184pub struct ConstrainedType {
185    /// The type that is constrained
186    pub data_type: String,
187    /// All extra constraints for the type, must be written as an expression where
188    /// the constrained value is denoted x
189    pub constraints: Vec<String>,
190}
191
192/// Describes whether a field is required or optional
193#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
194pub enum DefaultType {
195    /// The field must be supplied
196    Required,
197    /// The field can be supplied, the type of the field will be
198    /// Option<data_type>, if not supplied it defaults to None
199    Optional,
200    /// The field can be supplied, if not supplied it defaults to the default
201    /// value
202    Default(SerializationModel),
203}
204
205/// A generic serialization model which can be used to serialize any data model
206#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
207#[serde(untagged)]
208pub enum SerializationModel {
209    /// A generic key-value pair map where the key must be a string
210    Map(HashMap<String, SerializationModel>),
211    /// An array of other serialization models
212    Array(Vec<SerializationModel>),
213    /// A single value, must be a string
214    Value(String),
215}
216
217/// Expands all macros in a serialization model
218///
219/// # Parameters
220///
221/// value: The serialization model to expand macros in
222///
223/// macros: The macros to use for expansions
224///
225/// used_macros: A set of macros that are currently being used, used to prevent infinite recursion
226pub(crate) fn expand_macros<'a>(
227    value: &SerializationModel,
228    macros: &'a HashMap<String, SerializationModel>,
229    used_macros: &mut HashSet<&'a str>,
230) -> Result<SerializationModel, Error> {
231    return match value {
232        SerializationModel::Map(value) => value
233            .iter()
234            .map(|(k, v)| match expand_macros(v, macros, used_macros) {
235                Ok(value) => Ok((k.clone(), value)),
236                Err(error) => Err(error.add_field(k)),
237            })
238            .collect::<Result<HashMap<_, _>, _>>()
239            .map(SerializationModel::Map),
240        SerializationModel::Array(value) => value
241            .iter()
242            .enumerate()
243            .map(|(i, v)| match expand_macros(v, macros, used_macros) {
244                Ok(value) => Ok(value),
245                Err(error) => Err(error.add_element(i)),
246            })
247            .collect::<Result<Vec<_>, _>>()
248            .map(SerializationModel::Array),
249        SerializationModel::Value(value) => {
250            // Do a full macro insert if the string is just a macro definition
251            if value.starts_with('$')
252                && value.ends_with('$')
253                && value.len() > 2
254                && value.chars().filter(|c| *c == '$').count() == 2
255            {
256                let macro_name = &value[1..value.len() - 1];
257
258                // Prevent infinite recursion
259                if used_macros.contains(macro_name) {
260                    return Err(Error {
261                        location: "".to_string(),
262                        error: ErrorCore::RecursiveMacro(macro_name.to_string()),
263                    });
264                }
265
266                // Insert the macro
267                return if let Some((macro_key, macro_value)) = macros.get_key_value(macro_name) {
268                    used_macros.insert(macro_key.as_str());
269                    let expanded_macro = expand_macros(macro_value, macros, used_macros);
270                    used_macros.remove(macro_key.as_str());
271                    match expanded_macro {
272                        Ok(value) => Ok(value),
273                        Err(error) => Err(error.add_macro(macro_name)),
274                    }
275                } else {
276                    Err(Error {
277                        location: "".to_string(),
278                        error: ErrorCore::MissingMacro(macro_name.to_string()),
279                    })
280                };
281            }
282
283            // Otherwise do a partial macro insertion
284            let mut expanded_string = String::new();
285            let mut current_index = 0;
286            while current_index < value.len() {
287                // Find the beginning of the next macro
288                if let Some(start_index) = value[current_index..].find('$') {
289                    let start_index = start_index + current_index + 1;
290                    expanded_string.push_str(&value[current_index..start_index - 1]);
291
292                    // Skip if it should just be interpreted as a dollar sign
293                    if start_index < value.len() && &value[start_index..start_index + 1] == "$" {
294                        expanded_string.push('$');
295                        current_index = start_index + 1;
296                        continue;
297                    }
298
299                    // Find the end of the macro
300                    if let Some(end_index) = value[start_index..].find('$') {
301                        let end_index = end_index + start_index;
302                        let macro_name = &value[start_index..end_index];
303
304                        // Prevent infinite recursion
305                        if used_macros.contains(macro_name) {
306                            return Err(Error {
307                                location: "".to_string(),
308                                error: ErrorCore::RecursiveMacro(macro_name.to_string()),
309                            });
310                        }
311
312                        if let Some((macro_key, macro_value)) = macros.get_key_value(macro_name) {
313                            // Insert the macro
314                            used_macros.insert(macro_key.as_str());
315                            let expanded_macro = expand_macros(macro_value, macros, used_macros);
316                            used_macros.remove(macro_key.as_str());
317                            match expanded_macro {
318                                Ok(ok_value) => match ok_value {
319                                    SerializationModel::Value(value) => {
320                                        expanded_string.push_str(&value);
321                                    }
322                                    _ => {
323                                        return Err(Error {
324                                            location: "".to_string(),
325                                            error: ErrorCore::PartialMacro(
326                                                macro_name.to_string(),
327                                                value.clone(),
328                                            ),
329                                        });
330                                    }
331                                },
332                                Err(error) => {
333                                    return Err(error.add_macro(macro_name));
334                                }
335                            }
336                        } else {
337                            return Err(Error {
338                                location: "".to_string(),
339                                error: ErrorCore::MissingMacro(macro_name.to_string()),
340                            });
341                        }
342
343                        current_index = end_index + 1;
344                    } else {
345                        return Err(Error {
346                            location: "".to_string(),
347                            error: ErrorCore::IncompleteMacro(value.clone()),
348                        });
349                    }
350                } else {
351                    expanded_string.push_str(&value[current_index..]);
352                    break;
353                }
354            }
355
356            Ok(SerializationModel::Value(expanded_string))
357        }
358    };
359}
360
361/// Errors for when converting generic data models into JSON schema data models
362/// including location
363#[derive(Debug, Clone)]
364pub struct Error {
365    /// The location where the error occured
366    pub location: String,
367    /// The actual error that occured
368    pub error: ErrorCore,
369}
370
371impl Error {
372    /// Sets the current location to be the field of the given base
373    ///
374    /// # Parameters
375    ///
376    /// base: The base to set in the location
377    fn add_field(self, base: &str) -> Error {
378        let location = format!(".{}{}", base, self.location);
379
380        return Error {
381            location,
382            error: self.error,
383        };
384    }
385
386    /// Sets the current location to be the element of a field of the given base
387    ///
388    /// # Parameters
389    ///
390    /// index: The index of the field
391    fn add_element(self, index: usize) -> Error {
392        let location = format!("[{}]{}", index, self.location);
393
394        return Error {
395            location,
396            error: self.error,
397        };
398    }
399
400    /// Sets the current location to be the element of a field of the given base
401    ///
402    /// # Parameters
403    ///
404    /// index: The index of the field
405    fn add_macro(self, index: &str) -> Error {
406        let location = format!("[{}]{}", index, self.location);
407
408        return Error {
409            location,
410            error: self.error,
411        };
412    }
413}
414
415impl fmt::Display for Error {
416    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
417        return write!(f, "{}: {}", self.location, self.error);
418    }
419}
420
421/// Errors for when converting generic data models into JSON schema data models
422#[derive(thiserror::Error, Debug, Clone)]
423pub enum ErrorCore {
424    /// Macros used recursively
425    #[error("The macro \"{}\" is used recursively", .0)]
426    RecursiveMacro(String),
427    /// Macro is missing
428    #[error("The macro \"{}\" is not defined", .0)]
429    MissingMacro(String),
430    /// Macro is incomplete
431    #[error("The string \"{}\" begins a macro without ending it", .0)]
432    IncompleteMacro(String),
433    /// A partial macro insertion can only have a string value
434    #[error("The partial macro insertion of \"{}\" in \"{}\" must be a string", .0, .1)]
435    PartialMacro(String, String),
436}