Skip to content

Adds data models and builder functions for remaining constraints #238

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions ion-schema/src/constraint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1140,7 +1140,7 @@ impl ConstraintValidator for ContainerLengthConstraint {
let size = if let Some(element_iter) = value.as_sequence_iter() {
element_iter.count()
} else if let Some(strukt) = value.as_struct() {
strukt.fields().map(|(k, v)| v).count()
strukt.len()
} else {
return Err(Violation::new(
"container_length",
Expand Down Expand Up @@ -1929,7 +1929,10 @@ impl RegexConstraint {

/// Converts given string to a pattern based on regex features supported by Ion Schema Specification
/// For more information: `<https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#regex>`
fn convert_to_pattern(expression: String, isl_version: IslVersion) -> IonSchemaResult<String> {
pub(crate) fn convert_to_pattern(
expression: String,
isl_version: IslVersion,
) -> IonSchemaResult<String> {
let mut sb = String::new();
let mut si = expression.as_str().chars().peekable();

Expand Down
30 changes: 15 additions & 15 deletions ion-schema/src/model/constraints/any_constraint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,23 +99,23 @@ any_constraint!(
// AnnotationsV1,
AnnotationsV2Simple,
// AnnotationsV2Standard,
// AnyOf,
// ByteLength,
// CodepointLength,
// ContainerLength,
// Contains,
// ElementConstraint,
// Exponent,
AnyOf,
ByteLength,
CodepointLength,
ContainerLength,
Contains,
ElementType,
Exponent,
Fields,
// FieldNames,
// Ieee754Float,
// Not,
// OneOf,
FieldNames,
Ieee754Float,
Not,
OneOf,
OrderedElements,
// Precision,
// Regex,
// Scale,
// TimestampOffset,
Precision,
Regex,
Scale,
TimestampOffset,
TimestampPrecision,
TypeConstraint,
Utf8ByteLength,
Expand Down
174 changes: 174 additions & 0 deletions ion-schema/src/model/constraints/any_of.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use crate::internal_traits::{
LoaderContext, ValidateInternal, ValidationContext, WriteAsIsl, WriteContext,
};
use crate::ion_schema_version::Versioned;
use crate::model::constraints::{ConstraintName, ReadConstraint};
use crate::model::type_argument::TypeArgument;
use crate::model::{TypeDefinitionBuilder, VersionedTypeArgumentList};
use crate::result::IonSchemaResult;
use crate::{IonSchemaElement, IslVersion, ViolationRecorder};
use ion_rs::{Element, ValueWriter};
use std::ops::ControlFlow;

impl ConstraintName for AnyOf {
const CONSTRAINT_NAME: &'static str = "any_of";
}

/// Represents the `any_of` constraint (ref. [ISL 1.0], [ISL 2.0]).
///
/// [ISL 1.0]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#any_of
/// [ISL 2.0]: https://amazon-ion.github.io/ion-schema/docs/isl-2-0/spec#any_of
#[derive(Debug, PartialEq, Clone)]
pub struct AnyOf {
type_arguments: Vec<TypeArgument>,
}

impl AnyOf {
fn new(mut type_arguments: Vec<TypeArgument>) -> Self {
// Sorting the vec allows us to get Bag-like equality semantics.
// TODO: Replace the vec with a HashBag or come up with a less hacky way to sort the type arguments.
Comment on lines +31 to +32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is used in a few places, consider making a naive Bag type wrapping a sorted Vec<TypeArgument> that you can optimize later.

type_arguments.sort_by_cached_key(|item| format!("{item:?}"));

Self { type_arguments }
}

pub fn type_arguments(&self) -> impl Iterator<Item = &TypeArgument> {
self.type_arguments.iter()
}
}

impl<V: IslVersion> TypeDefinitionBuilder<V> {
pub fn any_of<T: Into<VersionedTypeArgumentList<V>>>(self, type_arguments: T) -> Self {
let type_arguments = Versioned::into_inner(type_arguments.into());
self.with_constraint(AnyOf::new(type_arguments).into())
}
}

impl ValidateInternal for AnyOf {
fn validate_internal<'top: 'call, 'call, R>(
&'top self,
value: &IonSchemaElement<'top>,
ctx: &ValidationContext,
recorder: &'call mut R,
) -> ControlFlow<()>
where
R: ViolationRecorder<'top>,
{
todo!("Implement this once the ValidationContext allows looking up a type")
}
}

impl<V: IslVersion> WriteAsIsl<V> for AnyOf
where
TypeArgument: WriteAsIsl<V>,
{
fn write_as_isl<W: ValueWriter>(
&self,
writer: W,
ctx: &WriteContext<V>,
) -> IonSchemaResult<()> {
todo!()
}
}

impl<V: IslVersion> ReadConstraint<V> for AnyOf {
fn read_constraint(ion: &Element, ctx: &LoaderContext<V>) -> IonSchemaResult<Option<Self>> {
todo!()
}
}

#[cfg(test)]
mod tests {
use crate::isl::isl_type_reference::NullabilityModifier;
use crate::model::{IntoTypeArgument, TypeDefinition, TypeReference};
use crate::{ISL_1_0, ISL_2_0};

use super::*;

#[test]
fn test_builder() {
let type_ = TypeDefinitionBuilder::<ISL_1_0>::new()
.any_of((
// This is a heterogeneous "list" (really a tuple) of things that can become a type arg.
"foo_type",
TypeDefinitionBuilder::new().build(),
TypeReference::imported("bar.isl", "baz"),
"quux_type".or_null(true),
))
.build();

assert_eq!(
type_.constraints().cloned().collect::<Vec<_>>(),
vec![AnyOf::new(vec![
TypeArgument::from_type_ref(
NullabilityModifier::Nothing,
TypeReference::from("foo_type")
),
TypeArgument::from_type_def(
NullabilityModifier::Nothing,
TypeDefinition::new(vec![], vec![])
),
TypeArgument::from_type_ref(
NullabilityModifier::Nothing,
TypeReference::imported("bar.isl", "baz")
),
TypeArgument::from_type_ref(
NullabilityModifier::Nullable,
TypeReference::from("quux_type")
),
],)
Comment on lines +105 to +122
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These explicitly-constructed vec!s would be good candidates for the tuple-of-Into<_>s trick. (Not in this PR.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be, but I'm intentionally not using it here so that if there's something wrong with the tuple-of-Into<_>s logic, it won't silently pass these tests. In other words, I'm intentionally constructing these the verbose way in order to make the tests more thorough.

.into()]
);

let type_ = TypeDefinitionBuilder::<ISL_2_0>::new()
.any_of(("foo_type", "quux_type".or_null(true)))
.build();

assert_eq!(
type_.constraints().cloned().collect::<Vec<_>>(),
vec![AnyOf::new(vec![
TypeArgument::from_type_ref(
NullabilityModifier::Nothing,
TypeReference::from("foo_type")
),
TypeArgument::from_type_ref(
NullabilityModifier::NullOr,
TypeReference::from("quux_type")
),
],)
.into()]
);
}

#[test]
fn test_equality() {
// NOTE: this is checking for _bag_ equivalence.
assert_eq!(
TypeDefinitionBuilder::<ISL_1_0>::new()
.any_of(("a", "b", "c"))
.build(),
TypeDefinitionBuilder::<ISL_1_0>::new()
.any_of(("b", "c", "a"))
.build(),
);
assert_eq!(
TypeDefinitionBuilder::<ISL_1_0>::new()
.any_of(("a", "b", "c", "a", "a"))
.build(),
TypeDefinitionBuilder::<ISL_1_0>::new()
.any_of(("a", "b", "a", "c", "a"))
.build(),
);
assert_ne!(
TypeDefinitionBuilder::<ISL_1_0>::new()
.any_of(("a", "b", "c", "a"))
.build(),
TypeDefinitionBuilder::<ISL_1_0>::new()
.any_of(("a", "b", "c", "b"))
.build(),
);
}
}
74 changes: 74 additions & 0 deletions ion-schema/src/model/constraints/byte_length.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use crate::internal_traits::{
LoaderContext, ValidateInternal, ValidationContext, WriteAsIsl, WriteContext,
};
use crate::model::constraints::{ConstraintName, ReadConstraint};
use crate::model::{IonSchemaRange, TypeDefinitionBuilder};
use crate::result::IonSchemaResult;
use crate::{IonSchemaElement, IslVersion, ViolationRecorder};
use ion_rs::{Element, ValueWriter};
use std::ops::ControlFlow;

impl ConstraintName for ByteLength {
const CONSTRAINT_NAME: &'static str = "byte_length";
}

/// Represents the `byte_length` constraint (ref. [ISL 1.0], [ISL 2.0]).
///
/// [ISL 1.0]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#byte_length
/// [ISL 2.0]: https://amazon-ion.github.io/ion-schema/docs/isl-2-0/spec#byte_length
#[derive(Debug, PartialEq, Clone)]
pub struct ByteLength {
range: IonSchemaRange<usize>,
}

impl ByteLength {
pub(crate) fn new<T: Into<IonSchemaRange<usize>>>(range: T) -> Self {
Self {
range: range.into(),
}
}

pub fn range(&self) -> IonSchemaRange<usize> {
self.range
}
}

impl<V: IslVersion> TypeDefinitionBuilder<V> {
pub fn byte_length<T: Into<IonSchemaRange<usize>>>(self, range: T) -> Self {
let constraint = ByteLength::new(range.into());
self.with_constraint(constraint.into())
}
}

impl ValidateInternal for ByteLength {
fn validate_internal<'top: 'call, 'call, R>(
&'top self,
value: &'top IonSchemaElement,
ctx: &ValidationContext,
recorder: &'call mut R,
) -> ControlFlow<()>
where
R: ViolationRecorder<'top>,
{
todo!()
}
}

impl<V: IslVersion> WriteAsIsl<V> for ByteLength {
fn write_as_isl<W: ValueWriter>(
&self,
writer: W,
ctx: &WriteContext<V>,
) -> IonSchemaResult<()> {
todo!()
}
}

impl<V: IslVersion> ReadConstraint<V> for ByteLength {
fn read_constraint(ion: &Element, ctx: &LoaderContext<V>) -> IonSchemaResult<Option<Self>> {
todo!()
}
}
74 changes: 74 additions & 0 deletions ion-schema/src/model/constraints/codepoint_length.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use crate::internal_traits::{
LoaderContext, ValidateInternal, ValidationContext, WriteAsIsl, WriteContext,
};
use crate::model::constraints::{ConstraintName, ReadConstraint};
use crate::model::{IonSchemaRange, TypeDefinitionBuilder};
use crate::result::IonSchemaResult;
use crate::{IonSchemaElement, IslVersion, ViolationRecorder};
use ion_rs::{Element, ValueWriter};
use std::ops::ControlFlow;

impl ConstraintName for CodepointLength {
const CONSTRAINT_NAME: &'static str = "codepoint_length";
}

/// Represents the `codepoint_length` constraint (ref. [ISL 1.0], [ISL 2.0]).
///
/// [ISL 1.0]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#codepoint_length
/// [ISL 2.0]: https://amazon-ion.github.io/ion-schema/docs/isl-2-0/spec#codepoint_length
#[derive(Debug, PartialEq, Clone)]
pub struct CodepointLength {
range: IonSchemaRange<usize>,
}

impl CodepointLength {
pub(crate) fn new<T: Into<IonSchemaRange<usize>>>(range: T) -> Self {
Self {
range: range.into(),
}
}

pub fn range(&self) -> IonSchemaRange<usize> {
self.range
}
}

impl<V: IslVersion> TypeDefinitionBuilder<V> {
pub fn codepoint_length<T: Into<IonSchemaRange<usize>>>(self, range: T) -> Self {
let constraint = CodepointLength::new(range.into());
self.with_constraint(constraint.into())
}
}

impl ValidateInternal for CodepointLength {
fn validate_internal<'top: 'call, 'call, R>(
&'top self,
value: &'top IonSchemaElement,
ctx: &ValidationContext,
recorder: &'call mut R,
) -> ControlFlow<()>
where
R: ViolationRecorder<'top>,
{
todo!()
}
}

impl<V: IslVersion> WriteAsIsl<V> for CodepointLength {
fn write_as_isl<W: ValueWriter>(
&self,
writer: W,
ctx: &WriteContext<V>,
) -> IonSchemaResult<()> {
todo!()
}
}

impl<V: IslVersion> ReadConstraint<V> for CodepointLength {
fn read_constraint(ion: &Element, ctx: &LoaderContext<V>) -> IonSchemaResult<Option<Self>> {
todo!()
}
}
Loading
Loading