fog_pack/validator/
checklist.rs

1use std::collections::HashMap;
2
3use super::*;
4use crate::Hash;
5use crate::{
6    document::Document,
7    error::{Error, Result},
8};
9
10/// An item in a Checklist. To complete it, find a document whose hash matches the one that was
11/// provided alongside this item, then feed that document to the [`check`][ListItem::check]
12/// function of this item. If the check fails, checking should be halted and the checklist should
13/// be discarded.
14#[derive(Clone, Debug)]
15pub struct ListItem<'a> {
16    inner: InnerListItem<'a>,
17    schema: &'a Hash,
18    types: &'a BTreeMap<String, Validator>,
19}
20
21impl<'a> ListItem<'a> {
22    /// Check an item in the checklist using the provided document. If the
23    /// document passes, it returns a `Ok(())`. On failure, future checks should
24    /// be halted and the checklist discarded.
25    pub fn check(self, doc: &Document) -> Result<()> {
26        // Check that the Document meets all the `schema` requirements from each Hash validator
27        if !self.inner.schema.is_empty() {
28            let doc_schema = match doc.schema_hash() {
29                Some(schema) => schema,
30                None => {
31                    return Err(Error::FailValidate(
32                        "Document has no schema, but must pass `schema` validation".into(),
33                    ))
34                }
35            };
36            let all_schema_pass = self.inner.schema.iter().all(|list| {
37                list.iter().any(|schema| match schema {
38                    None => self.schema == doc_schema,
39                    Some(schema) => schema == doc_schema,
40                })
41            });
42            if !all_schema_pass {
43                return Err(Error::FailValidate(
44                    "Document schema didn't satisfy all `schema` requirements".into(),
45                ));
46            }
47        }
48
49        // Check that the Document meets all the `link` validators from each Hash validator
50        // Note: we have no new checklist, because this is a document.
51        // We don't need to `finish()` the parser after each validation because that's to
52        // catch sitautions where the inner data contains more than one fog-pack value in sequence.
53        // Because we already have a Document, that check was already performed.
54        let parser = Parser::new(doc.data());
55        let all_link_pass = self
56            .inner
57            .link
58            .iter()
59            .all(|validator| validator.validate(self.types, parser.clone(), None).is_ok());
60        if !all_link_pass {
61            return Err(Error::FailValidate(
62                "Document schema didn't satisfy all `link` requirements".into(),
63            ));
64        }
65        Ok(())
66    }
67}
68
69#[derive(Clone, Debug)]
70struct InnerListItem<'a> {
71    schema: Vec<&'a [Option<Hash>]>,
72    link: Vec<&'a Validator>,
73}
74
75impl<'a> InnerListItem<'a> {
76    fn new() -> Self {
77        Self {
78            schema: Vec::new(),
79            link: Vec::new(),
80        }
81    }
82}
83
84/// A Checklist of documents that must be verified before the contained data is
85/// yielded.
86///
87/// A checklist can be completed by performing the following steps:
88/// 1. Call [`iter`][DataChecklist::iter] to get an iterator over the list.
89/// 2. For each [item][ListItem], fetch the Document matching the hash that was
90///     provided alongside the item, then provide it to the item by calling
91///     [`check`][ListItem::check].
92/// 3. When all the items have been completed successfully, call
93///     [`complete`][DataChecklist::complete] to get the contained data.
94///
95#[derive(Clone, Debug)]
96pub struct DataChecklist<'a, T> {
97    list: Checklist<'a>,
98    data: T,
99}
100
101impl<'a, T> DataChecklist<'a, T> {
102    pub(crate) fn from_checklist(list: Checklist<'a>, data: T) -> Self {
103        Self { list, data }
104    }
105
106    /// Iterate through the whole checklist, going through one Hash and list item at a time. For
107    /// each item, look up a Document with the same hash and check it with the [`ListItem`]'s
108    /// [`check`][ListItem::check] function.
109    pub fn iter(&mut self) -> impl Iterator<Item = (Hash, ListItem)> {
110        self.list.iter()
111    }
112
113    /// Complete a checklist, yielding the inner data value if the checklist was
114    /// successfully iterated over.
115    pub fn complete(self) -> Result<T> {
116        self.list.complete()?;
117        Ok(self.data)
118    }
119}
120
121#[derive(Clone, Debug)]
122pub(crate) struct Checklist<'a> {
123    list: HashMap<Hash, InnerListItem<'a>>,
124    types: &'a BTreeMap<String, Validator>,
125    schema: &'a Hash,
126}
127
128impl<'a> Checklist<'a> {
129    pub(crate) fn new(schema: &'a Hash, types: &'a BTreeMap<String, Validator>) -> Self {
130        Self {
131            list: HashMap::new(),
132            types,
133            schema,
134        }
135    }
136
137    pub(crate) fn insert(
138        &mut self,
139        hash: Hash,
140        schema: Option<&'a [Option<Hash>]>,
141        link: Option<&'a Validator>,
142    ) {
143        let entry = self.list.entry(hash).or_insert_with(InnerListItem::new);
144        if let Some(schema) = schema {
145            entry.schema.push(schema)
146        }
147        if let Some(link) = link {
148            entry.link.push(link)
149        }
150    }
151
152    /// Iterate through the whole checklist, going through one item at a time. Each item should be
153    /// checked; see [`ListItem`] for details.
154    pub(crate) fn iter(&mut self) -> impl Iterator<Item = (Hash, ListItem)> {
155        let schema = self.schema;
156        let types = self.types;
157        self.list.drain().map(move |(doc, inner)| {
158            (
159                doc,
160                ListItem {
161                    inner,
162                    types,
163                    schema,
164                },
165            )
166        })
167    }
168
169    /// Complete the checklsit
170    fn complete(self) -> Result<()> {
171        if self.list.is_empty() {
172            Ok(())
173        } else {
174            Err(Error::FailValidate(
175                "Not all verification checklist items were completed".into(),
176            ))
177        }
178    }
179}
180
181#[cfg(test)]
182mod test {
183    use crate::{document::NewDocument, schema::*, types::Integer};
184
185    use super::*;
186
187    #[test]
188    fn runthrough() {
189        // Set up the schemas
190        let schema1 = SchemaBuilder::new(IntValidator::default().build())
191            .build()
192            .unwrap();
193        let schema1 = Schema::from_doc(&schema1).unwrap();
194        let validator = IntValidator {
195            min: Integer::from(0),
196            ..IntValidator::default()
197        }
198        .build();
199        let schema2 = SchemaBuilder::new(validator).build().unwrap();
200        let schema2 = Schema::from_doc(&schema2).unwrap();
201
202        let doc1 = NoSchema::validate_new_doc(NewDocument::new(None, 0u8).unwrap()).unwrap();
203        let doc2 = NoSchema::validate_new_doc(NewDocument::new(None, 1u8).unwrap()).unwrap();
204        let doc3 = schema1
205            .validate_new_doc(NewDocument::new(Some(schema1.hash()), 0u8).unwrap())
206            .unwrap();
207        let doc4 = schema2
208            .validate_new_doc(NewDocument::new(Some(schema2.hash()), 0u8).unwrap())
209            .unwrap();
210
211        let types = BTreeMap::new();
212        let mut checklist = Checklist::new(schema1.hash(), &types);
213        let validator = IntValidator {
214            min: Integer::from(0u32),
215            ..IntValidator::default()
216        }
217        .build();
218        let schema2_schema = [Some(schema2.hash().clone())];
219        checklist.insert(doc1.hash().clone(), None, Some(&validator));
220        checklist.insert(doc2.hash().clone(), None, Some(&validator));
221        checklist.insert(doc3.hash().clone(), Some(&[None]), None);
222        checklist.insert(doc4.hash().clone(), Some(&schema2_schema), Some(&validator));
223        let mut checklist = DataChecklist::from_checklist(checklist, ());
224
225        let mut map = HashMap::new();
226        map.insert(doc1.hash().clone(), doc1);
227        map.insert(doc2.hash().clone(), doc2);
228        map.insert(doc3.hash().clone(), doc3);
229        map.insert(doc4.hash().clone(), doc4);
230
231        checklist
232            .iter()
233            .try_for_each(|(hash, item)| {
234                let doc = map
235                    .get(&hash)
236                    .ok_or_else(|| Error::FailValidate("".into()))?;
237                item.check(doc)
238            })
239            .unwrap();
240        checklist.complete().unwrap();
241    }
242}