Skip to main content

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<Constraint>,
190}
191
192/// Defines a single constraint
193#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
194pub enum Constraint {
195    /// Any constraint using c-like arithmetic, must result in a boolean value
196    Arithmetic(String),
197    /// Name of a function to call f_name(x), must return a boolean value, the
198    /// name must be a C++ style with possible namespaces
199    Function(String),
200}
201
202/// Describes whether a field is required or optional
203#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
204pub enum DefaultType {
205    /// The field must be supplied
206    Required,
207    /// The field can be supplied, the type of the field will be
208    /// Option<data_type>, if not supplied it defaults to None
209    Optional,
210    /// The field can be supplied, if not supplied it defaults to the default
211    /// value
212    Default(SerializationModel),
213}
214
215/// A generic serialization model which can be used to serialize any data model
216#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
217#[serde(untagged)]
218pub enum SerializationModel {
219    /// A generic key-value pair map where the key must be a string
220    Map(HashMap<String, SerializationModel>),
221    /// An array of other serialization models
222    Array(Vec<SerializationModel>),
223    /// A single value, must be a string
224    Value(String),
225}
226
227/// Expands all macros in a serialization model
228///
229/// # Parameters
230///
231/// value: The serialization model to expand macros in
232///
233/// macros: The macros to use for expansions
234///
235/// used_macros: A set of macros that are currently being used, used to prevent infinite recursion
236pub(crate) fn expand_macros<'a>(
237    value: &SerializationModel,
238    macros: &'a HashMap<String, SerializationModel>,
239    used_macros: &mut HashSet<&'a str>,
240) -> Result<SerializationModel, Error> {
241    return match value {
242        SerializationModel::Map(value) => value
243            .iter()
244            .map(|(k, v)| match expand_macros(v, macros, used_macros) {
245                Ok(value) => Ok((k.clone(), value)),
246                Err(error) => Err(error.add_field(k)),
247            })
248            .collect::<Result<HashMap<_, _>, _>>()
249            .map(SerializationModel::Map),
250        SerializationModel::Array(value) => value
251            .iter()
252            .enumerate()
253            .map(|(i, v)| match expand_macros(v, macros, used_macros) {
254                Ok(value) => Ok(value),
255                Err(error) => Err(error.add_element(i)),
256            })
257            .collect::<Result<Vec<_>, _>>()
258            .map(SerializationModel::Array),
259        SerializationModel::Value(value) => {
260            // Do a full macro insert if the string is just a macro definition
261            if value.starts_with('$')
262                && value.ends_with('$')
263                && value.len() > 2
264                && value.chars().filter(|c| *c == '$').count() == 2
265            {
266                let macro_name = &value[1..value.len() - 1];
267
268                // Prevent infinite recursion
269                if used_macros.contains(macro_name) {
270                    return Err(Error {
271                        location: "".to_string(),
272                        error: ErrorCore::RecursiveMacro(macro_name.to_string()),
273                    });
274                }
275
276                // Insert the macro
277                return if let Some((macro_key, macro_value)) = macros.get_key_value(macro_name) {
278                    used_macros.insert(macro_key.as_str());
279                    let expanded_macro = expand_macros(macro_value, macros, used_macros);
280                    used_macros.remove(macro_key.as_str());
281                    match expanded_macro {
282                        Ok(value) => Ok(value),
283                        Err(error) => Err(error.add_macro(macro_name)),
284                    }
285                } else {
286                    Err(Error {
287                        location: "".to_string(),
288                        error: ErrorCore::MissingMacro(macro_name.to_string()),
289                    })
290                };
291            }
292
293            // Otherwise do a partial macro insertion
294            let mut expanded_string = String::new();
295            let mut current_index = 0;
296            while current_index < value.len() {
297                // Find the beginning of the next macro
298                if let Some(start_index) = value[current_index..].find('$') {
299                    let start_index = start_index + current_index + 1;
300                    expanded_string.push_str(&value[current_index..start_index - 1]);
301
302                    // Skip if it should just be interpreted as a dollar sign
303                    if start_index < value.len() && &value[start_index..start_index + 1] == "$" {
304                        expanded_string.push('$');
305                        current_index = start_index + 1;
306                        continue;
307                    }
308
309                    // Find the end of the macro
310                    if let Some(end_index) = value[start_index..].find('$') {
311                        let end_index = end_index + start_index;
312                        let macro_name = &value[start_index..end_index];
313
314                        // Prevent infinite recursion
315                        if used_macros.contains(macro_name) {
316                            return Err(Error {
317                                location: "".to_string(),
318                                error: ErrorCore::RecursiveMacro(macro_name.to_string()),
319                            });
320                        }
321
322                        if let Some((macro_key, macro_value)) = macros.get_key_value(macro_name) {
323                            // Insert the macro
324                            used_macros.insert(macro_key.as_str());
325                            let expanded_macro = expand_macros(macro_value, macros, used_macros);
326                            used_macros.remove(macro_key.as_str());
327                            match expanded_macro {
328                                Ok(ok_value) => match ok_value {
329                                    SerializationModel::Value(value) => {
330                                        expanded_string.push_str(&value);
331                                    }
332                                    _ => {
333                                        return Err(Error {
334                                            location: "".to_string(),
335                                            error: ErrorCore::PartialMacro(
336                                                macro_name.to_string(),
337                                                value.clone(),
338                                            ),
339                                        });
340                                    }
341                                },
342                                Err(error) => {
343                                    return Err(error.add_macro(macro_name));
344                                }
345                            }
346                        } else {
347                            return Err(Error {
348                                location: "".to_string(),
349                                error: ErrorCore::MissingMacro(macro_name.to_string()),
350                            });
351                        }
352
353                        current_index = end_index + 1;
354                    } else {
355                        return Err(Error {
356                            location: "".to_string(),
357                            error: ErrorCore::IncompleteMacro(value.clone()),
358                        });
359                    }
360                } else {
361                    expanded_string.push_str(&value[current_index..]);
362                    break;
363                }
364            }
365
366            Ok(SerializationModel::Value(expanded_string))
367        }
368    };
369}
370
371/// Errors for when converting generic data models into JSON schema data models
372/// including location
373#[derive(Debug, Clone)]
374pub struct Error {
375    /// The location where the error occured
376    pub location: String,
377    /// The actual error that occured
378    pub error: ErrorCore,
379}
380
381impl Error {
382    /// Sets the current location to be the field of the given base
383    ///
384    /// # Parameters
385    ///
386    /// base: The base to set in the location
387    fn add_field(self, base: &str) -> Error {
388        let location = format!(".{}{}", base, self.location);
389
390        return Error {
391            location,
392            error: self.error,
393        };
394    }
395
396    /// Sets the current location to be the element of a field of the given base
397    ///
398    /// # Parameters
399    ///
400    /// index: The index of the field
401    fn add_element(self, index: usize) -> Error {
402        let location = format!("[{}]{}", index, self.location);
403
404        return Error {
405            location,
406            error: self.error,
407        };
408    }
409
410    /// Sets the current location to be the element of a field of the given base
411    ///
412    /// # Parameters
413    ///
414    /// index: The index of the field
415    fn add_macro(self, index: &str) -> Error {
416        let location = format!("[{}]{}", index, self.location);
417
418        return Error {
419            location,
420            error: self.error,
421        };
422    }
423}
424
425impl fmt::Display for Error {
426    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
427        return write!(f, "{}: {}", self.location, self.error);
428    }
429}
430
431/// Errors for when converting generic data models into JSON schema data models
432#[derive(thiserror::Error, Debug, Clone)]
433pub enum ErrorCore {
434    /// Macros used recursively
435    #[error("The macro \"{}\" is used recursively", .0)]
436    RecursiveMacro(String),
437    /// Macro is missing
438    #[error("The macro \"{}\" is not defined", .0)]
439    MissingMacro(String),
440    /// Macro is incomplete
441    #[error("The string \"{}\" begins a macro without ending it", .0)]
442    IncompleteMacro(String),
443    /// A partial macro insertion can only have a string value
444    #[error("The partial macro insertion of \"{}\" in \"{}\" must be a string", .0, .1)]
445    PartialMacro(String, String),
446    /// A header macro insertion can only have a string value
447    #[error("The macro insertion in the header \"{}\" must be a string", .0)]
448    HeaderMacro(String),
449    /// A footer macro insertion can only have a string value
450    #[error("The macro insertion in the footer \"{}\" must be a string", .0)]
451    FooterMacro(String),
452}