Skip to content
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

Adds writers for SchemaDocument and types #294

Merged
merged 1 commit into from
Oct 25, 2023
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
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
Loading