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 skeleton for ISL Writer #289

Merged
merged 2 commits into from
Oct 3, 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
@@ -1,3 +1,6 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package com.amazon.ionschema.model

/**
Expand All @@ -8,4 +11,9 @@ data class UserReservedFields(
val type: Set<String> = emptySet(),
val header: Set<String> = emptySet(),
val footer: Set<String> = emptySet(),
)
) {
companion object {
@JvmStatic
val EMPTY = UserReservedFields()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ sealed class IonSchemaResult<T, E : Any> {
/**
* Gets the [Ok] value or `null` if not [Ok].
*/
fun okValueOrNull(): E? = (this as? Err)?.err
fun okValueOrNull(): T? = (this as? Ok)?.value

/**
* Gets the [Err] value or `null` if not an [Err].
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package com.amazon.ionschema.writer

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

/**
* Writes Ion Schema model to an IonWriter.
*/
@ExperimentalIonSchemaModel
interface 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)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// 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.SchemaFooter

@ExperimentalIonSchemaModel
object FooterWriter {
fun writeFooter(ionWriter: IonWriter, schemaFooter: SchemaFooter) {
ionWriter.setTypeAnnotations("schema_footer")
ionWriter.writeStruct {
for ((fieldName, fieldValue) in schemaFooter.openContent) {
ionWriter.setFieldName(fieldName)
fieldValue.writeTo(ionWriter)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// 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.HeaderImport
import com.amazon.ionschema.model.SchemaHeader
import com.amazon.ionschema.model.UserReservedFields

@ExperimentalIonSchemaModel
object HeaderWriter {
fun writeHeader(ionWriter: IonWriter, schemaHeader: SchemaHeader) {
ionWriter.setTypeAnnotations("schema_header")
ionWriter.writeStruct {
if (schemaHeader.imports.isNotEmpty()) {
setFieldName("imports")
writeToList(schemaHeader.imports) { writeImport(it) }
}
if (schemaHeader.userReservedFields != UserReservedFields.EMPTY) {
ionWriter.writeUserReservedFields(schemaHeader.userReservedFields)
}

for ((fieldName, fieldValue) in schemaHeader.openContent) {
ionWriter.setFieldName(fieldName)
fieldValue.writeTo(ionWriter)
}
}
}

private fun IonWriter.writeUserReservedFields(userReservedFields: UserReservedFields) {
setFieldName("user_reserved_fields")
writeStruct {
if (userReservedFields.header.isNotEmpty()) {
setFieldName("schema_header")
writeToList(userReservedFields.header) { writeSymbol(it) }
}
if (userReservedFields.type.isNotEmpty()) {
setFieldName("type")
writeToList(userReservedFields.type) { writeSymbol(it) }
}
if (userReservedFields.footer.isNotEmpty()) {
setFieldName("schema_footer")
writeToList(userReservedFields.footer) { writeSymbol(it) }
}
}
}

private fun IonWriter.writeImport(import: HeaderImport) {
writeStruct {
when (import) {
is HeaderImport.Type -> {
setFieldName("id")
writeString(import.id)
setFieldName("type")
writeSymbol(import.targetType)
import.asType?.let {
setFieldName("as")
writeSymbol(it)
}
}
is HeaderImport.Wildcard -> {
setFieldName("id")
writeString(import.id)
}
}

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

View check run for this annotation

Codecov / codecov/patch

ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/HeaderWriter.kt#L67

Added line #L67 was not covered by tests
}
}
}
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

import com.amazon.ion.IonWriter
import com.amazon.ionschema.model.DiscreteIntRange
import com.amazon.ionschema.model.ExperimentalIonSchemaModel
import com.amazon.ionschema.model.NamedTypeDefinition
import com.amazon.ionschema.model.TypeArgument
import com.amazon.ionschema.model.TypeArguments
import com.amazon.ionschema.model.VariablyOccurringTypeArgument

@ExperimentalIonSchemaModel
internal interface TypeWriter {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Question) This interface contains both type argument and type definition write methods. I am not sure how these methods will be used though. If you can provide an example that would be helpful.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The schema writer will call the type definition method to write top-level types in a schema document. Constraint writers can call the type argument method if they have a type argument.

They could be in separate interfaces, because they are called from mutually exclusive locations, but since it's an internal interface, I don't think it's a big deal whether they're together or separate.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. It wouldn't matter for an internal interface but since this PR doesn't add any usage of this interface, I was just curious how this is intended to be used.
IMO, it might be worth adding interface definition with at least one place where its implemented or an example/summary of how the interface definition will be used in the PR description.


/**
* Writes a [NamedTypeDefinition] to the given [IonWriter].
*/
fun writeNamedTypeDefinition(ionWriter: IonWriter, namedTypeDefinition: NamedTypeDefinition)

/**
* Writes a [TypeArgument] to the given [IonWriter].
*/
fun writeTypeArg(ionWriter: IonWriter, typeArg: TypeArgument)

/**
* Writes a [VariablyOccurringTypeArgument] to the given [IonWriter].
*/
fun writeVariablyOccurringTypeArg(ionWriter: IonWriter, varTypeArg: VariablyOccurringTypeArgument, elideOccursValue: DiscreteIntRange)

/**
* Writes a [TypeArguments] to the given [IonWriter].
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Writes a [TypeArguments] to the given [IonWriter].
* Writes a [TypeArgument]s to the given [IonWriter].

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a type alias called TypeArguments so this is correct, despite how it looks.

*/
fun writeTypeArguments(ionWriter: IonWriter, typeArgs: TypeArguments) {
ionWriter.writeToList(typeArgs) { writeTypeArg(ionWriter, it) }

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

View check run for this annotation

Codecov / codecov/patch

ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/TypeWriter.kt#L36

Added line #L36 was not covered by tests
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// 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 kotlin.reflect.KClass

/**
* Allows us to compose TypeWriters out of different combinations of constraint writers to enable code reuse across
* multiple Ion Schema versions.
*/
@ExperimentalIonSchemaModel
internal interface ConstraintWriter {
/**
* Returns the constraint types that can be written by this constraint writer.
*/
val supportedClasses: Set<KClass<out Constraint>>

/**
* Writes a [Constraint] instance to the given IonWriter.
* Must throw [IllegalStateException] if called for an unsupported constraint type.
* Equivalent to [write], but more convenient for callers.
*/
fun writeTo(ionWriter: IonWriter, constraint: Constraint) = ionWriter.write(constraint)

Check warning on line 27 in ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/constraints/ConstraintWriter.kt

View check run for this annotation

Codecov / codecov/patch

ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/constraints/ConstraintWriter.kt#L27

Added line #L27 was not covered by tests

/**
* Writes a [Constraint] instance to the given IonWriter.
* Must throw [IllegalStateException] if called for an unsupported constraint type.
* Equivalent to [writeTo], but more convenient for implementers.
*/
fun IonWriter.write(c: Constraint)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// 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.IonType
import com.amazon.ion.IonValue
import com.amazon.ion.IonWriter

/**
* Steps into a struct, writes [content] to this [IonWriter], and steps out of the struct in a `finally` block.
*/
internal inline fun IonWriter.writeStruct(content: IonWriter.() -> Unit) {
try {
stepIn(IonType.STRUCT)
content()
} finally {
stepOut()
}
}

/**
* Steps into a struct, invokes [valueWriter] for each element of [values], and steps out of the struct in a `finally` block.
*/
internal inline fun <T> IonWriter.writeToStruct(values: Map<String, T>, valueWriter: IonWriter.(T) -> Unit) {
try {
stepIn(IonType.STRUCT)
values.forEach { (k, v) ->
setFieldName(k)
valueWriter(v)
}

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

View check run for this annotation

Codecov / codecov/patch

ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/util.kt#L26-L31

Added lines #L26 - L31 were not covered by tests
} finally {
stepOut()
}

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

View check run for this annotation

Codecov / codecov/patch

ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/util.kt#L33-L34

Added lines #L33 - L34 were not covered by tests
}

/**
* Steps into a list, writes [content] to this [IonWriter], and steps out of the list in a `finally` block.
*/
internal inline fun IonWriter.writeList(content: IonWriter.() -> Unit) {
try {
stepIn(IonType.LIST)
content()

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

View check run for this annotation

Codecov / codecov/patch

ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/util.kt#L41-L43

Added lines #L41 - L43 were not covered by tests
} finally {
stepOut()
}

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

View check run for this annotation

Codecov / codecov/patch

ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/util.kt#L45-L46

Added lines #L45 - L46 were not covered by tests
}

/**
* Steps into a list, invokes [valueWriter] for each element of [values], and steps out of the list in a `finally` block.
*/
internal inline fun <T> IonWriter.writeToList(values: Iterable<T>, valueWriter: IonWriter.(T) -> Unit) {
try {
stepIn(IonType.LIST)
values.forEach { valueWriter(it) }
} finally {
stepOut()
}
}

/**
* Writes an [IonValue] to an [IonWriter].
*/
internal fun IonWriter.writeIonValue(value: IonValue) = value.writeTo(this)

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

View check run for this annotation

Codecov / codecov/patch

ion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/util.kt#L64

Added line #L64 was not covered by tests
17 changes: 17 additions & 0 deletions ion-schema/src/test/kotlin/com/amazon/ionschema/TestUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ import com.amazon.ion.IonString
import com.amazon.ion.IonStruct
import com.amazon.ion.IonText
import com.amazon.ion.IonValue
import com.amazon.ion.IonWriter
import com.amazon.ion.system.IonSystemBuilder
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue

Expand Down Expand Up @@ -76,3 +78,18 @@ fun Type.assertValidity(expectIsValid: Boolean, value: IonValue) {
}

internal fun IonStruct.getTextField(fieldName: String) = (get(fieldName) as IonText).stringValue()

/**
* Asserts that the values written to an [IonWriter] match the expected Ion.
*/
fun assertEqualIon(expected: String, block: (IonWriter) -> Unit) = assertEqualIon(ION.loader.load(expected), block)

/**
* Asserts that the values written to an [IonWriter] match the expected Ion.
*/
fun assertEqualIon(expected: IonDatagram, block: (IonWriter) -> Unit) {
val newDg = ION.newDatagram()
val ionWriter = ION.newWriter(newDg)
ionWriter.apply(block)
Assertions.assertEquals(expected, newDg)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// 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.system.IonSystemBuilder
import com.amazon.ionschema.assertEqualIon
import com.amazon.ionschema.model.ExperimentalIonSchemaModel
import com.amazon.ionschema.model.SchemaFooter
import com.amazon.ionschema.util.bagOf
import org.junit.jupiter.api.Test

@OptIn(ExperimentalIonSchemaModel::class)
class FooterWriterTest {

val ION = IonSystemBuilder.standard().build()
private val footerWriter = FooterWriter

@Test
fun `writeFooter can write an empty footer`() {
assertEqualIon("schema_footer::{}") { w -> footerWriter.writeFooter(w, SchemaFooter()) }
}

@Test
fun `writeFooter can write a footer with open content`() {
val footer = SchemaFooter(
openContent = bagOf(
"foo" to ION.newInt(1),
"bar" to ION.newInt(2),
)
)
assertEqualIon("schema_footer::{foo:1,bar:2}") { w -> footerWriter.writeFooter(w, footer) }
}
}
Loading
Loading