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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
// TODO: remove the following line once we have a basic implementation ready
#![allow(dead_code, unused_variables)]

use crate::external::ion_rs::IonType;
use crate::violation::{Violation, ViolationCode};
use ion_rs::value::owned::OwnedElement;
use ion_rs::value::reader::{element_reader, ElementReader};
use ion_rs::value::{Element, Sequence, SymbolToken};
use std::fmt::{Display, Formatter};
/// A [`try`]-like macro to workaround the [`Option`]/[`Result`] nested APIs.
/// These API require checking the type and then calling the appropriate getter function
/// (which returns a None if you got it wrong). This macro turns the `None` into
/// an `IonSchemaError` which cannot be currently done with `?`.
macro_rules! try_to {
    ($getter:expr) => {
        match $getter {
            Some(value) => value,
            None => invalid_schema_error(format!("Missing a value: {}", stringify!($getter)))?,
        }
    };
}

// TODO: consider changing some of these modules to public if required
pub mod authority;
mod constraint;
mod import;
pub mod isl;
pub mod result;
pub mod schema;
pub mod system;
pub mod types;
mod violation;

/// Re-export of the ion-rs dependency that is part of our public API.
pub mod external {
    pub use ion_rs;
}

/// Provide an Ion schema element which includes all OwnedElements and a document type
///
/// ## Example:
/// In general `TypeRef` `validate()` takes in IonSchemaElement as the value to be validated.
/// In order to create an `IonSchemaElement`:
///
/// ```
/// use ion_rs::value::owned::OwnedElement;
/// use ion_schema::IonSchemaElement;
///
/// // create an IonSchemaElement from an OwnedElement
/// let owned_element: OwnedElement = 4.into();
/// let ion_schema_element: IonSchemaElement = (&owned_element).into();
///
/// // create an IonSchemaElement for document type based on vector of owned elements
/// let document: IonSchemaElement = IonSchemaElement::Document(vec![owned_element]);
/// ```
#[derive(Debug, Clone)]
pub enum IonSchemaElement {
    SingleElement(OwnedElement),
    Document(Vec<OwnedElement>),
}

impl IonSchemaElement {
    pub fn as_element(&self) -> Option<&OwnedElement> {
        match self {
            IonSchemaElement::SingleElement(element) => Some(element),
            IonSchemaElement::Document(_) => None,
        }
    }

    pub fn as_document(&self) -> Option<&Vec<OwnedElement>> {
        match self {
            IonSchemaElement::SingleElement(_) => None,
            IonSchemaElement::Document(document) => Some(document),
        }
    }

    fn expect_element_of_type(
        &self,
        types: &[IonType],
        constraint_name: &str,
    ) -> Result<&OwnedElement, Violation> {
        match self {
            IonSchemaElement::SingleElement(element) => {
                if !types.contains(&element.ion_type()) || element.is_null() {
                    // If it's an OwnedElement but the type isn't one of `types`,
                    // return a Violation with the constraint name.
                    return Err(Violation::new(
                        constraint_name,
                        ViolationCode::TypeMismatched,
                        &format!("expected {:?} but found {}", types, element.ion_type()),
                    ));
                }
                // If it's an OwnedElement of an expected type, return a ref to it.
                Ok(element)
            }
            IonSchemaElement::Document(_) => {
                // If it's a Document, return a Violation with the constraint name
                Err(Violation::new(
                    constraint_name,
                    ViolationCode::TypeMismatched,
                    &format!("expected {:?} but found document", types),
                ))
            }
        }
    }
}

impl Display for IonSchemaElement {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
        match self {
            IonSchemaElement::SingleElement(element) => {
                write!(f, "{}", element)
            }
            IonSchemaElement::Document(document) => {
                write!(f, "/* Ion document */ ")?;
                for value in document {
                    write!(f, "{} ", value)?;
                }
                write!(f, "/* end */")
            }
        }
    }
}

impl From<&OwnedElement> for IonSchemaElement {
    fn from(value: &OwnedElement) -> Self {
        if value.annotations().any(|a| a.text() == Some("document")) {
            let sequence = match value.ion_type() {
                IonType::String => load(value.as_str().unwrap()),
                IonType::List | IonType::SExpression => {
                    let elements: Vec<OwnedElement> = value
                        .as_sequence()
                        .unwrap()
                        .iter()
                        .map(|oe| oe.to_owned())
                        .collect();
                    elements
                }
                _ => {
                    panic!("invalid document")
                }
            };
            return IonSchemaElement::Document(sequence);
        }
        IonSchemaElement::SingleElement(value.to_owned())
    }
}

impl From<&Vec<OwnedElement>> for IonSchemaElement {
    fn from(value: &Vec<OwnedElement>) -> Self {
        IonSchemaElement::Document(value.to_owned())
    }
}

// helper function to be used by schema tests
fn load(text: &str) -> Vec<OwnedElement> {
    element_reader()
        .read_all(text.as_bytes())
        .expect("parsing failed unexpectedly")
}