Skip to content

Commit

Permalink
Adds writers for SchemaDocument and types
Browse files Browse the repository at this point in the history
  • Loading branch information
popematt committed Oct 20, 2023
1 parent fee907d commit 566ab1c
Show file tree
Hide file tree
Showing 6 changed files with 372 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,26 @@
package com.amazon.ionschema.writer

import com.amazon.ion.IonWriter
import com.amazon.ionschema.IonSchemaVersion
import com.amazon.ionschema.model.ExperimentalIonSchemaModel
import com.amazon.ionschema.model.NamedTypeDefinition
import com.amazon.ionschema.model.SchemaDocument
import com.amazon.ionschema.model.TypeDefinition
import com.amazon.ionschema.writer.internal.IonSchemaWriterV2_0
import com.amazon.ionschema.writer.internal.VersionedIonSchemaWriter

/**
* Writes Ion Schema model to an IonWriter.
*/
@ExperimentalIonSchemaModel
interface IonSchemaWriter {
object IonSchemaWriter {
/**
* Writes a [SchemaDocument].
*/
fun writeSchema(ionWriter: IonWriter, schemaDocument: SchemaDocument)

/**
* Writes an orphaned [TypeDefinition]—that is an anonymous type definition that does not belong to any schema.
*/
fun writeType(ionWriter: IonWriter, typeDefinition: TypeDefinition)

/**
* Writes a [NamedTypeDefinition].
*/
fun writeNamedType(ionWriter: IonWriter, namedTypeDefinition: NamedTypeDefinition)
@JvmStatic
fun writeSchema(ionWriter: IonWriter, schemaDocument: SchemaDocument) {
val delegate: VersionedIonSchemaWriter = when (schemaDocument.ionSchemaVersion) {
IonSchemaVersion.v1_0 -> TODO("IonSchemaWriter does not support ISL 1.0")
IonSchemaVersion.v2_0 -> IonSchemaWriterV2_0
}
delegate.writeSchema(ionWriter, schemaDocument)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package com.amazon.ionschema.writer.internal

import com.amazon.ion.IonWriter
import com.amazon.ionschema.IonSchemaVersion
import com.amazon.ionschema.internal.util.islRequire
import com.amazon.ionschema.model.ExperimentalIonSchemaModel
import com.amazon.ionschema.model.NamedTypeDefinition
import com.amazon.ionschema.model.SchemaDocument
import com.amazon.ionschema.model.SchemaFooter
import com.amazon.ionschema.model.SchemaHeader
import com.amazon.ionschema.model.TypeDefinition

@ExperimentalIonSchemaModel
internal object IonSchemaWriterV2_0 : VersionedIonSchemaWriter {

private val headerWriter = HeaderWriter
private val typeWriter = TypeWriterV2_0
private val footerWriter = FooterWriter

override fun writeSchema(ionWriter: IonWriter, schemaDocument: SchemaDocument) {
islRequire(schemaDocument.ionSchemaVersion == IonSchemaVersion.v2_0) { "IonSchemaWriterV2_0 only supports ISL 2.0" }
ionWriter.writeSymbol("\$ion_schema_2_0")
for (item in schemaDocument.items) {
when (item) {
is SchemaHeader -> headerWriter.writeHeader(ionWriter, item)
is NamedTypeDefinition -> writeNamedType(ionWriter, item)
is SchemaFooter -> footerWriter.writeFooter(ionWriter, item)
is SchemaDocument.OpenContent -> item.value.writeTo(ionWriter)
}

Check warning on line 32 in ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/IonSchemaWriterV2_0.kt

View check run for this annotation

Codecov / codecov/patch

ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/IonSchemaWriterV2_0.kt#L32

Added line #L32 was not covered by tests
}
}

override fun writeType(ionWriter: IonWriter, typeDefinition: TypeDefinition) {
typeWriter.writeOrphanedTypeDefinition(ionWriter, typeDefinition)

Check warning on line 37 in ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/IonSchemaWriterV2_0.kt

View check run for this annotation

Codecov / codecov/patch

ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/IonSchemaWriterV2_0.kt#L37

Added line #L37 was not covered by tests
}

override fun writeNamedType(ionWriter: IonWriter, namedTypeDefinition: NamedTypeDefinition) {
typeWriter.writeNamedTypeDefinition(ionWriter, namedTypeDefinition)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package com.amazon.ionschema.writer.internal

import com.amazon.ion.IonWriter
import com.amazon.ionschema.IonSchemaVersion
import com.amazon.ionschema.model.Constraint
import com.amazon.ionschema.model.DiscreteIntRange
import com.amazon.ionschema.model.ExperimentalIonSchemaModel
import com.amazon.ionschema.model.NamedTypeDefinition
import com.amazon.ionschema.model.OpenContentFields
import com.amazon.ionschema.model.TypeArgument
import com.amazon.ionschema.model.TypeDefinition
import com.amazon.ionschema.model.VariablyOccurringTypeArgument
import com.amazon.ionschema.writer.internal.constraints.AnnotationsV2Writer
import com.amazon.ionschema.writer.internal.constraints.ContainsWriter
import com.amazon.ionschema.writer.internal.constraints.ElementWriter
import com.amazon.ionschema.writer.internal.constraints.ExponentWriter
import com.amazon.ionschema.writer.internal.constraints.FieldNamesWriter
import com.amazon.ionschema.writer.internal.constraints.FieldsWriter
import com.amazon.ionschema.writer.internal.constraints.Ieee754FloatWriter
import com.amazon.ionschema.writer.internal.constraints.LengthConstraintsWriter
import com.amazon.ionschema.writer.internal.constraints.LogicConstraintsWriter
import com.amazon.ionschema.writer.internal.constraints.OrderedElementsWriter
import com.amazon.ionschema.writer.internal.constraints.PrecisionWriter
import com.amazon.ionschema.writer.internal.constraints.RegexWriter
import com.amazon.ionschema.writer.internal.constraints.TimestampOffsetWriter
import com.amazon.ionschema.writer.internal.constraints.TimestampPrecisionWriter
import com.amazon.ionschema.writer.internal.constraints.ValidValuesWriter

@ExperimentalIonSchemaModel
internal object TypeWriterV2_0 : TypeWriter {
private val constraintWriters = listOf(
AnnotationsV2Writer(this),
ContainsWriter,
ElementWriter(this, IonSchemaVersion.v2_0),
ExponentWriter,
FieldNamesWriter(this),
FieldsWriter(this, IonSchemaVersion.v2_0),
Ieee754FloatWriter,
LengthConstraintsWriter,
LogicConstraintsWriter(this),
OrderedElementsWriter(this),
PrecisionWriter,
RegexWriter,
TimestampOffsetWriter,
TimestampPrecisionWriter,
ValidValuesWriter,
).flatMap { w -> w.supportedClasses.map { it to w } }
.toMap()

override fun writeNamedTypeDefinition(ionWriter: IonWriter, namedTypeDefinition: NamedTypeDefinition) {
ionWriter.setTypeAnnotations("type")
ionWriter.writeStruct {
ionWriter.setFieldName("name")
ionWriter.writeSymbol(namedTypeDefinition.typeName)
writeConstraints(ionWriter, namedTypeDefinition.typeDefinition.constraints)
writeOpenContent(ionWriter, namedTypeDefinition.typeDefinition.openContent)
}
}

override fun writeTypeArg(ionWriter: IonWriter, typeArg: TypeArgument) {
if (typeArg.nullability == TypeArgument.Nullability.OrNull) {
ionWriter.addTypeAnnotation("\$null_or")
}

when (typeArg) {
is TypeArgument.Import -> ionWriter.writeStruct {
ionWriter.setFieldName("id")
ionWriter.writeString(typeArg.schemaId)
ionWriter.setFieldName("type")
ionWriter.writeSymbol(typeArg.typeName)
}
is TypeArgument.InlineType -> ionWriter.writeStruct {
writeConstraints(ionWriter, typeArg.typeDefinition.constraints)
writeOpenContent(ionWriter, typeArg.typeDefinition.openContent)
}
is TypeArgument.Reference -> ionWriter.writeSymbol(typeArg.typeName)
}

Check warning on line 80 in ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/TypeWriterV2_0.kt

View check run for this annotation

Codecov / codecov/patch

ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/TypeWriterV2_0.kt#L80

Added line #L80 was not covered by tests
}

override fun writeVariablyOccurringTypeArg(ionWriter: IonWriter, varTypeArg: VariablyOccurringTypeArgument, elideOccursValue: DiscreteIntRange) {
if (varTypeArg.occurs == elideOccursValue) {
writeTypeArg(ionWriter, varTypeArg.typeArg)
} else {
ionWriter.writeStruct {
setFieldName("occurs")
writeRange(varTypeArg.occurs)
if (varTypeArg.typeArg.nullability == TypeArgument.Nullability.None && varTypeArg.typeArg is TypeArgument.InlineType) {
writeConstraints(ionWriter, varTypeArg.typeArg.typeDefinition.constraints)
writeOpenContent(ionWriter, varTypeArg.typeArg.typeDefinition.openContent)
} else {
setFieldName("type")
writeTypeArg(ionWriter, varTypeArg.typeArg)
}

Check warning on line 96 in ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/TypeWriterV2_0.kt

View check run for this annotation

Codecov / codecov/patch

ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/TypeWriterV2_0.kt#L94-L96

Added lines #L94 - L96 were not covered by tests
}
}

Check warning on line 98 in ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/TypeWriterV2_0.kt

View check run for this annotation

Codecov / codecov/patch

ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/TypeWriterV2_0.kt#L98

Added line #L98 was not covered by tests
}

/**
* Writes a type that exists outside the context of any schema.
*/
fun writeOrphanedTypeDefinition(ionWriter: IonWriter, typeDefinition: TypeDefinition) {
ionWriter.setTypeAnnotations("type")
ionWriter.writeStruct {
writeConstraints(ionWriter, typeDefinition.constraints)
writeOpenContent(ionWriter, typeDefinition.openContent)
}

Check warning on line 109 in ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/TypeWriterV2_0.kt

View check run for this annotation

Codecov / codecov/patch

ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/TypeWriterV2_0.kt#L105-L109

Added lines #L105 - L109 were not covered by tests
}

private fun writeOpenContent(ionWriter: IonWriter, openContentFields: OpenContentFields) {
for ((fieldName, fieldValue) in openContentFields) {
ionWriter.setFieldName(fieldName)
fieldValue.writeTo(ionWriter)
}
}

private fun writeConstraints(ionWriter: IonWriter, constraints: Set<Constraint>) {
for (c in constraints) {
constraintWriters[c::class]?.writeTo(ionWriter, c)
?: TODO("Constraint not supported in Ion Schema 2.0: ${c::class}")

Check warning on line 122 in ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/TypeWriterV2_0.kt

View check run for this annotation

Codecov / codecov/patch

ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/TypeWriterV2_0.kt#L122

Added line #L122 was not covered by tests
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package com.amazon.ionschema.writer.internal

import com.amazon.ion.IonWriter
import com.amazon.ionschema.model.ExperimentalIonSchemaModel
import com.amazon.ionschema.model.NamedTypeDefinition
import com.amazon.ionschema.model.SchemaDocument
import com.amazon.ionschema.model.TypeDefinition

@OptIn(ExperimentalIonSchemaModel::class)
interface VersionedIonSchemaWriter {
/**
* Writes a [SchemaDocument].
*/
fun writeSchema(ionWriter: IonWriter, schemaDocument: SchemaDocument)

/**
* Writes an orphaned [TypeDefinition]—that is an anonymous type definition that does not belong to any schema.
*/
fun writeType(ionWriter: IonWriter, typeDefinition: TypeDefinition)

/**
* Writes a [NamedTypeDefinition].
*/
fun writeNamedType(ionWriter: IonWriter, namedTypeDefinition: NamedTypeDefinition)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package com.amazon.ionschema.writer

import com.amazon.ionschema.ION
import com.amazon.ionschema.IonSchemaVersion
import com.amazon.ionschema.assertEqualIon
import com.amazon.ionschema.model.ExperimentalIonSchemaModel
import com.amazon.ionschema.model.SchemaDocument
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows

@OptIn(ExperimentalIonSchemaModel::class)
class IonSchemaWriterTest {
// The purpose of these tests is just to check that it delegates to the correct writer implementation.
// Testing the actual serialization is done in WriterTests.kt

@Test
fun `IonSchemaWriter throw UnsupportedOperationException for ISL 1 0`() {
val writer = ION.newTextWriter(StringBuilder())
val schema = SchemaDocument("schema.isl", IonSchemaVersion.v1_0, emptyList())
assertThrows<NotImplementedError> {
IonSchemaWriter.writeSchema(writer, schema)
}
}

@Test
fun `IonSchemaWriter writes a schema document for ISL 2 0`() {
val schema = SchemaDocument("schema.isl", IonSchemaVersion.v2_0, emptyList())
// Since there's no content added to the schema, we expect just a version marker
assertEqualIon("\$ion_schema_2_0 ") {
IonSchemaWriter.writeSchema(it, schema)
}
}
}
Loading

0 comments on commit 566ab1c

Please sign in to comment.