diff --git a/ion-schema/src/main/kotlin/com/amazon/ionschema/model/Constraint.kt b/ion-schema/src/main/kotlin/com/amazon/ionschema/model/Constraint.kt index d0c184a..28ea479 100644 --- a/ion-schema/src/main/kotlin/com/amazon/ionschema/model/Constraint.kt +++ b/ion-schema/src/main/kotlin/com/amazon/ionschema/model/Constraint.kt @@ -20,7 +20,9 @@ interface Constraint { * See relevant section in [ISL 1.0 spec](https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#all_of) and * [ISL 2.0 spec](https://amazon-ion.github.io/ion-schema/docs/isl-2-0/spec#all_of). */ - data class AllOf(val types: TypeArguments) : Constraint + data class AllOf(val types: TypeArguments) : Constraint { + constructor(vararg types: TypeArgument) : this(types.toSet()) + } /** * Represents the `annotations` constraint for Ion Schema 1.0. @@ -54,7 +56,9 @@ interface Constraint { * See relevant section in [ISL 1.0 spec](https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#any_of) and * [ISL 2.0 spec](https://amazon-ion.github.io/ion-schema/docs/isl-2-0/spec#any_of). */ - data class AnyOf(val types: TypeArguments) : Constraint + data class AnyOf(val types: TypeArguments) : Constraint { + constructor(vararg types: TypeArgument) : this(types.toSet()) + } /** * Represents the `byte_length` constraint. @@ -152,14 +156,18 @@ interface Constraint { * See relevant section in [ISL 1.0 spec](https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#one_of) and * [ISL 2.0 spec](https://amazon-ion.github.io/ion-schema/docs/isl-2-0/spec#one_of). */ - data class OneOf(val types: TypeArguments) : Constraint + data class OneOf(val types: TypeArguments) : Constraint { + constructor(vararg types: TypeArgument) : this(types.toSet()) + } /** * Represents the `ordered_elements` constraint. * See relevant section in [ISL 1.0 spec](https://amazon-ion.github.io/ion-schema/docs/isl-1-0/spec#ordered_elements) and * [ISL 2.0 spec](https://amazon-ion.github.io/ion-schema/docs/isl-2-0/spec#ordered_elements). */ - data class OrderedElements(val types: List) : Constraint + data class OrderedElements(val types: List) : Constraint { + constructor(vararg types: VariablyOccurringTypeArgument) : this(types.toList()) + } /** * Represents the `precision` constraint. diff --git a/ion-schema/src/main/kotlin/com/amazon/ionschema/model/VariablyOccurringTypeArgument.kt b/ion-schema/src/main/kotlin/com/amazon/ionschema/model/VariablyOccurringTypeArgument.kt index 99b80aa..3a5d4ac 100644 --- a/ion-schema/src/main/kotlin/com/amazon/ionschema/model/VariablyOccurringTypeArgument.kt +++ b/ion-schema/src/main/kotlin/com/amazon/ionschema/model/VariablyOccurringTypeArgument.kt @@ -12,5 +12,23 @@ data class VariablyOccurringTypeArgument(val occurs: DiscreteIntRange, val typeA val OCCURS_OPTIONAL = DiscreteIntRange(0, 1) @JvmStatic val OCCURS_REQUIRED = DiscreteIntRange(1, 1) + + @JvmStatic + fun optional(arg: TypeArgument) = VariablyOccurringTypeArgument(OCCURS_OPTIONAL, arg) + + @JvmStatic + fun required(arg: TypeArgument) = VariablyOccurringTypeArgument(OCCURS_REQUIRED, arg) } } + +@ExperimentalIonSchemaModel +fun TypeArgument.optional() = VariablyOccurringTypeArgument.optional(this) + +@ExperimentalIonSchemaModel +fun TypeArgument.required() = VariablyOccurringTypeArgument.required(this) + +@ExperimentalIonSchemaModel +fun TypeArgument.occurs(n: Int) = VariablyOccurringTypeArgument(DiscreteIntRange(n), this) + +@ExperimentalIonSchemaModel +fun TypeArgument.occurs(min: Int?, max: Int?) = VariablyOccurringTypeArgument(DiscreteIntRange(min, max), this) diff --git a/ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/TypeWriter.kt b/ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/TypeWriter.kt index c9652d7..a9a7390 100644 --- a/ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/TypeWriter.kt +++ b/ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/TypeWriter.kt @@ -28,11 +28,12 @@ internal interface TypeWriter { * Writes a [VariablyOccurringTypeArgument] to the given [IonWriter]. */ fun writeVariablyOccurringTypeArg(ionWriter: IonWriter, varTypeArg: VariablyOccurringTypeArgument, elideOccursValue: DiscreteIntRange) +} - /** - * Writes a [TypeArguments] to the given [IonWriter]. - */ - fun writeTypeArguments(ionWriter: IonWriter, typeArgs: TypeArguments) { - ionWriter.writeToList(typeArgs) { writeTypeArg(ionWriter, it) } - } +/** + * Writes a [TypeArguments] to the given [IonWriter]. + */ +@ExperimentalIonSchemaModel +internal fun TypeWriter.writeTypeArguments(ionWriter: IonWriter, typeArgs: TypeArguments) { + ionWriter.writeToList(typeArgs) { writeTypeArg(ionWriter, it) } } diff --git a/ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/constraints/FieldsWriter.kt b/ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/constraints/FieldsWriter.kt new file mode 100644 index 0000000..7dd280f --- /dev/null +++ b/ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/constraints/FieldsWriter.kt @@ -0,0 +1,32 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazon.ionschema.writer.internal.constraints + +import com.amazon.ion.IonWriter +import com.amazon.ionschema.IonSchemaVersion +import com.amazon.ionschema.model.Constraint +import com.amazon.ionschema.model.ExperimentalIonSchemaModel +import com.amazon.ionschema.model.VariablyOccurringTypeArgument.Companion.OCCURS_OPTIONAL +import com.amazon.ionschema.writer.internal.TypeWriter +import com.amazon.ionschema.writer.internal.writeToStruct + +@ExperimentalIonSchemaModel +internal class FieldsWriter(private val typeWriter: TypeWriter, private val ionSchemaVersion: IonSchemaVersion) : ConstraintWriter { + override val supportedClasses = setOf(Constraint.Fields::class) + + override fun IonWriter.write(c: Constraint) { + check(c is Constraint.Fields) + + if (c.closed && ionSchemaVersion == IonSchemaVersion.v1_0) { + setFieldName("content") + writeSymbol("closed") + } + + setFieldName("fields") + if (c.closed && ionSchemaVersion != IonSchemaVersion.v1_0) setTypeAnnotations("closed") + writeToStruct(c.fields) { + typeWriter.writeVariablyOccurringTypeArg(this@writeToStruct, it, elideOccursValue = OCCURS_OPTIONAL) + } + } +} diff --git a/ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/constraints/LogicConstraintsWriter.kt b/ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/constraints/LogicConstraintsWriter.kt new file mode 100644 index 0000000..700caf4 --- /dev/null +++ b/ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/constraints/LogicConstraintsWriter.kt @@ -0,0 +1,48 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazon.ionschema.writer.internal.constraints + +import com.amazon.ion.IonWriter +import com.amazon.ionschema.model.Constraint +import com.amazon.ionschema.model.ExperimentalIonSchemaModel +import com.amazon.ionschema.writer.internal.TypeWriter +import com.amazon.ionschema.writer.internal.writeTypeArguments +import kotlin.reflect.KClass + +@ExperimentalIonSchemaModel +internal class LogicConstraintsWriter(private val typeWriter: TypeWriter) : ConstraintWriter { + override val supportedClasses: Set> = setOf( + Constraint.AllOf::class, + Constraint.AnyOf::class, + Constraint.Not::class, + Constraint.OneOf::class, + Constraint.Type::class, + ) + + override fun IonWriter.write(c: Constraint) { + when (c) { + is Constraint.AllOf -> { + setFieldName("all_of") + typeWriter.writeTypeArguments(this@write, c.types) + } + is Constraint.AnyOf -> { + setFieldName("any_of") + typeWriter.writeTypeArguments(this@write, c.types) + } + is Constraint.OneOf -> { + setFieldName("one_of") + typeWriter.writeTypeArguments(this@write, c.types) + } + is Constraint.Not -> { + setFieldName("not") + typeWriter.writeTypeArg(this@write, c.type) + } + is Constraint.Type -> { + setFieldName("type") + typeWriter.writeTypeArg(this@write, c.type) + } + else -> check(false) + } + } +} diff --git a/ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/constraints/OrderedElementsWriter.kt b/ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/constraints/OrderedElementsWriter.kt new file mode 100644 index 0000000..37417fa --- /dev/null +++ b/ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/constraints/OrderedElementsWriter.kt @@ -0,0 +1,24 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazon.ionschema.writer.internal.constraints + +import com.amazon.ion.IonWriter +import com.amazon.ionschema.model.Constraint +import com.amazon.ionschema.model.ExperimentalIonSchemaModel +import com.amazon.ionschema.model.VariablyOccurringTypeArgument.Companion.OCCURS_REQUIRED +import com.amazon.ionschema.writer.internal.TypeWriter +import com.amazon.ionschema.writer.internal.writeToList + +@ExperimentalIonSchemaModel +internal class OrderedElementsWriter(private val typeWriter: TypeWriter) : ConstraintWriter { + override val supportedClasses = setOf(Constraint.OrderedElements::class) + + override fun IonWriter.write(c: Constraint) { + check(c is Constraint.OrderedElements) + setFieldName("ordered_elements") + writeToList(c.types) { + typeWriter.writeVariablyOccurringTypeArg(this, it, elideOccursValue = OCCURS_REQUIRED) + } + } +} diff --git a/ion-schema/src/test/kotlin/com/amazon/ionschema/writer/internal/constraints/FieldsWriterTest.kt b/ion-schema/src/test/kotlin/com/amazon/ionschema/writer/internal/constraints/FieldsWriterTest.kt new file mode 100644 index 0000000..1d6da6b --- /dev/null +++ b/ion-schema/src/test/kotlin/com/amazon/ionschema/writer/internal/constraints/FieldsWriterTest.kt @@ -0,0 +1,38 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazon.ionschema.writer.internal.constraints + +import com.amazon.ionschema.IonSchemaVersion +import com.amazon.ionschema.model.Constraint +import com.amazon.ionschema.model.ExperimentalIonSchemaModel +import com.amazon.ionschema.model.TypeArgument +import com.amazon.ionschema.model.occurs +import com.amazon.ionschema.model.optional +import org.junit.jupiter.api.Test + +@OptIn(ExperimentalIonSchemaModel::class) +class FieldsWriterTest : ConstraintTestBase( + writer = FieldsWriter(stubTypeWriterWithRefs("foo_type", "bar_type"), IonSchemaVersion.v2_0), + expectedConstraints = setOf(Constraint.Fields::class), + writeTestCases = listOf( + Constraint.Fields(fieldsMap, closed = true) to "fields: closed::{ a: foo_type, b: bar_type }", + Constraint.Fields(fieldsMap, closed = false) to "fields: { a: foo_type, b: bar_type }", + ) +) { + companion object { + private val fieldsMap = mapOf( + "a" to TypeArgument.Reference("foo_type").optional(), + "b" to TypeArgument.Reference("bar_type").occurs(0, 1), + ) + } + + @Test + fun `writer should write content closed for v1_0`() { + val writer = FieldsWriter(stubTypeWriterWithRefs("foo_type", "bar_type"), IonSchemaVersion.v1_0) + runWriteCase( + writer, + Constraint.Fields(fieldsMap, closed = true) to "content: closed, fields: { a: foo_type, b: bar_type }" + ) + } +} diff --git a/ion-schema/src/test/kotlin/com/amazon/ionschema/writer/internal/constraints/LogicConstraintsWriterTest.kt b/ion-schema/src/test/kotlin/com/amazon/ionschema/writer/internal/constraints/LogicConstraintsWriterTest.kt new file mode 100644 index 0000000..b8d86c0 --- /dev/null +++ b/ion-schema/src/test/kotlin/com/amazon/ionschema/writer/internal/constraints/LogicConstraintsWriterTest.kt @@ -0,0 +1,33 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazon.ionschema.writer.internal.constraints + +import com.amazon.ionschema.model.Constraint +import com.amazon.ionschema.model.ExperimentalIonSchemaModel +import com.amazon.ionschema.model.TypeArgument.Reference + +@OptIn(ExperimentalIonSchemaModel::class) +class LogicConstraintsWriterTest : ConstraintTestBase( + writer = LogicConstraintsWriter(stubTypeWriterWithRefs("foo", "bar")), + expectedConstraints = setOf( + Constraint.AllOf::class, + Constraint.AnyOf::class, + Constraint.OneOf::class, + Constraint.Not::class, + Constraint.Type::class, + ), + writeTestCases = listOf( + Constraint.AllOf(emptySet()) to "all_of: []", + Constraint.AllOf(Reference("foo")) to "all_of: [foo]", + Constraint.AllOf(Reference("foo"), Reference("bar")) to "all_of: [foo, bar]", + Constraint.AnyOf(emptySet()) to "any_of: []", + Constraint.AnyOf(Reference("foo")) to "any_of: [foo]", + Constraint.AnyOf(Reference("foo"), Reference("bar")) to "any_of: [foo, bar]", + Constraint.OneOf(emptySet()) to "one_of: []", + Constraint.OneOf(Reference("foo")) to "one_of: [foo]", + Constraint.OneOf(Reference("foo"), Reference("bar")) to "one_of: [foo, bar]", + Constraint.Not(Reference("foo")) to "not: foo", + Constraint.Type(Reference("foo")) to "type: foo", + ) +) diff --git a/ion-schema/src/test/kotlin/com/amazon/ionschema/writer/internal/constraints/OrderedElementsWriterTest.kt b/ion-schema/src/test/kotlin/com/amazon/ionschema/writer/internal/constraints/OrderedElementsWriterTest.kt new file mode 100644 index 0000000..31f0459 --- /dev/null +++ b/ion-schema/src/test/kotlin/com/amazon/ionschema/writer/internal/constraints/OrderedElementsWriterTest.kt @@ -0,0 +1,24 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazon.ionschema.writer.internal.constraints + +import com.amazon.ionschema.model.Constraint +import com.amazon.ionschema.model.ExperimentalIonSchemaModel +import com.amazon.ionschema.model.TypeArgument.Reference +import com.amazon.ionschema.model.required + +@OptIn(ExperimentalIonSchemaModel::class) +class OrderedElementsWriterTest : ConstraintTestBase( + writer = OrderedElementsWriter(stubTypeWriterWithRefs("foo", "bar")), + expectedConstraints = setOf(Constraint.OrderedElements::class), + writeTestCases = listOf( + Constraint.OrderedElements(emptyList()) to "ordered_elements: []", + Constraint.OrderedElements(Reference("foo").required()) to "ordered_elements: [foo]", + Constraint.OrderedElements( + Reference("foo").required(), + Reference("bar").required(), + Reference("foo").required(), + ) to "ordered_elements: [foo, bar, foo]", + ) +) diff --git a/ion-schema/src/test/kotlin/com/amazon/ionschema/writer/internal/constraints/util.kt b/ion-schema/src/test/kotlin/com/amazon/ionschema/writer/internal/constraints/util.kt index d7cd3ee..93716ce 100644 --- a/ion-schema/src/test/kotlin/com/amazon/ionschema/writer/internal/constraints/util.kt +++ b/ion-schema/src/test/kotlin/com/amazon/ionschema/writer/internal/constraints/util.kt @@ -3,9 +3,37 @@ package com.amazon.ionschema.writer.internal.constraints +import com.amazon.ion.IonWriter import com.amazon.ion.system.IonSystemBuilder +import com.amazon.ionschema.model.ExperimentalIonSchemaModel +import com.amazon.ionschema.model.TypeArgument +import com.amazon.ionschema.model.optional +import com.amazon.ionschema.model.required +import com.amazon.ionschema.writer.internal.TypeWriter +import io.mockk.every +import io.mockk.mockk private val ION = IonSystemBuilder.standard().build() /** Helper fun for creating IonValue instances */ fun ion(text: String) = ION.singleValue(text) + +/** + * Creates a mock TypeWriter that can write the given type names. + * This is a mock, though, and it's functionality is not complete. Does not write nullability annotations. + * Does not respect the "occurs" value of any [VariablyOccurringTypeArgument]s. + */ +@OptIn(ExperimentalIonSchemaModel::class) +internal fun stubTypeWriterWithRefs(vararg refs: String) = mockk { + refs.forEach { ref -> + every { writeTypeArg(any(), TypeArgument.Reference(ref)) } answers { + (it.invocation.args[0] as IonWriter).writeSymbol(ref) + } + every { writeVariablyOccurringTypeArg(any(), TypeArgument.Reference(ref).optional(), any()) } answers { + (it.invocation.args[0] as IonWriter).writeSymbol(ref) + } + every { writeVariablyOccurringTypeArg(any(), TypeArgument.Reference(ref).required(), any()) } answers { + (it.invocation.args[0] as IonWriter).writeSymbol(ref) + } + } +}