ion_schema/isl/
isl_constraint.rs

1use crate::ion_extension::ElementExtensions;
2use crate::isl::isl_import::IslImportType;
3use crate::isl::isl_type_reference::{IslTypeRef, IslVariablyOccurringTypeRef};
4use crate::isl::ranges::{I64Range, TimestampPrecisionRange, U64Range, UsizeRange};
5use crate::isl::util::{
6    Annotation, Ieee754InterchangeFormat, TimestampOffset, TimestampPrecision, ValidValue,
7};
8use crate::isl::IslVersion;
9use crate::result::{invalid_schema_error, invalid_schema_error_raw, IonSchemaResult};
10use crate::{isl, isl_require};
11use ion_rs::{Element, Value};
12use ion_rs::{IonResult, SequenceWriter, StructWriter, ValueWriter, WriteAsIon};
13use ion_rs::{IonType, Symbol};
14use std::collections::HashMap;
15use std::convert::TryInto;
16
17/// Provides public facing APIs for constructing ISL constraints programmatically for ISL 1.0
18pub mod v_1_0 {
19    use crate::isl::isl_constraint::{
20        IslAnnotationsConstraint, IslConstraint, IslConstraintValue, IslRegexConstraint,
21        IslSimpleAnnotationsConstraint, IslTimestampOffsetConstraint, IslValidValuesConstraint,
22    };
23    use crate::isl::isl_type_reference::{IslTypeRef, IslVariablyOccurringTypeRef};
24    use crate::isl::ranges::{I64Range, TimestampPrecisionRange, U64Range, UsizeRange};
25    use crate::isl::util::{Annotation, TimestampOffset, ValidValue};
26    use crate::isl::IslVersion;
27    use crate::result::IonSchemaResult;
28    use ion_rs::Element;
29
30    /// Creates a `type` constraint using the [IslTypeRef] referenced inside it
31    // type is rust keyword hence this method is named type_constraint unlike other ISL constraint methods
32    pub fn type_constraint(isl_type: IslTypeRef) -> IslConstraint {
33        IslConstraint::new(IslVersion::V1_0, IslConstraintValue::Type(isl_type))
34    }
35
36    /// Creates an `all_of` constraint using the [IslTypeRef] referenced inside it
37    pub fn all_of<A: Into<Vec<IslTypeRef>>>(isl_types: A) -> IslConstraint {
38        IslConstraint::new(
39            IslVersion::V1_0,
40            IslConstraintValue::AllOf(isl_types.into()),
41        )
42    }
43
44    /// Creates an `any_of` constraint using the [IslTypeRef] referenced inside it
45    pub fn any_of<A: Into<Vec<IslTypeRef>>>(isl_types: A) -> IslConstraint {
46        IslConstraint::new(
47            IslVersion::V1_0,
48            IslConstraintValue::AnyOf(isl_types.into()),
49        )
50    }
51
52    /// Creates a `one_of` constraint using the [IslTypeRef] referenced inside it
53    pub fn one_of<A: Into<Vec<IslTypeRef>>>(isl_types: A) -> IslConstraint {
54        IslConstraint::new(
55            IslVersion::V1_0,
56            IslConstraintValue::OneOf(isl_types.into()),
57        )
58    }
59
60    /// Creates an `ordered_elements` constraint using the [IslVariablyOccurringTypeRef] referenced inside it
61    pub fn ordered_elements<A: Into<Vec<IslVariablyOccurringTypeRef>>>(
62        isl_types: A,
63    ) -> IslConstraint {
64        IslConstraint::new(
65            IslVersion::V1_0,
66            IslConstraintValue::OrderedElements(isl_types.into()),
67        )
68    }
69
70    /// Creates a `precision` constraint using the range specified in it
71    pub fn precision(precision: U64Range) -> IslConstraint {
72        IslConstraint::new(IslVersion::V1_0, IslConstraintValue::Precision(precision))
73    }
74
75    /// Creates a `scale` constraint using the range specified in it
76    pub fn scale(scale: I64Range) -> IslConstraint {
77        IslConstraint::new(IslVersion::V1_0, IslConstraintValue::Scale(scale))
78    }
79
80    /// Creates a `fields` constraint using the field names and [IslVariablyOccurringTypeRef]s referenced inside it
81    pub fn fields<I>(fields: I) -> IslConstraint
82    where
83        I: Iterator<Item = (String, IslVariablyOccurringTypeRef)>,
84    {
85        IslConstraint::new(
86            IslVersion::V1_0,
87            IslConstraintValue::Fields(fields.collect(), false),
88        )
89    }
90
91    /// Creates a `not` constraint using the [IslTypeRef] referenced inside it
92    pub fn not(isl_type: IslTypeRef) -> IslConstraint {
93        IslConstraint::new(IslVersion::V1_0, IslConstraintValue::Not(isl_type))
94    }
95
96    /// Creates a `contains` constraint using the [Element] specified inside it
97    pub fn contains<A: Into<Vec<Element>>>(values: A) -> IslConstraint {
98        IslConstraint::new(
99            IslVersion::V1_0,
100            IslConstraintValue::Contains(values.into()),
101        )
102    }
103
104    /// Creates a `container_length` constraint using the range specified in it
105    pub fn container_length(length: UsizeRange) -> IslConstraint {
106        IslConstraint::new(
107            IslVersion::V1_0,
108            IslConstraintValue::ContainerLength(length),
109        )
110    }
111
112    /// Creates a `byte_length` constraint using the range specified in it
113    pub fn byte_length(length: UsizeRange) -> IslConstraint {
114        IslConstraint::new(IslVersion::V1_0, IslConstraintValue::ByteLength(length))
115    }
116
117    /// Creates a `codepoint_length` constraint using the range specified in it
118    pub fn codepoint_length(length: UsizeRange) -> IslConstraint {
119        IslConstraint::new(
120            IslVersion::V1_0,
121            IslConstraintValue::CodepointLength(length),
122        )
123    }
124
125    /// Creates a `timestamp_precision` constraint using the range specified in it
126    pub fn timestamp_precision(precision: TimestampPrecisionRange) -> IslConstraint {
127        IslConstraint::new(
128            IslVersion::V1_0,
129            IslConstraintValue::TimestampPrecision(precision),
130        )
131    }
132
133    /// Creates a `timestamp_offset` constraint using the offset list specified in it
134    pub fn timestamp_offset(offsets: Vec<TimestampOffset>) -> IslConstraint {
135        IslConstraint::new(
136            IslVersion::V1_0,
137            IslConstraintValue::TimestampOffset(IslTimestampOffsetConstraint::new(offsets)),
138        )
139    }
140
141    /// Creates an `utf_byte_length` constraint using the range specified in it
142    pub fn utf8_byte_length(length: UsizeRange) -> IslConstraint {
143        IslConstraint::new(IslVersion::V1_0, IslConstraintValue::Utf8ByteLength(length))
144    }
145
146    /// Creates an `element` constraint using the [IslTypeRef] referenced inside it
147    pub fn element(isl_type: IslTypeRef) -> IslConstraint {
148        IslConstraint::new(
149            IslVersion::V1_0,
150            IslConstraintValue::Element(isl_type, false),
151        )
152    }
153
154    /// Creates an `annotations` constraint using [str]s and [Element]s specified inside it
155    pub fn annotations<'a, A: IntoIterator<Item = &'a str>, B: IntoIterator<Item = Element>>(
156        annotations_modifiers: A,
157        annotations: B,
158    ) -> IslConstraint {
159        let annotations_modifiers: Vec<&str> = annotations_modifiers.into_iter().collect();
160        let annotations: Vec<Annotation> = annotations
161            .into_iter()
162            .map(|a| {
163                Annotation::new(
164                    a.as_text().unwrap().to_owned(),
165                    Annotation::is_annotation_required(
166                        &a,
167                        annotations_modifiers.contains(&"required"),
168                    ),
169                    IslVersion::V1_0,
170                )
171            })
172            .collect();
173        IslConstraint::new(
174            IslVersion::V1_0,
175            IslConstraintValue::Annotations(IslAnnotationsConstraint::SimpleAnnotations(
176                IslSimpleAnnotationsConstraint::new(
177                    annotations_modifiers.contains(&"closed"),
178                    annotations_modifiers.contains(&"ordered"),
179                    annotations_modifiers.contains(&"required"),
180                    annotations,
181                ),
182            )),
183        )
184    }
185
186    /// Creates a `valid_values` constraint using the [Element]s specified inside it
187    pub fn valid_values(valid_values: Vec<ValidValue>) -> IonSchemaResult<IslConstraint> {
188        Ok(IslConstraint::new(
189            IslVersion::V1_0,
190            IslConstraintValue::ValidValues(IslValidValuesConstraint { valid_values }),
191        ))
192    }
193
194    /// Creates a `regex` constraint using the expression and flags (case_insensitive, multi_line)
195    pub fn regex(case_insensitive: bool, multi_line: bool, expression: String) -> IslConstraint {
196        IslConstraint::new(
197            IslVersion::V1_0,
198            IslConstraintValue::Regex(IslRegexConstraint::new(
199                case_insensitive,
200                multi_line,
201                expression,
202            )),
203        )
204    }
205}
206
207/// Provides public facing APIs for constructing ISL constraints programmatically for ISL 2.0
208pub mod v_2_0 {
209    use crate::isl::isl_constraint::{
210        IslAnnotationsConstraint, IslConstraint, IslSimpleAnnotationsConstraint,
211    };
212    use crate::isl::isl_constraint::{
213        IslConstraintValue, IslTimestampOffsetConstraint, IslValidValuesConstraint,
214    };
215    use crate::isl::isl_type_reference::{IslTypeRef, IslVariablyOccurringTypeRef};
216    use crate::isl::ranges::{I64Range, TimestampPrecisionRange, U64Range, UsizeRange};
217    use crate::isl::util::{Annotation, Ieee754InterchangeFormat, TimestampOffset, ValidValue};
218    use crate::isl::IslVersion;
219    use crate::result::{invalid_schema_error, IonSchemaResult};
220    use ion_rs::Element;
221
222    /// Creates a `type` constraint using the [IslTypeRef] referenced inside it
223    // type is rust keyword hence this method is named type_constraint unlike other ISL constraint methods
224    pub fn type_constraint(isl_type: IslTypeRef) -> IslConstraint {
225        IslConstraint::new(IslVersion::V2_0, IslConstraintValue::Type(isl_type))
226    }
227
228    /// Creates an `all_of` constraint using the [IslTypeRef] referenced inside it
229    pub fn all_of<A: Into<Vec<IslTypeRef>>>(isl_types: A) -> IslConstraint {
230        IslConstraint::new(
231            IslVersion::V2_0,
232            IslConstraintValue::AllOf(isl_types.into()),
233        )
234    }
235
236    /// Creates an `any_of` constraint using the [IslTypeRef] referenced inside it
237    pub fn any_of<A: Into<Vec<IslTypeRef>>>(isl_types: A) -> IslConstraint {
238        IslConstraint::new(
239            IslVersion::V2_0,
240            IslConstraintValue::AnyOf(isl_types.into()),
241        )
242    }
243
244    /// Creates an `one_of` constraint using the [IslTypeRef] referenced inside it
245    pub fn one_of<A: Into<Vec<IslTypeRef>>>(isl_types: A) -> IslConstraint {
246        IslConstraint::new(
247            IslVersion::V2_0,
248            IslConstraintValue::OneOf(isl_types.into()),
249        )
250    }
251
252    /// Creates an `ordered_elements` constraint using the [IslVariablyOccurringTypeRef] referenced inside it
253    pub fn ordered_elements<A: Into<Vec<IslVariablyOccurringTypeRef>>>(
254        isl_types: A,
255    ) -> IslConstraint {
256        IslConstraint::new(
257            IslVersion::V2_0,
258            IslConstraintValue::OrderedElements(isl_types.into()),
259        )
260    }
261
262    /// Creates a `precision` constraint using the range specified in it
263    pub fn precision(precision: U64Range) -> IslConstraint {
264        IslConstraint::new(IslVersion::V2_0, IslConstraintValue::Precision(precision))
265    }
266
267    /// Creates an `exponent` constraint from a [Range] specifying an exponent range.
268    pub fn exponent(exponent: I64Range) -> IslConstraint {
269        IslConstraint::new(IslVersion::V2_0, IslConstraintValue::Exponent(exponent))
270    }
271
272    /// Creates a `fields` constraint using the field names and [IslVariablyOccurringTypeRef]s referenced inside it
273    /// and specify if the `fields` are closed or not (i.e. indicates whether only fields that are explicitly specified should be allowed or not).
274    pub fn fields<I>(fields: I, is_closed: bool) -> IslConstraint
275    where
276        I: Iterator<Item = (String, IslVariablyOccurringTypeRef)>,
277    {
278        IslConstraint::new(
279            IslVersion::V2_0,
280            IslConstraintValue::Fields(fields.collect(), is_closed),
281        )
282    }
283
284    /// Creates a `field_names` constraint using the [IslTypeRef] referenced inside it and considers whether distinct elements are required or not
285    pub fn field_names(isl_type: IslTypeRef, require_distinct_field_names: bool) -> IslConstraint {
286        IslConstraint::new(
287            IslVersion::V2_0,
288            IslConstraintValue::FieldNames(isl_type, require_distinct_field_names),
289        )
290    }
291
292    /// Creates a `not` constraint using the [IslTypeRef] referenced inside it
293    pub fn not(isl_type: IslTypeRef) -> IslConstraint {
294        IslConstraint::new(IslVersion::V2_0, IslConstraintValue::Not(isl_type))
295    }
296
297    /// Creates a `contains` constraint using the [Element] specified inside it
298    pub fn contains<A: Into<Vec<Element>>>(values: A) -> IslConstraint {
299        IslConstraint::new(
300            IslVersion::V2_0,
301            IslConstraintValue::Contains(values.into()),
302        )
303    }
304
305    /// Creates a `container_length` constraint using the range specified in it
306    pub fn container_length(length: UsizeRange) -> IslConstraint {
307        IslConstraint::new(
308            IslVersion::V2_0,
309            IslConstraintValue::ContainerLength(length),
310        )
311    }
312
313    /// Creates a `byte_length` constraint using the range specified in it
314    pub fn byte_length(length: UsizeRange) -> IslConstraint {
315        IslConstraint::new(IslVersion::V2_0, IslConstraintValue::ByteLength(length))
316    }
317
318    /// Creates a `codepoint_length` constraint using the range specified in it
319    pub fn codepoint_length(length: UsizeRange) -> IslConstraint {
320        IslConstraint::new(
321            IslVersion::V2_0,
322            IslConstraintValue::CodepointLength(length),
323        )
324    }
325
326    /// Creates a `timestamp_precision` constraint using the range specified in it
327    pub fn timestamp_precision(precision: TimestampPrecisionRange) -> IslConstraint {
328        IslConstraint::new(
329            IslVersion::V2_0,
330            IslConstraintValue::TimestampPrecision(precision),
331        )
332    }
333
334    /// Creates a `timestamp_offset` constraint using the offset list specified in it
335    pub fn timestamp_offset(offsets: Vec<TimestampOffset>) -> IslConstraint {
336        IslConstraint::new(
337            IslVersion::V2_0,
338            IslConstraintValue::TimestampOffset(IslTimestampOffsetConstraint::new(offsets)),
339        )
340    }
341
342    /// Creates a `utf8_byte_length` constraint using the range specified in it
343    pub fn utf8_byte_length(length: UsizeRange) -> IslConstraint {
344        IslConstraint::new(IslVersion::V2_0, IslConstraintValue::Utf8ByteLength(length))
345    }
346
347    /// Creates an `element` constraint using the [IslTypeRef] referenced inside it and considers whether distinct elements are required or not
348    pub fn element(isl_type: IslTypeRef, require_distinct_elements: bool) -> IslConstraint {
349        IslConstraint::new(
350            IslVersion::V2_0,
351            IslConstraintValue::Element(isl_type, require_distinct_elements),
352        )
353    }
354
355    /// Creates an `annotations` constraint using a list of valid annotations and specify whether annotations are required or closed or both.
356    /// If both `is_required` and `is_closed` are false then this returns an error as ISL 2.0 requires that either annotations are closed or required or both.
357    pub fn annotations_simplified<A: IntoIterator<Item = Element>>(
358        is_required: bool,
359        is_closed: bool,
360        annotations: A,
361    ) -> IonSchemaResult<IslConstraint> {
362        let annotations: Vec<Annotation> = annotations
363            .into_iter()
364            .map(|a| {
365                Annotation::new(
366                    a.as_text().unwrap().to_owned(),
367                    Annotation::is_annotation_required(&a, is_required),
368                    IslVersion::V2_0,
369                )
370            })
371            .collect();
372
373        if !is_required && !is_closed {
374            return invalid_schema_error(
375                "annotations constraints must either be required or closed or both.",
376            );
377        }
378
379        Ok(IslConstraint::new(
380            IslVersion::V2_0,
381            IslConstraintValue::Annotations(IslAnnotationsConstraint::SimpleAnnotations(
382                IslSimpleAnnotationsConstraint::new(is_closed, false, is_required, annotations),
383            )),
384        ))
385    }
386
387    /// Creates an `annotations` constraint using an [IslTypeRef].
388    pub fn annotations(isl_type: IslTypeRef) -> IslConstraint {
389        IslConstraint::new(
390            IslVersion::V2_0,
391            IslConstraintValue::Annotations(IslAnnotationsConstraint::StandardAnnotations(
392                isl_type,
393            )),
394        )
395    }
396
397    /// Creates a `valid_values` constraint using the [`ValidValue`]s specified inside it
398    pub fn valid_values(valid_values: Vec<ValidValue>) -> IonSchemaResult<IslConstraint> {
399        Ok(IslConstraint::new(
400            IslVersion::V2_0,
401            IslConstraintValue::ValidValues(IslValidValuesConstraint { valid_values }),
402        ))
403    }
404
405    /// Creates a `regex` constraint using the expression and flags (case_insensitive, multi_line)
406    pub fn regex(case_insensitive: bool, multi_line: bool, expression: String) -> IslConstraint {
407        todo!()
408    }
409
410    /// Creates a `ieee754_float` constraint using `Ieee754InterchangeFormat` specified in it.
411    pub fn ieee754_float(interchange_format: Ieee754InterchangeFormat) -> IslConstraint {
412        IslConstraint::new(
413            IslVersion::V2_0,
414            IslConstraintValue::Ieee754Float(interchange_format),
415        )
416    }
417}
418
419/// Represents schema constraints [IslConstraint]
420#[derive(Debug, Clone, PartialEq)]
421pub struct IslConstraint {
422    pub(crate) version: IslVersion,
423    pub(crate) constraint_value: IslConstraintValue,
424}
425
426impl IslConstraint {
427    pub(crate) fn new(version: IslVersion, constraint: IslConstraintValue) -> Self {
428        Self {
429            constraint_value: constraint,
430            version,
431        }
432    }
433
434    /// Provides an enum to match constraint types and get underlying constraint value
435    pub fn constraint(&self) -> &IslConstraintValue {
436        &self.constraint_value
437    }
438}
439
440#[derive(Debug, Clone, PartialEq)]
441pub enum IslConstraintValue {
442    AllOf(Vec<IslTypeRef>),
443    Annotations(IslAnnotationsConstraint),
444    AnyOf(Vec<IslTypeRef>),
445    ByteLength(UsizeRange),
446    CodepointLength(UsizeRange),
447    Contains(Vec<Element>),
448    ContentClosed,
449    ContainerLength(UsizeRange),
450    // Represents Element(type_reference, expected_distinct).
451    // For ISL 2.0 true/false is specified based on whether `distinct` annotation is present or not.
452    // For ISL 1.0 which doesn't support `distinct` elements this will be (type_reference, false).
453    Element(IslTypeRef, bool),
454    Exponent(I64Range),
455    // Represents Fields(fields, content_closed)
456    // For ISL 2.0 true/false is specified based on whether `closed::` annotation is present or not
457    // For ISL 1.0 this will always be (fields, false) as it doesn't support `closed::` annotation on fields constraint
458    Fields(HashMap<String, IslVariablyOccurringTypeRef>, bool),
459    // Represents FieldNames(type_reference, expected_distinct).
460    // For ISL 2.0 true/false is specified based on whether `distinct` annotation is present or not.
461    // For ISL 1.0 which doesn't support `field_names` constraint this will be (type_reference, false).
462    FieldNames(IslTypeRef, bool),
463    Ieee754Float(Ieee754InterchangeFormat),
464    Not(IslTypeRef),
465    OneOf(Vec<IslTypeRef>),
466    OrderedElements(Vec<IslVariablyOccurringTypeRef>),
467    Precision(U64Range),
468    Regex(IslRegexConstraint),
469    Scale(I64Range),
470    TimestampOffset(IslTimestampOffsetConstraint),
471    TimestampPrecision(TimestampPrecisionRange),
472    Type(IslTypeRef),
473    Unknown(String, Element), // Unknown constraint is used to store open contents
474    Utf8ByteLength(UsizeRange),
475    ValidValues(IslValidValuesConstraint),
476}
477
478impl IslConstraintValue {
479    /// Parse constraints inside an [Element] to an [IslConstraint]
480    pub fn from_ion_element(
481        isl_version: IslVersion,
482        constraint_name: &str,
483        value: &Element,
484        type_name: &str,
485        inline_imported_types: &mut Vec<IslImportType>,
486    ) -> IonSchemaResult<IslConstraintValue> {
487        // TODO: add more constraints to match below
488        match constraint_name {
489            "all_of" => {
490                let types: Vec<IslTypeRef> =
491                    IslConstraintValue::isl_type_references_from_ion_element(
492                        isl_version,
493                        value,
494                        inline_imported_types,
495                        "all_of",
496                    )?;
497                Ok(IslConstraintValue::AllOf(types))
498            }
499            "annotations" => {
500                if value.is_null() {
501                    return invalid_schema_error(
502                        "annotations constraint was a null instead of a list",
503                    );
504                }
505
506                if value.ion_type() == IonType::List {
507                    Ok(IslConstraintValue::Annotations(
508                        IslAnnotationsConstraint::SimpleAnnotations(
509                            IslSimpleAnnotationsConstraint::from_ion_element(value, isl_version)?,
510                        ),
511                    ))
512                } else if value.ion_type() == IonType::Struct && isl_version == IslVersion::V2_0 {
513                    let type_reference: IslTypeRef =
514                        IslTypeRef::from_ion_element(isl_version, value, inline_imported_types)?;
515
516                    Ok(IslConstraintValue::Annotations(
517                        IslAnnotationsConstraint::StandardAnnotations(type_reference),
518                    ))
519                } else {
520                    return invalid_schema_error(format!(
521                        "annotations constraint was a {:?} instead of a list",
522                        value.ion_type()
523                    ));
524                }
525            }
526            "any_of" => {
527                let types: Vec<IslTypeRef> =
528                    IslConstraintValue::isl_type_references_from_ion_element(
529                        isl_version,
530                        value,
531                        inline_imported_types,
532                        "any_of",
533                    )?;
534                Ok(IslConstraintValue::AnyOf(types))
535            }
536            "byte_length" => Ok(IslConstraintValue::ByteLength(
537                UsizeRange::from_ion_element(value, Element::as_usize)?,
538            )),
539            "codepoint_length" => Ok(IslConstraintValue::CodepointLength(
540                UsizeRange::from_ion_element(value, Element::as_usize)?,
541            )),
542            "contains" => {
543                if value.is_null() {
544                    return invalid_schema_error(
545                        "contains constraint was a null instead of a list",
546                    );
547                }
548
549                if value.ion_type() != IonType::List {
550                    return invalid_schema_error(format!(
551                        "contains constraint was a {:?} instead of a list",
552                        value.ion_type()
553                    ));
554                }
555
556                if !value.annotations().is_empty() {
557                    return invalid_schema_error("contains list can not have any annotations");
558                }
559
560                let values: Vec<Element> = value
561                    .as_sequence()
562                    .unwrap()
563                    .elements()
564                    .map(|e| e.to_owned())
565                    .collect();
566                Ok(IslConstraintValue::Contains(values))
567            }
568            "content" => {
569                if value.is_null() {
570                    return invalid_schema_error(
571                        "content constraint was a null instead of a symbol `closed`",
572                    );
573                }
574
575                if value.ion_type() != IonType::Symbol {
576                    return invalid_schema_error(format!(
577                        "content constraint was a {:?} instead of a symbol `closed`",
578                        value.ion_type()
579                    ));
580                }
581
582                if let Some(closed) = value.as_text() {
583                    if closed != "closed" {
584                        return invalid_schema_error(format!(
585                            "content constraint was a {closed} instead of a symbol `closed`"
586                        ));
587                    }
588                }
589
590                Ok(IslConstraintValue::ContentClosed)
591            }
592
593            "container_length" => Ok(IslConstraintValue::ContainerLength(
594                UsizeRange::from_ion_element(value, Element::as_usize)?,
595            )),
596            "element" => {
597                let type_reference: IslTypeRef =
598                    IslTypeRef::from_ion_element(isl_version, value, inline_imported_types)?;
599                match isl_version {
600                    IslVersion::V1_0 => {
601                        // for ISL 1.0 `distinct annotation on `element` constraint is not supported which is represented by `false` here
602                        Ok(IslConstraintValue::Element(type_reference, false))
603                    }
604                    IslVersion::V2_0 => {
605                        // return error if there are any annotations other than `distinct` or `$null_or`
606                        if value
607                            .annotations()
608                            .iter()
609                            .any(|a| a.text() != Some("distinct") && a.text() != Some("$null_or"))
610                        {
611                            return invalid_schema_error(
612                                "element constraint can only contain `distinct` annotation",
613                            );
614                        }
615
616                        // verify whether `distinct`annotation is present or not
617                        let require_distinct = value.annotations().contains("distinct");
618
619                        Ok(IslConstraintValue::Element(
620                            type_reference,
621                            require_distinct,
622                        ))
623                    }
624                }
625            }
626            "field_names" => {
627                let type_reference =
628                    IslTypeRef::from_ion_element(isl_version, value, inline_imported_types)?;
629                match isl_version {
630                    IslVersion::V1_0 => {
631                        // for ISL 1.0 `field_names` constraint does not exist hence `field_names` will be considered as open content
632                        Ok(IslConstraintValue::Unknown(
633                            constraint_name.to_string(),
634                            value.to_owned(),
635                        ))
636                    }
637                    IslVersion::V2_0 => {
638                        // return error if there are any annotations other than `distinct`
639                        if value.annotations().len() > 1
640                            || value
641                                .annotations()
642                                .iter()
643                                .any(|a| a.text() != Some("distinct"))
644                        {
645                            return invalid_schema_error(
646                                "field_names constraint can only contain `distinct` annotation",
647                            );
648                        }
649
650                        Ok(IslConstraintValue::FieldNames(
651                            type_reference,
652                            value.annotations().contains("distinct"),
653                        ))
654                    }
655                }
656            }
657            "fields" => {
658                let fields: HashMap<String, IslVariablyOccurringTypeRef> =
659                    IslConstraintValue::isl_fields_from_ion_element(
660                        isl_version,
661                        value,
662                        inline_imported_types,
663                    )?;
664
665                if fields.is_empty() {
666                    return invalid_schema_error("fields constraint can not be empty");
667                }
668                match isl_version {
669                    IslVersion::V1_0 => Ok(IslConstraintValue::Fields(fields, false)),
670                    IslVersion::V2_0 => {
671                        if value.annotations().len() > 1
672                            || value
673                                .annotations()
674                                .iter()
675                                .any(|a| a.text() != Some("closed"))
676                        {
677                            return invalid_schema_error(
678                                "fields constraint may only be annotated with 'closed'",
679                            );
680                        }
681                        Ok(IslConstraintValue::Fields(
682                            fields,
683                            value.annotations().contains("closed"),
684                        ))
685                    }
686                }
687            }
688            "ieee754_float" => {
689                if !value.annotations().is_empty() {
690                    return invalid_schema_error(
691                        "`ieee_754_float` argument must not have annotations",
692                    );
693                }
694                let string_value =
695                    value
696                        .as_symbol()
697                        .map(|s| s.text().unwrap())
698                        .ok_or_else(|| {
699                            invalid_schema_error_raw(format!(
700                                "expected ieee754_float to be one of 'binary16', 'binary32', or 'binary64', but it was: {value}"))
701                        })?;
702                Ok(IslConstraintValue::Ieee754Float(string_value.try_into()?))
703            }
704            "one_of" => {
705                let types: Vec<IslTypeRef> =
706                    IslConstraintValue::isl_type_references_from_ion_element(
707                        isl_version,
708                        value,
709                        inline_imported_types,
710                        "one_of",
711                    )?;
712                Ok(IslConstraintValue::OneOf(types))
713            }
714            "not" => {
715                let type_reference: IslTypeRef =
716                    IslTypeRef::from_ion_element(isl_version, value, inline_imported_types)?;
717                Ok(IslConstraintValue::Not(type_reference))
718            }
719            "type" => {
720                let type_reference: IslTypeRef =
721                    IslTypeRef::from_ion_element(isl_version, value, inline_imported_types)?;
722                Ok(IslConstraintValue::Type(type_reference))
723            }
724            "ordered_elements" => {
725                if value.is_null() {
726                    return invalid_schema_error(
727                        "ordered_elements constraint was a null instead of a list",
728                    );
729                }
730                isl_require!(value.annotations().is_empty() => "ordered_elements list may not be annotated")?;
731                if value.ion_type() != IonType::List {
732                    return invalid_schema_error(format!(
733                        "ordered_elements constraint was a {:?} instead of a list",
734                        value.ion_type()
735                    ));
736                }
737
738                let types: Vec<IslVariablyOccurringTypeRef> = value
739                    .as_sequence()
740                    .unwrap()
741                    .elements()
742                    .map(|e| {
743                        IslVariablyOccurringTypeRef::from_ion_element(
744                            constraint_name,
745                            isl_version,
746                            e,
747                            inline_imported_types,
748                        )
749                    })
750                    .collect::<IonSchemaResult<Vec<IslVariablyOccurringTypeRef>>>()?;
751                Ok(IslConstraintValue::OrderedElements(types))
752            }
753            "precision" => Ok(IslConstraintValue::Precision(U64Range::from_ion_element(
754                value,
755                Element::as_u64,
756            )?)),
757            "regex" => {
758                let case_insensitive = value.annotations().contains("i");
759                let multi_line = value.annotations().contains("m");
760
761                if value
762                    .annotations()
763                    .iter()
764                    .any(|a| a.text().unwrap() != "i" && a.text().unwrap() != "m")
765                {
766                    return invalid_schema_error(
767                        "regex constraint must only contain 'i' or 'm' annotation",
768                    );
769                }
770
771                let expression = value.as_string().ok_or_else(|| {
772                    invalid_schema_error_raw(format!(
773                        "expected regex to contain a string expression but found: {}",
774                        value.ion_type()
775                    ))
776                })?;
777
778                if expression.is_empty() {
779                    return invalid_schema_error(
780                        "regex constraint must contain a non empty expression",
781                    );
782                }
783
784                Ok(IslConstraintValue::Regex(IslRegexConstraint::new(
785                    case_insensitive,
786                    multi_line,
787                    expression.to_string(),
788                )))
789            }
790            "scale" => match isl_version {
791                IslVersion::V1_0 => Ok(IslConstraintValue::Scale(I64Range::from_ion_element(
792                    value,
793                    Element::as_i64,
794                )?)),
795                IslVersion::V2_0 => {
796                    // for ISL 2.0 scale constraint does not exist hence `scale` will be considered as open content
797                    Ok(IslConstraintValue::Unknown(
798                        constraint_name.to_string(),
799                        value.to_owned(),
800                    ))
801                }
802            },
803            "timestamp_precision" => Ok(IslConstraintValue::TimestampPrecision(
804                TimestampPrecisionRange::from_ion_element(value, |e| {
805                    let symbol_text = e.as_symbol().and_then(Symbol::text)?;
806                    TimestampPrecision::try_from(symbol_text).ok()
807                })?,
808            )),
809            "exponent" => match isl_version {
810                IslVersion::V1_0 => {
811                    // for ISL 1.0 exponent constraint does not exist hence `exponent` will be considered as open content
812                    Ok(IslConstraintValue::Unknown(
813                        constraint_name.to_string(),
814                        value.to_owned(),
815                    ))
816                }
817                IslVersion::V2_0 => Ok(IslConstraintValue::Exponent(I64Range::from_ion_element(
818                    value,
819                    Element::as_i64,
820                )?)),
821            },
822            "timestamp_offset" => {
823                use IonType::*;
824                if value.is_null() {
825                    return invalid_schema_error(
826                        "expected a list of valid offsets for an `timestamp_offset` constraint, found null",
827                    );
828                }
829
830                if !value.annotations().is_empty() {
831                    return invalid_schema_error("`timestamp_offset` list may not be annotated");
832                }
833
834                let valid_offsets: Vec<TimestampOffset> = match value.ion_type() {
835                    List => {
836                        let list_values = value.as_sequence().unwrap();
837                        if list_values.is_empty() {
838                            return invalid_schema_error(
839                                "`timestamp_offset` constraint must contain at least one offset",
840                            );
841                        }
842                        let list_vec: IonSchemaResult<Vec<TimestampOffset>> = list_values
843                            .elements()
844                            .map(|e| {
845                                if e.is_null() {
846                                    return invalid_schema_error(
847                                        "`timestamp_offset` values must be non-null strings, found null",
848                                    );
849                                }
850
851                                if e.ion_type() != IonType::String {
852                                    return invalid_schema_error(format!(
853                                        "`timestamp_offset` values must be non-null strings, found {e}"
854                                    ));
855                                }
856
857                                if !e.annotations().is_empty() {
858                                    return invalid_schema_error(format!(
859                                        "`timestamp_offset` values may not be annotated, found {e}"
860                                    ));
861                                }
862
863                                // unwrap here will not panic as we have already verified the ion type to be a string
864                                let string_value = e.as_string().unwrap();
865
866                                // convert the string to TimestampOffset which stores offset in minutes
867                                string_value.try_into()
868                            })
869                            .collect();
870                        list_vec?
871                    }
872                    _ => {
873                        return invalid_schema_error(format!(
874                        "`timestamp_offset` requires a list of offset strings, but found: {value}"
875                    ))
876                    }
877                };
878                Ok(IslConstraintValue::TimestampOffset(
879                    IslTimestampOffsetConstraint::new(valid_offsets),
880                ))
881            }
882            "utf8_byte_length" => Ok(IslConstraintValue::Utf8ByteLength(
883                UsizeRange::from_ion_element(value, Element::as_usize)?,
884            )),
885            "valid_values" => Ok(IslConstraintValue::ValidValues(
886                IslValidValuesConstraint::from_ion_element(value, isl_version)?,
887            )),
888            _ => Ok(IslConstraintValue::Unknown(
889                constraint_name.to_string(),
890                value.to_owned(),
891            )),
892        }
893    }
894
895    // helper method for from_ion_element to get isl type references from given ion element
896    fn isl_type_references_from_ion_element(
897        isl_version: IslVersion,
898        value: &Element,
899        inline_imported_types: &mut Vec<IslImportType>,
900        constraint_name: &str,
901    ) -> IonSchemaResult<Vec<IslTypeRef>> {
902        //TODO: create a method/macro for this ion type check which can be reused
903        if value.is_null() {
904            return invalid_schema_error(format!(
905                "{constraint_name} constraint was a null instead of a list"
906            ));
907        }
908        if value.ion_type() != IonType::List {
909            return invalid_schema_error(format!(
910                "{} constraint was a {:?} instead of a list",
911                constraint_name,
912                value.ion_type()
913            ));
914        }
915        value
916            .as_sequence()
917            .unwrap()
918            .elements()
919            .map(|e| IslTypeRef::from_ion_element(isl_version, e, inline_imported_types))
920            .collect::<IonSchemaResult<Vec<IslTypeRef>>>()
921    }
922
923    // helper method for from_ion_element to get isl fields from given ion element
924    fn isl_fields_from_ion_element(
925        isl_version: IslVersion,
926        value: &Element,
927        inline_imported_types: &mut Vec<IslImportType>,
928    ) -> IonSchemaResult<HashMap<String, IslVariablyOccurringTypeRef>> {
929        if value.is_null() {
930            return invalid_schema_error("fields constraint was a null instead of a struct");
931        }
932
933        if value.ion_type() != IonType::Struct {
934            return invalid_schema_error(format!(
935                "fields constraint was a {:?} instead of a struct",
936                value.ion_type()
937            ));
938        }
939
940        let fields_map = value
941            .as_struct()
942            .unwrap()
943            .iter()
944            .map(|(f, v)| {
945                IslVariablyOccurringTypeRef::from_ion_element(
946                    "fields",
947                    isl_version,
948                    v,
949                    inline_imported_types,
950                )
951                .map(|t| (f.text().unwrap().to_owned(), t))
952            })
953            .collect::<IonSchemaResult<HashMap<String, IslVariablyOccurringTypeRef>>>()?;
954
955        // verify the map length with struct length to check for duplicates
956        if fields_map.len() < value.as_struct().unwrap().len() {
957            return invalid_schema_error("fields must be a struct with no repeated field names");
958        }
959
960        Ok(fields_map)
961    }
962
963    pub(crate) fn field_name(&self) -> &str {
964        match self {
965            IslConstraintValue::AllOf(_) => "all_of",
966            IslConstraintValue::Annotations(_) => "annotations",
967            IslConstraintValue::AnyOf(_) => "any_of",
968            IslConstraintValue::ByteLength(_) => "byte_length",
969            IslConstraintValue::CodepointLength(_) => "codepoint_length",
970            IslConstraintValue::Contains(_) => "contains",
971            IslConstraintValue::ContentClosed => "content",
972            IslConstraintValue::ContainerLength(_) => "container_length",
973            IslConstraintValue::Element(_, _) => "element",
974            IslConstraintValue::Exponent(_) => "exponent",
975            IslConstraintValue::Fields(_, _) => "fields",
976            IslConstraintValue::FieldNames(_, _) => "field_names",
977            IslConstraintValue::Ieee754Float(_) => "ieee754_float",
978            IslConstraintValue::Not(_) => "not",
979            IslConstraintValue::OneOf(_) => "one_of",
980            IslConstraintValue::OrderedElements(_) => "ordered_elements",
981            IslConstraintValue::Precision(_) => "precision",
982            IslConstraintValue::Regex(_) => "regex",
983            IslConstraintValue::Scale(_) => "scale",
984            IslConstraintValue::TimestampOffset(_) => "timestamp_offset",
985            IslConstraintValue::TimestampPrecision(_) => "timestamp_precision",
986            IslConstraintValue::Type(_) => "type",
987            IslConstraintValue::Unknown(field_name, _) => field_name.as_str(),
988            IslConstraintValue::Utf8ByteLength(_) => "utf8_byte_length",
989            IslConstraintValue::ValidValues(_) => "valid_values",
990        }
991    }
992}
993
994impl WriteAsIon for IslConstraintValue {
995    fn write_as_ion<V: ValueWriter>(&self, writer: V) -> IonResult<()> {
996        match self {
997            IslConstraintValue::AllOf(type_refs) => writer.write(type_refs),
998            IslConstraintValue::Annotations(annotations) => writer.write(annotations),
999            IslConstraintValue::AnyOf(type_refs) => writer.write(type_refs),
1000            IslConstraintValue::ByteLength(range) => writer.write(range),
1001            IslConstraintValue::CodepointLength(range) => writer.write(range),
1002            IslConstraintValue::Contains(elements) => {
1003                let mut list_writer = writer.list_writer()?;
1004                for element in elements {
1005                    list_writer.write(element)?;
1006                }
1007                list_writer.close()
1008            }
1009            IslConstraintValue::ContentClosed => writer.write_symbol("closed"),
1010            IslConstraintValue::ContainerLength(range) => writer.write(range),
1011            IslConstraintValue::Element(type_ref, is_distinct) => {
1012                if *is_distinct {
1013                    writer.with_annotations(["distinct"])?.write(type_ref)
1014                } else {
1015                    writer.write(type_ref)
1016                }
1017            }
1018            IslConstraintValue::Exponent(range) => writer.write(range),
1019            IslConstraintValue::Fields(fields, content_closed) => {
1020                let annotations: &[&'static str] = if *content_closed { &["closed"] } else { &[] };
1021                let mut struct_writer = writer.with_annotations(annotations)?.struct_writer()?;
1022                for (field_name, type_ref) in fields.iter() {
1023                    struct_writer.write(field_name.as_str(), type_ref)?;
1024                }
1025                struct_writer.close()
1026            }
1027            IslConstraintValue::FieldNames(type_ref, is_distinct) => {
1028                if *is_distinct {
1029                    writer.with_annotations(["distinct"])?.write(type_ref)
1030                } else {
1031                    writer.write(type_ref)
1032                }
1033            }
1034            IslConstraintValue::Ieee754Float(format) => writer.write(format),
1035            IslConstraintValue::Not(type_ref) => writer.write(type_ref),
1036            IslConstraintValue::OneOf(type_refs) => writer.write(type_refs),
1037            IslConstraintValue::OrderedElements(type_refs) => writer.write(type_refs),
1038            IslConstraintValue::Precision(range) => writer.write(range),
1039            IslConstraintValue::Regex(regex) => writer.write(regex),
1040            IslConstraintValue::Scale(range) => writer.write(range),
1041            IslConstraintValue::TimestampOffset(timestamp_offset) => writer.write(timestamp_offset),
1042            IslConstraintValue::TimestampPrecision(range) => writer.write(range),
1043            IslConstraintValue::Type(type_ref) => writer.write(type_ref),
1044            IslConstraintValue::Unknown(field_name, value) => writer.write(value),
1045            IslConstraintValue::Utf8ByteLength(range) => writer.write(range),
1046            IslConstraintValue::ValidValues(valid_values) => writer.write(valid_values),
1047        }
1048    }
1049}
1050
1051/// Represents the [annotations] constraint
1052///
1053/// [annotations]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#annotations
1054#[derive(Debug, Clone, PartialEq)]
1055pub enum IslAnnotationsConstraint {
1056    SimpleAnnotations(IslSimpleAnnotationsConstraint),
1057    StandardAnnotations(IslTypeRef),
1058}
1059
1060impl WriteAsIon for IslAnnotationsConstraint {
1061    fn write_as_ion<V: ValueWriter>(&self, writer: V) -> IonResult<()> {
1062        match self {
1063            IslAnnotationsConstraint::SimpleAnnotations(ann) => writer.write(ann),
1064            IslAnnotationsConstraint::StandardAnnotations(ann) => writer.write(ann),
1065        }
1066    }
1067}
1068
1069/// Represents the [simple syntax] for annotations constraint
1070/// ```ion
1071/// Grammar: <ANNOTATIONS> ::= annotations: <ANNOTATIONS_MODIFIER>... [ <SYMBOL>... ]
1072///          <ANNOTATIONS_MODIFIER> ::= required::
1073///                                   | closed::
1074/// ```
1075///
1076/// [simple syntax]: https://amazon-ion.github.io/ion-schema/docs/isl-2-0/spec#simple-syntax
1077#[derive(Debug, Clone, PartialEq, Eq)]
1078pub struct IslSimpleAnnotationsConstraint {
1079    pub is_closed: bool,
1080    pub is_ordered: bool,
1081    pub is_required: bool,
1082    pub annotations: Vec<Annotation>,
1083}
1084
1085impl IslSimpleAnnotationsConstraint {
1086    pub fn new(
1087        is_closed: bool,
1088        is_ordered: bool,
1089        is_required: bool,
1090        annotations: Vec<Annotation>,
1091    ) -> Self {
1092        Self {
1093            is_closed,
1094            is_ordered,
1095            is_required,
1096            annotations,
1097        }
1098    }
1099
1100    pub(crate) fn from_ion_element(
1101        value: &Element,
1102        isl_version: IslVersion,
1103    ) -> IonSchemaResult<Self> {
1104        let annotation_modifiers: Vec<&str> = value
1105            .annotations()
1106            .iter()
1107            .map(|sym| sym.text().unwrap())
1108            .collect();
1109
1110        if (annotation_modifiers
1111            .iter()
1112            .any(|a| a != &"closed" && a != &"required")
1113            || annotation_modifiers.is_empty())
1114            && isl_version == IslVersion::V2_0
1115        {
1116            return invalid_schema_error(
1117                "annotations constraint must only be annotated with 'required' or 'closed' annotation",
1118            );
1119        }
1120
1121        let annotations: Vec<Annotation> = value
1122            .as_sequence()
1123            .unwrap()
1124            .elements()
1125            .map(|e| {
1126                if !e.annotations().is_empty() && isl_version == IslVersion::V2_0 {
1127                    return invalid_schema_error(
1128                        "annotations constraint must only contain symbols without any annotations",
1129                    );
1130                }
1131                e.as_symbol()
1132                    .map(|s| {
1133                        Annotation::new(
1134                            s.text().unwrap().to_owned(),
1135                            Annotation::is_annotation_required(
1136                                e,
1137                                annotation_modifiers.contains(&"required"),
1138                            ),
1139                            isl_version,
1140                        )
1141                    })
1142                    .ok_or(invalid_schema_error_raw(
1143                        "annotations constraint must only contain symbols",
1144                    ))
1145            })
1146            .collect::<IonSchemaResult<Vec<Annotation>>>()?;
1147
1148        Ok(IslSimpleAnnotationsConstraint::new(
1149            annotation_modifiers.contains(&"closed"),
1150            annotation_modifiers.contains(&"ordered"),
1151            annotation_modifiers.contains(&"required"),
1152            annotations,
1153        ))
1154    }
1155
1156    pub(crate) fn convert_to_type_reference(&self) -> IonSchemaResult<IslTypeRef> {
1157        let mut isl_constraints = vec![];
1158        if self.is_closed {
1159            isl_constraints.push(isl::isl_constraint::v_2_0::element(
1160                isl::isl_type_reference::v_2_0::anonymous_type_ref(vec![
1161                    isl::isl_constraint::v_2_0::valid_values(
1162                        self.annotations
1163                            .iter()
1164                            .map(|a| ValidValue::Element(Value::Symbol(a.value().into())))
1165                            .collect(),
1166                    )?,
1167                ]),
1168                false,
1169            ));
1170        }
1171
1172        if self.is_required {
1173            isl_constraints.push(isl::isl_constraint::v_2_0::contains(
1174                self.annotations
1175                    .iter()
1176                    .map(|a| Element::symbol(a.value()))
1177                    .collect::<Vec<Element>>(),
1178            ))
1179        }
1180
1181        Ok(isl::isl_type_reference::v_2_0::anonymous_type_ref(
1182            isl_constraints,
1183        ))
1184    }
1185}
1186
1187impl WriteAsIon for IslSimpleAnnotationsConstraint {
1188    fn write_as_ion<V: ValueWriter>(&self, writer: V) -> IonResult<()> {
1189        let mut annotation_modifiers = vec![];
1190        if self.is_ordered {
1191            annotation_modifiers.push("ordered");
1192        }
1193        if self.is_closed {
1194            annotation_modifiers.push("closed");
1195        }
1196        if self.is_required {
1197            annotation_modifiers.push("required");
1198        }
1199        writer
1200            .with_annotations(annotation_modifiers)?
1201            .write(&self.annotations)
1202    }
1203}
1204
1205/// Represents the [valid_values] constraint
1206///
1207/// [valid_values]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#annotations
1208#[derive(Debug, Clone, PartialEq)]
1209pub struct IslValidValuesConstraint {
1210    pub(crate) valid_values: Vec<ValidValue>,
1211}
1212
1213impl IslValidValuesConstraint {
1214    /// Provides a way to programmatically construct valid_values constraint
1215    pub fn new(valid_values: Vec<ValidValue>) -> IonSchemaResult<Self> {
1216        Ok(Self { valid_values })
1217    }
1218
1219    pub fn values(&self) -> &Vec<ValidValue> {
1220        &self.valid_values
1221    }
1222
1223    pub fn from_ion_element(value: &Element, isl_version: IslVersion) -> IonSchemaResult<Self> {
1224        let valid_values = if value.annotations().contains("range") {
1225            vec![ValidValue::from_ion_element(value, isl_version)?]
1226        } else {
1227            isl_require!(value.ion_type() == IonType::List && !value.is_null() => "Expected a list of valid values; found: {value}")?;
1228            let valid_values: Result<Vec<_>, _> = value
1229                .as_sequence()
1230                .unwrap()
1231                .elements()
1232                .map(|e| ValidValue::from_ion_element(e, isl_version))
1233                .collect();
1234            valid_values?
1235        };
1236        IslValidValuesConstraint::new(valid_values)
1237    }
1238}
1239
1240impl WriteAsIon for IslValidValuesConstraint {
1241    fn write_as_ion<V: ValueWriter>(&self, writer: V) -> IonResult<()> {
1242        let mut list_writer = writer.list_writer()?;
1243        for vv in self.values() {
1244            list_writer.write(vv)?;
1245        }
1246        list_writer.close()
1247    }
1248}
1249
1250/// Represents the [regex] constraint
1251///
1252/// [regex]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#regex
1253#[derive(Debug, Clone, PartialEq, Eq)]
1254pub struct IslRegexConstraint {
1255    case_insensitive: bool,
1256    multi_line: bool,
1257    expression: String,
1258}
1259
1260impl IslRegexConstraint {
1261    pub(crate) fn new(case_insensitive: bool, multi_line: bool, expression: String) -> Self {
1262        Self {
1263            case_insensitive,
1264            multi_line,
1265            expression,
1266        }
1267    }
1268
1269    pub fn expression(&self) -> &String {
1270        &self.expression
1271    }
1272
1273    pub fn case_insensitive(&self) -> bool {
1274        self.case_insensitive
1275    }
1276
1277    pub fn multi_line(&self) -> bool {
1278        self.multi_line
1279    }
1280}
1281
1282impl WriteAsIon for IslRegexConstraint {
1283    fn write_as_ion<V: ValueWriter>(&self, writer: V) -> IonResult<()> {
1284        let mut regex_modifiers = vec![];
1285        if self.case_insensitive {
1286            regex_modifiers.push("i");
1287        }
1288        if self.multi_line {
1289            regex_modifiers.push("m");
1290        }
1291        writer
1292            .with_annotations(regex_modifiers)?
1293            .write_string(&self.expression)
1294    }
1295}
1296
1297/// Represents the [timestamp_offset] constraint
1298///
1299/// [timestamp_offset]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#timestamp_offset
1300#[derive(Debug, Clone, PartialEq, Eq)]
1301pub struct IslTimestampOffsetConstraint {
1302    valid_offsets: Vec<TimestampOffset>,
1303}
1304
1305impl IslTimestampOffsetConstraint {
1306    pub(crate) fn new(valid_offsets: Vec<TimestampOffset>) -> Self {
1307        Self { valid_offsets }
1308    }
1309
1310    pub fn valid_offsets(&self) -> &[TimestampOffset] {
1311        &self.valid_offsets
1312    }
1313}
1314
1315impl WriteAsIon for IslTimestampOffsetConstraint {
1316    fn write_as_ion<V: ValueWriter>(&self, writer: V) -> IonResult<()> {
1317        let mut list_writer = writer.list_writer()?;
1318        for timestamp_offset in &self.valid_offsets {
1319            list_writer.write(timestamp_offset)?;
1320        }
1321        list_writer.close()
1322    }
1323}