ion_schema/
constraint.rs

1use crate::ion_extension::ElementExtensions;
2use crate::ion_path::{IonPath, IonPathElement};
3use crate::isl::isl_constraint::{
4    IslAnnotationsConstraint, IslConstraintValue, IslRegexConstraint,
5};
6use crate::isl::isl_type_reference::{
7    IslTypeRef, IslVariablyOccurringTypeRef, NullabilityModifier,
8};
9use crate::isl::ranges::{I64Range, Limit, TimestampPrecisionRange, U64Range, UsizeRange};
10use crate::isl::util::{
11    Annotation, Ieee754InterchangeFormat, TimestampOffset, TimestampPrecision, ValidValue,
12};
13use crate::isl::IslVersion;
14use crate::isl_require;
15use crate::ordered_elements_nfa::OrderedElementsNfa;
16use crate::result::{
17    invalid_schema_error, invalid_schema_error_raw, IonSchemaResult, ValidationResult,
18};
19use crate::system::{PendingTypes, TypeId, TypeStore};
20use crate::type_reference::{TypeReference, VariablyOccurringTypeRef};
21use crate::types::TypeValidator;
22use crate::violation::{Violation, ViolationCode};
23use crate::IonSchemaElement;
24use ion_rs::Element;
25use ion_rs::IonData;
26use ion_rs::IonType;
27use ion_rs::Value;
28use num_traits::ToPrimitive;
29use regex::{Regex, RegexBuilder};
30use std::collections::{HashMap, HashSet};
31use std::fmt::{Display, Formatter};
32use std::iter::Peekable;
33use std::ops::Neg;
34use std::str::Chars;
35
36/// Provides validation for schema Constraint
37pub trait ConstraintValidator {
38    /// Checks this constraint against the provided value,
39    /// adding [Violation]s and/or [ViolationChild]ren to `Err(violation)`
40    /// if the constraint is violated.
41    /// Otherwise, if the value passes the validation against the constraint then returns `Ok(())`.
42    fn validate(
43        &self,
44        value: &IonSchemaElement,
45        type_store: &TypeStore,
46        ion_path: &mut IonPath,
47    ) -> ValidationResult;
48}
49
50/// Defines schema Constraints
51#[derive(Debug, Clone, PartialEq)]
52// TODO: add other constraints
53pub enum Constraint {
54    AllOf(AllOfConstraint),
55    Annotations(AnnotationsConstraint),
56    Annotations2_0(AnnotationsConstraint2_0),
57    AnyOf(AnyOfConstraint),
58    ByteLength(ByteLengthConstraint),
59    CodepointLength(CodepointLengthConstraint),
60    Contains(ContainsConstraint),
61    ContentClosed,
62    ContainerLength(ContainerLengthConstraint),
63    Element(ElementConstraint),
64    Exponent(ExponentConstraint),
65    FieldNames(FieldNamesConstraint),
66    Fields(FieldsConstraint),
67    Ieee754Float(Ieee754FloatConstraint),
68    Not(NotConstraint),
69    OneOf(OneOfConstraint),
70    OrderedElements(OrderedElementsConstraint),
71    Precision(PrecisionConstraint),
72    Regex(RegexConstraint),
73    Scale(ScaleConstraint),
74    TimestampOffset(TimestampOffsetConstraint),
75    TimestampPrecision(TimestampPrecisionConstraint),
76    Type(TypeConstraint),
77    Unknown(String, Element),
78    Utf8ByteLength(Utf8ByteLengthConstraint),
79    ValidValues(ValidValuesConstraint),
80}
81
82impl Constraint {
83    /// Creates a [Constraint::Type] referring to the type represented by the provided [TypeId].
84    pub fn type_constraint(type_id: TypeId) -> Constraint {
85        Constraint::Type(TypeConstraint::new(TypeReference::new(
86            type_id,
87            NullabilityModifier::Nothing,
88        )))
89    }
90
91    /// Creates a [Constraint::AllOf] referring to the types represented by the provided [TypeId]s.
92    pub fn all_of<A: Into<Vec<TypeId>>>(type_ids: A) -> Constraint {
93        let type_references = type_ids
94            .into()
95            .iter()
96            .map(|id| TypeReference::new(*id, NullabilityModifier::Nothing))
97            .collect();
98        Constraint::AllOf(AllOfConstraint::new(type_references))
99    }
100
101    /// Creates a [Constraint::AnyOf] referring to the types represented by the provided [TypeId]s.
102    pub fn any_of<A: Into<Vec<TypeId>>>(type_ids: A) -> Constraint {
103        let type_references = type_ids
104            .into()
105            .iter()
106            .map(|id| TypeReference::new(*id, NullabilityModifier::Nothing))
107            .collect();
108        Constraint::AnyOf(AnyOfConstraint::new(type_references))
109    }
110
111    /// Creates a [Constraint::OneOf] referring to the types represented by the provided [TypeId]s.
112    pub fn one_of<A: Into<Vec<TypeId>>>(type_ids: A) -> Constraint {
113        let type_references = type_ids
114            .into()
115            .iter()
116            .map(|id| TypeReference::new(*id, NullabilityModifier::Nothing))
117            .collect();
118        Constraint::OneOf(OneOfConstraint::new(type_references))
119    }
120
121    /// Creates a [Constraint::Not] referring to the type represented by the provided [TypeId].
122    pub fn not(type_id: TypeId) -> Constraint {
123        Constraint::Not(NotConstraint::new(TypeReference::new(
124            type_id,
125            NullabilityModifier::Nothing,
126        )))
127    }
128
129    /// Creates a [Constraint::OrderedElements] referring to the types represented by the provided [TypeId]s.
130    pub fn ordered_elements<A: Into<Vec<TypeId>>>(type_ids: A) -> Constraint {
131        let type_references = type_ids
132            .into()
133            .iter()
134            .map(|id| {
135                VariablyOccurringTypeRef::new(
136                    TypeReference::new(*id, NullabilityModifier::Nothing),
137                    UsizeRange::new_single_value(1),
138                )
139            })
140            .collect();
141        Constraint::OrderedElements(OrderedElementsConstraint::new(type_references))
142    }
143
144    /// Creates a [Constraint::Contains] referring to [Elements] specified inside it
145    pub fn contains<A: Into<Vec<Element>>>(values: A) -> Constraint {
146        Constraint::Contains(ContainsConstraint::new(values.into()))
147    }
148
149    /// Creates a [Constraint::ContainerLength] from a [UsizeRange] specifying a length range.
150    pub fn container_length(length: UsizeRange) -> Constraint {
151        Constraint::ContainerLength(ContainerLengthConstraint::new(length))
152    }
153
154    /// Creates a [Constraint::ByteLength] from a [UsizeRange] specifying a length range.
155    pub fn byte_length(length: UsizeRange) -> Constraint {
156        Constraint::ByteLength(ByteLengthConstraint::new(length))
157    }
158
159    /// Creates a [Constraint::CodePointLength] from a [UsizeRange] specifying a length range.
160    pub fn codepoint_length(length: UsizeRange) -> Constraint {
161        Constraint::CodepointLength(CodepointLengthConstraint::new(length))
162    }
163
164    /// Creates a [Constraint::Element] referring to the type represented by the provided [TypeId] and the boolean represents whether distinct elements are required or not.
165    pub fn element(type_id: TypeId, requires_distinct: bool) -> Constraint {
166        Constraint::Element(ElementConstraint::new(
167            TypeReference::new(type_id, NullabilityModifier::Nothing),
168            requires_distinct,
169        ))
170    }
171
172    /// Creates a [Constraint::Annotations] using [str]s and [Element]s specified inside it
173    pub fn annotations<'a, A: IntoIterator<Item = &'a str>, B: IntoIterator<Item = Element>>(
174        annotations_modifiers: A,
175        annotations: B,
176    ) -> Constraint {
177        let annotations_modifiers: Vec<&str> = annotations_modifiers.into_iter().collect();
178        let annotations: Vec<Annotation> = annotations
179            .into_iter()
180            .map(|a| {
181                Annotation::new(
182                    a.as_text().unwrap().to_owned(),
183                    Annotation::is_annotation_required(
184                        &a,
185                        annotations_modifiers.contains(&"required"),
186                    ),
187                    IslVersion::V1_0,
188                )
189            })
190            .collect();
191        Constraint::Annotations(AnnotationsConstraint::new(
192            annotations_modifiers.contains(&"closed"),
193            annotations_modifiers.contains(&"ordered"),
194            annotations,
195        ))
196    }
197
198    /// Creates a [Constraint::Annotations] using [TypeId] specified inside it
199    pub fn annotations_v2_0(id: TypeId) -> Constraint {
200        Constraint::Annotations2_0(AnnotationsConstraint2_0::new(TypeReference::new(
201            id,
202            NullabilityModifier::Nothing,
203        )))
204    }
205
206    /// Creates a [Constraint::Precision] from a [Range] specifying a precision range.
207    pub fn precision(precision: U64Range) -> Constraint {
208        Constraint::Precision(PrecisionConstraint::new(precision))
209    }
210
211    /// Creates a [Constraint::Scale] from a [Range] specifying a precision range.
212    pub fn scale(scale: I64Range) -> Constraint {
213        Constraint::Scale(ScaleConstraint::new(scale))
214    }
215    /// Creates a [Constraint::Exponent] from a [Range] specifying an exponent range.
216    pub fn exponent(exponent: I64Range) -> Constraint {
217        Constraint::Exponent(ExponentConstraint::new(exponent))
218    }
219
220    /// Creates a [Constraint::TimestampPrecision] from a [Range] specifying a precision range.
221    pub fn timestamp_precision(precision: TimestampPrecisionRange) -> Constraint {
222        Constraint::TimestampPrecision(TimestampPrecisionConstraint::new(precision))
223    }
224
225    /// Creates an [Constraint::TimestampOffset] using the offset list specified in it
226    pub fn timestamp_offset(offsets: Vec<TimestampOffset>) -> Constraint {
227        Constraint::TimestampOffset(TimestampOffsetConstraint::new(offsets))
228    }
229
230    /// Creates a [Constraint::Utf8ByteLength] from a [Range] specifying a length range.
231    pub fn utf8_byte_length(length: UsizeRange) -> Constraint {
232        Constraint::Utf8ByteLength(Utf8ByteLengthConstraint::new(length))
233    }
234
235    /// Creates a [Constraint::Fields] referring to the fields represented by the provided field name and [TypeId]s.
236    /// By default, fields created using this method will allow open content
237    pub fn fields<I>(fields: I) -> Constraint
238    where
239        I: Iterator<Item = (String, TypeId)>,
240    {
241        let fields = fields
242            .map(|(field_name, type_id)| {
243                (
244                    field_name,
245                    VariablyOccurringTypeRef::new(
246                        TypeReference::new(type_id, NullabilityModifier::Nothing),
247                        UsizeRange::zero_or_one(),
248                    ),
249                )
250            })
251            .collect();
252        Constraint::Fields(FieldsConstraint::new(fields, true))
253    }
254
255    /// Creates a [Constraint::FieldNames] referring to the type represented by the provided [TypeId] and the boolean represents whether each field name should be distinct or not.
256    pub fn field_names(type_id: TypeId, requires_distinct: bool) -> Constraint {
257        Constraint::FieldNames(FieldNamesConstraint::new(
258            TypeReference::new(type_id, NullabilityModifier::Nothing),
259            requires_distinct,
260        ))
261    }
262
263    /// Creates a [Constraint::ValidValues] using the [Element]s specified inside it
264    /// Returns an IonSchemaError if any of the Elements have an annotation other than `range`
265    pub fn valid_values(
266        valid_values: Vec<ValidValue>,
267        isl_version: IslVersion,
268    ) -> IonSchemaResult<Constraint> {
269        Ok(Constraint::ValidValues(ValidValuesConstraint::new(
270            valid_values,
271            isl_version,
272        )?))
273    }
274
275    /// Creates a [Constraint::Regex] from the expression and flags (case_insensitive, multi_line) and also specify the ISL version
276    pub fn regex(
277        case_insensitive: bool,
278        multi_line: bool,
279        expression: String,
280        isl_version: IslVersion,
281    ) -> IonSchemaResult<Constraint> {
282        let regex = IslRegexConstraint::new(case_insensitive, multi_line, expression);
283        Ok(Constraint::Regex(RegexConstraint::from_isl(
284            &regex,
285            isl_version,
286        )?))
287    }
288
289    /// Creates a [Constraint::Ieee754Float] from [Ieee754InterchangeFormat] specified inside it
290    pub fn ieee754_float(interchange_format: Ieee754InterchangeFormat) -> Constraint {
291        Constraint::Ieee754Float(Ieee754FloatConstraint::new(interchange_format))
292    }
293
294    /// Resolves all ISL type references to corresponding [TypeReference]s
295    fn resolve_type_references(
296        isl_version: IslVersion,
297        type_references: &[IslTypeRef],
298        type_store: &mut TypeStore,
299        pending_types: &mut PendingTypes,
300    ) -> IonSchemaResult<Vec<TypeReference>> {
301        type_references
302            .iter()
303            .map(|t| IslTypeRef::resolve_type_reference(isl_version, t, type_store, pending_types))
304            .collect::<IonSchemaResult<Vec<TypeReference>>>()
305    }
306
307    /// Parse an [IslConstraint] to a [Constraint]
308    pub(crate) fn resolve_from_isl_constraint(
309        isl_version: IslVersion,
310        isl_constraint: &IslConstraintValue,
311        type_store: &mut TypeStore,
312        pending_types: &mut PendingTypes,
313        open_content: bool, // this will be used by Fields constraint to verify if open content is allowed or not
314    ) -> IonSchemaResult<Constraint> {
315        // TODO: add more constraints below
316        match isl_constraint {
317            IslConstraintValue::AllOf(isl_type_references) => {
318                let type_references = Constraint::resolve_type_references(
319                    isl_version,
320                    isl_type_references,
321                    type_store,
322                    pending_types,
323                )?;
324                Ok(Constraint::AllOf(AllOfConstraint::new(type_references)))
325            }
326            IslConstraintValue::Annotations(isl_annotations) => match isl_annotations {
327                IslAnnotationsConstraint::SimpleAnnotations(simple_annotations) => {
328                    match isl_version {
329                        IslVersion::V1_0 => {
330                            Ok(Constraint::Annotations(AnnotationsConstraint::new(
331                                simple_annotations.is_closed,
332                                simple_annotations.is_ordered,
333                                simple_annotations.annotations.to_owned(),
334                            )))
335                        }
336                        IslVersion::V2_0 => {
337                            let type_ref = IslTypeRef::resolve_type_reference(
338                                IslVersion::V2_0,
339                                &simple_annotations.convert_to_type_reference()?,
340                                type_store,
341                                pending_types,
342                            )?;
343
344                            Ok(Constraint::Annotations2_0(AnnotationsConstraint2_0::new(
345                                type_ref,
346                            )))
347                        }
348                    }
349                }
350                IslAnnotationsConstraint::StandardAnnotations(isl_type_ref) => {
351                    let type_ref = IslTypeRef::resolve_type_reference(
352                        isl_version,
353                        isl_type_ref,
354                        type_store,
355                        pending_types,
356                    )?;
357                    Ok(Constraint::Annotations2_0(AnnotationsConstraint2_0::new(
358                        type_ref,
359                    )))
360                }
361            },
362            IslConstraintValue::AnyOf(isl_type_references) => {
363                let type_references = Constraint::resolve_type_references(
364                    isl_version,
365                    isl_type_references,
366                    type_store,
367                    pending_types,
368                )?;
369                Ok(Constraint::AnyOf(AnyOfConstraint::new(type_references)))
370            }
371            IslConstraintValue::ByteLength(byte_length) => Ok(Constraint::ByteLength(
372                ByteLengthConstraint::new(byte_length.to_owned()),
373            )),
374            IslConstraintValue::CodepointLength(codepoint_length) => {
375                Ok(Constraint::CodepointLength(CodepointLengthConstraint::new(
376                    codepoint_length.to_owned(),
377                )))
378            }
379            IslConstraintValue::Contains(values) => {
380                let contains_constraint: ContainsConstraint =
381                    ContainsConstraint::new(values.to_owned());
382                Ok(Constraint::Contains(contains_constraint))
383            }
384            IslConstraintValue::ContentClosed => Ok(Constraint::ContentClosed),
385            IslConstraintValue::ContainerLength(isl_length) => Ok(Constraint::ContainerLength(
386                ContainerLengthConstraint::new(isl_length.to_owned()),
387            )),
388            IslConstraintValue::Element(type_reference, require_distinct_elements) => {
389                let type_id = IslTypeRef::resolve_type_reference(
390                    isl_version,
391                    type_reference,
392                    type_store,
393                    pending_types,
394                )?;
395                Ok(Constraint::Element(ElementConstraint::new(
396                    type_id,
397                    *require_distinct_elements,
398                )))
399            }
400            IslConstraintValue::FieldNames(isl_type_reference, distinct) => {
401                let type_reference = IslTypeRef::resolve_type_reference(
402                    isl_version,
403                    isl_type_reference,
404                    type_store,
405                    pending_types,
406                )?;
407                Ok(Constraint::FieldNames(FieldNamesConstraint::new(
408                    type_reference,
409                    *distinct,
410                )))
411            }
412            IslConstraintValue::Fields(fields, content_closed) => {
413                let open_content = match isl_version {
414                    IslVersion::V1_0 => open_content,
415                    IslVersion::V2_0 => !content_closed, // for ISL 2.0 whether open content is allowed or not depends on `fields` constraint
416                };
417                let fields_constraint: FieldsConstraint =
418                    FieldsConstraint::resolve_from_isl_constraint(
419                        isl_version,
420                        fields,
421                        type_store,
422                        pending_types,
423                        open_content,
424                    )?;
425                Ok(Constraint::Fields(fields_constraint))
426            }
427            IslConstraintValue::Ieee754Float(iee754_interchange_format) => Ok(
428                Constraint::Ieee754Float(Ieee754FloatConstraint::new(*iee754_interchange_format)),
429            ),
430            IslConstraintValue::OneOf(isl_type_references) => {
431                let type_references = Constraint::resolve_type_references(
432                    isl_version,
433                    isl_type_references,
434                    type_store,
435                    pending_types,
436                )?;
437                Ok(Constraint::OneOf(OneOfConstraint::new(type_references)))
438            }
439            IslConstraintValue::Not(type_reference) => {
440                let type_id = IslTypeRef::resolve_type_reference(
441                    isl_version,
442                    type_reference,
443                    type_store,
444                    pending_types,
445                )?;
446                Ok(Constraint::Not(NotConstraint::new(type_id)))
447            }
448            IslConstraintValue::Type(type_reference) => {
449                let type_id = IslTypeRef::resolve_type_reference(
450                    isl_version,
451                    type_reference,
452                    type_store,
453                    pending_types,
454                )?;
455                Ok(Constraint::Type(TypeConstraint::new(type_id)))
456            }
457            IslConstraintValue::OrderedElements(isl_type_references) => {
458                Ok(Constraint::OrderedElements(
459                    OrderedElementsConstraint::resolve_from_isl_constraint(
460                        isl_version,
461                        isl_type_references,
462                        type_store,
463                        pending_types,
464                    )?,
465                ))
466            }
467            IslConstraintValue::Precision(precision_range) => {
468                isl_require!(precision_range.lower() != &Limit::Inclusive(0) => "precision range must have non-zero values")?;
469                Ok(Constraint::Precision(PrecisionConstraint::new(
470                    precision_range.to_owned(),
471                )))
472            }
473            IslConstraintValue::Regex(regex) => Ok(Constraint::Regex(RegexConstraint::from_isl(
474                regex,
475                isl_version,
476            )?)),
477            IslConstraintValue::Scale(scale_range) => Ok(Constraint::Scale(ScaleConstraint::new(
478                scale_range.to_owned(),
479            ))),
480            IslConstraintValue::TimestampOffset(timestamp_offset) => {
481                Ok(Constraint::TimestampOffset(TimestampOffsetConstraint::new(
482                    timestamp_offset.valid_offsets().to_vec(),
483                )))
484            }
485            IslConstraintValue::Exponent(exponent_range) => Ok(Constraint::Exponent(
486                ExponentConstraint::new(exponent_range.to_owned()),
487            )),
488            IslConstraintValue::TimestampPrecision(timestamp_precision_range) => {
489                Ok(Constraint::TimestampPrecision(
490                    TimestampPrecisionConstraint::new(timestamp_precision_range.to_owned()),
491                ))
492            }
493            IslConstraintValue::Utf8ByteLength(utf8_byte_length) => Ok(Constraint::Utf8ByteLength(
494                Utf8ByteLengthConstraint::new(utf8_byte_length.to_owned()),
495            )),
496            IslConstraintValue::ValidValues(valid_values) => {
497                Ok(Constraint::ValidValues(ValidValuesConstraint {
498                    valid_values: valid_values.values().to_owned(),
499                }))
500            }
501            IslConstraintValue::Unknown(constraint_name, element) => Ok(Constraint::Unknown(
502                constraint_name.to_owned(),
503                element.to_owned(),
504            )),
505        }
506    }
507
508    pub fn validate(
509        &self,
510        value: &IonSchemaElement,
511        type_store: &TypeStore,
512        ion_path: &mut IonPath,
513    ) -> ValidationResult {
514        match self {
515            Constraint::AllOf(all_of) => all_of.validate(value, type_store, ion_path),
516            Constraint::Annotations(annotations) => {
517                annotations.validate(value, type_store, ion_path)
518            }
519            Constraint::Annotations2_0(annotations) => {
520                annotations.validate(value, type_store, ion_path)
521            }
522            Constraint::AnyOf(any_of) => any_of.validate(value, type_store, ion_path),
523            Constraint::ByteLength(byte_length) => {
524                byte_length.validate(value, type_store, ion_path)
525            }
526            Constraint::CodepointLength(codepoint_length) => {
527                codepoint_length.validate(value, type_store, ion_path)
528            }
529            Constraint::Contains(contains) => contains.validate(value, type_store, ion_path),
530            Constraint::ContentClosed => {
531                // No op
532                // `content: closed` does not work as a constraint by its own, it needs to be used with other container constraints
533                // e.g. `fields`
534                // the validation for `content: closed` is done within these other constraints
535                Ok(())
536            }
537            Constraint::ContainerLength(container_length) => {
538                container_length.validate(value, type_store, ion_path)
539            }
540            Constraint::Element(element) => element.validate(value, type_store, ion_path),
541            Constraint::FieldNames(field_names) => {
542                field_names.validate(value, type_store, ion_path)
543            }
544            Constraint::Fields(fields) => fields.validate(value, type_store, ion_path),
545            Constraint::Ieee754Float(ieee754_float) => {
546                ieee754_float.validate(value, type_store, ion_path)
547            }
548            Constraint::Not(not) => not.validate(value, type_store, ion_path),
549            Constraint::OneOf(one_of) => one_of.validate(value, type_store, ion_path),
550            Constraint::Type(type_constraint) => {
551                type_constraint.validate(value, type_store, ion_path)
552            }
553            Constraint::OrderedElements(ordered_elements) => {
554                ordered_elements.validate(value, type_store, ion_path)
555            }
556            Constraint::Precision(precision) => precision.validate(value, type_store, ion_path),
557            Constraint::Regex(regex) => regex.validate(value, type_store, ion_path),
558            Constraint::Scale(scale) => scale.validate(value, type_store, ion_path),
559            Constraint::Exponent(exponent) => exponent.validate(value, type_store, ion_path),
560            Constraint::TimestampOffset(timestamp_offset) => {
561                timestamp_offset.validate(value, type_store, ion_path)
562            }
563            Constraint::TimestampPrecision(timestamp_precision) => {
564                timestamp_precision.validate(value, type_store, ion_path)
565            }
566            Constraint::Utf8ByteLength(utf8_byte_length) => {
567                utf8_byte_length.validate(value, type_store, ion_path)
568            }
569            Constraint::ValidValues(valid_values) => {
570                valid_values.validate(value, type_store, ion_path)
571            }
572            Constraint::Unknown(_, _) => {
573                // No op
574                // `Unknown` represents open content which can be ignored for validation
575                Ok(())
576            }
577        }
578    }
579}
580
581/// Implements an `all_of` constraint of Ion Schema
582/// [all_of]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#all_of
583#[derive(Debug, Clone, PartialEq, Eq)]
584pub struct AllOfConstraint {
585    type_references: Vec<TypeReference>,
586}
587
588impl AllOfConstraint {
589    pub fn new(type_references: Vec<TypeReference>) -> Self {
590        Self { type_references }
591    }
592}
593
594impl ConstraintValidator for AllOfConstraint {
595    fn validate(
596        &self,
597        value: &IonSchemaElement,
598        type_store: &TypeStore,
599        ion_path: &mut IonPath,
600    ) -> ValidationResult {
601        let mut violations: Vec<Violation> = vec![];
602        let mut valid_types = vec![];
603        for type_reference in &self.type_references {
604            match type_reference.validate(value, type_store, ion_path) {
605                Ok(_) => valid_types.push(type_reference.type_id()),
606                Err(violation) => violations.push(violation),
607            }
608        }
609        if !violations.is_empty() {
610            return Err(Violation::with_violations(
611                "all_of",
612                ViolationCode::AllTypesNotMatched,
613                format!(
614                    "value matches {} types, expected {}",
615                    valid_types.len(),
616                    self.type_references.len()
617                ),
618                ion_path,
619                violations,
620            ));
621        }
622        Ok(())
623    }
624}
625
626/// Implements an `any_of` constraint of Ion Schema
627/// [any_of]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#any_of
628#[derive(Debug, Clone, PartialEq, Eq)]
629pub struct AnyOfConstraint {
630    type_references: Vec<TypeReference>,
631}
632
633impl AnyOfConstraint {
634    pub fn new(type_references: Vec<TypeReference>) -> Self {
635        Self { type_references }
636    }
637}
638
639impl ConstraintValidator for AnyOfConstraint {
640    fn validate(
641        &self,
642        value: &IonSchemaElement,
643        type_store: &TypeStore,
644        ion_path: &mut IonPath,
645    ) -> ValidationResult {
646        let mut violations: Vec<Violation> = vec![];
647        let mut valid_types = vec![];
648        for type_reference in &self.type_references {
649            match type_reference.validate(value, type_store, ion_path) {
650                Ok(_) => valid_types.push(type_reference.type_id()),
651                Err(violation) => violations.push(violation),
652            }
653        }
654        let total_valid_types = valid_types.len();
655        if total_valid_types == 0 {
656            return Err(Violation::with_violations(
657                "any_of",
658                ViolationCode::NoTypesMatched,
659                "value matches none of the types",
660                ion_path,
661                violations,
662            ));
663        }
664        Ok(())
665    }
666}
667
668/// Implements an `one_of` constraint of Ion Schema
669/// [one_of]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#one_of
670#[derive(Debug, Clone, PartialEq, Eq)]
671pub struct OneOfConstraint {
672    type_references: Vec<TypeReference>,
673}
674
675impl OneOfConstraint {
676    pub fn new(type_references: Vec<TypeReference>) -> Self {
677        Self { type_references }
678    }
679}
680
681impl ConstraintValidator for OneOfConstraint {
682    fn validate(
683        &self,
684        value: &IonSchemaElement,
685        type_store: &TypeStore,
686        ion_path: &mut IonPath,
687    ) -> ValidationResult {
688        let mut violations: Vec<Violation> = vec![];
689        let mut valid_types = vec![];
690        for type_reference in &self.type_references {
691            match type_reference.validate(value, type_store, ion_path) {
692                Ok(_) => valid_types.push(type_reference.type_id()),
693                Err(violation) => violations.push(violation),
694            }
695        }
696        let total_valid_types = valid_types.len();
697        match total_valid_types {
698            0 => Err(Violation::with_violations(
699                "one_of",
700                ViolationCode::NoTypesMatched,
701                "value matches none of the types",
702                ion_path,
703                violations,
704            )),
705            1 => Ok(()),
706            _ => Err(Violation::with_violations(
707                "one_of",
708                ViolationCode::MoreThanOneTypeMatched,
709                format!("value matches {total_valid_types} types, expected 1"),
710                ion_path,
711                violations,
712            )),
713        }
714    }
715}
716
717/// Implements a `not` constraint
718/// [type]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#not
719#[derive(Debug, Clone, PartialEq, Eq)]
720pub struct NotConstraint {
721    type_reference: TypeReference,
722}
723
724impl NotConstraint {
725    pub fn new(type_reference: TypeReference) -> Self {
726        Self { type_reference }
727    }
728}
729
730impl ConstraintValidator for NotConstraint {
731    fn validate(
732        &self,
733        value: &IonSchemaElement,
734        type_store: &TypeStore,
735        ion_path: &mut IonPath,
736    ) -> ValidationResult {
737        let violation = self.type_reference.validate(value, type_store, ion_path);
738        match violation {
739            Err(violation) => Ok(()),
740            Ok(_) => {
741                // if there were no violations for the types then not constraint was unsatisfied
742                Err(Violation::new(
743                    "not",
744                    ViolationCode::TypeMatched,
745                    "value unexpectedly matches type",
746                    ion_path,
747                ))
748            }
749        }
750    }
751}
752
753/// Implements a `type` constraint
754/// [type]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#type
755#[derive(Debug, Clone, PartialEq, Eq)]
756pub struct TypeConstraint {
757    pub(crate) type_reference: TypeReference,
758}
759
760impl TypeConstraint {
761    pub fn new(type_reference: TypeReference) -> Self {
762        Self { type_reference }
763    }
764}
765
766impl ConstraintValidator for TypeConstraint {
767    fn validate(
768        &self,
769        value: &IonSchemaElement,
770        type_store: &TypeStore,
771        ion_path: &mut IonPath,
772    ) -> ValidationResult {
773        self.type_reference.validate(value, type_store, ion_path)
774    }
775}
776
777/// Implements an `ordered_elements` constraint of Ion Schema
778/// [ordered_elements]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#ordered_elements
779#[derive(Debug, Clone, PartialEq)]
780pub struct OrderedElementsConstraint {
781    nfa: OrderedElementsNfa,
782}
783
784impl OrderedElementsConstraint {
785    pub fn new(type_references: Vec<VariablyOccurringTypeRef>) -> Self {
786        let states: Vec<_> = type_references
787            .into_iter()
788            // TODO: See if we can potentially add a more informative description.
789            .map(|ty| (ty, None))
790            .collect();
791        OrderedElementsConstraint {
792            nfa: OrderedElementsNfa::new(states),
793        }
794    }
795
796    fn resolve_from_isl_constraint(
797        isl_version: IslVersion,
798        type_references: &[IslVariablyOccurringTypeRef],
799        type_store: &mut TypeStore,
800        pending_types: &mut PendingTypes,
801    ) -> IonSchemaResult<Self> {
802        let resolved_types = type_references
803            .iter()
804            .map(|t| {
805                // resolve type references and create variably occurring type reference with occurs range
806                let var_type_ref = t.resolve_type_reference(isl_version, type_store, pending_types);
807                // TODO: See if we can potentially add a more informative description.
808                var_type_ref.map(|it| (it, None))
809            })
810            .collect::<IonSchemaResult<Vec<_>>>()?;
811
812        Ok(OrderedElementsConstraint {
813            nfa: OrderedElementsNfa::new(resolved_types),
814        })
815    }
816}
817
818impl ConstraintValidator for OrderedElementsConstraint {
819    fn validate(
820        &self,
821        value: &IonSchemaElement,
822        type_store: &TypeStore,
823        ion_path: &mut IonPath,
824    ) -> ValidationResult {
825        let violations: Vec<Violation> = vec![];
826
827        let element_iter = match value.as_sequence_iter() {
828            Some(iter) => iter,
829            None => {
830                return Err(Violation::with_violations(
831                    "ordered_elements",
832                    ViolationCode::TypeMismatched,
833                    format!(
834                        "expected list, sexp, or document; found {}",
835                        if value.is_null() {
836                            format!("{value}")
837                        } else {
838                            format!("{}", value.ion_schema_type())
839                        }
840                    ),
841                    ion_path,
842                    violations,
843                ));
844            }
845        };
846
847        self.nfa.matches(element_iter, type_store, ion_path)
848    }
849}
850
851/// Implements an `fields` constraint of Ion Schema
852/// [fields]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#fields
853#[derive(Debug, Clone, PartialEq)]
854pub struct FieldsConstraint {
855    fields: HashMap<String, VariablyOccurringTypeRef>,
856    open_content: bool,
857}
858
859impl FieldsConstraint {
860    pub fn new(fields: HashMap<String, VariablyOccurringTypeRef>, open_content: bool) -> Self {
861        Self {
862            fields,
863            open_content,
864        }
865    }
866
867    /// Provides boolean value indicating whether open content is allowed or not for the fields
868    pub fn open_content(&self) -> bool {
869        self.open_content
870    }
871
872    /// Tries to create an [Fields] constraint from the given Element
873    fn resolve_from_isl_constraint(
874        isl_version: IslVersion,
875        fields: &HashMap<String, IslVariablyOccurringTypeRef>,
876        type_store: &mut TypeStore,
877        pending_types: &mut PendingTypes,
878        open_content: bool, // Indicates if open content is allowed or not for the fields in the container
879    ) -> IonSchemaResult<Self> {
880        let resolved_fields: HashMap<String, VariablyOccurringTypeRef> = fields
881            .iter()
882            .map(|(f, t)| {
883                // resolve type references and create variably occurring type reference with occurs range
884                // default `occurs` field value for `fields` constraint is `occurs: optional` or `occurs: range::[0, 1]`
885                t.resolve_type_reference(isl_version, type_store, pending_types)
886                    .map(|variably_occurring_type_ref| (f.to_owned(), variably_occurring_type_ref))
887            })
888            .collect::<IonSchemaResult<HashMap<String, VariablyOccurringTypeRef>>>()?;
889        Ok(FieldsConstraint::new(resolved_fields, open_content))
890    }
891}
892
893impl ConstraintValidator for FieldsConstraint {
894    fn validate(
895        &self,
896        value: &IonSchemaElement,
897        type_store: &TypeStore,
898        ion_path: &mut IonPath,
899    ) -> ValidationResult {
900        let mut violations: Vec<Violation> = vec![];
901
902        // get struct value
903        let ion_struct = value
904            .expect_element_of_type(&[IonType::Struct], "fields", ion_path)?
905            .as_struct()
906            .unwrap();
907
908        // Verify if open content exists in the struct fields
909        if !self.open_content() {
910            for (field_name, value) in ion_struct.iter() {
911                if !self.fields.contains_key(field_name.text().unwrap()) {
912                    violations.push(Violation::new(
913                        "fields",
914                        ViolationCode::InvalidOpenContent,
915                        format!("Found open content in the struct: {field_name}: {value}"),
916                        ion_path,
917                    ));
918                }
919            }
920        }
921
922        // get the values corresponding to the field_name and perform occurs_validation based on the type_def
923        for (field_name, variably_occurring_type_ref) in &self.fields {
924            let type_reference = variably_occurring_type_ref.type_ref();
925            let values: Vec<&Element> = ion_struct.get_all(field_name).collect();
926
927            // add parent value for current field in ion path
928            ion_path.push(IonPathElement::Field(field_name.to_owned()));
929
930            // perform occurs validation for type_def for all values of the given field_name
931            let occurs_range: &UsizeRange = variably_occurring_type_ref.occurs_range();
932
933            // verify if values follow occurs_range constraint
934            if !occurs_range.contains(&values.len()) {
935                violations.push(Violation::new(
936                    "fields",
937                    ViolationCode::TypeMismatched,
938                    format!(
939                        "Expected {} of field {}: found {}",
940                        occurs_range,
941                        field_name,
942                        values.len()
943                    ),
944                    ion_path,
945                ));
946            }
947
948            // verify if all the values for this field name are valid according to type_def
949            for value in values {
950                let schema_element: IonSchemaElement = value.into();
951                if let Err(violation) =
952                    type_reference.validate(&schema_element, type_store, ion_path)
953                {
954                    violations.push(violation);
955                }
956            }
957
958            // remove current field from list of parents
959            ion_path.pop();
960        }
961
962        // return error if there were any violation found during validation
963        if !violations.is_empty() {
964            return Err(Violation::with_violations(
965                "fields",
966                ViolationCode::FieldsNotMatched,
967                "value didn't satisfy fields constraint",
968                ion_path,
969                violations,
970            ));
971        }
972        Ok(())
973    }
974}
975
976/// Implements an `field_names` constraint of Ion Schema
977/// [field_names]: https://amazon-ion.github.io/ion-schema/docs/isl-2-0/spec#field_names
978#[derive(Debug, Clone, PartialEq)]
979pub struct FieldNamesConstraint {
980    type_reference: TypeReference,
981    requires_distinct: bool,
982}
983
984impl FieldNamesConstraint {
985    pub fn new(type_reference: TypeReference, requires_distinct: bool) -> Self {
986        Self {
987            type_reference,
988            requires_distinct,
989        }
990    }
991}
992
993impl ConstraintValidator for FieldNamesConstraint {
994    fn validate(
995        &self,
996        value: &IonSchemaElement,
997        type_store: &TypeStore,
998        ion_path: &mut IonPath,
999    ) -> ValidationResult {
1000        let mut violations: Vec<Violation> = vec![];
1001
1002        // create a set for checking duplicate field names
1003        let mut field_name_set = HashSet::new();
1004
1005        let ion_struct = value
1006            .expect_element_of_type(&[IonType::Struct], "field_names", ion_path)?
1007            .as_struct()
1008            .unwrap();
1009
1010        for (field_name, _) in ion_struct.iter() {
1011            ion_path.push(IonPathElement::Field(field_name.text().unwrap().to_owned()));
1012            let field_name_symbol_as_element = Element::symbol(field_name);
1013            let schema_element: IonSchemaElement = (&field_name_symbol_as_element).into();
1014
1015            if let Err(violation) =
1016                self.type_reference
1017                    .validate(&schema_element, type_store, ion_path)
1018            {
1019                violations.push(violation);
1020            }
1021            if self.requires_distinct && !field_name_set.insert(field_name.text().unwrap()) {
1022                violations.push(Violation::new(
1023                    "field_names",
1024                    ViolationCode::FieldNamesNotDistinct,
1025                    format!(
1026                        "expected distinct field names but found duplicate field name {field_name}",
1027                    ),
1028                    ion_path,
1029                ))
1030            }
1031            ion_path.pop();
1032        }
1033
1034        if !violations.is_empty() {
1035            return Err(Violation::with_violations(
1036                "field_names",
1037                ViolationCode::FieldNamesMismatched,
1038                "one or more field names don't satisfy field_names constraint",
1039                ion_path,
1040                violations,
1041            ));
1042        }
1043        Ok(())
1044    }
1045}
1046
1047/// Implements Ion Schema's `contains` constraint
1048/// [contains]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#contains
1049#[derive(Debug, Clone, PartialEq, Eq)]
1050pub struct ContainsConstraint {
1051    // TODO: convert this into a HashSet once we have an implementation of Hash for Element in ion-rust
1052    // Reference ion-rust issue: https://github.com/amazon-ion/ion-rust/issues/220
1053    values: Vec<Element>,
1054}
1055
1056impl ContainsConstraint {
1057    pub fn new(values: Vec<Element>) -> Self {
1058        Self { values }
1059    }
1060}
1061
1062impl ConstraintValidator for ContainsConstraint {
1063    fn validate(
1064        &self,
1065        value: &IonSchemaElement,
1066        type_store: &TypeStore,
1067        ion_path: &mut IonPath,
1068    ) -> ValidationResult {
1069        let values: Vec<IonData<&Element>> = if let Some(element_iter) = value.as_sequence_iter() {
1070            element_iter.map(IonData::from).collect()
1071        } else if let Some(strukt) = value.as_struct() {
1072            strukt.fields().map(|(k, v)| v).map(IonData::from).collect()
1073        } else {
1074            return Err(Violation::new(
1075                "contains",
1076                ViolationCode::TypeMismatched,
1077                format!(
1078                    "expected list/sexp/struct/document found {}",
1079                    if value.is_null() {
1080                        format!("{value}")
1081                    } else {
1082                        format!("{}", value.ion_schema_type())
1083                    }
1084                ),
1085                ion_path,
1086            ));
1087        };
1088
1089        // add all the missing values found during validation
1090        let mut missing_values = vec![];
1091
1092        // for each value in expected values if it does not exist in ion sequence
1093        // then add it to missing_values to keep track of missing values
1094        for expected_value in self.values.iter() {
1095            let expected = expected_value.into();
1096            if !values.contains(&expected) {
1097                missing_values.push(expected_value);
1098            }
1099        }
1100
1101        // return Violation if there were any values added to the missing values vector
1102        if !missing_values.is_empty() {
1103            return Err(Violation::new(
1104                "contains",
1105                ViolationCode::MissingValue,
1106                format!("{value} has missing value(s): {missing_values:?}"),
1107                ion_path,
1108            ));
1109        }
1110
1111        Ok(())
1112    }
1113}
1114
1115/// Implements an `container_length` constraint of Ion Schema
1116/// [container_length]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#container_length
1117#[derive(Debug, Clone, PartialEq)]
1118pub struct ContainerLengthConstraint {
1119    length_range: UsizeRange,
1120}
1121
1122impl ContainerLengthConstraint {
1123    pub fn new(length_range: UsizeRange) -> Self {
1124        Self { length_range }
1125    }
1126
1127    pub fn length(&self) -> &UsizeRange {
1128        &self.length_range
1129    }
1130}
1131
1132impl ConstraintValidator for ContainerLengthConstraint {
1133    fn validate(
1134        &self,
1135        value: &IonSchemaElement,
1136        type_store: &TypeStore,
1137        ion_path: &mut IonPath,
1138    ) -> ValidationResult {
1139        // get the size of given value container
1140        let size = if let Some(element_iter) = value.as_sequence_iter() {
1141            element_iter.count()
1142        } else if let Some(strukt) = value.as_struct() {
1143            strukt.fields().map(|(k, v)| v).count()
1144        } else {
1145            return Err(Violation::new(
1146                "container_length",
1147                ViolationCode::TypeMismatched,
1148                if value.is_null() {
1149                    format!("expected a container found {value}")
1150                } else {
1151                    format!(
1152                        "expected a container (a list/sexp/struct) but found {}",
1153                        value.ion_schema_type()
1154                    )
1155                },
1156                ion_path,
1157            ));
1158        };
1159
1160        // get isl length as a range
1161        let length_range: &UsizeRange = self.length();
1162
1163        // return a Violation if the container size didn't follow container_length constraint
1164        if !length_range.contains(&size) {
1165            return Err(Violation::new(
1166                "container_length",
1167                ViolationCode::InvalidLength,
1168                format!("expected container length {length_range} found {size}"),
1169                ion_path,
1170            ));
1171        }
1172
1173        Ok(())
1174    }
1175}
1176
1177/// Implements Ion Schema's `byte_length` constraint
1178/// [byte_length]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#byte_length
1179#[derive(Debug, Clone, PartialEq)]
1180pub struct ByteLengthConstraint {
1181    length_range: UsizeRange,
1182}
1183
1184impl ByteLengthConstraint {
1185    pub fn new(length_range: UsizeRange) -> Self {
1186        Self { length_range }
1187    }
1188
1189    pub fn length(&self) -> &UsizeRange {
1190        &self.length_range
1191    }
1192}
1193
1194impl ConstraintValidator for ByteLengthConstraint {
1195    fn validate(
1196        &self,
1197        value: &IonSchemaElement,
1198        type_store: &TypeStore,
1199        ion_path: &mut IonPath,
1200    ) -> ValidationResult {
1201        // get the size of given bytes
1202        let size = value
1203            .expect_element_of_type(&[IonType::Blob, IonType::Clob], "byte_length", ion_path)?
1204            .as_lob()
1205            .unwrap()
1206            .len();
1207
1208        // get isl length as a range
1209        let length_range: &UsizeRange = self.length();
1210
1211        // return a Violation if the clob/blob size didn't follow byte_length constraint
1212        if !length_range.contains(&size) {
1213            return Err(Violation::new(
1214                "byte_length",
1215                ViolationCode::InvalidLength,
1216                format!("expected byte length {length_range} found {size}"),
1217                ion_path,
1218            ));
1219        }
1220
1221        Ok(())
1222    }
1223}
1224
1225/// Implements an `codepoint_length` constraint of Ion Schema
1226/// [codepoint_length]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#codepoint_length
1227#[derive(Debug, Clone, PartialEq)]
1228pub struct CodepointLengthConstraint {
1229    length_range: UsizeRange,
1230}
1231
1232impl CodepointLengthConstraint {
1233    pub fn new(length_range: UsizeRange) -> Self {
1234        Self { length_range }
1235    }
1236
1237    pub fn length(&self) -> &UsizeRange {
1238        &self.length_range
1239    }
1240}
1241
1242impl ConstraintValidator for CodepointLengthConstraint {
1243    fn validate(
1244        &self,
1245        value: &IonSchemaElement,
1246        type_store: &TypeStore,
1247        ion_path: &mut IonPath,
1248    ) -> ValidationResult {
1249        // get the size of given string/symbol Unicode codepoints
1250        let size = value
1251            .expect_element_of_type(
1252                &[IonType::String, IonType::Symbol],
1253                "codepoint_length",
1254                ion_path,
1255            )?
1256            .as_text()
1257            .unwrap()
1258            .chars()
1259            .count();
1260
1261        // get isl length as a range
1262        let length_range: &UsizeRange = self.length();
1263
1264        // return a Violation if the string/symbol codepoint size didn't follow codepoint_length constraint
1265        if !length_range.contains(&size) {
1266            return Err(Violation::new(
1267                "codepoint_length",
1268                ViolationCode::InvalidLength,
1269                format!("expected codepoint length {length_range} found {size}"),
1270                ion_path,
1271            ));
1272        }
1273
1274        Ok(())
1275    }
1276}
1277
1278/// Implements the `element` constraint
1279/// [element]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#element
1280#[derive(Debug, Clone, PartialEq, Eq)]
1281pub struct ElementConstraint {
1282    type_reference: TypeReference,
1283    /// This field is used for ISL 2.0 and it represents whether validation for distinct elements is required or not.
1284    /// For ISL 1.0 this is always false as it doesn't support distinct elements validation.
1285    required_distinct_elements: bool,
1286}
1287
1288impl ElementConstraint {
1289    pub fn new(type_reference: TypeReference, required_distinct_elements: bool) -> Self {
1290        Self {
1291            type_reference,
1292            required_distinct_elements,
1293        }
1294    }
1295}
1296
1297impl ConstraintValidator for ElementConstraint {
1298    fn validate(
1299        &self,
1300        value: &IonSchemaElement,
1301        type_store: &TypeStore,
1302        ion_path: &mut IonPath,
1303    ) -> ValidationResult {
1304        let mut violations: Vec<Violation> = vec![];
1305
1306        // create a set for checking duplicate elements
1307        let mut element_set = vec![];
1308
1309        // get elements for given container in the form (ion_path_element, element_value)
1310        let elements: Vec<(IonPathElement, &Element)> =
1311            if let Some(element_iter) = value.as_sequence_iter() {
1312                element_iter
1313                    .enumerate()
1314                    .map(|(index, val)| (IonPathElement::Index(index), val))
1315                    .collect()
1316            } else if let Some(strukt) = value.as_struct() {
1317                strukt
1318                    .fields()
1319                    .map(|(name, val)| (IonPathElement::Field(name.to_string()), val))
1320                    .collect()
1321            } else {
1322                // Check for null container
1323                if value.is_null() {
1324                    return Err(Violation::new(
1325                        "element",
1326                        ViolationCode::TypeMismatched,
1327                        format!("expected a container but found {value}"),
1328                        ion_path,
1329                    ));
1330                }
1331                // return Violation if value is not an Ion container
1332                return Err(Violation::new(
1333                    "element",
1334                    ViolationCode::TypeMismatched,
1335                    format!(
1336                        "expected a container (a list/sexp/struct) but found {}",
1337                        value.ion_schema_type()
1338                    ),
1339                    ion_path,
1340                ));
1341            };
1342
1343        // validate element constraint
1344        for (ion_path_element, val) in elements {
1345            ion_path.push(ion_path_element);
1346            let schema_element: IonSchemaElement = val.into();
1347
1348            if let Err(violation) =
1349                self.type_reference
1350                    .validate(&schema_element, type_store, ion_path)
1351            {
1352                violations.push(violation);
1353            }
1354            if self.required_distinct_elements && element_set.contains(&val) {
1355                violations.push(Violation::new(
1356                    "element",
1357                    ViolationCode::ElementNotDistinct,
1358                    format!("expected distinct elements but found duplicate element {val}",),
1359                    ion_path,
1360                ))
1361            }
1362            element_set.push(val);
1363            ion_path.pop();
1364        }
1365
1366        if !violations.is_empty() {
1367            return Err(Violation::with_violations(
1368                "element",
1369                ViolationCode::ElementMismatched,
1370                "one or more elements don't satisfy element constraint",
1371                ion_path,
1372                violations,
1373            ));
1374        }
1375        Ok(())
1376    }
1377}
1378
1379/// Implements the `annotations` constraint
1380/// [annotations]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#annotations
1381// This is used for both simple and standard syntax of annotations constraint.
1382// The simple syntax will be converted to a standard syntax for removing complexity in the validation logic.
1383#[derive(Debug, Clone, PartialEq, Eq)]
1384pub struct AnnotationsConstraint2_0 {
1385    type_ref: TypeReference,
1386}
1387
1388impl AnnotationsConstraint2_0 {
1389    pub fn new(type_ref: TypeReference) -> Self {
1390        Self { type_ref }
1391    }
1392}
1393
1394impl ConstraintValidator for AnnotationsConstraint2_0 {
1395    fn validate(
1396        &self,
1397        value: &IonSchemaElement,
1398        type_store: &TypeStore,
1399        ion_path: &mut IonPath,
1400    ) -> ValidationResult {
1401        if let Some(element) = value.as_element() {
1402            let annotations: Vec<Element> =
1403                element.annotations().iter().map(Element::symbol).collect();
1404            let annotations_element: Element = ion_rs::Value::List(annotations.into()).into();
1405            let annotations_ion_schema_element = IonSchemaElement::from(&annotations_element);
1406
1407            self.type_ref
1408                .validate(&annotations_ion_schema_element, type_store, ion_path)
1409                .map_err(|v| {
1410                    Violation::with_violations(
1411                        "annotations",
1412                        ViolationCode::AnnotationMismatched,
1413                        "one or more annotations don't satisfy annotations constraint",
1414                        ion_path,
1415                        vec![v],
1416                    )
1417                })
1418        } else {
1419            // document type can not have annotations
1420            Err(Violation::new(
1421                "annotations",
1422                ViolationCode::AnnotationMismatched,
1423                "annotations constraint is not applicable for document type",
1424                ion_path,
1425            ))
1426        }
1427    }
1428}
1429
1430/// Implements the `annotations` constraint
1431/// [annotations]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#annotations
1432// The `required` annotation provided on the list of annotations is not represented here,
1433// requirement of an annotation is represented in the annotation itself by the field `is_required` of `Annotation` struct.
1434#[derive(Debug, Clone, PartialEq, Eq)]
1435pub struct AnnotationsConstraint {
1436    is_closed: bool,
1437    is_ordered: bool,
1438    annotations: Vec<Annotation>,
1439}
1440
1441impl AnnotationsConstraint {
1442    pub fn new(is_closed: bool, is_ordered: bool, annotations: Vec<Annotation>) -> Self {
1443        Self {
1444            is_closed,
1445            is_ordered,
1446            annotations,
1447        }
1448    }
1449
1450    // Find the required expected annotation from value annotations
1451    // This is a helper method used by validate_ordered_annotations
1452    pub fn find_expected_annotation<'a, I: Iterator<Item = &'a str>>(
1453        &self,
1454        value_annotations: &mut Peekable<I>,
1455        expected_annotation: &Annotation,
1456    ) -> bool {
1457        // As there are open content possible for annotations that doesn't have list-level `closed` annotation
1458        // traverse through the next annotations to find this expected, ordered and required annotation
1459        while Option::is_some(&value_annotations.peek()) && !self.is_closed {
1460            if expected_annotation.value() == value_annotations.next().unwrap() {
1461                return true;
1462            }
1463        }
1464
1465        // if we didn't find the expected annotation return false
1466        false
1467    }
1468
1469    pub fn validate_ordered_annotations(
1470        &self,
1471        value: &Element,
1472        type_store: &TypeStore,
1473        violations: Vec<Violation>,
1474        ion_path: &mut IonPath,
1475    ) -> ValidationResult {
1476        let mut value_annotations = value
1477            .annotations()
1478            .iter()
1479            .map(|sym| sym.text().unwrap())
1480            .peekable();
1481
1482        // iterate over the expected annotations and see if there are any unexpected value annotations found
1483        for expected_annotation in &self.annotations {
1484            if let Some(actual_annotation) = value_annotations.peek() {
1485                if expected_annotation.is_required()
1486                    && expected_annotation.value() != actual_annotation
1487                {
1488                    // iterate over the actual value annotations to find the required expected annotation
1489                    if !self.find_expected_annotation(&mut value_annotations, expected_annotation) {
1490                        // missing required expected annotation
1491                        return Err(Violation::new(
1492                            "annotations",
1493                            ViolationCode::AnnotationMismatched,
1494                            "annotations don't match expectations",
1495                            ion_path,
1496                        ));
1497                    }
1498                } else if expected_annotation.value() == actual_annotation {
1499                    let _ = value_annotations.next(); // consume the annotation if its equal to the expected annotation
1500                }
1501            } else if expected_annotation.is_required() {
1502                // we already exhausted value annotations and didn't find the required expected annotation
1503                return Err(Violation::new(
1504                    "annotations",
1505                    ViolationCode::AnnotationMismatched,
1506                    "annotations don't match expectations",
1507                    ion_path,
1508                ));
1509            }
1510        }
1511
1512        if self.is_closed && Option::is_some(&value_annotations.peek()) {
1513            // check if there are still annotations left at the end of the list
1514            return Err(Violation::with_violations(
1515                "annotations",
1516                ViolationCode::AnnotationMismatched,
1517                // unwrap as we already verified with peek that there is a value
1518                format!(
1519                    "Unexpected annotations found {}",
1520                    value_annotations.next().unwrap()
1521                ),
1522                ion_path,
1523                violations,
1524            ));
1525        }
1526
1527        Ok(())
1528    }
1529
1530    pub fn validate_unordered_annotations(
1531        &self,
1532        value: &Element,
1533        type_store: &TypeStore,
1534        violations: Vec<Violation>,
1535        ion_path: &mut IonPath,
1536    ) -> ValidationResult {
1537        // This will be used by a violation to to return all the missing annotations
1538        let mut missing_annotations: Vec<&Annotation> = vec![];
1539
1540        let value_annotations: Vec<&str> = value
1541            .annotations()
1542            .iter()
1543            .map(|sym| sym.text().unwrap())
1544            .collect();
1545
1546        for expected_annotation in &self.annotations {
1547            // verify if the expected_annotation is required and if it matches with value annotation
1548            if expected_annotation.is_required()
1549                && !value.annotations().contains(expected_annotation.value())
1550            {
1551                missing_annotations.push(expected_annotation);
1552            }
1553        }
1554
1555        // if missing_annotations is not empty return violation
1556        if !missing_annotations.is_empty() {
1557            return Err(Violation::with_violations(
1558                "annotations",
1559                ViolationCode::MissingAnnotation,
1560                format!("missing annotation(s): {missing_annotations:?}"),
1561                ion_path,
1562                violations,
1563            ));
1564        }
1565
1566        // if the annotations is annotated with `closed` at list-level then verify
1567        // there are no unexpected annotations in the value annotations
1568        if self.is_closed
1569            && !value_annotations.iter().all(|v| {
1570                self.annotations
1571                    .iter()
1572                    .any(|expected_ann| v == expected_ann.value())
1573            })
1574        {
1575            return Err(Violation::with_violations(
1576                "annotations",
1577                ViolationCode::UnexpectedAnnotation,
1578                "found one or more unexpected annotations",
1579                ion_path,
1580                violations,
1581            ));
1582        }
1583
1584        Ok(())
1585    }
1586}
1587
1588impl ConstraintValidator for AnnotationsConstraint {
1589    fn validate(
1590        &self,
1591        value: &IonSchemaElement,
1592        type_store: &TypeStore,
1593        ion_path: &mut IonPath,
1594    ) -> ValidationResult {
1595        let violations: Vec<Violation> = vec![];
1596
1597        if let Some(element) = value.as_element() {
1598            // validate annotations that have list-level `ordered` annotation
1599            if self.is_ordered {
1600                return self
1601                    .validate_ordered_annotations(element, type_store, violations, ion_path);
1602            }
1603
1604            // validate annotations that does not have list-level `ordered` annotation
1605            self.validate_unordered_annotations(element, type_store, violations, ion_path)
1606        } else {
1607            // document type can not have annotations
1608            Err(Violation::new(
1609                "annotations",
1610                ViolationCode::AnnotationMismatched,
1611                "annotations constraint is not applicable for document type",
1612                ion_path,
1613            ))
1614        }
1615    }
1616}
1617
1618/// Implements Ion Schema's `precision` constraint
1619/// [precision]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#precision
1620#[derive(Debug, Clone, PartialEq)]
1621pub struct PrecisionConstraint {
1622    precision_range: U64Range,
1623}
1624
1625impl PrecisionConstraint {
1626    pub fn new(precision_range: U64Range) -> Self {
1627        Self { precision_range }
1628    }
1629
1630    pub fn precision(&self) -> &U64Range {
1631        &self.precision_range
1632    }
1633}
1634
1635impl ConstraintValidator for PrecisionConstraint {
1636    fn validate(
1637        &self,
1638        value: &IonSchemaElement,
1639        type_store: &TypeStore,
1640        ion_path: &mut IonPath,
1641    ) -> ValidationResult {
1642        // get precision of decimal value
1643        let value_precision = value
1644            .expect_element_of_type(&[IonType::Decimal], "precision", ion_path)?
1645            .as_decimal()
1646            .unwrap()
1647            .precision();
1648
1649        // get isl decimal precision as a range
1650        let precision_range: &U64Range = self.precision();
1651
1652        // return a Violation if the value didn't follow precision constraint
1653        if !precision_range.contains(&value_precision) {
1654            return Err(Violation::new(
1655                "precision",
1656                ViolationCode::InvalidLength,
1657                format!("expected precision {precision_range} found {value_precision}"),
1658                ion_path,
1659            ));
1660        }
1661
1662        Ok(())
1663    }
1664}
1665
1666/// Implements Ion Schema's `scale` constraint
1667/// [scale]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#scale
1668#[derive(Debug, Clone, PartialEq)]
1669pub struct ScaleConstraint {
1670    scale_range: I64Range,
1671}
1672
1673impl ScaleConstraint {
1674    pub fn new(scale_range: I64Range) -> Self {
1675        Self { scale_range }
1676    }
1677
1678    pub fn scale(&self) -> &I64Range {
1679        &self.scale_range
1680    }
1681}
1682
1683impl ConstraintValidator for ScaleConstraint {
1684    fn validate(
1685        &self,
1686        value: &IonSchemaElement,
1687        type_store: &TypeStore,
1688        ion_path: &mut IonPath,
1689    ) -> ValidationResult {
1690        // get scale of decimal value
1691        let value_scale = value
1692            .expect_element_of_type(&[IonType::Decimal], "scale", ion_path)?
1693            .as_decimal()
1694            .unwrap()
1695            .scale();
1696
1697        // get isl decimal scale as a range
1698        let scale_range: &I64Range = self.scale();
1699
1700        // return a Violation if the value didn't follow scale constraint
1701        if !scale_range.contains(&value_scale) {
1702            return Err(Violation::new(
1703                "scale",
1704                ViolationCode::InvalidLength,
1705                format!("expected scale {scale_range} found {value_scale}"),
1706                ion_path,
1707            ));
1708        }
1709
1710        Ok(())
1711    }
1712}
1713
1714/// Implements Ion Schema's `exponent` constraint
1715/// [exponent]: https://amazon-ion.github.io/ion-schema/docs/isl-2-0/spec#exponent
1716#[derive(Debug, Clone, PartialEq)]
1717pub struct ExponentConstraint {
1718    exponent_range: I64Range,
1719}
1720
1721impl ExponentConstraint {
1722    pub fn new(exponent_range: I64Range) -> Self {
1723        Self { exponent_range }
1724    }
1725
1726    pub fn exponent(&self) -> &I64Range {
1727        &self.exponent_range
1728    }
1729}
1730
1731impl ConstraintValidator for ExponentConstraint {
1732    fn validate(
1733        &self,
1734        value: &IonSchemaElement,
1735        type_store: &TypeStore,
1736        ion_path: &mut IonPath,
1737    ) -> ValidationResult {
1738        // get exponent of decimal value
1739        let value_exponent = value
1740            .expect_element_of_type(&[IonType::Decimal], "exponent", ion_path)?
1741            .as_decimal()
1742            .unwrap()
1743            .scale()
1744            .neg();
1745
1746        // get isl decimal exponent as a range
1747        let exponent_range: &I64Range = self.exponent();
1748
1749        // return a Violation if the value didn't follow exponent constraint
1750        if !exponent_range.contains(&value_exponent) {
1751            return Err(Violation::new(
1752                "exponent",
1753                ViolationCode::InvalidLength,
1754                format!("expected exponent {exponent_range} found {value_exponent}"),
1755                ion_path,
1756            ));
1757        }
1758
1759        Ok(())
1760    }
1761}
1762
1763/// Implements Ion Schema's `timestamp_precision` constraint
1764/// [timestamp_precision]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#timestamp_precision
1765#[derive(Debug, Clone, PartialEq)]
1766pub struct TimestampPrecisionConstraint {
1767    timestamp_precision_range: TimestampPrecisionRange,
1768}
1769
1770impl TimestampPrecisionConstraint {
1771    pub fn new(scale_range: TimestampPrecisionRange) -> Self {
1772        Self {
1773            timestamp_precision_range: scale_range,
1774        }
1775    }
1776
1777    pub fn timestamp_precision(&self) -> &TimestampPrecisionRange {
1778        &self.timestamp_precision_range
1779    }
1780}
1781
1782impl ConstraintValidator for TimestampPrecisionConstraint {
1783    fn validate(
1784        &self,
1785        value: &IonSchemaElement,
1786        type_store: &TypeStore,
1787        ion_path: &mut IonPath,
1788    ) -> ValidationResult {
1789        // get timestamp value
1790        let timestamp_value = value
1791            .expect_element_of_type(&[IonType::Timestamp], "timestamp_precision", ion_path)?
1792            .as_timestamp()
1793            .unwrap();
1794
1795        // get isl timestamp precision as a range
1796        let precision_range: &TimestampPrecisionRange = self.timestamp_precision();
1797        let precision = &TimestampPrecision::from_timestamp(&timestamp_value);
1798        // return a Violation if the value didn't follow timestamp precision constraint
1799        if !precision_range.contains(precision) {
1800            return Err(Violation::new(
1801                "precision",
1802                ViolationCode::InvalidLength,
1803                format!("expected precision {precision_range} found {precision:?}"),
1804                ion_path,
1805            ));
1806        }
1807
1808        Ok(())
1809    }
1810}
1811
1812/// Implements Ion Schema's `valid_values` constraint
1813/// [valid_values]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#valid_values
1814#[derive(Debug, Clone, PartialEq)]
1815pub struct ValidValuesConstraint {
1816    valid_values: Vec<ValidValue>,
1817}
1818
1819impl ValidValuesConstraint {
1820    pub fn new(valid_values: Vec<ValidValue>, isl_version: IslVersion) -> IonSchemaResult<Self> {
1821        Ok(Self { valid_values })
1822    }
1823}
1824
1825impl Display for ValidValuesConstraint {
1826    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
1827        write!(f, "[ ")?;
1828        let mut itr = self.valid_values.iter();
1829        if let Some(item) = itr.next() {
1830            write!(f, "{item}")?;
1831        }
1832        for item in itr {
1833            write!(f, ", {item}")?;
1834        }
1835        write!(f, " ]")
1836    }
1837}
1838
1839impl ConstraintValidator for ValidValuesConstraint {
1840    fn validate(
1841        &self,
1842        value: &IonSchemaElement,
1843        type_store: &TypeStore,
1844        ion_path: &mut IonPath,
1845    ) -> ValidationResult {
1846        match value.as_element() {
1847            Some(element) => {
1848                for valid_value in &self.valid_values {
1849                    let does_match = match valid_value {
1850                        ValidValue::Element(valid_value) => {
1851                            // this comparison uses the Ion equivalence based on Ion specification
1852                            IonData::eq(valid_value, element.value())
1853                        }
1854                        ValidValue::NumberRange(range) => match element.any_number_as_decimal() {
1855                            Some(d) => range.contains(&d),
1856                            _ => false,
1857                        },
1858                        ValidValue::TimestampRange(range) => {
1859                            if let Value::Timestamp(t) = element.value() {
1860                                range.contains(t)
1861                            } else {
1862                                false
1863                            }
1864                        }
1865                    };
1866                    if does_match {
1867                        return Ok(());
1868                    }
1869                }
1870                Err(Violation::new(
1871                    "valid_values",
1872                    ViolationCode::InvalidValue,
1873                    format!(
1874                        "expected valid_values to be from {}, found {}",
1875                        &self, element
1876                    ),
1877                    ion_path,
1878                ))
1879            }
1880            _ => Err(Violation::new(
1881                "valid_values",
1882                ViolationCode::InvalidValue,
1883                format!(
1884                    "expected valid_values to be from {}, found {}",
1885                    &self, value
1886                ),
1887                ion_path,
1888            )),
1889        }
1890    }
1891}
1892
1893/// Implements Ion Schema's `regex` constraint
1894/// [regex]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#regex
1895#[derive(Debug, Clone)]
1896pub struct RegexConstraint {
1897    expression: Regex,
1898    case_insensitive: bool,
1899    multiline: bool,
1900}
1901
1902impl RegexConstraint {
1903    fn new(expression: Regex, case_insensitive: bool, multiline: bool) -> Self {
1904        Self {
1905            expression,
1906            case_insensitive,
1907            multiline,
1908        }
1909    }
1910
1911    fn from_isl(isl_regex: &IslRegexConstraint, isl_version: IslVersion) -> IonSchemaResult<Self> {
1912        let pattern =
1913            RegexConstraint::convert_to_pattern(isl_regex.expression().to_owned(), isl_version)?;
1914
1915        let regex = RegexBuilder::new(pattern.as_str())
1916            .case_insensitive(isl_regex.case_insensitive())
1917            .multi_line(isl_regex.multi_line())
1918            .build()
1919            .map_err(|e| {
1920                invalid_schema_error_raw(format!("Invalid regex {}", isl_regex.expression()))
1921            })?;
1922
1923        Ok(RegexConstraint::new(
1924            regex,
1925            isl_regex.case_insensitive(),
1926            isl_regex.multi_line(),
1927        ))
1928    }
1929
1930    /// Converts given string to a pattern based on regex features supported by Ion Schema Specification
1931    /// For more information: `<https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#regex>`
1932    fn convert_to_pattern(expression: String, isl_version: IslVersion) -> IonSchemaResult<String> {
1933        let mut sb = String::new();
1934        let mut si = expression.as_str().chars().peekable();
1935
1936        while let Some(ch) = si.next() {
1937            match ch {
1938                '[' => {
1939                    // push the starting bracket `[` to the result string
1940                    // and then parse the next characters using parse_char_class
1941                    sb.push(ch);
1942                    RegexConstraint::parse_char_class(&mut sb, &mut si, isl_version)?;
1943                }
1944                '(' => {
1945                    sb.push(ch);
1946                    if let Some(ch) = si.next() {
1947                        if ch == '?' {
1948                            return invalid_schema_error(format!("invalid character {ch}"));
1949                        }
1950                        sb.push(ch)
1951                    }
1952                }
1953                '\\' => {
1954                    if let Some(ch) = si.next() {
1955                        match ch {
1956                            // Note: Ion text a backslash must itself be escaped, so correct escaping
1957                            // of below characters requires two backslashes, e.g.: \\.
1958                            '.' | '^' | '$' | '|' | '?' | '*' | '+' | '\\' | '[' | ']' | '('
1959                            | ')' | '{' | '}' | 'w' | 'W' | 'd' | 'D' => {
1960                                sb.push('\\');
1961                                sb.push(ch)
1962                            }
1963                            's' => sb.push_str("[ \\f\\n\\r\\t]"),
1964                            'S' => sb.push_str("[^ \\f\\n\\r\\t]"),
1965                            _ => {
1966                                return invalid_schema_error(format!(
1967                                    "invalid escape character {ch}",
1968                                ))
1969                            }
1970                        }
1971                    }
1972                }
1973                // TODO: remove below match statement once we have fixed issue: https://github.com/amazon-ion/ion-rust/issues/399
1974                '\r' => sb.push('\n'), // Replace '\r' with '\n'
1975                _ => sb.push(ch),
1976            }
1977            RegexConstraint::parse_quantifier(&mut sb, &mut si)?;
1978        }
1979
1980        Ok(sb)
1981    }
1982
1983    fn parse_char_class(
1984        sb: &mut String,
1985        si: &mut Peekable<Chars<'_>>,
1986        isl_version: IslVersion,
1987    ) -> IonSchemaResult<()> {
1988        while let Some(ch) = si.next() {
1989            sb.push(ch);
1990            match ch {
1991                '&' => {
1992                    if si.peek() == Some(&'&') {
1993                        return invalid_schema_error("'&&' is not supported in a character class");
1994                    }
1995                }
1996                '[' => return invalid_schema_error("'[' must be escaped within a character class"),
1997                '\\' => {
1998                    if let Some(ch2) = si.next() {
1999                        match ch2 {
2000                            // escaped `[` or ']' are allowed within character class
2001                            '[' | ']' | '\\' => sb.push(ch2),
2002                            'd' | 's' | 'w' | 'D' | 'S' | 'W' => {
2003                                match isl_version {
2004                                    IslVersion::V1_0 => {
2005                                        // returns an error for ISL 1.0 as it does not support pre-defined char classes (i.e., \d, \s, \w)
2006                                        return invalid_schema_error(format!(
2007                                            "invalid sequence '{:?}' in character class",
2008                                            si
2009                                        ));
2010                                    }
2011                                    IslVersion::V2_0 => {
2012                                        // Change \w and \W to be [a-zA-Z0-9_] and [^a-zA-Z0-9_] respectively
2013                                        // Since `regex` supports unicode perl style character classes,
2014                                        // i.e. \w is represented as (\p{Alphabetic} + \p{M} + \d + \p{Pc} + \p{Join_Control})
2015                                        if ch2 == 'w' {
2016                                            sb.pop();
2017                                            sb.push_str(r"[a-zA-Z0-9_]");
2018                                        } else if ch2 == 'W' {
2019                                            sb.pop();
2020                                            sb.push_str(r"[^a-zA-Z0-9_]");
2021                                        } else {
2022                                            sb.push(ch2);
2023                                        }
2024                                    }
2025                                }
2026                            }
2027                            _ => {
2028                                return invalid_schema_error(format!(
2029                                    "invalid sequence '\\{ch2}' in character class"
2030                                ))
2031                            }
2032                        }
2033                    }
2034                }
2035                ']' => return Ok(()),
2036                _ => {}
2037            }
2038        }
2039
2040        invalid_schema_error("character class missing ']'")
2041    }
2042
2043    fn parse_quantifier(sb: &mut String, si: &mut Peekable<Chars<'_>>) -> IonSchemaResult<()> {
2044        let initial_length = sb.len();
2045        if let Some(ch) = si.peek().cloned() {
2046            match ch {
2047                '?' | '*' | '+' => {
2048                    if let Some(ch) = si.next() {
2049                        sb.push(ch);
2050                    }
2051                }
2052                '{' => {
2053                    // we know next is `{` so unwrap it and add it to the result string
2054                    let ch = si.next().unwrap();
2055                    sb.push(ch);
2056                    // process occurrences specified within `{` and `}`
2057                    let mut complete = false;
2058                    for ch in si.by_ref() {
2059                        match ch {
2060                            '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | ',' => {
2061                                sb.push(ch)
2062                            }
2063                            '}' => {
2064                                sb.push(ch);
2065                                // at this point occurrences are completely specified
2066                                complete = true;
2067                                break;
2068                            }
2069                            _ => return invalid_schema_error(format!("invalid character {ch}")),
2070                        }
2071                    }
2072
2073                    if !complete {
2074                        return invalid_schema_error("range quantifier missing '}'");
2075                    }
2076                }
2077                _ => {}
2078            }
2079            if sb.len() > initial_length {
2080                if let Some(ch) = si.peek().cloned() {
2081                    match ch {
2082                        '?' => return invalid_schema_error(format!("invalid character {ch}")),
2083
2084                        '+' => return invalid_schema_error(format!("invalid character {ch}")),
2085                        _ => {}
2086                    }
2087                }
2088            }
2089        }
2090
2091        Ok(())
2092    }
2093}
2094
2095impl ConstraintValidator for RegexConstraint {
2096    fn validate(
2097        &self,
2098        value: &IonSchemaElement,
2099        type_store: &TypeStore,
2100        ion_path: &mut IonPath,
2101    ) -> ValidationResult {
2102        // get string value and return violation if its not a string or symbol type
2103        let string_value = value
2104            .expect_element_of_type(&[IonType::String, IonType::Symbol], "regex", ion_path)?
2105            .as_text()
2106            .unwrap();
2107
2108        // create regular expression
2109        let re = Regex::new(r"\r").unwrap();
2110        let result = re.replace_all(string_value, "\n");
2111        let value = result.to_string();
2112
2113        // verify if given value matches regular expression
2114        if !self.expression.is_match(value.as_str()) {
2115            return Err(Violation::new(
2116                "regex",
2117                ViolationCode::RegexMismatched,
2118                format!("{} doesn't match regex {}", value, self.expression),
2119                ion_path,
2120            ));
2121        }
2122
2123        Ok(())
2124    }
2125}
2126
2127impl PartialEq for RegexConstraint {
2128    fn eq(&self, other: &Self) -> bool {
2129        self.expression.as_str().eq(other.expression.as_str())
2130            && self.case_insensitive == other.case_insensitive
2131            && self.multiline == other.multiline
2132    }
2133}
2134
2135/// Implements Ion Schema's `utf8_byte_length` constraint
2136/// [utf8_byte_length]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#utf8_byte_length
2137#[derive(Debug, Clone, PartialEq)]
2138pub struct Utf8ByteLengthConstraint {
2139    length_range: UsizeRange,
2140}
2141
2142impl Utf8ByteLengthConstraint {
2143    pub fn new(length_range: UsizeRange) -> Self {
2144        Self { length_range }
2145    }
2146
2147    pub fn length(&self) -> &UsizeRange {
2148        &self.length_range
2149    }
2150}
2151
2152impl ConstraintValidator for Utf8ByteLengthConstraint {
2153    fn validate(
2154        &self,
2155        value: &IonSchemaElement,
2156        type_store: &TypeStore,
2157        ion_path: &mut IonPath,
2158    ) -> ValidationResult {
2159        // get the size of given bytes
2160        let size = value
2161            .expect_element_of_type(
2162                &[IonType::String, IonType::Symbol],
2163                "utf8_byte_length",
2164                ion_path,
2165            )?
2166            .as_text()
2167            .unwrap()
2168            .len();
2169
2170        // get isl length as a range
2171        let length_range: &UsizeRange = self.length();
2172
2173        // return a Violation if the string/symbol size didn't follow utf8_byte_length constraint
2174        if !length_range.contains(&size) {
2175            return Err(Violation::new(
2176                "utf8_byte_length",
2177                ViolationCode::InvalidLength,
2178                format!("expected utf8 byte length {length_range} found {size}"),
2179                ion_path,
2180            ));
2181        }
2182
2183        Ok(())
2184    }
2185}
2186
2187/// Implements Ion Schema's `timestamp_offset` constraint
2188/// [timestamp_offset]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#timestamp_offset
2189#[derive(Debug, Clone, PartialEq, Eq)]
2190pub struct TimestampOffsetConstraint {
2191    valid_offsets: Vec<TimestampOffset>,
2192}
2193
2194impl TimestampOffsetConstraint {
2195    pub fn new(valid_offsets: Vec<TimestampOffset>) -> Self {
2196        Self { valid_offsets }
2197    }
2198
2199    pub fn valid_offsets(&self) -> &Vec<TimestampOffset> {
2200        &self.valid_offsets
2201    }
2202}
2203
2204impl ConstraintValidator for TimestampOffsetConstraint {
2205    fn validate(
2206        &self,
2207        value: &IonSchemaElement,
2208        type_store: &TypeStore,
2209        ion_path: &mut IonPath,
2210    ) -> ValidationResult {
2211        // get timestamp value
2212        let timestamp_value = value
2213            .expect_element_of_type(&[IonType::Timestamp], "timestamp_offset", ion_path)?
2214            .as_timestamp()
2215            .unwrap();
2216
2217        // get isl timestamp precision as a range
2218        let valid_offsets: &Vec<TimestampOffset> = self.valid_offsets();
2219
2220        // return a Violation if the value didn't follow timestamp precision constraint
2221        if !valid_offsets.contains(&timestamp_value.offset().into()) {
2222            let formatted_valid_offsets: Vec<String> =
2223                valid_offsets.iter().map(|t| format!("{t}")).collect();
2224
2225            return Err(Violation::new(
2226                "timestamp_offset",
2227                ViolationCode::InvalidLength,
2228                format!(
2229                    "expected timestamp offset from {:?} found {}",
2230                    formatted_valid_offsets,
2231                    <Option<i32> as Into<TimestampOffset>>::into(timestamp_value.offset())
2232                ),
2233                ion_path,
2234            ));
2235        }
2236
2237        Ok(())
2238    }
2239}
2240
2241/// Implements Ion Schema's `ieee754_float` constraint
2242/// [ieee754_float]: https://amazon-ion.github.io/ion-schema/docs/isl-2-0/spec#ieee754_float
2243#[derive(Debug, Clone, PartialEq, Eq)]
2244pub struct Ieee754FloatConstraint {
2245    interchange_format: Ieee754InterchangeFormat,
2246}
2247
2248impl Ieee754FloatConstraint {
2249    pub fn new(interchange_format: Ieee754InterchangeFormat) -> Self {
2250        Self { interchange_format }
2251    }
2252
2253    pub fn interchange_format(&self) -> Ieee754InterchangeFormat {
2254        self.interchange_format
2255    }
2256}
2257
2258impl ConstraintValidator for Ieee754FloatConstraint {
2259    fn validate(
2260        &self,
2261        value: &IonSchemaElement,
2262        type_store: &TypeStore,
2263        ion_path: &mut IonPath,
2264    ) -> ValidationResult {
2265        // get ieee interchange format value
2266        let float_value = value
2267            .expect_element_of_type(&[IonType::Float], "ieee754_float", ion_path)?
2268            .as_float()
2269            .unwrap();
2270
2271        if !float_value.is_finite() {
2272            return Ok(());
2273        }
2274
2275        let is_valid = match self.interchange_format {
2276            Ieee754InterchangeFormat::Binary16 => {
2277                half::f16::from_f64(float_value).to_f64() == float_value
2278            }
2279            Ieee754InterchangeFormat::Binary32 => float_value
2280                .to_f32()
2281                .and_then(|f32_value| f32_value.to_f64().map(|f64_value| f64_value == float_value))
2282                .unwrap_or(false),
2283            Ieee754InterchangeFormat::Binary64 => true,
2284        };
2285
2286        if is_valid {
2287            Ok(())
2288        } else {
2289            Err(Violation::new(
2290                "ieee754_float",
2291                ViolationCode::InvalidIeee754Float,
2292                format!(
2293                    "value cannot be losslessly represented by the IEEE-754 {} interchange format.",
2294                    self.interchange_format
2295                ),
2296                ion_path,
2297            ))
2298        }
2299    }
2300}