diff --git a/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/AvroArraySchema.java b/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/AvroArraySchema.java index e2543a6c..48c4ab27 100644 --- a/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/AvroArraySchema.java +++ b/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/AvroArraySchema.java @@ -1,19 +1,45 @@ package com.asyncapi.v3.schema.avro; +import com.asyncapi.v3.schema.avro.jackson.AvroRecordFieldSchemaTypeDeserializer; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; import org.jetbrains.annotations.NotNull; /** * @see Arrays */ +@Data +@EqualsAndHashCode(callSuper = true) public class AvroArraySchema extends AvroSchema { public AvroArraySchema() { super(AvroSchemaType.ARRAY); } + @Builder(builderMethodName = "arrayBuilder") + public AvroArraySchema( + @NotNull Object items + ) { + super(AvroSchemaType.ARRAY); + this.items = items; + } + @NotNull @JsonProperty("items") - private String items; + @JsonDeserialize(using = AvroRecordFieldSchemaTypeDeserializer.class) + private Object items; + + @NotNull + @Override + public AvroSchemaType getType() { + return AvroSchemaType.ARRAY; + } + + public void setType(@NotNull AvroSchemaType type) { + super.setType(AvroSchemaType.ARRAY); + } } diff --git a/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/AvroEnumSchema.java b/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/AvroEnumSchema.java index 4fe12a8d..f82d37f9 100644 --- a/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/AvroEnumSchema.java +++ b/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/AvroEnumSchema.java @@ -1,6 +1,11 @@ package com.asyncapi.v3.schema.avro; +import com.asyncapi.v3.schema.avro.jackson.AvroRecordFieldSchemaTypeDeserializer; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -12,12 +17,33 @@ * * @see Enums */ +@Data +@EqualsAndHashCode(callSuper = true) public class AvroEnumSchema extends AvroSchema { public AvroEnumSchema() { super(AvroSchemaType.ENUM); } + @Builder(builderMethodName = "enumBuilder") + public AvroEnumSchema( + @NotNull String name, + @Nullable String namespace, + @Nullable String doc, + @NotNull List<@NotNull String> symbols, + @Nullable List<@NotNull String> aliases, + @Nullable Object defaultValue + ) { + super(AvroSchemaType.ENUM); + + this.name = name; + this.namespace = namespace; + this.doc = doc; + this.symbols = symbols; + this.aliases = aliases; + this.defaultValue = defaultValue; + } + @NotNull @JsonProperty("name") private String name = ""; @@ -59,6 +85,7 @@ public AvroEnumSchema() { */ @Nullable @JsonProperty("default") - private String defaultValue; + @JsonDeserialize(using = AvroRecordFieldSchemaTypeDeserializer.class) + private Object defaultValue; } diff --git a/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/AvroFixedSchema.java b/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/AvroFixedSchema.java index 7aba05ed..092b446f 100644 --- a/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/AvroFixedSchema.java +++ b/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/AvroFixedSchema.java @@ -1,6 +1,9 @@ package com.asyncapi.v3.schema.avro; import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -9,12 +12,29 @@ /** * @see Arrays */ +@Data +@EqualsAndHashCode(callSuper = true) public class AvroFixedSchema extends AvroSchema { public AvroFixedSchema() { super(AvroSchemaType.FIXED); } + @Builder(builderMethodName = "fixedBuilder") + public AvroFixedSchema( + @NotNull String name, + @Nullable String namespace, + @Nullable List<@NotNull String> aliases, + @NotNull Integer size + ) { + super(AvroSchemaType.FIXED); + + this.name = name; + this.namespace = namespace; + this.aliases = aliases; + this.size = size; + } + @NotNull @JsonProperty("name") private String name; diff --git a/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/AvroMapSchema.java b/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/AvroMapSchema.java index 1b0ecfd3..d1488029 100644 --- a/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/AvroMapSchema.java +++ b/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/AvroMapSchema.java @@ -1,19 +1,50 @@ package com.asyncapi.v3.schema.avro; +import com.asyncapi.v3.schema.avro.jackson.AvroRecordFieldSchemaTypeDeserializer; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; /** * @see Maps */ +@Data +@EqualsAndHashCode(callSuper = true) public class AvroMapSchema extends AvroSchema { public AvroMapSchema() { super(AvroSchemaType.MAP); } + public AvroMapSchema(@NotNull Object values) { + this.values = values; + } + + @Builder(builderMethodName = "mapBuilder") + public AvroMapSchema(@NotNull Object values, @Nullable Map metadata) { + this.values = values; + this.metadata = metadata; + } + @NotNull @JsonProperty("values") - private String values; + @JsonDeserialize(using = AvroRecordFieldSchemaTypeDeserializer.class) + private Object values; + + @NotNull + @Override + public AvroSchemaType getType() { + return AvroSchemaType.MAP; + } + + public void setType(@NotNull AvroSchemaType type) { + super.setType(AvroSchemaType.MAP); + } } diff --git a/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/AvroMetadata.java b/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/AvroMetadata.java new file mode 100644 index 00000000..80372d23 --- /dev/null +++ b/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/AvroMetadata.java @@ -0,0 +1,33 @@ +package com.asyncapi.v3.schema.avro; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonIgnoreProperties({"metadata"}) +public class AvroMetadata { + + @Nullable + @JsonAnyGetter + protected Map metadata; + + @JsonAnySetter + protected final void readMetadata(String name, Object value) { + if (metadata == null) { + metadata = new HashMap<>(); + } + + metadata.put(name, value); + } + +} diff --git a/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/AvroRecordFieldSchema.java b/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/AvroRecordFieldSchema.java index a53a54d4..b886f41a 100644 --- a/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/AvroRecordFieldSchema.java +++ b/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/AvroRecordFieldSchema.java @@ -1,11 +1,16 @@ package com.asyncapi.v3.schema.avro; +import com.asyncapi.v3.schema.avro.jackson.AvroRecordFieldSchemaTypeDeserializer; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Collections; import java.util.List; +import java.util.Map; /** * Avro Record Field. @@ -14,10 +19,31 @@ * @version 3.0.0 * @see Avro Record */ -public class AvroRecordFieldSchema extends AvroSchema { +@Data +@EqualsAndHashCode(callSuper = true) +public class AvroRecordFieldSchema extends AvroMetadata { public AvroRecordFieldSchema() { - super(AvroSchemaType.RECORD); + this.type = AvroSchemaType.RECORD; + } + + @Builder + public AvroRecordFieldSchema( + @NotNull Object type, + @NotNull String name, + @Nullable Order order, + @Nullable String doc, + @Nullable List<@NotNull String> aliases, + @Nullable Object defaultValue, + @Nullable Map metadata + ) { + this.type = type; + this.name = name; + this.order = order == null ? Order.ASCENDING : order ; + this.doc = doc; + this.aliases = aliases; + this.defaultValue = defaultValue; + this.metadata = metadata; } /** @@ -25,6 +51,7 @@ public AvroRecordFieldSchema() { */ @NotNull @JsonProperty("type") + @JsonDeserialize(using = AvroRecordFieldSchemaTypeDeserializer.class) private Object type; /** @@ -37,9 +64,23 @@ public AvroRecordFieldSchema() { /** * Specifies how this field impacts sort ordering of this record (optional). */ - @Nullable("order") + @Nullable + @JsonProperty("order") private Order order = Order.ASCENDING; + @NotNull + public Order getOrder() { + if (order == null) { + setOrder(Order.ASCENDING); + } + + return order; + } + + public void setOrder(@NotNull Order order) { + this.order = order; + } + /** * A JSON string providing documentation to the user of this schema (optional). */ diff --git a/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/AvroRecordSchema.java b/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/AvroRecordSchema.java index 464ba693..bd49e0c3 100644 --- a/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/AvroRecordSchema.java +++ b/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/AvroRecordSchema.java @@ -1,5 +1,6 @@ package com.asyncapi.v3.schema.avro; +import lombok.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -13,12 +14,36 @@ * @version 3.0.0 * @see Avro Record */ +@Data +@EqualsAndHashCode(callSuper = true) public class AvroRecordSchema extends AvroSchema { public AvroRecordSchema() { super(AvroSchemaType.RECORD); } + @Builder(builderMethodName = "recordBuilder") + public AvroRecordSchema( + @Nullable AvroSchemaType type, + @NotNull String name, + @Nullable String namespace, + @Nullable String doc, + @Nullable List<@NotNull String> aliases, + @NotNull List<@NotNull AvroRecordFieldSchema> fields + ) { + if (AvroSchemaType.RECORD.equals(type) || AvroSchemaType.ERROR.equals(type)) { + super.setType(type); + } else { + super.setType(AvroSchemaType.RECORD); + } + + this.name = name; + this.namespace = namespace; + this.doc = doc; + this.aliases = aliases; + this.fields = fields; + } + /** * A JSON string providing the name of the record (required). */ @@ -49,4 +74,14 @@ public AvroRecordSchema() { @NotNull private List<@NotNull AvroRecordFieldSchema> fields = Collections.emptyList(); + @NotNull + @Override + public AvroSchemaType getType() { + return AvroSchemaType.RECORD; + } + + public void setType(@NotNull AvroSchemaType type) { + super.setType(AvroSchemaType.RECORD); + } + } diff --git a/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/AvroSchema.java b/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/AvroSchema.java index 9273f76d..f2e060e6 100644 --- a/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/AvroSchema.java +++ b/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/AvroSchema.java @@ -1,9 +1,11 @@ package com.asyncapi.v3.schema.avro; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; -import lombok.Data; +import lombok.*; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * Apache Avro Schema. @@ -13,16 +15,31 @@ * @see Avro Specification */ @Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type", visible = true ) -@JsonSubTypes.Type(value = AvroSchema.class, names = { - "null", "boolean", "int", "long", "float", "double", "bytes", "string" +@JsonSubTypes({ + @JsonSubTypes.Type(value = AvroSchema.class, names = { + "null", "boolean", "int", "long", "float", "double", "bytes", "string" + }), + @JsonSubTypes.Type(value = AvroRecordSchema.class, names = {"record", "error"}), + @JsonSubTypes.Type(value = AvroArraySchema.class, name = "array"), + @JsonSubTypes.Type(value = AvroMapSchema.class, name = "map"), + @JsonSubTypes.Type(value = AvroEnumSchema.class, name = "enum"), + @JsonSubTypes.Type(value = AvroFixedSchema.class, name = "fixed"), }) -public class AvroSchema { +public class AvroSchema extends AvroMetadata { + + public AvroSchema(@NotNull AvroSchemaType avroSchemaType) { + this.type = avroSchemaType; + } /** * Avro Schema type. @@ -30,4 +47,45 @@ public class AvroSchema { @NotNull private AvroSchemaType type; + @Nullable + private Integer scale; + + /* + Type: bytes, fixed + */ + + @Nullable + private Integer precision; + + @Nullable + private LogicalType logicalType; + + public enum LogicalType { + + @JsonProperty("decimal") + DECIMAL, + + @JsonProperty("uuid") + UUID, + + @JsonProperty("date") + DATE, + + @JsonProperty("time-millis") + TIME_MILLIS, + + @JsonProperty("time-micros") + TIME_MICROS, + + @JsonProperty("timestamp-millis") + TIMESTAMP_MILLIS, + + @JsonProperty("timestamp-micros") + TIMESTAMP_MICROS, + + @JsonProperty("duration") + DURATION + + } + } diff --git a/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/jackson/AvroRecordFieldSchemaTypeDeserializer.java b/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/jackson/AvroRecordFieldSchemaTypeDeserializer.java new file mode 100644 index 00000000..f409589e --- /dev/null +++ b/asyncapi-core/src/main/java/com/asyncapi/v3/schema/avro/jackson/AvroRecordFieldSchemaTypeDeserializer.java @@ -0,0 +1,63 @@ +package com.asyncapi.v3.schema.avro.jackson; + +import com.asyncapi.v3.schema.avro.AvroSchema; +import com.asyncapi.v3.schema.avro.AvroSchemaType; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeType; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class AvroRecordFieldSchemaTypeDeserializer extends JsonDeserializer { + + @Override + final public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { + ObjectCodec objectCodec = p.getCodec(); + JsonNode node = objectCodec.readTree(p); + + return chooseKnownPojo(node, objectCodec); + } + + private Object chooseKnownPojo(JsonNode jsonNode, final ObjectCodec objectCodec) throws IOException { + try (JsonParser jsonParser = jsonNode.traverse(objectCodec)) { + JsonNodeType nodeType = jsonNode.getNodeType(); + + switch (nodeType) { + case ARRAY: + return readAsList((ArrayNode) jsonNode, objectCodec); + case BOOLEAN: + return jsonNode.asBoolean(); + case NUMBER: + return jsonParser.readValueAs(Number.class); + case OBJECT: + return jsonParser.readValueAs(AvroSchema.class); + case STRING: + return jsonParser.readValueAs(AvroSchemaType.class); + case BINARY: + case POJO: + case MISSING: + case NULL: + return null; + } + + return null; + } + } + + private List readAsList(ArrayNode arrayNode, ObjectCodec objectCodec) throws IOException { + List list = new ArrayList<>(); + for (JsonNode childNode : arrayNode) { + list.add(chooseKnownPojo(childNode, objectCodec)); + } + + return list; + } + +} diff --git a/asyncapi-core/src/test/kotlin/com/asyncapi/v3/schema/avro/AvroSchemaTest.kt b/asyncapi-core/src/test/kotlin/com/asyncapi/v3/schema/avro/AvroSchemaTest.kt new file mode 100644 index 00000000..07fc0716 --- /dev/null +++ b/asyncapi-core/src/test/kotlin/com/asyncapi/v3/schema/avro/AvroSchemaTest.kt @@ -0,0 +1,59 @@ +package com.asyncapi.v3.schema.avro + +import com.asyncapi.v3.ClasspathUtils +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.ArgumentsProvider +import org.junit.jupiter.params.provider.ArgumentsSource +import java.util.stream.Stream + +class AvroSchemaTest { + + fun compareSchemas( + schemaToCheckPath: String, + schemaToCheck: AvroSchema + ) { + val schemaAsJson = ClasspathUtils.readAsString(schemaToCheckPath) + val schema = objectMapper.readValue(schemaAsJson, AvroSchema::class.java) + + Assertions.assertEquals(schema, schemaToCheck) + } + + private val objectMapper: ObjectMapper = ObjectMapper(YAMLFactory()) + .setSerializationInclusion(JsonInclude.Include.NON_NULL) + .findAndRegisterModules() + + @ArgumentsSource(Avro::class) + @ParameterizedTest(name = "Read: {0}") + fun read(schemaToCheckPath: String, avroSchema: AvroSchema) { + compareSchemas(schemaToCheckPath, avroSchema) + } + + class Avro: ArgumentsProvider { + + override fun provideArguments(context: ExtensionContext?): Stream { + return Stream.of( +// Arguments.of("/json/v3/schema/avro/ApplicationEvent.avsc", AvroSchemasProvider().applicationEventTest()), // TODO: fix - Cannot deserialize value of type `com.asyncapi.v3.schema.avro.AvroSchemaType` from String "model.DocumentInfo" + Arguments.of("/json/v3/schema/avro/DocumentInfo.avsc", AvroSchemasProvider().documentInfo()), + Arguments.of("/json/v3/schema/avro/foo.Bar.avsc", AvroSchemasProvider().fooBar()), + Arguments.of("/json/v3/schema/avro/full_record_v1.avsc", AvroSchemasProvider().fullRecordV1()), + Arguments.of("/json/v3/schema/avro/full_record_v2.avsc", AvroSchemasProvider().fullRecordV2()), + Arguments.of("/json/v3/schema/avro/logical-uuid.avsc", AvroSchemasProvider().logicalUUID()), + Arguments.of("/json/v3/schema/avro/logical_types_with_multiple_fields.avsc", AvroSchemasProvider().logicalTypesWithMultipleFields()), + Arguments.of("/json/v3/schema/avro/MyResponse.avsc", AvroSchemasProvider().myResponse()), + Arguments.of("/json/v3/schema/avro/regression_error_field_in_record.avsc", AvroSchemasProvider().regressionErrorFieldInRecord()), + Arguments.of("/json/v3/schema/avro/schema-location.json", AvroSchemasProvider().schemaLocation()), + Arguments.of("/json/v3/schema/avro/schema-location-read.json", AvroSchemasProvider().schemaLocationRead()), + Arguments.of("/json/v3/schema/avro/schema-location-write.json", AvroSchemasProvider().schemaLocationWrite()), + Arguments.of("/json/v3/schema/avro/SchemaBuilder.avsc", AvroSchemasProvider().schemaBuilder()), + ) + } + + } + +} \ No newline at end of file diff --git a/asyncapi-core/src/test/kotlin/com/asyncapi/v3/schema/avro/AvroSchemasProvider.kt b/asyncapi-core/src/test/kotlin/com/asyncapi/v3/schema/avro/AvroSchemasProvider.kt new file mode 100644 index 00000000..e14cc847 --- /dev/null +++ b/asyncapi-core/src/test/kotlin/com/asyncapi/v3/schema/avro/AvroSchemasProvider.kt @@ -0,0 +1,748 @@ +package com.asyncapi.v3.schema.avro + +class AvroSchemasProvider { + + fun applicationEventTest(): AvroRecordSchema { + return AvroRecordSchema.recordBuilder() + .name("ApplicationEvent") + .namespace("model") + .doc("") + .fields(listOf( + AvroRecordFieldSchema.builder() + .name("applicationId") + .type(AvroSchemaType.STRING) + .doc("Application ID") + .build(), + AvroRecordFieldSchema.builder() + .name("status") + .type(AvroSchemaType.STRING) + .doc("Application Status") + .build(), + AvroRecordFieldSchema.builder() + .name("documents") + // TODO: union type + .type(listOf( + AvroSchemaType.NULL, + AvroArraySchema("model.DocumentInfo") + )) + .doc("") + .defaultValue(null) + .build() + )) + .build() + } + + fun documentInfo(): AvroRecordSchema { + return AvroRecordSchema.recordBuilder() + .name("DocumentInfo") + .namespace("model") + .doc("") + .fields(listOf( + AvroRecordFieldSchema.builder() + .name("documentId") + .type(AvroSchemaType.STRING) + .doc("Document ID") + .build(), + AvroRecordFieldSchema.builder() + .name("filePath") + .type(AvroSchemaType.STRING) + .doc("Document Path") + .build() + )) + .build() + } + + fun fooBar(): AvroRecordSchema { + return AvroRecordSchema.recordBuilder() + .name("Bar") + .namespace("foo") + .fields(listOf( + AvroRecordFieldSchema.builder() + .name("title") + .type(AvroSchemaType.STRING) + .build(), + AvroRecordFieldSchema.builder() + .name("created_at") + // TODO: union type + .type(listOf( + AvroSchemaType.NULL, + AvroSchema.builder() + .type(AvroSchemaType.LONG) + .logicalType(AvroSchema.LogicalType.TIMESTAMP_MILLIS) + .build() + )) + .build() + )) + .build() + } + + fun fullRecordV1(): AvroRecordSchema { + return AvroRecordSchema.recordBuilder() + .name("FullRecordV1") + .namespace("org.apache.avro.specific.test") + .doc("Test schema changes: this is the 'old' schema the SpecificRecord expects to see") + .fields(listOf( + AvroRecordFieldSchema.builder() + .name("b") + .type(AvroSchemaType.BOOLEAN) + .build(), + AvroRecordFieldSchema.builder() + .name("i32") + .type(AvroSchemaType.INT) + .build(), + AvroRecordFieldSchema.builder() + .name("i64") + .type(AvroSchemaType.LONG) + .build(), + AvroRecordFieldSchema.builder() + .name("f32") + .type(AvroSchemaType.FLOAT) + .build(), + AvroRecordFieldSchema.builder() + .name("f64") + .type(AvroSchemaType.DOUBLE) + .build(), + AvroRecordFieldSchema.builder() + .name("s") + // TODO: union type + .type(listOf( + AvroSchemaType.NULL, AvroSchemaType.STRING + )) + .build(), + AvroRecordFieldSchema.builder() + .name("h") + // TODO: union type + .type(listOf( + AvroSchemaType.NULL, AvroSchemaType.STRING + )) + .build() + )) + .build() + } + + fun fullRecordV2(): AvroRecordSchema { + return AvroRecordSchema.recordBuilder() + .name("FullRecordV2") + .namespace("org.apache.avro.specific.test") + .doc("Test schema changes: this is the 'new' schema actually used to write data") + .fields(listOf( + AvroRecordFieldSchema.builder() + .name("b") + .type(AvroSchemaType.BOOLEAN) + .build(), + AvroRecordFieldSchema.builder() + .name("i64") + .type(AvroSchemaType.INT) + .build(), + AvroRecordFieldSchema.builder() + .name("i32") + .type(AvroSchemaType.INT) + .build(), + AvroRecordFieldSchema.builder() + .name("f64") + .type(AvroSchemaType.LONG) + .build(), + AvroRecordFieldSchema.builder() + .name("f32") + // TODO: union type + .type(listOf( + AvroSchemaType.FLOAT, AvroSchemaType.NULL + )) + .build(), + AvroRecordFieldSchema.builder() + .name("newfield") + .type(AvroSchemaType.STRING) + .build(), + AvroRecordFieldSchema.builder() + .name("h") + .type(AvroSchemaType.BYTES) + .build(), + AvroRecordFieldSchema.builder() + .name("myMap") + .type(AvroMapSchema(AvroSchemaType.STRING)) + .build() + )) + .build() + } + + fun logicalUUID(): AvroRecordSchema { + return AvroRecordSchema.recordBuilder() + .name("Action") + .namespace("schema.common") + .fields(listOf( + AvroRecordFieldSchema.builder() + .name("name") + .type(AvroSchemaType.STRING) + .build(), + AvroRecordFieldSchema.builder() + .name("guid") + .type(AvroSchema.builder() + .type(AvroSchemaType.STRING) + .logicalType(AvroSchema.LogicalType.UUID) + .build() + ) + .build(), + AvroRecordFieldSchema.builder() + .name("time") + .type(AvroSchema.builder() + .type(AvroSchemaType.LONG) + .logicalType(AvroSchema.LogicalType.TIMESTAMP_MILLIS) + .build() + ) + .build(), + AvroRecordFieldSchema.builder() + .name("requestId") + // TODO: union type + .type(listOf( + AvroSchemaType.NULL, AvroSchemaType.STRING + )) + .build() + )) + .build() + } + + fun logicalTypesWithMultipleFields(): AvroRecordSchema { + return AvroRecordSchema.recordBuilder() + .name("Action") + .namespace("schema.common") + .fields(listOf( + AvroRecordFieldSchema.builder() + .name("name") + .type(AvroSchemaType.STRING) + .build(), + AvroRecordFieldSchema.builder() + .name("uuid") + .type(AvroSchemaType.STRING) + .build(), + AvroRecordFieldSchema.builder() + .name("time") + .type(AvroSchema.builder() + .type(AvroSchemaType.LONG) + .logicalType(AvroSchema.LogicalType.TIMESTAMP_MILLIS) + .build() + ) + .build(), + AvroRecordFieldSchema.builder() + .name("requestId") + // TODO: union type + .type(listOf( + AvroSchemaType.NULL, AvroSchemaType.STRING + )) + .build() + )) + .build() + } + + fun myResponse(): AvroRecordSchema { + return AvroRecordSchema.recordBuilder() + .name("MyResponse") + .namespace("model") + .doc("") + .fields(listOf( + AvroRecordFieldSchema.builder() + .name("isSuccessful") + .doc("Indicator for successful or unsuccessful call") + .type(AvroSchemaType.BOOLEAN) + .build() + )) + .build() + } + + fun regressionErrorFieldInRecord(): AvroRecordSchema { + return AvroRecordSchema.recordBuilder() + .name("RecordWithErrorField") + .namespace("org.apache.avro.specific.test") + .doc("With custom coders in Avro 1.9, previously successful records with error fields now fail to compile.") + .fields(listOf( + AvroRecordFieldSchema.builder() + .name("s") + // TODO: union type + .type(listOf( + AvroSchemaType.NULL, AvroSchemaType.STRING + )) + .defaultValue(null) + .build(), + AvroRecordFieldSchema.builder() + .name("e") + // TODO: union type + .type(listOf( + AvroSchemaType.NULL, AvroRecordSchema.recordBuilder() + .type(AvroSchemaType.ERROR) + .name("TestError") + .fields(listOf( + AvroRecordFieldSchema.builder() + .name("message") + .type(AvroSchemaType.STRING) + .build() + )) + .build() + )) + .defaultValue(null) + .build() + )) + .build() + } + + fun schemaLocation(): AvroRecordSchema { + val lat = AvroRecordFieldSchema.builder() + .name("lat") + .type(AvroSchemaType.FLOAT) + .build() + lat.metadata = mapOf(Pair("field-id", 1)) + + val long = AvroRecordFieldSchema.builder() + .name("long") + .type(listOf(AvroSchemaType.NULL, AvroSchemaType.FLOAT)) + .defaultValue(null) + .build() + long.metadata = mapOf(Pair("field-id", 2)) + + return AvroRecordSchema.recordBuilder() + .name("r7") + .fields(listOf(lat, long)) + .build() + } + + fun schemaLocationRead(): AvroRecordSchema { + return AvroRecordSchema.recordBuilder() + .name("table") + .fields(listOf( + AvroRecordFieldSchema.builder() + .name("location") + // TODO: union type + .type(listOf(AvroSchemaType.NULL, AvroMapSchema.mapBuilder() + // TODO: union type + .values(listOf( + AvroSchemaType.NULL, + AvroRecordSchema.recordBuilder() + .name("r7") + .fields(listOf( + AvroRecordFieldSchema.builder() + .name("lat") + .type(AvroSchemaType.FLOAT) + .metadata(mapOf(Pair("field-id", 1))) + .build(), + AvroRecordFieldSchema.builder() + .name("long_r2") + // TODO: union type + .type(listOf(AvroSchemaType.NULL, AvroSchemaType.FLOAT)) + .defaultValue(null) + .metadata(mapOf(Pair("field-id", 2))) + .build() + )) + .build() + )) + .metadata(mapOf( + Pair("key-id", 6), + Pair("value-id", 7) + )) + .build()) + ) + .defaultValue(null) + .metadata(mapOf(Pair("field-id", 5))) + .build() + )) + .build() + } + + fun schemaLocationWrite(): AvroRecordSchema { + return AvroRecordSchema.recordBuilder() + .name("table") + .fields(listOf( + AvroRecordFieldSchema.builder() + .name("location") + // TODO: union type + .type(listOf(AvroSchemaType.NULL, AvroMapSchema.mapBuilder() + // TODO: union type + .values(listOf( + AvroSchemaType.NULL, + AvroRecordSchema.recordBuilder() + .name("r7") + .fields(listOf( + AvroRecordFieldSchema.builder() + .name("lat") + .type(AvroSchemaType.FLOAT) + .metadata(mapOf(Pair("field-id", 1))) + .build(), + AvroRecordFieldSchema.builder() + .name("long") + // TODO: union type + .type(listOf(AvroSchemaType.NULL, AvroSchemaType.FLOAT)) + .defaultValue(null) + .metadata(mapOf(Pair("field-id", 2))) + .build() + )) + .build() + )) + .metadata(mapOf( + Pair("key-id", 6), + Pair("value-id", 7) + )) + .build()) + ) + .defaultValue(null) + .metadata(mapOf(Pair("field-id", 5))) + .build() + )) + .build() + } + + fun schemaBuilder(): AvroRecordSchema { + return AvroRecordSchema.recordBuilder() + .name("recordAll") + .fields(listOf( + AvroRecordFieldSchema.builder() + .name("requiredBoolean") + .type(AvroSchemaType.BOOLEAN) + .build(), + AvroRecordFieldSchema.builder() + .name("requiredBooleanWithDefault") + .type(AvroSchemaType.BOOLEAN) + .defaultValue(true) + .build(), + AvroRecordFieldSchema.builder() + .name("optionalBoolean") + // TODO: union type + .type(listOf(AvroSchemaType.NULL, AvroSchemaType.BOOLEAN)) + .defaultValue(null) + .build(), + AvroRecordFieldSchema.builder() + .name("optionalBooleanWithDefault") + // TODO: union type + .type(listOf(AvroSchemaType.BOOLEAN, AvroSchemaType.NULL)) + .defaultValue(true) + .build(), + AvroRecordFieldSchema.builder() + .name("requiredInt") + .type(AvroSchemaType.INT) + .build(), + AvroRecordFieldSchema.builder() + .name("optionalInt") + // TODO: union type + .type(listOf(AvroSchemaType.NULL, AvroSchemaType.INT)) + .defaultValue(null) + .build(), + AvroRecordFieldSchema.builder() + .name("optionalIntWithDefault") + // TODO: union type + .type(listOf(AvroSchemaType.INT, AvroSchemaType.NULL)) + .defaultValue(1) + .build(), + AvroRecordFieldSchema.builder() + .name("requiredLong") + .type(AvroSchemaType.LONG) + .build(), + AvroRecordFieldSchema.builder() + .name("optionalLong") + // TODO: union type + .type(listOf(AvroSchemaType.NULL, AvroSchemaType.LONG)) + .defaultValue(null) + .build(), + AvroRecordFieldSchema.builder() + .name("optionalLongWithDefault") + // TODO: union type + .type(listOf(AvroSchemaType.LONG, AvroSchemaType.NULL)) + .defaultValue(1) + .build(), + AvroRecordFieldSchema.builder() + .name("requiredFloat") + .type(AvroSchemaType.FLOAT) + .build(), + AvroRecordFieldSchema.builder() + .name("optionalFloat") + // TODO: union type + .type(listOf(AvroSchemaType.NULL, AvroSchemaType.FLOAT)) + .defaultValue(null) + .build(), + AvroRecordFieldSchema.builder() + .name("optionalFloatWithDefault") + // TODO: union type + .type(listOf(AvroSchemaType.FLOAT, AvroSchemaType.NULL)) + .defaultValue(1.0) + .build(), + AvroRecordFieldSchema.builder() + .name("requiredDouble") + .type(AvroSchemaType.DOUBLE) + .build(), + AvroRecordFieldSchema.builder() + .name("optionalDouble") + // TODO: union type + .type(listOf(AvroSchemaType.NULL, AvroSchemaType.DOUBLE)) + .defaultValue(null) + .build(), + AvroRecordFieldSchema.builder() + .name("optionalDoubleWithDefault") + // TODO: union type + .type(listOf(AvroSchemaType.DOUBLE, AvroSchemaType.NULL)) + .defaultValue(1.0) + .build(), + AvroRecordFieldSchema.builder() + .name("requiredBytes") + .type(AvroSchemaType.BYTES) + .build(), + AvroRecordFieldSchema.builder() + .name("optionalBytes") + // TODO: union type + .type(listOf(AvroSchemaType.NULL, AvroSchemaType.BYTES)) + .defaultValue(null) + .build(), + AvroRecordFieldSchema.builder() + .name("optionalBytesWithDefault") + // TODO: union type + .type(listOf(AvroSchemaType.BYTES, AvroSchemaType.NULL)) + .defaultValue("A") + .build(), + AvroRecordFieldSchema.builder() + .name("requiredString") + .type(AvroSchemaType.STRING) + .build(), + AvroRecordFieldSchema.builder() + .name("optionalString") + // TODO: union type + .type(listOf(AvroSchemaType.NULL, AvroSchemaType.STRING)) + .defaultValue(null) + .build(), + AvroRecordFieldSchema.builder() + .name("optionalStringWithDefault") + // TODO: union type + .type(listOf(AvroSchemaType.STRING, AvroSchemaType.NULL)) + .defaultValue("a") + .build(), + AvroRecordFieldSchema.builder() + .name("requiredRecord") + .type(AvroRecordSchema.recordBuilder() + .name("nestedRequiredRecord") + .fields(listOf(AvroRecordFieldSchema.builder() + .name("nestedRequiredBoolean") + .type(AvroSchemaType.BOOLEAN) + .build()) + ) + .build() + ) + .build(), + AvroRecordFieldSchema.builder() + .name("optionalRecord") + // TODO: union type + .type(listOf(AvroSchemaType.NULL, AvroRecordSchema.recordBuilder() + .name("nestedOptionalRecord") + .fields(listOf(AvroRecordFieldSchema.builder() + .name("nestedRequiredBoolean") + .type(AvroSchemaType.BOOLEAN) + .build()) + ) + .build() + )) + .defaultValue(null) + .build(), + AvroRecordFieldSchema.builder() + .name("optionalRecordWithDefault") + // TODO: union type + .type(listOf(AvroRecordSchema.recordBuilder() + .name("nestedOptionalRecordWithDefault") + .fields(listOf(AvroRecordFieldSchema.builder() + .name("nestedRequiredBoolean") + .type(AvroSchemaType.BOOLEAN) + .build()) + ) + .build(), AvroSchemaType.NULL + )) + .defaultValue(mapOf(Pair("nestedRequiredBoolean", true))) + .build(), + AvroRecordFieldSchema.builder() + .name("requiredEnum") + .type(AvroEnumSchema.enumBuilder() + .name("requiredEnum") + .symbols(listOf("a", "b")) + .build() + ) + .build(), + AvroRecordFieldSchema.builder() + .name("optionalEnum") + // TODO: union type + .type(listOf(AvroSchemaType.NULL, AvroEnumSchema.enumBuilder() + .name("optionalEnum") + .symbols(listOf("a", "b")) + .build() + )) + .defaultValue(null) + .build(), + AvroRecordFieldSchema.builder() + .name("optionalEnumWithDefault") + // TODO: union type + .type(listOf(AvroEnumSchema.enumBuilder() + .name("optionalEnumWithDefault") + .symbols(listOf("a", "b")) + .build(), AvroSchemaType.NULL, + )) + .defaultValue("b") + .build(), + AvroRecordFieldSchema.builder() + .name("requiredArray") + .type(AvroArraySchema.arrayBuilder() + .items(AvroSchemaType.STRING) + .build() + ) + .build(), + AvroRecordFieldSchema.builder() + .name("optionalArray") + // TODO: union type + .type(listOf(AvroSchemaType.NULL, AvroArraySchema.arrayBuilder() + .items(AvroSchemaType.STRING) + .build() + )) + .defaultValue(null) + .build(), + AvroRecordFieldSchema.builder() + .name("optionalArrayWithDefault") + // TODO: union type + .type(listOf(AvroArraySchema.arrayBuilder() + .items(AvroSchemaType.STRING) + .build(), AvroSchemaType.NULL + )) + .defaultValue(listOf("a")) + .build(), + AvroRecordFieldSchema.builder() + .name("requiredMap") + .type(AvroMapSchema.mapBuilder() + .values(AvroSchemaType.STRING) + .build() + ) + .build(), + AvroRecordFieldSchema.builder() + .name("optionalMap") + // TODO: union type + .type(listOf(AvroSchemaType.NULL, AvroMapSchema.mapBuilder() + .values(AvroSchemaType.STRING) + .build() + )) + .defaultValue(null) + .build(), + AvroRecordFieldSchema.builder() + .name("optionalMapWithDefault") + // TODO: union type + .type(listOf(AvroMapSchema.mapBuilder() + .values(AvroSchemaType.STRING) + .build(), AvroSchemaType.NULL + )) + .defaultValue(mapOf(Pair("a", "b"))) + .build(), + AvroRecordFieldSchema.builder() + .name("requiredFixed") + .type(AvroFixedSchema.fixedBuilder() + .name("requiredFixed") + .size(1) + .build() + ) + .build(), + AvroRecordFieldSchema.builder() + .name("optionalFixed") + // TODO: union type + .type(listOf(AvroSchemaType.NULL, AvroFixedSchema.fixedBuilder() + .name("optionalFixed") + .size(1) + .build() + )) + .defaultValue(null) + .build(), + AvroRecordFieldSchema.builder() + .name("optionalFixedWithDefault") + // TODO: union type + .type(listOf(AvroFixedSchema.fixedBuilder() + .name("optionalFixedWithDefault") + .size(1) + .build(), AvroSchemaType.NULL + )) + .defaultValue("A") + .build(), + AvroRecordFieldSchema.builder() + .name("unionType") + .type(listOf(AvroSchemaType.LONG, AvroSchemaType.NULL)) + .build(), + AvroRecordFieldSchema.builder() + .name("unionBooleanWithDefault") + .type(listOf(AvroSchemaType.BOOLEAN, AvroSchemaType.INT)) + .defaultValue(true) + .build(), + AvroRecordFieldSchema.builder() + .name("unionIntWithDefault") + .type(listOf(AvroSchemaType.INT, AvroSchemaType.NULL)) + .defaultValue(1) + .build(), + AvroRecordFieldSchema.builder() + .name("unionLongWithDefault") + .type(listOf(AvroSchemaType.LONG, AvroSchemaType.INT)) + .defaultValue(1) + .build(), + AvroRecordFieldSchema.builder() + .name("unionFloatWithDefault") + .type(listOf(AvroSchemaType.FLOAT, AvroSchemaType.INT)) + .defaultValue(1.0) + .build(), + AvroRecordFieldSchema.builder() + .name("unionDoubleWithDefault") + .type(listOf(AvroSchemaType.DOUBLE, AvroSchemaType.INT)) + .defaultValue(1.0) + .build(), + AvroRecordFieldSchema.builder() + .name("unionBytesWithDefault") + .type(listOf(AvroSchemaType.BYTES, AvroSchemaType.INT)) + .defaultValue("A") + .build(), + AvroRecordFieldSchema.builder() + .name("unionStringWithDefault") + .type(listOf(AvroSchemaType.STRING, AvroSchemaType.INT)) + .defaultValue("a") + .build(), + AvroRecordFieldSchema.builder() + .name("unionRecordWithDefault") + .type(listOf(AvroRecordSchema.recordBuilder() + .name("nestedUnionRecordWithDefault") + .fields(listOf( + AvroRecordFieldSchema.builder() + .name("nestedRequiredBoolean") + .type(AvroSchemaType.BOOLEAN) + .build() + )) + .build(), AvroSchemaType.INT + )) + .defaultValue(mapOf(Pair("nestedRequiredBoolean", true))) + .build(), + AvroRecordFieldSchema.builder() + .name("unionEnumWithDefault") + .type(listOf(AvroEnumSchema.enumBuilder() + .name("nestedUnionEnumWithDefault") + .symbols(listOf("a", "b")) + .build(), AvroSchemaType.INT + )) + .defaultValue("b") + .build(), + AvroRecordFieldSchema.builder() + .name("unionArrayWithDefault") + .type(listOf(AvroArraySchema.arrayBuilder() + .items(AvroSchemaType.STRING) + .build(), AvroSchemaType.INT + )) + .defaultValue(listOf("a")) + .build(), + AvroRecordFieldSchema.builder() + .name("unionMapWithDefault") + .type(listOf(AvroMapSchema.mapBuilder() + .values(AvroSchemaType.STRING) + .build(), AvroSchemaType.INT + )) + .defaultValue(mapOf(Pair("a", "b"))) + .build(), + AvroRecordFieldSchema.builder() + .name("unionFixedWithDefault") + .type(listOf(AvroFixedSchema.fixedBuilder() + .name("nestedUnionFixedWithDefault") + .size(1) + .build(), AvroSchemaType.INT + )) + .defaultValue("A") + .build(), + )) + .build() + } + +} \ No newline at end of file