fog_pack/validator/
hash.rs

1use super::*;
2use crate::element::*;
3use crate::error::{Error, Result};
4use crate::Hash;
5use serde::{Deserialize, Deserializer, Serialize};
6use std::default::Default;
7
8#[inline]
9fn is_false(v: &bool) -> bool {
10    !v
11}
12
13fn get_validator<'de, D: Deserializer<'de>>(
14    deserializer: D,
15) -> Result<Option<Box<Validator>>, D::Error> {
16    // Decode the validator. If this function is called, there should be an actual validator
17    // present. Otherwise we fail. In other words, no `null` allowed.
18    Ok(Some(Box::new(Validator::deserialize(deserializer)?)))
19}
20
21/// Validator for hashes.
22///
23/// This validator type will only pass hash values. Validation passes if:
24///
25/// - If the `in` list is not empty, the hash must be among the hashes in the list.
26/// - The hash must not be among the hashes in the `nin` list.
27/// - If `link` has a validator, the data in the Document referred to by the hash must pass that
28///     validator.
29/// - If the `schema` list is not empty, the Document referred to by the hash must use one of the
30///     schemas listed. A `null` value on the list means the schema containing *this* validator is
31///     also accepted.
32///
33/// **The `link` and `schema` checks only apply when validating Entries, not Documents.**
34///
35/// Hash validators are unique in that they do not always complete validation after examining a
36/// single value. If used for checking an Entry, they can require an additional Document for
37/// validation. For this reason, completing validation of an Entry requires completing a
38/// [`DataChecklist`][DataChecklist]. See the [`Schema`][crate::schema::Schema] documentation for more
39/// details.
40///
41/// # Defaults
42///
43/// Fields that aren't specified for the validator use their defaults instead. The defaults for
44/// each field are:
45///
46/// - comment: ""
47/// - link: None
48/// - schema: empty
49/// - in_list: empty
50/// - nin_list: empty
51/// - query: false
52/// - link_ok: false
53/// - schema_ok: false
54///
55/// # Query Checking
56///
57/// Queries for hashes are only allowed to use non-default values for each field if the
58/// corresponding query permission is set in the schema's validator:
59///
60/// - query: `in` and `nin` lists
61/// - link_ok: `link`
62/// - schema_ok: `schema`
63///
64/// In addition, if there is a validator for `link`, it is validated against the schema validator's
65/// `link` validator.
66///
67#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
68#[serde(deny_unknown_fields, default)]
69pub struct HashValidator {
70    /// An optional comment explaining the validator.
71    #[serde(skip_serializing_if = "String::is_empty")]
72    pub comment: String,
73    /// An optional validator used to validate the data in a Document linked to by the hash. If
74    /// not present, any data is allowed in the linked Document.
75    #[serde(
76        skip_serializing_if = "Option::is_none",
77        deserialize_with = "get_validator"
78    )]
79    pub link: Option<Box<Validator>>,
80    /// A list of allowed schemas for a Document linked to by the hash. A `None` value refers to
81    /// the validator's containing schema. For validators used in queries, `None` is skipped. If
82    /// empty, this list is ignored during checking.
83    #[serde(skip_serializing_if = "Vec::is_empty")]
84    pub schema: Vec<Option<Hash>>,
85    /// A vector of specific allowed values, stored under the `in` field. If empty, this vector is not checked against.
86    #[serde(rename = "in", skip_serializing_if = "Vec::is_empty")]
87    pub in_list: Vec<Hash>,
88    /// A vector of specific unallowed values, stored under the `nin` field.
89    #[serde(rename = "nin", skip_serializing_if = "Vec::is_empty")]
90    pub nin_list: Vec<Hash>,
91    /// If true, queries against matching spots may have values in the `in` or `nin` lists.
92    #[serde(skip_serializing_if = "is_false")]
93    pub query: bool,
94    /// If true, queries against matching spots may have a validator in `link`.
95    #[serde(skip_serializing_if = "is_false")]
96    pub link_ok: bool,
97    /// If true, queries against matching spots may have values in the `schema` list.
98    #[serde(skip_serializing_if = "is_false")]
99    pub schema_ok: bool,
100}
101
102impl HashValidator {
103    /// Make a new validator with the default configuration.
104    pub fn new() -> Self {
105        Self::default()
106    }
107
108    /// Set the `link` validator.
109    pub fn link(mut self, link: Validator) -> Self {
110        self.link = Some(Box::new(link));
111        self
112    }
113
114    /// Add a Hash to the `schema` list.
115    pub fn schema_add(mut self, add: impl Into<Hash>) -> Self {
116        self.schema.push(Some(add.into()));
117        self
118    }
119
120    /// Allow referred-to documents to use this validator's containing schema.
121    pub fn schema_self(mut self) -> Self {
122        self.schema.push(None);
123        self
124    }
125
126    /// Add a value to the `in` list.
127    pub fn in_add(mut self, add: impl Into<Hash>) -> Self {
128        self.in_list.push(add.into());
129        self
130    }
131
132    /// Add a value to the `nin` list.
133    pub fn nin_add(mut self, add: impl Into<Hash>) -> Self {
134        self.nin_list.push(add.into());
135        self
136    }
137
138    /// Set whether or not queries can use the `in` and `nin` lists.
139    pub fn query(mut self, query: bool) -> Self {
140        self.query = query;
141        self
142    }
143
144    /// Set whether or not queries can use `link`.
145    pub fn link_ok(mut self, link_ok: bool) -> Self {
146        self.link_ok = link_ok;
147        self
148    }
149
150    /// Set whether or not queries can use `schema`.
151    pub fn schema_ok(mut self, schema_ok: bool) -> Self {
152        self.schema_ok = schema_ok;
153        self
154    }
155
156    /// Build this into a [`Validator`] enum.
157    pub fn build(self) -> Validator {
158        Validator::Hash(Box::new(self))
159    }
160
161    pub(crate) fn validate<'c>(
162        &'c self,
163        parser: &mut Parser,
164        checklist: &mut Option<Checklist<'c>>,
165    ) -> Result<()> {
166        let elem = parser
167            .next()
168            .ok_or_else(|| Error::FailValidate("Expected a hash".to_string()))??;
169        let val = if let Element::Hash(v) = elem {
170            v
171        } else {
172            return Err(Error::FailValidate(format!(
173                "Expected Hash, got {}",
174                elem.name()
175            )));
176        };
177
178        // in/nin checks
179        if !self.in_list.is_empty() && !self.in_list.iter().any(|v| *v == val) {
180            return Err(Error::FailValidate(
181                "Timestamp is not on `in` list".to_string(),
182            ));
183        }
184        if self.nin_list.iter().any(|v| *v == val) {
185            return Err(Error::FailValidate(
186                "Timestamp is on `nin` list".to_string(),
187            ));
188        }
189
190        if let Some(checklist) = checklist {
191            match (self.schema.is_empty(), self.link.as_ref()) {
192                (false, Some(link)) => checklist.insert(val, Some(&self.schema), Some(link)),
193                (false, None) => checklist.insert(val, Some(&self.schema), None),
194                (true, Some(link)) => checklist.insert(val, None, Some(link)),
195                _ => (),
196            }
197        }
198
199        Ok(())
200    }
201
202    fn query_check_self(&self, types: &BTreeMap<String, Validator>, other: &HashValidator) -> bool {
203        let initial_check = (self.query || (other.in_list.is_empty() && other.nin_list.is_empty()))
204            && (self.link_ok || other.link.is_none())
205            && (self.schema_ok || other.schema.is_empty());
206        if !initial_check {
207            return false;
208        }
209        if self.link_ok {
210            match (&self.link, &other.link) {
211                (None, None) => true,
212                (Some(_), None) => true,
213                (None, Some(_)) => false,
214                (Some(s), Some(o)) => s.query_check(types, o.as_ref()),
215            }
216        } else {
217            true
218        }
219    }
220
221    pub(crate) fn query_check(
222        &self,
223        types: &BTreeMap<String, Validator>,
224        other: &Validator,
225    ) -> bool {
226        match other {
227            Validator::Hash(other) => self.query_check_self(types, other),
228            Validator::Multi(list) => list.iter().all(|other| match other {
229                Validator::Hash(other) => self.query_check_self(types, other),
230                _ => false,
231            }),
232            Validator::Any => true,
233            _ => false,
234        }
235    }
236}
237
238#[cfg(test)]
239mod test {
240    use super::*;
241    use crate::{de::FogDeserializer, ser::FogSerializer};
242
243    #[test]
244    fn ser_default() {
245        // Should be an empty map if we use the defaults
246        let schema = HashValidator::default();
247        let mut ser = FogSerializer::default();
248        schema.serialize(&mut ser).unwrap();
249        let expected: Vec<u8> = vec![0x80];
250        let actual = ser.finish();
251        println!("expected: {:x?}", expected);
252        println!("actual:   {:x?}", actual);
253        assert_eq!(expected, actual);
254
255        let mut de = FogDeserializer::with_debug(&actual, "    ");
256        let decoded = HashValidator::deserialize(&mut de).unwrap();
257        println!("{}", de.get_debug().unwrap());
258        assert_eq!(schema, decoded);
259    }
260
261    #[test]
262    fn verify_simple() {
263        let mut schema = HashValidator {
264            link: Some(Box::new(HashValidator::default().build())),
265            ..HashValidator::default()
266        };
267        schema
268            .schema
269            .push(Some(Hash::new(b"Pretend I am a real schema")));
270        schema.schema.push(None);
271        let mut ser = FogSerializer::default();
272
273        Hash::new(b"Data to make a hash")
274            .serialize(&mut ser)
275            .unwrap();
276        let encoded = ser.finish();
277        let mut parser = Parser::new(&encoded);
278        let fake_schema = Hash::new(b"Pretend I, too, am a real schema");
279        let fake_types = BTreeMap::new();
280        let mut checklist = Some(Checklist::new(&fake_schema, &fake_types));
281        schema
282            .validate(&mut parser, &mut checklist)
283            .expect("should succeed as a validator");
284    }
285}