Skip to content
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
9 changes: 7 additions & 2 deletions lang/java/idl/src/main/java/org/apache/avro/idl/IdlUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public void serialize(JsonProperties.Null value, JsonGenerator gen, SerializerPr
module.addSerializer(new StdSerializer<byte[]>(byte[].class) {
@Override
public void serialize(byte[] value, JsonGenerator gen, SerializerProvider provider) throws IOException {
MAPPER.writeValueAsString(new String(value, StandardCharsets.ISO_8859_1));
gen.writeString(new String(value, StandardCharsets.ISO_8859_1));
}
});

Expand Down Expand Up @@ -285,7 +285,12 @@ private static void writeSchema(Schema schema, boolean insideProtocol, Writer wr
} else {
throw new AvroRuntimeException("Enum schema must have at least a symbol " + schema);
}
writer.append(NEWLINE).append(indent).append("}").append(NEWLINE);
writer.append(NEWLINE).append(indent).append("}");
String enumDefault = schema.getEnumDefault();
if (enumDefault != null) {
writer.append(" = ").append(enumDefault).append(";");
}
writer.append(NEWLINE);
Comment thread
prygunov marked this conversation as resolved.
} else /* (type == Schema.Type.FIXED) */ {
writer.append(indent).append("fixed ").append(schemaName).append('(')
.append(Integer.toString(schema.getFixedSize())).append(");").append(NEWLINE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
package org.apache.avro.idl;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
Expand All @@ -26,20 +27,24 @@
import java.util.LinkedHashMap;
import java.util.Map;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.apache.avro.AvroRuntimeException;
import org.apache.avro.JsonProperties;
import org.apache.avro.Protocol;
import org.apache.avro.Schema;
import org.junit.jupiter.api.Test;

import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.Objects.requireNonNull;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class IdlUtilsTest {
public class TestIdlUtils {
@Test
public void idlUtilsUtilitiesThrowRuntimeExceptionsOnProgrammerError() {
assertThrows(IllegalStateException.class, () -> IdlUtils.getField(Object.class, "noSuchField"), "Programmer error");
Expand Down Expand Up @@ -92,7 +97,7 @@ public void validateHappyFlowForSingleSchema() throws IOException {
Schema mainSchema = idlFile.getMainSchema();

StringWriter buffer = new StringWriter();
IdlUtils.writeIdlSchema(buffer, mainSchema.getTypes().iterator().next());
IdlUtils.writeIdlSchema(buffer, mainSchema);

assertEquals(getResourceAsString("idl_utils_test_schema.avdl"), buffer.toString());
}
Expand All @@ -103,6 +108,56 @@ public void cannotWriteProtocolWithUnnamedTypes() {
() -> IdlUtils.writeIdlProtocol(new StringWriter(), Schema.create(Schema.Type.STRING)));
}

@Test
public void enumDefaultIsWrittenToIdl() throws IOException {
Schema withDefault = Schema.createEnum("Status", null, "naming", asList("ACTIVE", "INACTIVE"), "ACTIVE");
Schema withoutDefault = Schema.createEnum("Status", null, "naming", asList("ACTIVE", "INACTIVE"));

StringWriter withDefaultWriter = new StringWriter();
IdlUtils.writeIdlProtocol(withDefaultWriter, withDefault);
StringWriter withoutDefaultWriter = new StringWriter();
IdlUtils.writeIdlProtocol(withoutDefaultWriter, withoutDefault);

assertTrue(withDefaultWriter.toString().contains("} = ACTIVE;"),
"Enum with default should serialize default value");
assertFalse(withoutDefaultWriter.toString().contains("="), "Enum without default should not serialize a default");
}

@Test
public void enumDefaultSurvivesWriteThenParse() throws IOException {
Schema withDefault = Schema.createEnum("Status", null, "naming", asList("ACTIVE", "INACTIVE"), "ACTIVE");

StringWriter withDefaultWriter = new StringWriter();
IdlUtils.writeIdlProtocol(withDefaultWriter, withDefault);

// The written IDL must parse back into an equivalent schema: this is the bug
// that was reported.
IdlReader reader = new IdlReader();
Schema roundTripped;
try (InputStream in = new ByteArrayInputStream(withDefaultWriter.toString().getBytes(StandardCharsets.UTF_8))) {
roundTripped = reader.parse(in).getNamedSchemas().get("naming.Status");
}
assertEquals("ACTIVE", roundTripped.getEnumDefault());
}

@Test
public void enumDefaultIsReadFromIdlFile() throws IOException {
Protocol protocol = parseIdlResource("idl_utils_test_enum_default.avdl").getProtocol();

assertEquals("ACTIVE", protocol.getType("naming.Status").getEnumDefault());
assertNull(protocol.getType("naming.Color").getEnumDefault());
}

@Test
public void idlFileWithEnumDefaultIsWrittenBackUnchanged() throws IOException {
Protocol protocol = parseIdlResource("idl_utils_test_enum_default.avdl").getProtocol();

StringWriter buffer = new StringWriter();
IdlUtils.writeIdlProtocol(buffer, protocol);

assertEquals(getResourceAsString("idl_utils_test_enum_default.avdl"), buffer.toString());
}

@Test
public void cannotWriteEmptyEnums() {
assertThrows(AvroRuntimeException.class,
Expand All @@ -126,12 +181,12 @@ public void validateMapToJson() throws IOException {
Map<String, Object> data = new LinkedHashMap<>();
data.put("key", "name");
data.put("value", 81763);
assertEquals("{\"key\":\"name\",\"value\":81763}", callToJson(data));
assertEquals("{\"key\":\"name\", \"value\":81763}", callToJson(data));
}

@Test
public void validateCollectionToJson() throws IOException {
assertEquals("[123,\"abc\"]", callToJson(Arrays.asList(123, "abc")));
assertEquals("[123, \"abc\"]", callToJson(Arrays.asList(123, "abc")));
}

@Test
Expand Down Expand Up @@ -179,12 +234,12 @@ public void validateUnknownCannotBeWrittenAsJson() {
assertThrows(AvroRuntimeException.class, () -> callToJson(new Object()));
}

private String callToJson(Object datum) throws IOException {
StringWriter buffer = new StringWriter();
try (JsonGenerator generator = IdlUtils.MAPPER.createGenerator(buffer)) {
IdlUtils.MAPPER.writeValueAsString(datum);
private String callToJson(Object datum) {
try {
return IdlUtils.MAPPER.writeValueAsString(datum);
} catch (JsonProcessingException e) {
throw new AvroRuntimeException(e);
}
return buffer.toString();
}

private enum SingleValue {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@namespace("naming")
protocol EnumDefaults {
enum Status {
ACTIVE, INACTIVE
} = ACTIVE;

enum Color {
RED, GREEN
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ protocol HappyFlow {
@aliases(["naming.OldMessage"])
record NewMessage {
string @generator("uuid-type1") id;
@my-key("my-value") string? @aliases(["text","msg"]) message = null;
@my-key("my-value") string? @aliases(["text", "msg"]) message = null;
@my-key("my-value") map<common.Flag> @order("DESCENDING") flags;
Counter mainCounter;
/** A list of counters. */
Expand All @@ -20,7 +20,9 @@ protocol HappyFlow {
}

@namespace("common")
enum Flag {ON, OFF, CANARY}
enum Flag {
ON, OFF, CANARY
}

record Counter {
string name;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
namespace naming;

schema NewMessage;

/** A sample record type. */
@version(2)
@aliases(["naming.OldMessage"])
record NewMessage {
string @generator("uuid-type1") id;
@my-key("my-value") string? @aliases(["text", "msg"]) message = null;
@my-key("my-value") map<common.Flag> @order("DESCENDING") flags;
Counter mainCounter;
/** A list of counters. */
union{null, @my-key("my-value") array<Counter>} otherCounters = null;
Nonce nonce;
date my_date;
time_ms my_time;
timestamp_ms my_timestamp;
decimal(12,3) my_number;
@logicalType("time-micros") long my_dummy;
}

@namespace("common")
enum Flag {
ON, OFF, CANARY
}

record Counter {
string name;
int count;
/** Because the Flag field is defined earlier in NewMessage, it's already defined and does not need repeating below. */
common.Flag flag;
}

fixed Nonce(8);

This file was deleted.

Loading