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
88
//! Validates a document matches a given Schema.

use super::compose;
use super::doc::*;
use super::normalize;
use super::schema::*;
use super::{Schema, Track};
use super::stepper::*;
use super::writer::*;
use failure::Error;
use std::borrow::ToOwned;
use std::cmp;
use std::collections::{HashMap, HashSet};
use term_painter::Attr::*;
use term_painter::Color::*;
use term_painter::ToStyle;

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

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

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

                if attrs["tag"] == "bullet" {
                    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 = RtfSchema::track_type_from_attrs(parent).unwrap();
                    let cur_type = RtfSchema::track_type_from_attrs(attrs).unwrap();
                    ensure!(
                        cur_type.parents().contains(&parent_type),
                        "Block has incorrect parent"
                    );
                } else {
                    // Top-level blocks
                    ensure!(
                        RtfSchema::track_type_from_attrs(attrs).unwrap().allowed_in_root(),
                        "Root block has incorrect parent"
                    );
                }
            }
            DocChars(ref text) => {
                ensure!(text.char_len() > 0, "Empty char string");

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

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