wire: Adding special value UNRECOGNIZED to the enum type

Are there any plans for Wire to add an UNRECOGNIZED value to enums, similar to the functionality provided by the proto3 compiler for Java?

https://protobuf.dev/reference/java/java-generated/#enum

The protocol buffer compiler will generate a Java enum type called Foo with the same set of values. If you are using proto3, it also adds the special value UNRECOGNIZED to the enum type. 

The context for my query is that I’m working on a project where we’re considering switching to Wire for generating Kotlin code from our proto3 schema. However, I’ve noticed that not all enums in our existing proto schema include a special type in the first position (value 0). This feature of handling unknown enum values automatically is important for our project’s compatibility and robustness, and I’m interested in knowing if Wire plans to support something similar for Kotlin. If this feature is not planned, is there a possibility for me to implement it myself using some custom handler or workaround?

Thank you in advance

About this issue

  • Original URL
  • State: open
  • Created 7 months ago
  • Reactions: 1
  • Comments: 21 (11 by maintainers)

Most upvoted comments

I think I’m starting to understand what’s happening. Protoc does generate the UNRECOGNIZED for Proto3 only (not on proto2).

Why?

With the new no-label field in proto3, an enum cannot be null. It has to be of some value, defaulting to its identity value (0).

The $10 question is: you receive the value 2 when your enum only accepts 0 or 1, what value do you get when you read the value after decoding?

  • Wire sets it to its identity value: 0 (which I think is a bug)
  • Protoc sets it to its generated-under-the-hood UNRECOGNIZED constant: -1. It exposes at the same time two methods: one which returns the constant, the other one returns the value. Here, myEnum() would return UNRECOGNIZED and myEnumValue() returns 2.

So how about this?

  • We add an option to generate UNRECOGNIZED for proto3 enum fields, both java & kotlin.
    • Need to check edge cases:
      • ~what if -1 is already there?~ this cannot happen in proto3
      • what if UNRECOGNIZED already exists? looks like protoc crashes.
  • Now, we need to treat UNRECOGNIZED like a special value: It is only ever assigned when decoding, should not be used when encoding. Can be manually set to a message, etc.

I might have time to do that but that’ll be Feb 2024. @JurajBegovac if you’re willing to do it, why not? We could start on one language at first (the adapter code is shared anyway). I’ll be out for a few weeks so won’t be able to give feedback for a while.

@JurajBegovac We started work on it 2 weeks ago. Should be ready by the end of April, or early May at the latest. We update https://github.com/square/wire/issues/2725#issuecomment-1936321944 as we go

I guess only kotlin is fine but as you wish 😄

Summary of some digging:

  • opt-in option (default false) to kotlin {} blocks.
  • generate UNRECOGNIZED -1 only for proto3
  • throw at runtime (like protoc) if one sets it to -1.
  • On encoding, we don’t send -1 but the actual value.
  • Test roundtrip bytes + JSON between Wire and Protoc.
  • Do we need it on Java right now? @JurajBegovac what do you use?

Steps (PR separation):

  • Only touching KotlinGenerator, add an option defaulted to false which generates UNRECOGNIZED(-1) field for both constructor and builder. This only concerns proto3.
  • Update ProtoAdapter’s encoding/decoding to handle that properly. Decoding: if unknown, we set unrecognized. Encoding: if unknown, we read from unknownFields.
  • Build a method which would allow one to read the unrecognized constant’s real value. For JVM, that might be something like private fun <M : Message<M, B>, B : Message.Builder<M, B>, P : Enum<P>> Message<M, B>.unknownEnumTag(property: KProperty1<M, P?>): Int? { which at call site would look like myMessage.unknownEnumTag(MyMessage::myEnumGetter). I’d like to have the same signature for native and JS if possible.
  • Write interop tests with Protoc, including JSON, and confirm we match.
  • Update JavaGenerator to generate UNRECOGNIZED(-1) if an opt-in setting is true.
  • Add option to Wire’s Gradle plugin.
  • Add options to WireCompiler

It’s on my radar but other priorities right now. I don’t have a clear image yet as how to implement it. I don’t think the constant -1 makes sense with the current API offered by Wire.

Hi @oldergod,

I’ve forked the repository and submitted a pull request with my proposed solution to address the issue

https://github.com/JurajBegovac/wire/pull/1