1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
//! Validates a document matches a given Schema.

use super::doc::*;
use super::schema::{
    Schema,
    Track,
};
use failure::Error;
use std::collections::HashSet;

#[derive(Clone)]
pub struct ValidateContext<S: Schema> {
    stack: Vec<S::GroupProperties>,
    carets: HashSet<String>,
}

impl<S: Schema> ValidateContext<S> {
    pub fn new() -> ValidateContext<S> {
        ValidateContext {
            stack: vec![],
            carets: hashset![],
        }
    }
}

// TODO caret-specific validation should be moved out to the schema!
pub fn validate_doc_span<S: Schema>(
    ctx: &mut ValidateContext<S>,
    span: &DocSpan<S>,
) -> Result<(), Error> {
    for elem in span {
        match *elem {
            DocGroup(ref attrs, ref span) => {
                // if let Attrs::Caret { .. } == attrs {
                // TODO Allow validation that only one caret exists per document.
                // if !ctx.carets.insert(attrs["client"].clone()) {
                //     bail!("Multiple carets for {:?} exist", attrs["client"]);
                // }
                // }

                // TODO This is disabled with the removal of attribute
                // introspection, but should be re-instated
                // if let Attrs::ListItem = attrs {
                //     ensure!(!span.is_empty(), "Expected non-empty bullet");
                // }

                ctx.stack.push(attrs.clone());
                validate_doc_span(ctx, span)?;
                ctx.stack.pop();

                // Check parentage.
                if let Some(parent) = ctx.stack.last() {
                    let parent_type = S::track_type_from_attrs(parent).unwrap();
                    let cur_type = S::track_type_from_attrs(attrs).unwrap();
                    ensure!(
                        cur_type.parents().contains(&parent_type),
                        "Block has incorrect parent"
                    );
                } else {
                    // Top-level blocks
                    ensure!(
                        S::track_type_from_attrs(attrs).unwrap().allowed_in_root(),
                        "Root block has incorrect parent"
                    );
                }
            }
            DocText(ref _styles, ref text) => {
                ensure!(text.char_len() > 0, "Empty char string");

                if let Some(block) = ctx.stack.last() {
                    ensure!(
                        S::track_type_from_attrs(block).unwrap().supports_text(),
                        "Char found outside block"
                    );
                } else {
                    bail!("Found char in root");
                }
            }
        }
    }
    Ok(())
}

pub fn validate_doc<S: Schema>(doc: &Doc<S>) -> Result<(), Error> {
    let mut ctx = ValidateContext::new();
    validate_doc_span(&mut ctx, &doc.0)
}