Skip to content

Commit

Permalink
Adds writers for Fields, OrderedElements, and logic constraints (#292)
Browse files Browse the repository at this point in the history
  • Loading branch information
popematt authored Oct 18, 2023
1 parent 6030538 commit 03dd056
Show file tree
Hide file tree
Showing 10 changed files with 264 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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<VariablyOccurringTypeArgument>) : Constraint
data class OrderedElements(val types: List<VariablyOccurringTypeArgument>) : Constraint {
constructor(vararg types: VariablyOccurringTypeArgument) : this(types.toList())
}

/**
* Represents the `precision` constraint.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
Original file line number Diff line number Diff line change
@@ -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<KClass<out Constraint>> = 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)
}
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
Original file line number Diff line number Diff line change
@@ -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 }"
)
}
}
Original file line number Diff line number Diff line change
@@ -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",
)
)
Original file line number Diff line number Diff line change
@@ -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]",
)
)
Original file line number Diff line number Diff line change
Expand Up @@ -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<TypeWriter> {
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)
}
}
}

0 comments on commit 03dd056

Please sign in to comment.