diff --git a/src/main/java/com/hubspot/jackson/datatype/protobuf/ProtobufJacksonConfig.java b/src/main/java/com/hubspot/jackson/datatype/protobuf/ProtobufJacksonConfig.java index 676db2a..d6d5302 100644 --- a/src/main/java/com/hubspot/jackson/datatype/protobuf/ProtobufJacksonConfig.java +++ b/src/main/java/com/hubspot/jackson/datatype/protobuf/ProtobufJacksonConfig.java @@ -5,10 +5,13 @@ public class ProtobufJacksonConfig { private final ExtensionRegistryWrapper extensionRegistry; private final boolean acceptLiteralFieldnames; + private final UnknownEnumSerializationStrategy unknownEnumSerializationStrategy; - private ProtobufJacksonConfig(ExtensionRegistryWrapper extensionRegistry, boolean acceptLiteralFieldnames) { + private ProtobufJacksonConfig(ExtensionRegistryWrapper extensionRegistry, boolean acceptLiteralFieldnames, + UnknownEnumSerializationStrategy unknownEnumSerializationStrategy) { this.extensionRegistry = extensionRegistry; this.acceptLiteralFieldnames = acceptLiteralFieldnames; + this.unknownEnumSerializationStrategy = unknownEnumSerializationStrategy; } public static Builder builder() { @@ -23,9 +26,14 @@ public boolean acceptLiteralFieldnames() { return acceptLiteralFieldnames; } + public UnknownEnumSerializationStrategy unknownEnumSerializationStrategy() { + return unknownEnumSerializationStrategy; + } + public static class Builder { private ExtensionRegistryWrapper extensionRegistry = ExtensionRegistryWrapper.empty(); private boolean acceptLiteralFieldnames = false; + private UnknownEnumSerializationStrategy unknownEnumSerializationStrategy = UnknownEnumSerializationStrategy.SERIALIZE; private Builder() {} @@ -43,8 +51,13 @@ public Builder acceptLiteralFieldnames(boolean acceptLiteralFieldnames) { return this; } + public Builder unknownEnumSerializationStrategy(UnknownEnumSerializationStrategy unknownEnumSerializationStrategy) { + this.unknownEnumSerializationStrategy = unknownEnumSerializationStrategy; + return this; + } + public ProtobufJacksonConfig build() { - return new ProtobufJacksonConfig(extensionRegistry, acceptLiteralFieldnames); + return new ProtobufJacksonConfig(extensionRegistry, acceptLiteralFieldnames, unknownEnumSerializationStrategy); } } } diff --git a/src/main/java/com/hubspot/jackson/datatype/protobuf/ProtobufModule.java b/src/main/java/com/hubspot/jackson/datatype/protobuf/ProtobufModule.java index d453f39..050365c 100644 --- a/src/main/java/com/hubspot/jackson/datatype/protobuf/ProtobufModule.java +++ b/src/main/java/com/hubspot/jackson/datatype/protobuf/ProtobufModule.java @@ -82,22 +82,22 @@ public Version version() { public void setupModule(SetupContext context) { SimpleSerializers serializers = new SimpleSerializers(); serializers.addSerializer(new MessageSerializer(config)); - serializers.addSerializer(new DurationSerializer()); - serializers.addSerializer(new FieldMaskSerializer()); - serializers.addSerializer(new ListValueSerializer()); + serializers.addSerializer(new DurationSerializer(config)); + serializers.addSerializer(new FieldMaskSerializer(config)); + serializers.addSerializer(new ListValueSerializer(config)); serializers.addSerializer(new NullValueSerializer()); - serializers.addSerializer(new StructSerializer()); - serializers.addSerializer(new TimestampSerializer()); - serializers.addSerializer(new ValueSerializer()); - serializers.addSerializer(new WrappedPrimitiveSerializer<>(DoubleValue.class)); - serializers.addSerializer(new WrappedPrimitiveSerializer<>(FloatValue.class)); - serializers.addSerializer(new WrappedPrimitiveSerializer<>(Int64Value.class)); - serializers.addSerializer(new WrappedPrimitiveSerializer<>(UInt64Value.class)); - serializers.addSerializer(new WrappedPrimitiveSerializer<>(Int32Value.class)); - serializers.addSerializer(new WrappedPrimitiveSerializer<>(UInt32Value.class)); - serializers.addSerializer(new WrappedPrimitiveSerializer<>(BoolValue.class)); - serializers.addSerializer(new WrappedPrimitiveSerializer<>(StringValue.class)); - serializers.addSerializer(new WrappedPrimitiveSerializer<>(BytesValue.class)); + serializers.addSerializer(new StructSerializer(config)); + serializers.addSerializer(new TimestampSerializer(config)); + serializers.addSerializer(new ValueSerializer(config)); + serializers.addSerializer(new WrappedPrimitiveSerializer<>(DoubleValue.class, config)); + serializers.addSerializer(new WrappedPrimitiveSerializer<>(FloatValue.class, config)); + serializers.addSerializer(new WrappedPrimitiveSerializer<>(Int64Value.class, config)); + serializers.addSerializer(new WrappedPrimitiveSerializer<>(UInt64Value.class, config)); + serializers.addSerializer(new WrappedPrimitiveSerializer<>(Int32Value.class, config)); + serializers.addSerializer(new WrappedPrimitiveSerializer<>(UInt32Value.class, config)); + serializers.addSerializer(new WrappedPrimitiveSerializer<>(BoolValue.class, config)); + serializers.addSerializer(new WrappedPrimitiveSerializer<>(StringValue.class, config)); + serializers.addSerializer(new WrappedPrimitiveSerializer<>(BytesValue.class, config)); context.addSerializers(serializers); diff --git a/src/main/java/com/hubspot/jackson/datatype/protobuf/ProtobufSerializer.java b/src/main/java/com/hubspot/jackson/datatype/protobuf/ProtobufSerializer.java index a0df340..a303963 100644 --- a/src/main/java/com/hubspot/jackson/datatype/protobuf/ProtobufSerializer.java +++ b/src/main/java/com/hubspot/jackson/datatype/protobuf/ProtobufSerializer.java @@ -25,9 +25,11 @@ public abstract class ProtobufSerializer extends Std private static final String NULL_VALUE_FULL_NAME = NullValue.getDescriptor().getFullName(); private final Map, JsonSerializer> serializerCache; + private final ProtobufJacksonConfig config; - public ProtobufSerializer(Class protobufType) { + public ProtobufSerializer(Class protobufType, ProtobufJacksonConfig config) { super(protobufType); + this.config = config; this.serializerCache = new ConcurrentHashMap<>(); } @@ -84,10 +86,16 @@ protected void writeValue( // special-case NullValue if (NULL_VALUE_FULL_NAME.equals(enumDescriptor.getType().getFullName())) { generator.writeNull(); - } else if (writeEnumsUsingIndex(serializerProvider)) { - generator.writeNumber(enumDescriptor.getNumber()); } else { - generator.writeString(enumDescriptor.getName()); + if (isUnknownEnumValue(enumDescriptor)) { + enumDescriptor = config.unknownEnumSerializationStrategy().handleUnknownEnumValue(enumDescriptor); + } + + if (writeEnumsUsingIndex(serializerProvider)) { + generator.writeNumber(enumDescriptor.getNumber()); + } else { + generator.writeString(enumDescriptor.getName()); + } } break; case BYTE_STRING: @@ -113,6 +121,10 @@ private static boolean writeEnumsUsingIndex(SerializerProvider config) { return config.isEnabled(SerializationFeature.WRITE_ENUMS_USING_INDEX); } + private static boolean isUnknownEnumValue(EnumValueDescriptor descriptor) { + return descriptor.getType().findValueByNumber(descriptor.getNumber()) == null; + } + private static IOException unrecognizedType(FieldDescriptor field, JsonGenerator generator) throws IOException { String error = format("Unrecognized java type '%s' for field %s", field.getJavaType(), field.getFullName()); throw new JsonGenerationException(error, generator); diff --git a/src/main/java/com/hubspot/jackson/datatype/protobuf/UnknownEnumSerializationStrategy.java b/src/main/java/com/hubspot/jackson/datatype/protobuf/UnknownEnumSerializationStrategy.java new file mode 100644 index 0000000..1581176 --- /dev/null +++ b/src/main/java/com/hubspot/jackson/datatype/protobuf/UnknownEnumSerializationStrategy.java @@ -0,0 +1,17 @@ +package com.hubspot.jackson.datatype.protobuf; + +import com.google.protobuf.Descriptors.EnumValueDescriptor; + +@FunctionalInterface +public interface UnknownEnumSerializationStrategy { + + EnumValueDescriptor handleUnknownEnumValue(EnumValueDescriptor descriptor); + + UnknownEnumSerializationStrategy SERIALIZE = descriptor -> descriptor; + + UnknownEnumSerializationStrategy FAIL = + descriptor -> { + throw new IllegalArgumentException( + "Unable to serialize an unknown enum value " + descriptor.getFullName()); + }; +} diff --git a/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/serializers/DurationSerializer.java b/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/serializers/DurationSerializer.java index 030f865..5a7167c 100644 --- a/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/serializers/DurationSerializer.java +++ b/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/serializers/DurationSerializer.java @@ -1,5 +1,6 @@ package com.hubspot.jackson.datatype.protobuf.builtin.serializers; +import com.hubspot.jackson.datatype.protobuf.ProtobufJacksonConfig; import java.io.IOException; import com.fasterxml.jackson.core.JsonGenerator; @@ -10,8 +11,8 @@ public class DurationSerializer extends ProtobufSerializer { - public DurationSerializer() { - super(Duration.class); + public DurationSerializer(ProtobufJacksonConfig config) { + super(Duration.class, config); } @Override diff --git a/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/serializers/FieldMaskSerializer.java b/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/serializers/FieldMaskSerializer.java index 08e74ec..c8b8066 100644 --- a/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/serializers/FieldMaskSerializer.java +++ b/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/serializers/FieldMaskSerializer.java @@ -1,5 +1,6 @@ package com.hubspot.jackson.datatype.protobuf.builtin.serializers; +import com.hubspot.jackson.datatype.protobuf.ProtobufJacksonConfig; import java.io.IOException; import com.fasterxml.jackson.core.JsonGenerator; @@ -10,8 +11,8 @@ public class FieldMaskSerializer extends ProtobufSerializer { - public FieldMaskSerializer() { - super(FieldMask.class); + public FieldMaskSerializer(ProtobufJacksonConfig config) { + super(FieldMask.class, config); } @Override diff --git a/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/serializers/ListValueSerializer.java b/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/serializers/ListValueSerializer.java index cbb18ba..259d4c2 100644 --- a/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/serializers/ListValueSerializer.java +++ b/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/serializers/ListValueSerializer.java @@ -1,5 +1,6 @@ package com.hubspot.jackson.datatype.protobuf.builtin.serializers; +import com.hubspot.jackson.datatype.protobuf.ProtobufJacksonConfig; import java.io.IOException; import com.fasterxml.jackson.core.JsonGenerator; @@ -12,8 +13,8 @@ public class ListValueSerializer extends ProtobufSerializer { private static final FieldDescriptor VALUES_FIELD = ListValue.getDescriptor().findFieldByName("values"); - public ListValueSerializer() { - super(ListValue.class); + public ListValueSerializer(ProtobufJacksonConfig config) { + super(ListValue.class, config); } @Override diff --git a/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/serializers/MessageSerializer.java b/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/serializers/MessageSerializer.java index 0e39a1f..a55f5ea 100644 --- a/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/serializers/MessageSerializer.java +++ b/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/serializers/MessageSerializer.java @@ -36,7 +36,7 @@ public MessageSerializer(ExtensionRegistryWrapper extensionRegistry) { } public MessageSerializer(ProtobufJacksonConfig config) { - super(MessageOrBuilder.class); + super(MessageOrBuilder.class, config); this.config = config; } diff --git a/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/serializers/StructSerializer.java b/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/serializers/StructSerializer.java index d61817c..dc4551b 100644 --- a/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/serializers/StructSerializer.java +++ b/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/serializers/StructSerializer.java @@ -1,5 +1,6 @@ package com.hubspot.jackson.datatype.protobuf.builtin.serializers; +import com.hubspot.jackson.datatype.protobuf.ProtobufJacksonConfig; import java.io.IOException; import com.fasterxml.jackson.core.JsonGenerator; @@ -11,8 +12,8 @@ public class StructSerializer extends ProtobufSerializer { private static final FieldDescriptor FIELDS_FIELD = Struct.getDescriptor().findFieldByName("fields"); - public StructSerializer() { - super(Struct.class); + public StructSerializer(ProtobufJacksonConfig config) { + super(Struct.class, config); } @Override diff --git a/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/serializers/TimestampSerializer.java b/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/serializers/TimestampSerializer.java index 4acd550..3430ce9 100644 --- a/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/serializers/TimestampSerializer.java +++ b/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/serializers/TimestampSerializer.java @@ -1,5 +1,6 @@ package com.hubspot.jackson.datatype.protobuf.builtin.serializers; +import com.hubspot.jackson.datatype.protobuf.ProtobufJacksonConfig; import java.io.IOException; import com.fasterxml.jackson.core.JsonGenerator; @@ -10,8 +11,8 @@ public class TimestampSerializer extends ProtobufSerializer { - public TimestampSerializer() { - super(Timestamp.class); + public TimestampSerializer(ProtobufJacksonConfig config) { + super(Timestamp.class, config); } @Override diff --git a/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/serializers/ValueSerializer.java b/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/serializers/ValueSerializer.java index d7c6b8e..d54f094 100644 --- a/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/serializers/ValueSerializer.java +++ b/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/serializers/ValueSerializer.java @@ -1,5 +1,6 @@ package com.hubspot.jackson.datatype.protobuf.builtin.serializers; +import com.hubspot.jackson.datatype.protobuf.ProtobufJacksonConfig; import java.io.IOException; import java.util.Map; import java.util.Map.Entry; @@ -12,8 +13,8 @@ public class ValueSerializer extends ProtobufSerializer { - public ValueSerializer() { - super(Value.class); + public ValueSerializer(ProtobufJacksonConfig config) { + super(Value.class, config); } @Override diff --git a/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/serializers/WrappedPrimitiveSerializer.java b/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/serializers/WrappedPrimitiveSerializer.java index f683e6c..cccf34d 100644 --- a/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/serializers/WrappedPrimitiveSerializer.java +++ b/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/serializers/WrappedPrimitiveSerializer.java @@ -1,5 +1,6 @@ package com.hubspot.jackson.datatype.protobuf.builtin.serializers; +import com.hubspot.jackson.datatype.protobuf.ProtobufJacksonConfig; import java.io.IOException; import com.fasterxml.jackson.core.JsonGenerator; @@ -10,8 +11,8 @@ public class WrappedPrimitiveSerializer extends ProtobufSerializer { - public WrappedPrimitiveSerializer(Class wrapperType) { - super(wrapperType); + public WrappedPrimitiveSerializer(Class wrapperType, ProtobufJacksonConfig config) { + super(wrapperType, config); } @Override diff --git a/src/test/java/com/hubspot/jackson/datatype/protobuf/proto3/EnumTest.java b/src/test/java/com/hubspot/jackson/datatype/protobuf/proto3/EnumTest.java new file mode 100644 index 0000000..5268be9 --- /dev/null +++ b/src/test/java/com/hubspot/jackson/datatype/protobuf/proto3/EnumTest.java @@ -0,0 +1,30 @@ +package com.hubspot.jackson.datatype.protobuf.proto3; + +import static com.hubspot.jackson.datatype.protobuf.UnknownEnumSerializationStrategy.FAIL; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.hubspot.jackson.datatype.protobuf.ProtobufJacksonConfig; +import com.hubspot.jackson.datatype.protobuf.ProtobufModule; +import com.hubspot.jackson.datatype.protobuf.util.TestProtobuf3.NestedProto3; +import org.junit.Test; + +public class EnumTest { + + @Test + public void itFailsToSerializeAnUnknownEnum() { + ProtobufJacksonConfig config = ProtobufJacksonConfig.builder() + .unknownEnumSerializationStrategy(FAIL) + .build(); + + ObjectMapper mapper = new ObjectMapper().registerModules(new ProtobufModule(config)); + + NestedProto3 message = NestedProto3.newBuilder() + .setEnumValue(42) + .build(); + + assertThatThrownBy(() -> mapper.writeValueAsString(message)) + .hasMessageContaining("Unable to serialize an unknown enum value") + .hasRootCauseInstanceOf(IllegalArgumentException.class); + } +}