From 79a2fa2085f31415c8d2aaa6c896f8c2ebf36544 Mon Sep 17 00:00:00 2001 From: Khushboo Desai Date: Thu, 8 Jun 2023 14:47:52 -0700 Subject: [PATCH 01/12] adds `WriteToIsl` to serialize isl model into schema file * Defines `WriteToIsl` trait * Implements `WriteToIsl` for `IslSchema` --- ion-schema/src/isl/isl_import.rs | 14 ++++++++ ion-schema/src/isl/isl_type.rs | 8 +++++ ion-schema/src/isl/mod.rs | 57 ++++++++++++++++++++++++++++++++ ion-schema/src/lib.rs | 9 ++++- 4 files changed, 87 insertions(+), 1 deletion(-) diff --git a/ion-schema/src/isl/isl_import.rs b/ion-schema/src/isl/isl_import.rs index 3c21161..c5b6ed5 100644 --- a/ion-schema/src/isl/isl_import.rs +++ b/ion-schema/src/isl/isl_import.rs @@ -1,5 +1,7 @@ +use crate::isl::WriteToIsl; use crate::result::{invalid_schema_error, invalid_schema_error_raw, IonSchemaResult}; use ion_rs::element::Element; +use ion_rs::{IonType, IonWriter}; /// Represents an [import] in an ISL schema. /// @@ -56,6 +58,12 @@ impl IslImport { } } +impl WriteToIsl for IslImport { + fn write_to(&self, writer: &mut W) -> IonSchemaResult<()> { + todo!() + } +} + /// Represents typed and type aliased [IslImport]s /// Typed import grammar: `{ id: , type: }` /// Type aliased import grammar: `{ id: , type: , as: }` @@ -87,3 +95,9 @@ impl IslImportType { &self.alias } } + +impl WriteToIsl for IslImportType { + fn write_to(&self, writer: &mut W) -> IonSchemaResult<()> { + todo!() + } +} diff --git a/ion-schema/src/isl/isl_type.rs b/ion-schema/src/isl/isl_type.rs index 9a7f365..612bd5e 100644 --- a/ion-schema/src/isl/isl_type.rs +++ b/ion-schema/src/isl/isl_type.rs @@ -1,8 +1,10 @@ use crate::isl::isl_constraint::{IslConstraint, IslConstraintImpl}; use crate::isl::isl_import::IslImportType; use crate::isl::IslVersion; +use crate::isl::WriteToIsl; use crate::result::{invalid_schema_error, invalid_schema_error_raw, IonSchemaResult}; use ion_rs::element::Element; +use ion_rs::{IonType, IonWriter}; /// Provides public facing APIs for constructing ISL types programmatically for ISL 1.0 pub mod v_1_0 { @@ -217,6 +219,12 @@ impl IslTypeImpl { } } +impl WriteToIsl for IslTypeImpl { + fn write_to(&self, writer: &mut W) -> IonSchemaResult<()> { + todo!() + } +} + // OwnedStruct doesn't preserve field order hence the PartialEq won't work properly for unordered constraints // Related issue: https://github.com/amazon-ion/ion-rust/issues/200 impl PartialEq for IslTypeImpl { diff --git a/ion-schema/src/isl/mod.rs b/ion-schema/src/isl/mod.rs index f31a557..3bf1027 100644 --- a/ion-schema/src/isl/mod.rs +++ b/ion-schema/src/isl/mod.rs @@ -81,8 +81,11 @@ use crate::isl::isl_import::{IslImport, IslImportType}; use crate::isl::isl_type::IslType; +use crate::result::IonSchemaResult; use crate::UserReservedFields; +use ion_rs::element::writer::ElementWriter; use ion_rs::element::Element; +use ion_rs::{IonType, IonWriter}; use std::fmt::{Display, Formatter}; pub mod isl_constraint; @@ -113,6 +116,11 @@ impl Display for IslVersion { } } +/// Provides a method to write an ISL model using an Ion writer +pub trait WriteToIsl { + fn write_to(&self, writer: &mut W) -> IonSchemaResult<()>; +} + /// Provides an internal representation of an schema file #[derive(Debug, Clone, PartialEq)] pub struct IslSchema { @@ -194,6 +202,55 @@ impl IslSchema { pub fn user_reserved_fields(&self) -> Option<&UserReservedFields> { self.schema.user_reserved_fields.as_ref() } + + fn write_header(&self, writer: &mut W) -> IonSchemaResult<()> { + writer.set_annotations(["schema_header"]); + writer.step_in(IonType::Struct)?; + if !self.schema.imports.is_empty() { + writer.set_field_name("imports"); + writer.step_in(IonType::List)?; + for import in &self.schema.imports { + import.write_to(writer)?; + } + writer.step_out()?; + } + if let Some(user_reserved_fields) = &self.schema.user_reserved_fields { + user_reserved_fields.write_to(writer)?; + } + writer.step_out()?; + + Ok(()) + } +} + +impl WriteToIsl for IslSchema { + fn write_to(&self, writer: &mut W) -> IonSchemaResult<()> { + let version = self.schema.version; + // write the version marker for given schema + match version { + IslVersion::V1_0 => { + writer.write_symbol("$ion_schema_1_0")?; + } + IslVersion::V2_0 => { + writer.write_symbol("$ion_schema_2_0")?; + } + } + self.write_header(writer)?; + for isl_type in &self.schema.types { + isl_type.type_definition.write_to(writer)?; + } + // write open content at the end of the schema + for value in &self.schema.open_content { + writer.write_element(value)?; + } + + // write footer for given schema + writer.set_annotations(["schema_footer"]); + writer.step_in(IonType::Struct)?; + writer.step_out()?; + writer.flush()?; + Ok(()) + } } #[derive(Debug, Clone, PartialEq)] diff --git a/ion-schema/src/lib.rs b/ion-schema/src/lib.rs index 7b05091..c85eddb 100644 --- a/ion-schema/src/lib.rs +++ b/ion-schema/src/lib.rs @@ -5,10 +5,11 @@ use crate::external::ion_rs::IonType; use crate::ion_path::IonPath; use crate::isl::isl_constraint::IslConstraintImpl; use crate::isl::isl_type::IslTypeImpl; +use crate::isl::WriteToIsl; use crate::result::{invalid_schema_error, invalid_schema_error_raw, IonSchemaResult}; use crate::violation::{Violation, ViolationCode}; use ion_rs::element::{Element, Struct}; -use ion_rs::Symbol; +use ion_rs::{IonWriter, Symbol}; use regex::Regex; use std::fmt::{Display, Formatter}; use std::sync::OnceLock; @@ -359,3 +360,9 @@ impl UserReservedFields { Ok(()) } } + +impl WriteToIsl for UserReservedFields { + fn write_to(&self, writer: &mut W) -> IonSchemaResult<()> { + todo!() + } +} From 0908a3becb9e474dcaa29ba4780bd806d015433d Mon Sep 17 00:00:00 2001 From: Khushboo Desai Date: Thu, 8 Jun 2023 14:56:19 -0700 Subject: [PATCH 02/12] adds `WriteToIsl` implementation for `UserReservedFields` --- ion-schema/src/lib.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/ion-schema/src/lib.rs b/ion-schema/src/lib.rs index c85eddb..a7a8a4c 100644 --- a/ion-schema/src/lib.rs +++ b/ion-schema/src/lib.rs @@ -363,6 +363,27 @@ impl UserReservedFields { impl WriteToIsl for UserReservedFields { fn write_to(&self, writer: &mut W) -> IonSchemaResult<()> { - todo!() + writer.set_field_name("user_reserved_fields"); + writer.step_in(IonType::Struct)?; + writer.set_field_name("schema_header"); + writer.step_in(IonType::List)?; + for value in &self.schema_header_fields { + writer.write_symbol(value)?; + } + writer.step_out()?; + writer.set_field_name("type"); + writer.step_in(IonType::List)?; + for value in &self.type_fields { + writer.write_symbol(value)?; + } + writer.step_out()?; + writer.set_field_name("schema_footer"); + writer.step_in(IonType::List)?; + for value in &self.schema_footer_fields { + writer.write_symbol(value)?; + } + writer.step_out()?; + writer.step_out()?; + Ok(()) } } From 3149d43d37728c6691fa1f1eff24ba36a754b2db Mon Sep 17 00:00:00 2001 From: Khushboo Desai Date: Thu, 8 Jun 2023 15:05:21 -0700 Subject: [PATCH 03/12] adds `WriteToIsl` implementation for ISL import --- ion-schema/src/isl/isl_import.rs | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/ion-schema/src/isl/isl_import.rs b/ion-schema/src/isl/isl_import.rs index c5b6ed5..027989a 100644 --- a/ion-schema/src/isl/isl_import.rs +++ b/ion-schema/src/isl/isl_import.rs @@ -60,7 +60,21 @@ impl IslImport { impl WriteToIsl for IslImport { fn write_to(&self, writer: &mut W) -> IonSchemaResult<()> { - todo!() + writer.step_in(IonType::Struct)?; + match self { + IslImport::Schema(schema_import) => { + writer.set_field_name("id"); + writer.write_string(schema_import)?; + } + IslImport::Type(type_import) => { + type_import.write_to(writer)?; + } + IslImport::TypeAlias(type_alias_import) => { + type_alias_import.write_to(writer)?; + } + } + writer.step_out()?; + Ok(()) } } @@ -98,6 +112,14 @@ impl IslImportType { impl WriteToIsl for IslImportType { fn write_to(&self, writer: &mut W) -> IonSchemaResult<()> { - todo!() + writer.set_field_name("id"); + writer.write_symbol(&self.id)?; + writer.set_field_name("type"); + writer.write_symbol(&self.type_name)?; + if let Some(alias) = &self.alias { + writer.set_field_name("as"); + writer.write_symbol(alias)?; + } + Ok(()) } } From 6b6e987c51678f3f034170fb1e65c28811caad26 Mon Sep 17 00:00:00 2001 From: Khushboo Desai Date: Thu, 8 Jun 2023 15:11:02 -0700 Subject: [PATCH 04/12] adds `writeToIsl` implementation for `IslTypeImpl` --- ion-schema/src/isl/isl_constraint.rs | 10 +++++++++- ion-schema/src/isl/isl_type.rs | 10 +++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/ion-schema/src/isl/isl_constraint.rs b/ion-schema/src/isl/isl_constraint.rs index 862221f..a8d94c6 100644 --- a/ion-schema/src/isl/isl_constraint.rs +++ b/ion-schema/src/isl/isl_constraint.rs @@ -4,9 +4,11 @@ use crate::isl::isl_range::{Range, RangeType}; use crate::isl::isl_type_reference::{IslTypeRefImpl, IslVariablyOccurringTypeRef}; use crate::isl::util::{Annotation, Ieee754InterchangeFormat, TimestampOffset, ValidValue}; use crate::isl::IslVersion; +use crate::isl::WriteToIsl; use crate::result::{invalid_schema_error, invalid_schema_error_raw, IonSchemaResult}; +use ion_rs::element::writer::ElementWriter; use ion_rs::element::Element; -use ion_rs::IonType; +use ion_rs::{IonType, IonWriter}; use std::collections::HashMap; use std::convert::TryInto; @@ -1055,6 +1057,12 @@ impl IslConstraintImpl { } } +impl WriteToIsl for IslConstraintImpl { + fn write_to(&self, writer: &mut W) -> IonSchemaResult<()> { + todo!() + } +} + /// Represents the [annotations] constraint /// /// [annotations]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#annotations diff --git a/ion-schema/src/isl/isl_type.rs b/ion-schema/src/isl/isl_type.rs index 612bd5e..27b648d 100644 --- a/ion-schema/src/isl/isl_type.rs +++ b/ion-schema/src/isl/isl_type.rs @@ -221,7 +221,15 @@ impl IslTypeImpl { impl WriteToIsl for IslTypeImpl { fn write_to(&self, writer: &mut W) -> IonSchemaResult<()> { - todo!() + writer.set_annotations(["type"]); + writer.step_in(IonType::Struct)?; + writer.set_field_name("name"); + writer.write_symbol(self.name.clone().unwrap())?; + for constraint in self.constraints() { + constraint.write_to(writer)?; + } + writer.step_out()?; + Ok(()) } } From ce64dbedbb1de5bf4a59ef01ba1229bf39146290 Mon Sep 17 00:00:00 2001 From: Khushboo Desai Date: Thu, 8 Jun 2023 15:14:18 -0700 Subject: [PATCH 05/12] adds `WriteToIsl` for `IslTyepRef` --- ion-schema/src/isl/isl_type_reference.rs | 72 +++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/ion-schema/src/isl/isl_type_reference.rs b/ion-schema/src/isl/isl_type_reference.rs index 56bab75..9d55975 100644 --- a/ion-schema/src/isl/isl_type_reference.rs +++ b/ion-schema/src/isl/isl_type_reference.rs @@ -2,6 +2,7 @@ use crate::isl::isl_import::{IslImport, IslImportType}; use crate::isl::isl_range::{Range, RangeType}; use crate::isl::isl_type::IslTypeImpl; use crate::isl::IslVersion; +use crate::isl::WriteToIsl; use crate::result::{ invalid_schema_error, invalid_schema_error_raw, unresolvable_schema_error, IonSchemaResult, }; @@ -9,7 +10,7 @@ use crate::system::{PendingTypes, TypeId, TypeStore}; use crate::type_reference::{TypeReference, VariablyOccurringTypeRef}; use crate::types::TypeDefinitionImpl; use ion_rs::element::Element; -use ion_rs::IonType; +use ion_rs::{IonType, IonWriter}; /// Provides public facing APIs for constructing ISL type references programmatically for ISL 1.0 pub mod v_1_0 { @@ -116,6 +117,21 @@ pub enum NullabilityModifier { Nothing, // Represents that no modifiers were provided with the type reference } +impl WriteToIsl for NullabilityModifier { + fn write_to(&self, writer: &mut W) -> IonSchemaResult<()> { + match self { + NullabilityModifier::Nullable => { + writer.set_annotations(["nullable"]); + } + NullabilityModifier::NullOr => { + writer.set_annotations(["$null_or"]); + } + NullabilityModifier::Nothing => {} + } + Ok(()) + } +} + #[derive(Debug, Clone, PartialEq)] pub(crate) enum IslTypeRefImpl { /// Represents a reference to a named type (including aliases and built-in types) @@ -332,6 +348,32 @@ impl IslTypeRefImpl { } } +impl WriteToIsl for IslTypeRefImpl { + fn write_to(&self, writer: &mut W) -> IonSchemaResult<()> { + match self { + IslTypeRefImpl::Named(name, nullability_modifier) => { + nullability_modifier.write_to(writer)?; + writer.write_symbol(name)?; + } + IslTypeRefImpl::TypeImport(type_import, nullability_modifier) => { + nullability_modifier.write_to(writer)?; + writer.step_in(IonType::Struct)?; + type_import.write_to(writer)?; + writer.step_out()?; + } + IslTypeRefImpl::Anonymous(type_def, nullability_modifier) => { + nullability_modifier.write_to(writer)?; + writer.step_in(IonType::Struct)?; + for constraint in type_def.constraints() { + constraint.write_to(writer)?; + } + writer.step_out()?; + } + } + Ok(()) + } +} + /// Represents a [variably occurring type reference] that will be used by `ordered_elements` and `fields` constraints. /// /// ```ion @@ -460,3 +502,31 @@ impl IslVariablyOccurringTypeRef { Ok(VariablyOccurringTypeRef::new(type_ref, self.occurs())) } } + +impl WriteToIsl for IslVariablyOccurringTypeRef { + fn write_to(&self, writer: &mut W) -> IonSchemaResult<()> { + match &self.type_ref { + IslTypeRefImpl::Named(name, nullability_modifier) => { + nullability_modifier.write_to(writer)?; + writer.write_symbol(name)?; + } + IslTypeRefImpl::TypeImport(type_import, nullability_modifier) => { + nullability_modifier.write_to(writer)?; + writer.step_in(IonType::Struct)?; + type_import.write_to(writer)?; + writer.step_out()?; + } + IslTypeRefImpl::Anonymous(type_def, nullability_modifier) => { + nullability_modifier.write_to(writer)?; + writer.step_in(IonType::Struct)?; + for constraint in type_def.constraints() { + constraint.write_to(writer)?; + } + writer.set_field_name("occurs"); + self.occurs.write_to(writer)?; + writer.step_out()?; + } + } + Ok(()) + } +} From f0c2a018b469deb3c53254d6772198a0e378b3a3 Mon Sep 17 00:00:00 2001 From: Khushboo Desai Date: Thu, 8 Jun 2023 15:15:40 -0700 Subject: [PATCH 06/12] adds `WriteToIsl` for model utils --- ion-schema/src/isl/util.rs | 55 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/ion-schema/src/isl/util.rs b/ion-schema/src/isl/util.rs index d2eaac0..f3ae72a 100644 --- a/ion-schema/src/isl/util.rs +++ b/ion-schema/src/isl/util.rs @@ -1,9 +1,10 @@ use crate::isl::isl_range::{Range, RangeType}; -use crate::isl::IslVersion; +use crate::isl::{IslVersion, WriteToIsl}; use crate::result::{invalid_schema_error, IonSchemaError, IonSchemaResult}; +use ion_rs::element::writer::ElementWriter; use ion_rs::element::Element; use ion_rs::types::timestamp::Precision; -use ion_rs::{Symbol, Timestamp}; +use ion_rs::{IonWriter, Symbol, Timestamp}; use num_traits::abs; use std::cmp::Ordering; use std::fmt; @@ -54,6 +55,20 @@ impl Annotation { } } +impl WriteToIsl for Annotation { + fn write_to(&self, writer: &mut W) -> IonSchemaResult<()> { + if self.isl_version == IslVersion::V1_0 { + if self.is_required { + writer.set_annotations(["required"]); + } else { + writer.set_annotations(["optional"]); + } + } + writer.write_symbol(&self.value)?; + Ok(()) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub enum TimestampPrecision { Year, @@ -206,6 +221,16 @@ impl Display for ValidValue { } } +impl WriteToIsl for ValidValue { + fn write_to(&self, writer: &mut W) -> IonSchemaResult<()> { + match self { + ValidValue::Range(range) => range.write_to(writer)?, + ValidValue::Element(element) => writer.write_element(element)?, + } + Ok(()) + } +} + /// Represent a timestamp offset /// Known timestamp offset value is stored in minutes as i32 value /// For example, "+07::00" wil be stored as `TimestampOffset::Known(420)` @@ -278,6 +303,21 @@ impl Display for TimestampOffset { } } +impl WriteToIsl for TimestampOffset { + fn write_to(&self, writer: &mut W) -> IonSchemaResult<()> { + match &self { + TimestampOffset::Known(offset) => { + let sign = if offset < &0 { "-" } else { "+" }; + let hours = abs(*offset) / 60; + let minutes = abs(*offset) - hours * 60; + writer.write_string(format!("{sign}{hours:02}:{minutes:02}"))?; + } + TimestampOffset::Unknown => writer.write_string("-00:00")?, + } + Ok(()) + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Ieee754InterchangeFormat { Binary16, @@ -314,3 +354,14 @@ impl Display for Ieee754InterchangeFormat { ) } } + +impl WriteToIsl for Ieee754InterchangeFormat { + fn write_to(&self, writer: &mut W) -> IonSchemaResult<()> { + writer.write_symbol(match self { + Ieee754InterchangeFormat::Binary16 => "binary16", + Ieee754InterchangeFormat::Binary32 => "binary32", + Ieee754InterchangeFormat::Binary64 => "binary64", + })?; + Ok(()) + } +} From df9362eb722ef6b8313ca35bfb355e76bdd971f4 Mon Sep 17 00:00:00 2001 From: Khushboo Desai Date: Thu, 8 Jun 2023 15:15:55 -0700 Subject: [PATCH 07/12] adds `WriteToIsl` for ISl ranges --- ion-schema/src/isl/isl_range.rs | 51 ++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/ion-schema/src/isl/isl_range.rs b/ion-schema/src/isl/isl_range.rs index 48cf983..e040955 100644 --- a/ion-schema/src/isl/isl_range.rs +++ b/ion-schema/src/isl/isl_range.rs @@ -1,14 +1,16 @@ use crate::isl::isl_range::RangeBoundaryValue::*; use crate::isl::util::TimestampPrecision; use crate::isl::IslVersion; +use crate::isl::WriteToIsl; use crate::result::{ invalid_schema_error, invalid_schema_error_raw, IonSchemaError, IonSchemaResult, }; +use ion_rs::element::writer::ElementWriter; use ion_rs::element::Element; use ion_rs::external::bigdecimal::num_bigint::BigInt; use ion_rs::external::bigdecimal::{BigDecimal, One}; use ion_rs::types::integer::IntAccess; -use ion_rs::{element, Decimal, Int, IonType, Timestamp}; +use ion_rs::{element, Decimal, Int, IonType, IonWriter, Timestamp}; use std::cmp::Ordering; use std::fmt::{Display, Formatter}; use std::prelude::rust_2021::TryInto; @@ -418,6 +420,26 @@ impl Display for Range { } } +impl WriteToIsl for Range { + fn write_to(&self, writer: &mut W) -> IonSchemaResult<()> { + writer.set_annotations(["range"]); + match &self { + Range::Integer(integer) => integer.write_to(writer)?, + Range::NonNegativeInteger(non_negative_integer) => { + non_negative_integer.write_to(writer)? + } + Range::TimestampPrecision(timestamp_precision) => { + timestamp_precision.write_to(writer)? + } + Range::Timestamp(timestamp) => timestamp.write_to(writer)?, + Range::Decimal(decimal) => decimal.write_to(writer)?, + Range::Float(float) => float.write_to(writer)?, + Range::Number(number) => number.write_to(writer)?, + } + Ok(()) + } +} + /// Represents a generic range where some constraints can be defined using this range // this is a generic implementation of ranges #[derive(Debug, Clone, PartialEq)] @@ -736,6 +758,16 @@ impl Display for RangeImpl { } } +impl WriteToIsl for RangeImpl { + fn write_to(&self, writer: &mut W) -> IonSchemaResult<()> { + writer.step_in(IonType::List)?; + self.start.write_to(writer)?; + self.end.write_to(writer)?; + writer.step_out()?; + Ok(()) + } +} + // This lets us turn any `T` into a RangeBoundaryValue::Value(_, Inclusive) impl From for RangeBoundaryValue { fn from(value: T) -> RangeBoundaryValue { @@ -944,6 +976,23 @@ impl Display for RangeBoundaryValue { } } +impl WriteToIsl for RangeBoundaryValue { + fn write_to(&self, writer: &mut W) -> IonSchemaResult<()> { + match &self { + Max => writer.write_symbol("max")?, + Min => writer.write_symbol("min")?, + Value(value, range_boundary_type) => { + if range_boundary_type == &RangeBoundaryType::Exclusive { + writer.set_annotations(["exclusive"]); + } + let element = Element::read_one(format!("{value}").as_bytes())?; + writer.write_element(&element)?; + } + } + Ok(()) + } +} + /// Represents the range boundary types in terms of exclusivity (i.e. inclusive or exclusive) #[derive(Debug, Clone, PartialEq, Eq, PartialOrd)] pub enum RangeBoundaryType { From 5c2a6926a66f8dcba92227761b3747a4fc2099e1 Mon Sep 17 00:00:00 2001 From: Khushboo Desai Date: Thu, 8 Jun 2023 15:43:12 -0700 Subject: [PATCH 08/12] adds `WriteToIsl` for ISL constraint --- ion-schema/src/isl/isl_constraint.rs | 204 ++++++++++++++++++++++++++- 1 file changed, 203 insertions(+), 1 deletion(-) diff --git a/ion-schema/src/isl/isl_constraint.rs b/ion-schema/src/isl/isl_constraint.rs index a8d94c6..4937a29 100644 --- a/ion-schema/src/isl/isl_constraint.rs +++ b/ion-schema/src/isl/isl_constraint.rs @@ -1059,7 +1059,144 @@ impl IslConstraintImpl { impl WriteToIsl for IslConstraintImpl { fn write_to(&self, writer: &mut W) -> IonSchemaResult<()> { - todo!() + match self { + IslConstraintImpl::AllOf(type_refs) => { + writer.set_field_name("all_of"); + writer.step_in(IonType::List)?; + for type_ref in type_refs { + type_ref.write_to(writer)?; + } + writer.step_out()?; + } + IslConstraintImpl::Annotations(annotations) => { + annotations.write_to(writer)?; + } + IslConstraintImpl::AnyOf(type_refs) => { + writer.set_field_name("any_of"); + writer.step_in(IonType::List)?; + for type_ref in type_refs { + type_ref.write_to(writer)?; + } + writer.step_out()?; + } + IslConstraintImpl::ByteLength(range) => { + writer.set_field_name("byte_length"); + range.write_to(writer)?; + } + IslConstraintImpl::CodepointLength(range) => { + writer.set_field_name("codepoint_length"); + range.write_to(writer)?; + } + IslConstraintImpl::Contains(elements) => { + writer.set_field_name("contains"); + writer.step_in(IonType::List)?; + for element in elements { + writer.write_element(element)?; + } + writer.step_out()?; + } + IslConstraintImpl::ContentClosed => { + writer.set_field_name("content"); + writer.write_symbol("closed")?; + } + IslConstraintImpl::ContainerLength(range) => { + writer.set_field_name("container_length"); + range.write_to(writer)?; + } + IslConstraintImpl::Element(type_ref, is_distinct) => { + writer.set_field_name("element"); + if *is_distinct { + writer.set_annotations(["distinct"]); + } + type_ref.write_to(writer)?; + } + IslConstraintImpl::Exponent(range) => { + writer.set_field_name("exponent"); + range.write_to(writer)?; + } + IslConstraintImpl::Fields(fields, content_closed) => { + writer.set_field_name("fields"); + if *content_closed { + writer.set_annotations(["closed"]); + } + writer.step_in(IonType::Struct)?; + for (field_name, type_ref) in fields.iter() { + writer.set_field_name(field_name); + type_ref.write_to(writer)?; + } + writer.step_out()?; + } + IslConstraintImpl::FieldNames(type_ref, is_distinct) => { + writer.set_field_name("field_names"); + if *is_distinct { + writer.set_annotations(["distinct"]); + } + type_ref.write_to(writer)?; + } + IslConstraintImpl::Ieee754Float(format) => { + writer.set_field_name("ieee754_float"); + format.write_to(writer)?; + } + IslConstraintImpl::Not(type_ref) => { + writer.set_field_name("not"); + type_ref.write_to(writer)?; + } + IslConstraintImpl::OneOf(type_refs) => { + writer.set_field_name("one_of"); + writer.step_in(IonType::List)?; + for type_ref in type_refs { + type_ref.write_to(writer)?; + } + writer.step_out()?; + } + IslConstraintImpl::OrderedElements(type_refs) => { + writer.set_field_name("ordered_elements"); + writer.step_in(IonType::List)?; + for type_ref in type_refs { + type_ref.write_to(writer)?; + } + writer.step_out()?; + } + IslConstraintImpl::Precision(range) => { + writer.set_field_name("precision"); + range.write_to(writer)?; + } + IslConstraintImpl::Regex(regex) => { + regex.write_to(writer)?; + } + IslConstraintImpl::Scale(range) => { + writer.set_field_name("scale"); + range.write_to(writer)?; + } + IslConstraintImpl::TimestampOffset(timestamp_offset) => { + timestamp_offset.write_to(writer)?; + } + IslConstraintImpl::TimestampPrecision(range) => { + writer.set_field_name("timestamp_precision"); + range.write_to(writer)?; + } + IslConstraintImpl::Type(type_ref) => { + writer.set_field_name("type"); + type_ref.write_to(writer)?; + } + IslConstraintImpl::Unknown(field_name, value) => { + writer.set_field_name(field_name); + writer.write_element(value)?; + } + IslConstraintImpl::Utf8ByteLength(range) => { + writer.set_field_name("utf8_byte_length"); + range.write_to(writer)?; + } + IslConstraintImpl::ValidValues(valid_values) => { + writer.set_field_name("valid_values"); + writer.step_in(IonType::List)?; + for valid_value in &valid_values.valid_values { + valid_value.write_to(writer)?; + } + writer.step_out()?; + } + } + Ok(()) } } @@ -1072,6 +1209,21 @@ pub(crate) enum IslAnnotationsConstraint { StandardAnnotations(IslTypeRefImpl), } +impl WriteToIsl for IslAnnotationsConstraint { + fn write_to(&self, writer: &mut W) -> IonSchemaResult<()> { + writer.set_field_name("annotations"); + match self { + IslAnnotationsConstraint::SimpleAnnotations(simple_annotations) => { + simple_annotations.write_to(writer)?; + } + IslAnnotationsConstraint::StandardAnnotations(standard_annotations) => { + standard_annotations.write_to(writer)?; + } + } + Ok(()) + } +} + /// Represents the [simple syntax] for annotations constraint /// ```ion /// Grammar: ::= annotations: ... [ ... ] @@ -1188,6 +1340,28 @@ impl IslSimpleAnnotationsConstraint { } } +impl WriteToIsl for IslSimpleAnnotationsConstraint { + fn write_to(&self, writer: &mut W) -> IonSchemaResult<()> { + let mut annotation_modifiers = vec![]; + if self.is_ordered { + annotation_modifiers.push("ordered"); + } + if self.is_closed { + annotation_modifiers.push("closed"); + } + if self.is_required { + annotation_modifiers.push("required"); + } + writer.set_annotations(annotation_modifiers); + writer.step_in(IonType::List)?; + for annotation in &self.annotations { + annotation.write_to(writer)?; + } + writer.step_out()?; + Ok(()) + } +} + /// Represents the [valid_values] constraint /// /// [valid_values]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#annotations @@ -1281,6 +1455,22 @@ impl IslRegexConstraint { } } +impl WriteToIsl for IslRegexConstraint { + fn write_to(&self, writer: &mut W) -> IonSchemaResult<()> { + writer.set_field_name("regex"); + let mut regex_modifiers = vec![]; + if self.case_insensitive { + regex_modifiers.push("i"); + } + if self.multi_line { + regex_modifiers.push("m"); + } + writer.set_annotations(regex_modifiers); + writer.write_string(&self.expression)?; + Ok(()) + } +} + /// Represents the [timestamp_offset] constraint /// /// [timestamp_offset]: https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#timestamp_offset @@ -1298,3 +1488,15 @@ impl IslTimestampOffsetConstraint { &self.valid_offsets } } + +impl WriteToIsl for IslTimestampOffsetConstraint { + fn write_to(&self, writer: &mut W) -> IonSchemaResult<()> { + writer.set_field_name("timestamp_offset"); + writer.step_in(IonType::List)?; + for timestamp_offset in &self.valid_offsets { + timestamp_offset.write_to(writer)?; + } + writer.step_out()?; + Ok(()) + } +} From 8778e69e253fc0c13a479f0aa612f6ee16a3c244 Mon Sep 17 00:00:00 2001 From: Khushboo Desai Date: Thu, 8 Jun 2023 15:48:22 -0700 Subject: [PATCH 09/12] adds unit tests for serializing ISL model into schema file --- ion-schema/Cargo.toml | 1 + ion-schema/src/isl/mod.rs | 47 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/ion-schema/Cargo.toml b/ion-schema/Cargo.toml index c563026..8b7d0a1 100644 --- a/ion-schema/Cargo.toml +++ b/ion-schema/Cargo.toml @@ -24,6 +24,7 @@ edition = "2021" [dependencies] ion-rs = "0.17.0" thiserror = "1.0" +nom = "7.1.3" num-bigint = "0.3" num-traits = "0.2" chrono = "0.4" diff --git a/ion-schema/src/isl/mod.rs b/ion-schema/src/isl/mod.rs index 3bf1027..bdfa0cb 100644 --- a/ion-schema/src/isl/mod.rs +++ b/ion-schema/src/isl/mod.rs @@ -312,6 +312,7 @@ impl IslSchemaImpl { #[cfg(test)] mod isl_tests { + use crate::authority::FileSystemDocumentAuthority; use crate::isl::isl_constraint::v_1_0::*; use crate::isl::isl_constraint::IslConstraint; use crate::isl::isl_range::DecimalRange; @@ -330,14 +331,18 @@ mod isl_tests { use crate::isl::IslVersion; use crate::isl::*; use crate::result::IonSchemaResult; + use crate::system::SchemaSystem; use ion_rs::element::Element; use ion_rs::types::decimal::*; use ion_rs::types::integer::Int as IntegerValue; use ion_rs::types::timestamp::Timestamp; - use ion_rs::IonType; use ion_rs::Symbol; + use ion_rs::{IonType, TextWriterBuilder}; + use nom::AsBytes; use rstest::*; use std::io::Write; + use std::path::Path; + use test_generator::test_resources; // helper function to create NamedIslType for isl tests using ISL 1.0 fn load_named_type(text: &str) -> IslType { @@ -1002,4 +1007,44 @@ mod isl_tests { write!(&mut buf, "{}", range.into()).unwrap(); assert_eq!(expected, String::from_utf8(buf).unwrap()); } + + const SKIP_LIST: [&str; 5] = [ + "ion-schema-schemas/json/json.isl", // the file contains `nan` which fails on equivalence for two schemas + "ion-schema-tests/ion_schema_1_0/nullable.isl", // Needs `nullable` annotation related fixes + "ion-schema-tests/ion_schema_1_0/schema/import/import_inline.isl", // related to order of types in the schema file + "ion-schema-tests/ion_schema_2_0/imports/tree/inline_import_a.isl", // related to order of types in the schema file + "ion-schema-tests/ion_schema_2_0/imports/tree/inline_import_c.isl", // related to order of types in the schema file + ]; + + #[test_resources("ion-schema-tests/**/*.isl")] + #[test_resources("ion-schema-schemas/**/*.isl")] + fn test_write_to_isl(file_name: &str) { + if SKIP_LIST.contains(&file_name) { + return; + } + let mut schema_system = + SchemaSystem::new(vec![Box::new(FileSystemDocumentAuthority::new( + Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap(), + ))]); + + let expected_schema_result = schema_system.load_isl_schema(file_name); + assert!(expected_schema_result.is_ok()); + + let expected_schema = expected_schema_result.unwrap(); + + let mut buffer = Vec::new(); + let mut writer = TextWriterBuilder::pretty().build(&mut buffer).unwrap(); + let write_schema_result = expected_schema.write_to(&mut writer); + assert!(write_schema_result.is_ok()); + + let schema_content = writer.output().as_bytes(); + println!("{}", String::from_utf8(schema_content.to_vec()).unwrap()); + + let actual_schema_result = schema_system.new_isl_schema(schema_content, file_name); + + assert!(actual_schema_result.is_ok()); + let actual_schema = actual_schema_result.unwrap(); + + assert_eq!(actual_schema, expected_schema); + } } From 9e302f1878dd961d0589c4625aab924e7b987cad Mon Sep 17 00:00:00 2001 From: Khushboo Desai Date: Thu, 8 Jun 2023 16:00:25 -0700 Subject: [PATCH 10/12] adds utility for unit tests --- ion-schema/src/isl/mod.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/ion-schema/src/isl/mod.rs b/ion-schema/src/isl/mod.rs index bdfa0cb..bfba354 100644 --- a/ion-schema/src/isl/mod.rs +++ b/ion-schema/src/isl/mod.rs @@ -341,7 +341,7 @@ mod isl_tests { use nom::AsBytes; use rstest::*; use std::io::Write; - use std::path::Path; + use std::path::{Path, MAIN_SEPARATOR}; use test_generator::test_resources; // helper function to create NamedIslType for isl tests using ISL 1.0 @@ -1016,10 +1016,17 @@ mod isl_tests { "ion-schema-tests/ion_schema_2_0/imports/tree/inline_import_c.isl", // related to order of types in the schema file ]; + fn is_skip_list_path(file_name: &str) -> bool { + SKIP_LIST + .iter() + .map(|p| p.replace('/', &MAIN_SEPARATOR.to_string())) + .any(|p| p == file_name) + } + #[test_resources("ion-schema-tests/**/*.isl")] #[test_resources("ion-schema-schemas/**/*.isl")] fn test_write_to_isl(file_name: &str) { - if SKIP_LIST.contains(&file_name) { + if is_skip_list_path(&file_name) { return; } let mut schema_system = @@ -1038,7 +1045,6 @@ mod isl_tests { assert!(write_schema_result.is_ok()); let schema_content = writer.output().as_bytes(); - println!("{}", String::from_utf8(schema_content.to_vec()).unwrap()); let actual_schema_result = schema_system.new_isl_schema(schema_content, file_name); From 3da8eaf08e302457afb2d62d3f6312e0cd51ce0e Mon Sep 17 00:00:00 2001 From: Khushboo Desai Date: Mon, 12 Jun 2023 11:01:56 -0700 Subject: [PATCH 11/12] adds suggested changes * adds more doc comments * `write_to` writes `occurs` field for `TypeImport` in `IslVariablyOccurringTypeRef` --- ion-schema/src/isl/isl_type_reference.rs | 2 ++ ion-schema/src/isl/mod.rs | 24 ++++++++++++++++-------- ion-schema/src/lib.rs | 9 +++++++++ 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/ion-schema/src/isl/isl_type_reference.rs b/ion-schema/src/isl/isl_type_reference.rs index 9d55975..8cd094a 100644 --- a/ion-schema/src/isl/isl_type_reference.rs +++ b/ion-schema/src/isl/isl_type_reference.rs @@ -514,6 +514,8 @@ impl WriteToIsl for IslVariablyOccurringTypeRef { nullability_modifier.write_to(writer)?; writer.step_in(IonType::Struct)?; type_import.write_to(writer)?; + writer.set_field_name("occurs"); + self.occurs.write_to(writer)?; writer.step_out()?; } IslTypeRefImpl::Anonymous(type_def, nullability_modifier) => { diff --git a/ion-schema/src/isl/mod.rs b/ion-schema/src/isl/mod.rs index bfba354..f6309ce 100644 --- a/ion-schema/src/isl/mod.rs +++ b/ion-schema/src/isl/mod.rs @@ -1011,9 +1011,11 @@ mod isl_tests { const SKIP_LIST: [&str; 5] = [ "ion-schema-schemas/json/json.isl", // the file contains `nan` which fails on equivalence for two schemas "ion-schema-tests/ion_schema_1_0/nullable.isl", // Needs `nullable` annotation related fixes - "ion-schema-tests/ion_schema_1_0/schema/import/import_inline.isl", // related to order of types in the schema file - "ion-schema-tests/ion_schema_2_0/imports/tree/inline_import_a.isl", // related to order of types in the schema file - "ion-schema-tests/ion_schema_2_0/imports/tree/inline_import_c.isl", // related to order of types in the schema file + // following skip list files are related to order of types in the schema file + // https://github.com/amazon-ion/ion-schema-rust/issues/184 + "ion-schema-tests/ion_schema_1_0/schema/import/import_inline.isl", + "ion-schema-tests/ion_schema_2_0/imports/tree/inline_import_a.isl", + "ion-schema-tests/ion_schema_2_0/imports/tree/inline_import_c.isl", ]; fn is_skip_list_path(file_name: &str) -> bool { @@ -1029,28 +1031,34 @@ mod isl_tests { if is_skip_list_path(&file_name) { return; } + + // creates a schema system that will be sued to load schema files into a schema model let mut schema_system = SchemaSystem::new(vec![Box::new(FileSystemDocumentAuthority::new( Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap(), ))]); + // construct an ISL model from a schema file let expected_schema_result = schema_system.load_isl_schema(file_name); assert!(expected_schema_result.is_ok()); - let expected_schema = expected_schema_result.unwrap(); + // initiate an Ion pretty text writer let mut buffer = Vec::new(); let mut writer = TextWriterBuilder::pretty().build(&mut buffer).unwrap(); + + // write the previously constructed ISL model into a schema file using `write_to` let write_schema_result = expected_schema.write_to(&mut writer); assert!(write_schema_result.is_ok()); - let schema_content = writer.output().as_bytes(); - - let actual_schema_result = schema_system.new_isl_schema(schema_content, file_name); - + // create a new schema model from the schema file generated by the previous step + let actual_schema_result = + schema_system.new_isl_schema(writer.output().as_bytes(), file_name); assert!(actual_schema_result.is_ok()); let actual_schema = actual_schema_result.unwrap(); + // compare the actual schema model that was generated from dynamically created schema file + // with the expected schema model that was constructed from given schema file assert_eq!(actual_schema, expected_schema); } } diff --git a/ion-schema/src/lib.rs b/ion-schema/src/lib.rs index a7a8a4c..0bf4737 100644 --- a/ion-schema/src/lib.rs +++ b/ion-schema/src/lib.rs @@ -363,26 +363,35 @@ impl UserReservedFields { impl WriteToIsl for UserReservedFields { fn write_to(&self, writer: &mut W) -> IonSchemaResult<()> { + // this function assumes that we are already inside a schema header struct + // writes `user_reserved_fields` in the schema header writer.set_field_name("user_reserved_fields"); writer.step_in(IonType::Struct)?; + + // writes user reserved fields for `schema_header` writer.set_field_name("schema_header"); writer.step_in(IonType::List)?; for value in &self.schema_header_fields { writer.write_symbol(value)?; } writer.step_out()?; + + // writes user reserved fields for `type` writer.set_field_name("type"); writer.step_in(IonType::List)?; for value in &self.type_fields { writer.write_symbol(value)?; } writer.step_out()?; + + // writes user reserved fields for `schema_footer` writer.set_field_name("schema_footer"); writer.step_in(IonType::List)?; for value in &self.schema_footer_fields { writer.write_symbol(value)?; } writer.step_out()?; + writer.step_out()?; Ok(()) } From 7cf626bc522f859b28c44e0b303165a77a2439b9 Mon Sep 17 00:00:00 2001 From: Khushboo Desai Date: Wed, 14 Jun 2023 12:25:57 -0700 Subject: [PATCH 12/12] adds more suggested changes --- ion-schema/Cargo.toml | 1 - ion-schema/src/isl/isl_type.rs | 8 +++++++- ion-schema/src/isl/mod.rs | 3 +-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/ion-schema/Cargo.toml b/ion-schema/Cargo.toml index 8b7d0a1..c563026 100644 --- a/ion-schema/Cargo.toml +++ b/ion-schema/Cargo.toml @@ -24,7 +24,6 @@ edition = "2021" [dependencies] ion-rs = "0.17.0" thiserror = "1.0" -nom = "7.1.3" num-bigint = "0.3" num-traits = "0.2" chrono = "0.4" diff --git a/ion-schema/src/isl/isl_type.rs b/ion-schema/src/isl/isl_type.rs index 27b648d..343dded 100644 --- a/ion-schema/src/isl/isl_type.rs +++ b/ion-schema/src/isl/isl_type.rs @@ -224,7 +224,13 @@ impl WriteToIsl for IslTypeImpl { writer.set_annotations(["type"]); writer.step_in(IonType::Struct)?; writer.set_field_name("name"); - writer.write_symbol(self.name.clone().unwrap())?; + let _ = self + .name + .as_ref() + .map(|name| writer.write_symbol(name)) + .ok_or(invalid_schema_error_raw( + "Top level type definitions must contain a `name` field", + ))?; for constraint in self.constraints() { constraint.write_to(writer)?; } diff --git a/ion-schema/src/isl/mod.rs b/ion-schema/src/isl/mod.rs index f6309ce..ec1e75a 100644 --- a/ion-schema/src/isl/mod.rs +++ b/ion-schema/src/isl/mod.rs @@ -338,7 +338,6 @@ mod isl_tests { use ion_rs::types::timestamp::Timestamp; use ion_rs::Symbol; use ion_rs::{IonType, TextWriterBuilder}; - use nom::AsBytes; use rstest::*; use std::io::Write; use std::path::{Path, MAIN_SEPARATOR}; @@ -1053,7 +1052,7 @@ mod isl_tests { // create a new schema model from the schema file generated by the previous step let actual_schema_result = - schema_system.new_isl_schema(writer.output().as_bytes(), file_name); + schema_system.new_isl_schema(writer.output().as_slice(), file_name); assert!(actual_schema_result.is_ok()); let actual_schema = actual_schema_result.unwrap();