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
17pub 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 pub fn type_constraint(isl_type: IslTypeRef) -> IslConstraint {
33 IslConstraint::new(IslVersion::V1_0, IslConstraintValue::Type(isl_type))
34 }
35
36 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 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 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 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 pub fn precision(precision: U64Range) -> IslConstraint {
72 IslConstraint::new(IslVersion::V1_0, IslConstraintValue::Precision(precision))
73 }
74
75 pub fn scale(scale: I64Range) -> IslConstraint {
77 IslConstraint::new(IslVersion::V1_0, IslConstraintValue::Scale(scale))
78 }
79
80 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 pub fn not(isl_type: IslTypeRef) -> IslConstraint {
93 IslConstraint::new(IslVersion::V1_0, IslConstraintValue::Not(isl_type))
94 }
95
96 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 pub fn container_length(length: UsizeRange) -> IslConstraint {
106 IslConstraint::new(
107 IslVersion::V1_0,
108 IslConstraintValue::ContainerLength(length),
109 )
110 }
111
112 pub fn byte_length(length: UsizeRange) -> IslConstraint {
114 IslConstraint::new(IslVersion::V1_0, IslConstraintValue::ByteLength(length))
115 }
116
117 pub fn codepoint_length(length: UsizeRange) -> IslConstraint {
119 IslConstraint::new(
120 IslVersion::V1_0,
121 IslConstraintValue::CodepointLength(length),
122 )
123 }
124
125 pub fn timestamp_precision(precision: TimestampPrecisionRange) -> IslConstraint {
127 IslConstraint::new(
128 IslVersion::V1_0,
129 IslConstraintValue::TimestampPrecision(precision),
130 )
131 }
132
133 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 pub fn utf8_byte_length(length: UsizeRange) -> IslConstraint {
143 IslConstraint::new(IslVersion::V1_0, IslConstraintValue::Utf8ByteLength(length))
144 }
145
146 pub fn element(isl_type: IslTypeRef) -> IslConstraint {
148 IslConstraint::new(
149 IslVersion::V1_0,
150 IslConstraintValue::Element(isl_type, false),
151 )
152 }
153
154 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 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 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
207pub 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 pub fn type_constraint(isl_type: IslTypeRef) -> IslConstraint {
225 IslConstraint::new(IslVersion::V2_0, IslConstraintValue::Type(isl_type))
226 }
227
228 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 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 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 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 pub fn precision(precision: U64Range) -> IslConstraint {
264 IslConstraint::new(IslVersion::V2_0, IslConstraintValue::Precision(precision))
265 }
266
267 pub fn exponent(exponent: I64Range) -> IslConstraint {
269 IslConstraint::new(IslVersion::V2_0, IslConstraintValue::Exponent(exponent))
270 }
271
272 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 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 pub fn not(isl_type: IslTypeRef) -> IslConstraint {
294 IslConstraint::new(IslVersion::V2_0, IslConstraintValue::Not(isl_type))
295 }
296
297 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 pub fn container_length(length: UsizeRange) -> IslConstraint {
307 IslConstraint::new(
308 IslVersion::V2_0,
309 IslConstraintValue::ContainerLength(length),
310 )
311 }
312
313 pub fn byte_length(length: UsizeRange) -> IslConstraint {
315 IslConstraint::new(IslVersion::V2_0, IslConstraintValue::ByteLength(length))
316 }
317
318 pub fn codepoint_length(length: UsizeRange) -> IslConstraint {
320 IslConstraint::new(
321 IslVersion::V2_0,
322 IslConstraintValue::CodepointLength(length),
323 )
324 }
325
326 pub fn timestamp_precision(precision: TimestampPrecisionRange) -> IslConstraint {
328 IslConstraint::new(
329 IslVersion::V2_0,
330 IslConstraintValue::TimestampPrecision(precision),
331 )
332 }
333
334 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 pub fn utf8_byte_length(length: UsizeRange) -> IslConstraint {
344 IslConstraint::new(IslVersion::V2_0, IslConstraintValue::Utf8ByteLength(length))
345 }
346
347 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 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 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 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 pub fn regex(case_insensitive: bool, multi_line: bool, expression: String) -> IslConstraint {
407 todo!()
408 }
409
410 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#[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 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 Element(IslTypeRef, bool),
454 Exponent(I64Range),
455 Fields(HashMap<String, IslVariablyOccurringTypeRef>, bool),
459 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), Utf8ByteLength(UsizeRange),
475 ValidValues(IslValidValuesConstraint),
476}
477
478impl IslConstraintValue {
479 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 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 Ok(IslConstraintValue::Element(type_reference, false))
603 }
604 IslVersion::V2_0 => {
605 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 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 Ok(IslConstraintValue::Unknown(
633 constraint_name.to_string(),
634 value.to_owned(),
635 ))
636 }
637 IslVersion::V2_0 => {
638 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 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 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 let string_value = e.as_string().unwrap();
865
866 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 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 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 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 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#[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#[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#[derive(Debug, Clone, PartialEq)]
1209pub struct IslValidValuesConstraint {
1210 pub(crate) valid_values: Vec<ValidValue>,
1211}
1212
1213impl IslValidValuesConstraint {
1214 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#[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#[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}