ion_schema/
ion_schema_element.rs

1use crate::ion_path::IonPath;
2use crate::violation::{Violation, ViolationCode};
3use ion_rs::{Element, IonType, Struct};
4use std::fmt::{Display, Formatter};
5
6/// Extends [IonType] by adding a "Document" variant for Ion Schema.
7#[derive(Debug, Clone, PartialEq, Copy)]
8pub(crate) enum IonSchemaElementType {
9    Null,
10    Bool,
11    Int,
12    Float,
13    Decimal,
14    Timestamp,
15    Symbol,
16    String,
17    Clob,
18    Blob,
19    List,
20    SExp,
21    Struct,
22    Document,
23}
24
25impl Display for IonSchemaElementType {
26    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
27        let text = match self {
28            IonSchemaElementType::Null => "null",
29            IonSchemaElementType::Bool => "bool",
30            IonSchemaElementType::Int => "int",
31            IonSchemaElementType::Float => "float",
32            IonSchemaElementType::Decimal => "decimal",
33            IonSchemaElementType::Timestamp => "timestamp",
34            IonSchemaElementType::Symbol => "symbol",
35            IonSchemaElementType::String => "string",
36            IonSchemaElementType::Clob => "clob",
37            IonSchemaElementType::Blob => "blob",
38            IonSchemaElementType::List => "list",
39            IonSchemaElementType::SExp => "sexp",
40            IonSchemaElementType::Struct => "struct",
41            IonSchemaElementType::Document => "document",
42        };
43        f.write_str(text)
44    }
45}
46
47impl From<IonType> for IonSchemaElementType {
48    fn from(value: IonType) -> Self {
49        match value {
50            IonType::Null => IonSchemaElementType::Null,
51            IonType::Bool => IonSchemaElementType::Bool,
52            IonType::Int => IonSchemaElementType::Int,
53            IonType::Float => IonSchemaElementType::Float,
54            IonType::Decimal => IonSchemaElementType::Decimal,
55            IonType::Timestamp => IonSchemaElementType::Timestamp,
56            IonType::Symbol => IonSchemaElementType::Symbol,
57            IonType::String => IonSchemaElementType::String,
58            IonType::Clob => IonSchemaElementType::Clob,
59            IonType::Blob => IonSchemaElementType::Blob,
60            IonType::List => IonSchemaElementType::List,
61            IonType::SExp => IonSchemaElementType::SExp,
62            IonType::Struct => IonSchemaElementType::Struct,
63        }
64    }
65}
66
67/// Internal-only backing representation for [IonSchemaElement].
68#[derive(Debug, Clone, PartialEq)]
69enum IonSchemaElementKind<'a> {
70    SingleElement(&'a Element),
71    Document(&'a [Element]),
72}
73
74/// Represents a value that can be validated by Ion Schema.
75///
76/// An [IonSchemaElement] can be constructed from [Element] to represent a single Ion value, or
77/// from [Document] to represent the Ion Schema document type.
78///
79/// In general, users do not need to construct this directly. Ion Schema APIs accept
80/// `Into<IonSchemaElement>` rather than directly accepting `IonSchemaElement`.
81///
82/// See [TypeDefinition::validate] for examples of use.
83#[derive(Debug, Clone, PartialEq)]
84pub struct IonSchemaElement<'a> {
85    content: IonSchemaElementKind<'a>,
86}
87
88impl<'a> IonSchemaElement<'a>
89where
90    Self: 'a,
91{
92    pub(crate) fn as_sequence_iter(&'a self) -> Option<impl Iterator<Item = &'a Element>> {
93        match &self.content {
94            IonSchemaElementKind::SingleElement(e) => e
95                .as_sequence()
96                .map(|s| AsRef::<[Element]>::as_ref(s).iter()),
97            IonSchemaElementKind::Document(d) => Some(d.iter()),
98        }
99    }
100
101    pub(crate) fn as_struct(&'a self) -> Option<&'a Struct> {
102        match self.content {
103            IonSchemaElementKind::SingleElement(e) => e.as_struct(),
104            _ => None,
105        }
106    }
107
108    pub(crate) fn as_element(&'a self) -> Option<&'a Element> {
109        match self.content {
110            IonSchemaElementKind::SingleElement(element) => Some(element),
111            _ => None,
112        }
113    }
114
115    pub(crate) fn as_document(&'a self) -> Option<impl Iterator<Item = &'a Element>> {
116        match &self.content {
117            IonSchemaElementKind::Document(d) => Some(d.iter()),
118            _ => None,
119        }
120    }
121
122    pub(crate) fn ion_schema_type(&self) -> IonSchemaElementType {
123        match self.as_element() {
124            Some(e) => e.ion_type().into(),
125            _ => IonSchemaElementType::Document,
126        }
127    }
128
129    pub(crate) fn is_null(&self) -> bool {
130        match self.as_element() {
131            Some(e) => e.is_null(),
132            _ => false,
133        }
134    }
135
136    pub(crate) fn expect_element_of_type(
137        &self,
138        types: &[IonType],
139        constraint_name: &str,
140        ion_path: &mut IonPath,
141    ) -> Result<&Element, Violation> {
142        match self.as_element() {
143            Some(element) => {
144                if !types.contains(&element.ion_type()) || element.is_null() {
145                    // If it's an Element but the type isn't one of `types`,
146                    // return a Violation with the constraint name.
147                    return Err(Violation::new(
148                        constraint_name,
149                        ViolationCode::TypeMismatched,
150                        format!("expected {:?} but found {}", types, element.ion_type()),
151                        ion_path,
152                    ));
153                }
154                // If it's an Element of an expected type, return a ref to it.
155                Ok(element)
156            }
157            None => {
158                // If it's a Document, return a Violation with the constraint name
159                Err(Violation::new(
160                    constraint_name,
161                    ViolationCode::TypeMismatched,
162                    format!("expected {types:?} but found document"),
163                    ion_path,
164                ))
165            }
166        }
167    }
168}
169
170impl<'a> Display for IonSchemaElement<'a> {
171    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
172        match &self.content {
173            IonSchemaElementKind::SingleElement(element) => {
174                write!(f, "{element}")
175            }
176            IonSchemaElementKind::Document(document) => {
177                write!(f, "/* Ion document */ ")?;
178                for value in document.iter() {
179                    write!(f, "{value} ")?;
180                }
181                write!(f, "/* end */")
182            }
183        }
184    }
185}
186
187impl<'a> From<&'a Element> for IonSchemaElement<'a> {
188    fn from(value: &'a Element) -> Self {
189        IonSchemaElement {
190            content: IonSchemaElementKind::SingleElement(value),
191        }
192    }
193}
194
195/// Marker type to indicate that a sequence of [Element] should be validated as a `document`.
196pub struct DocumentHint<'a>(&'a [Element]);
197
198pub trait AsDocumentHint<'a> {
199    fn as_document(&'a self) -> DocumentHint<'a>;
200}
201
202impl<'a, T> AsDocumentHint<'a> for T
203where
204    T: AsRef<[Element]> + 'a,
205{
206    fn as_document(&'a self) -> DocumentHint<'a> {
207        DocumentHint(self.as_ref())
208    }
209}
210
211impl<'a> From<DocumentHint<'a>> for IonSchemaElement<'a> {
212    fn from(value: DocumentHint<'a>) -> Self {
213        let content: IonSchemaElementKind = IonSchemaElementKind::Document(value.0);
214        IonSchemaElement { content }
215    }
216}