-
Notifications
You must be signed in to change notification settings - Fork 14
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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) | ||
} | ||
} | ||
} | ||
} | ||
} |
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 { | ||||||
|
||||||
/** | ||||||
* 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]. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's a type alias called |
||||||
*/ | ||||||
fun writeTypeArguments(ionWriter: IonWriter, typeArgs: TypeArguments) { | ||||||
ionWriter.writeToList(typeArgs) { writeTypeArg(ionWriter, it) } | ||||||
} | ||||||
} |
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 Codecov / codecov/patchion-schema/src/main/kotlin/com/amazon/ionschema/writer/internal/constraints/ConstraintWriter.kt#L27
|
||
|
||
/** | ||
* 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) | ||
} | ||
} finally { | ||
stepOut() | ||
} | ||
} | ||
|
||
/** | ||
* 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() | ||
} finally { | ||
stepOut() | ||
} | ||
} | ||
|
||
/** | ||
* 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) | ||
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) } | ||
} | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.