balena_cdsl/dsl/schema/
mod.rs

1//! Top-level constructs representing the configuration DSL language
2use std::collections::HashMap;
3
4use regex::Regex;
5use serde_derive::Deserialize;
6use serde_derive::Serialize;
7
8use crate::dsl::schema::object_types::ObjectType;
9
10pub mod deserialization;
11pub mod compiler;
12pub mod object_types;
13mod dynamic;
14
15/// Represents the root of the yaml DSL document
16#[derive(Clone, Debug)]
17pub struct DocumentRoot(Schema);
18
19/// A first-class collection of `NamedSchema`'s, has convenience methods exposed
20#[derive(Clone, Debug)]
21pub struct SchemaList {
22    entries: Vec<NamedSchema>,
23}
24
25/// A schema with an associated name, an entry in `SchemaList`
26// fixme move serialize implementation to the `output` module
27#[derive(Clone, Debug, Serialize)]
28pub struct NamedSchema {
29    pub name: String,
30    #[serde(flatten)]
31    pub schema: Schema,
32}
33
34/// Everything that a schema at any level can represent, see schema and subschema in the spec
35#[derive(Clone, Debug)]
36pub struct Schema {
37    pub version: Option<u64>,
38    pub object_type: ObjectType,
39    pub annotations: Annotations,
40    /// children of a schema are all schemas defined inside of this schema
41    pub children: Option<SchemaList>,
42    /// this represents `keys` and `values` - defining dynamic objects
43    pub dynamic: Option<Box<KeysValues>>,
44    /// unparsed formula, can't be evaluated by CDSL as we don't have data, just schema
45    pub formula: Option<String>,
46}
47
48/// Represents [`SchemaAnnotations`](https://github.com/balena-io/balena/blob/832f5551127dd8e1e82fa082bea97fc4db81c3ce/specs/configuration-dsl.md#schema-annotations) from the spec minus the `default` keyword, that lives in the object bounds information
49#[derive(Clone, Default, Debug, Deserialize)]
50pub struct Annotations {
51    pub title: Option<String>,
52    pub help: Option<String>,
53    pub warning: Option<String>,
54    pub description: Option<String>,
55    pub placeholder: Option<String>,
56    pub widget: Option<Widget>,
57    pub orderable: Option<bool>,
58    pub addable: Option<bool>,
59    pub removable: Option<bool>,
60    pub hidden: Option<bool>,
61    #[serde(rename = "readOnly")]
62    pub readonly: Option<bool>,
63    #[serde(rename = "writeOnly")]
64    pub writeonly: Option<bool>,
65}
66
67#[derive(Clone, Debug, Serialize, Deserialize)]
68#[serde(rename_all = "lowercase")]
69pub enum Widget {
70    Textarea,
71    Hidden,
72    Password,
73}
74
75#[derive(Clone, Debug)]
76pub struct KeysSchema {
77    pub pattern: Regex,
78    pub title: Option<String>,
79}
80
81#[derive(Clone, Debug)]
82pub struct KeysValues {
83    pub keys: KeysSchema,
84    pub values: Schema,
85}
86
87impl NamedSchema {
88    /// Unpacks named schema into (name, schema)
89    pub fn unpack(&self) -> (&str, &Schema) {
90        (self.name.as_ref(), &self.schema)
91    }
92}
93
94impl Schema {
95    pub fn with_version(self, version: u64) -> Schema {
96        Schema {
97            version: Some(version),
98            ..self
99        }
100    }
101}
102
103impl DocumentRoot {
104    pub fn schema(self) -> Schema {
105        self.0
106    }
107}
108
109// TODO: optimization: make the methods memoize the computed result
110impl SchemaList {
111    pub fn is_empty(&self) -> bool {
112        self.entries.is_empty()
113    }
114
115    /// schema name -> Schema
116    pub fn all_as_map(&self) -> HashMap<&str, &Schema> {
117        self.entries().iter().map(|schema| schema.unpack()).collect()
118    }
119
120    pub fn all_names(&self) -> Vec<&str> {
121        self.entries().iter().map(|schema| schema.name.as_str()).collect()
122    }
123
124    pub fn entries(&self) -> &Vec<NamedSchema> {
125        &self.entries
126    }
127
128    pub fn required_schema_names(&self) -> Vec<&str> {
129        self.entries
130            .iter()
131            .filter_map(|named_schema| {
132                let object_type = &named_schema.schema.object_type;
133                match object_type {
134                    ObjectType::Required(_) => Some(named_schema.name.as_str()),
135                    ObjectType::Optional(_) => None,
136                }
137            })
138            .collect()
139    }
140}
141
142impl Annotations {
143    pub fn with_title_option(title: Option<String>) -> Annotations {
144        let default = Annotations::default();
145        Annotations { title, ..default }
146    }
147}