fog_pack/validator/
enum_set.rs

1use super::*;
2use crate::error::{Error, Result};
3use serde::{Deserialize, Serialize};
4use std::default::Default;
5
6/// "Enum" validator that selects a validator based on the value's enum variant.
7///
8/// This validator expects a serialized Rust enum. A serialized enum consists of either a single
9/// string (a unit variant) or a map with a single key-value pair, where the key is the name of the
10/// enum variant and the value is the associated data. The associated data is validated against the
11/// matching validator in the contained `BTreeMap`. If there is no match, validation fails.
12///
13/// For unit variants, there is no validator, and they pass as long as their name is a key in the
14/// `BTreeMap`.
15///
16/// # Query Checking
17///
18/// The query validator must be an Any or an Enum validator, and the maps are directly checked against
19/// each other. The query validator may use a subset of the enum list. For unit variants, both the
20/// query validator and schema validator must have `None` instead of a validator. As an example,
21/// see the following:
22///
23/// ```
24/// # use fog_pack::{
25/// #     validator::*,
26/// #     schema::*,
27/// #     document::*,
28/// #     entry::*,
29/// #     query::*,
30/// #     types::*,
31/// # };
32/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
33///
34/// // Say we have a enum like this:
35/// enum ExampleEnum {
36///     Empty,
37///     Integer(Integer),
38///     String(String),
39/// }
40///
41/// // Let's use this as our schema-side validator
42/// let entry_validator = EnumValidator::new()
43///     .insert("Empty", None)
44///     .insert("Integer", Some(IntValidator::new().build()))
45///     .insert("String", Some(StrValidator::new().build()))
46///     .build();
47///
48/// // We'll build a full schema, so we can do query validation
49/// let schema_doc = SchemaBuilder::new(Validator::Null)
50///     .entry_add("item", entry_validator, None)
51///     .build()
52///     .unwrap();
53/// let schema = Schema::from_doc(&schema_doc).unwrap();
54///
55/// // This query is accepted because all enum validators match. Note how
56/// // String isn't present, because the query doesn't need to have all
57/// // possible enums.
58/// let query_validator = EnumValidator::new()
59///     .insert("Empty", None)
60///     .insert("Integer", Some(IntValidator::new().build()))
61///     .build();
62/// let query = NewQuery::new("item", query_validator);
63/// assert!(schema.encode_query(query).is_ok());
64///
65/// // This query, however, has a validator for "Empty", so it doesn't work:
66/// let query_validator = EnumValidator::new()
67///     .insert("Empty", Some(Validator::Null))
68///     .insert("Integer", Some(IntValidator::new().build()))
69///     .build();
70/// let query = NewQuery::new("item", query_validator);
71/// assert!(schema.encode_query(query).is_err());
72///
73/// # Ok(())
74/// # }
75/// ```
76///
77#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
78pub struct EnumValidator(pub BTreeMap<String, Option<Validator>>);
79
80impl EnumValidator {
81    /// Make a new validator with the default configuration.
82    pub fn new() -> Self {
83        Self::default()
84    }
85
86    /// Add a new enum to the set.
87    pub fn insert(mut self, variant: impl Into<String>, validator: Option<Validator>) -> Self {
88        self.0.insert(variant.into(), validator);
89        self
90    }
91
92    /// Build this into a [`Validator`] enum.
93    pub fn build(self) -> Validator {
94        Validator::Enum(self)
95    }
96
97    /// Iterate over all the enum variants.
98    pub fn iter(&self) -> std::collections::btree_map::Iter<String, Option<Validator>> {
99        self.0.iter()
100    }
101
102    /// Iterate over all the validators in this enum.
103    pub fn values(&self) -> std::collections::btree_map::Values<String, Option<Validator>> {
104        self.0.values()
105    }
106
107    pub(crate) fn validate<'de, 'c>(
108        &'c self,
109        types: &'c BTreeMap<String, Validator>,
110        mut parser: Parser<'de>,
111        checklist: Option<Checklist<'c>>,
112    ) -> Result<(Parser<'de>, Option<Checklist<'c>>)> {
113        // Get the enum itself, which should be a map with 1 key-value pair or a string.
114        let elem = parser
115            .next()
116            .ok_or_else(|| Error::FailValidate("expected a enum".to_string()))??;
117        let (key, has_value) = match elem {
118            Element::Str(v) => (v, false),
119            Element::Map(1) => {
120                let key = parser
121                    .next()
122                    .ok_or_else(|| Error::FailValidate("expected a string".to_string()))??;
123                if let Element::Str(key) = key {
124                    (key, true)
125                } else {
126                    return Err(Error::FailValidate("expected a string".to_string()));
127                }
128            }
129            _ => return Err(Error::FailValidate("expected an enum".to_string())),
130        };
131
132        // Find the matching validator and verify the (possible) content against it
133        let validator = self
134            .0
135            .get(key)
136            .ok_or_else(|| Error::FailValidate(format!("{} is not in enum list", key)))?;
137        match (validator, has_value) {
138            (None, false) => Ok((parser, checklist)),
139            (None, true) => Err(Error::FailValidate(format!(
140                "enum {} shouldn't have any associated value",
141                key
142            ))),
143            (Some(_), false) => Err(Error::FailValidate(format!(
144                "enum {} should have an associated value",
145                key
146            ))),
147            (Some(validator), true) => validator.validate(types, parser, checklist),
148        }
149    }
150
151    pub(crate) fn query_check(
152        &self,
153        types: &BTreeMap<String, Validator>,
154        other: &Validator,
155    ) -> bool {
156        match other {
157            Validator::Enum(other) => {
158                // For each entry in the query's enum, make sure it:
159                // 1. Has a corresponding entry in our enum
160                // 2. That our enum's matching validator would allow the query's validator
161                //    for that enum.
162                // 3. If both have a "None" instead of a validator, that's also OK
163                other
164                    .0
165                    .iter()
166                    .all(|(other_k, other_v)| match (self.0.get(other_k), other_v) {
167                        (Some(Some(validator)), Some(other_v)) => {
168                            validator.query_check(types, other_v)
169                        }
170                        (Some(None), None) => true,
171                        _ => false,
172                    })
173            }
174            Validator::Any => true,
175            _ => false,
176        }
177    }
178}
179
180#[cfg(test)]
181mod test {
182    use super::*;
183
184    #[test]
185    fn example_schema() {
186        use crate::schema::{Schema, SchemaBuilder};
187        // Let's use this as our schema-side validator, and make a full schema.
188        let entry_validator = EnumValidator::new()
189            .insert("Empty", None)
190            .insert("Integer", Some(IntValidator::new().build()))
191            .insert("String", Some(StrValidator::new().build()))
192            .build();
193        let schema_doc = SchemaBuilder::new(Validator::Null)
194            .entry_add("item", entry_validator, None)
195            .build()
196            .unwrap();
197        Schema::from_doc(&schema_doc).unwrap();
198    }
199}