Runtime support library for Wire-generated Protocol Buffer classes in Kotlin multiplatform applications
—
Support for protocol buffer enums with proper value mapping, unknown value handling, and cross-platform compatibility. Wire's enum system provides type-safe enum constants while maintaining protobuf wire compatibility.
Interface that all generated enum types implement to provide their integer tag values.
/**
* Interface for generated enum values to help serialization and deserialization
*/
interface WireEnum {
/** The tag value of an enum constant */
val value: Int
}Usage Examples:
// Generated enum implementing WireEnum
enum class Status(override val value: Int) : WireEnum {
UNKNOWN(0),
ACTIVE(1),
INACTIVE(2),
PENDING(3);
companion object {
fun fromValue(value: Int): Status? = when (value) {
0 -> UNKNOWN
1 -> ACTIVE
2 -> INACTIVE
3 -> PENDING
else -> null
}
}
}
// Usage
val status = Status.ACTIVE
println(status.value) // Prints: 1
// Wire format uses integer values
val encoded = StatusAdapter.encode(status) // Encodes as varint 1
val decoded = StatusAdapter.decode(encoded) // Decodes back to ACTIVEAbstract base class for enum adapters that converts enum values to and from their integer representations.
/**
* Abstract ProtoAdapter that converts enum values to and from integers
* @param E The enum type extending WireEnum
* @param type The Kotlin class of the enum
* @param syntax Proto syntax version (proto2 or proto3)
* @param identity Identity value for proto3 (typically the zero value)
*/
expect abstract class EnumAdapter<E : WireEnum> protected constructor(
type: KClass<E>,
syntax: Syntax,
identity: E?
) : ProtoAdapter<E> {
/** Size of encoded enum value (varint encoding) */
override fun encodedSize(value: E): Int
/** Encode enum as varint */
@Throws(IOException::class)
override fun encode(writer: ProtoWriter, value: E)
/** Encode enum as varint (reverse writer) */
@Throws(IOException::class)
override fun encode(writer: ReverseProtoWriter, value: E)
/** Decode varint to enum, throw exception for unknown values */
@Throws(IOException::class)
override fun decode(reader: ProtoReader): E
/** Return redacted enum (throws UnsupportedOperationException) */
override fun redact(value: E): E
/**
* Convert integer to enum value
* @param value Integer value from wire format
* @return Enum constant or null if unknown
*/
protected abstract fun fromValue(value: Int): E?
}Implementation Examples:
// Generated enum adapter
object StatusAdapter : EnumAdapter<Status>(
Status::class,
Syntax.PROTO_2,
Status.UNKNOWN
) {
override fun fromValue(value: Int): Status? = Status.fromValue(value)
}
// Usage with messages
class UserMessage(
val name: String,
val status: Status,
unknownFields: ByteString = ByteString.EMPTY
) : Message<UserMessage, UserMessage.Builder>(ADAPTER, unknownFields) {
companion object {
@JvmField
val ADAPTER = object : ProtoAdapter<UserMessage>(
FieldEncoding.LENGTH_DELIMITED,
UserMessage::class,
null,
Syntax.PROTO_2
) {
override fun encodedSize(value: UserMessage): Int {
return ProtoAdapter.STRING.encodedSizeWithTag(1, value.name) +
StatusAdapter.encodedSizeWithTag(2, value.status)
}
override fun encode(writer: ProtoWriter, value: UserMessage) {
ProtoAdapter.STRING.encodeWithTag(writer, 1, value.name)
StatusAdapter.encodeWithTag(writer, 2, value.status)
}
override fun decode(reader: ProtoReader): UserMessage {
var name = ""
var status = Status.UNKNOWN
reader.forEachTag { tag ->
when (tag) {
1 -> name = ProtoAdapter.STRING.decode(reader)
2 -> status = StatusAdapter.decode(reader)
else -> reader.readUnknownField(tag)
}
}
return UserMessage(name, status)
}
override fun redact(value: UserMessage) = value
}
}
}Wire handles unknown enum values by throwing EnumConstantNotFoundException for type safety.
// Exception for unknown enum values
class ProtoAdapter.EnumConstantNotFoundException(
value: Int,
type: KClass<*>?
) : IllegalArgumentException {
@JvmField
val value: Int
}Handling Unknown Enum Values:
import com.squareup.wire.ProtoAdapter.EnumConstantNotFoundException
// Reading message with potentially unknown enum values
try {
val message = UserMessage.ADAPTER.decode(wireData)
println("Status: ${message.status}")
} catch (e: EnumConstantNotFoundException) {
println("Unknown enum value: ${e.value}")
// Handle unknown enum gracefully
// Option 1: Use default value
val defaultMessage = UserMessage("Unknown User", Status.UNKNOWN)
// Option 2: Skip processing this message
return
// Option 3: Store unknown value for later processing
// (requires custom adapter implementation)
}
// Custom adapter that preserves unknown enum values
object SafeStatusAdapter : ProtoAdapter<Status?>(
FieldEncoding.VARINT,
Status::class,
null,
Syntax.PROTO_2,
null
) {
override fun encodedSize(value: Status?): Int {
return if (value != null) {
ProtoWriter.varint32Size(value.value)
} else 0
}
override fun encode(writer: ProtoWriter, value: Status?) {
if (value != null) {
writer.writeVarint32(value.value)
}
}
override fun decode(reader: ProtoReader): Status? {
val intValue = reader.readVarint32()
return Status.fromValue(intValue) // Returns null for unknown values
}
override fun redact(value: Status?): Status? = throw UnsupportedOperationException()
}Enum handling differs between proto2 and proto3 syntax:
// Proto2 enum (closed set, unknown values cause exceptions)
enum class Proto2Status(override val value: Int) : WireEnum {
UNKNOWN(0),
ACTIVE(1),
INACTIVE(2);
companion object {
val ADAPTER = object : EnumAdapter<Proto2Status>(
Proto2Status::class,
Syntax.PROTO_2,
UNKNOWN
) {
override fun fromValue(value: Int): Proto2Status? = when (value) {
0 -> UNKNOWN
1 -> ACTIVE
2 -> INACTIVE
else -> null // Unknown values not allowed in proto2
}
}
}
}
// Proto3 enum (open set, identity value omitted)
enum class Proto3Status(override val value: Int) : WireEnum {
UNSPECIFIED(0), // Proto3 requires zero value
ACTIVE(1),
INACTIVE(2);
companion object {
val ADAPTER = object : EnumAdapter<Proto3Status>(
Proto3Status::class,
Syntax.PROTO_3,
UNSPECIFIED // Identity value omitted when encoding
) {
override fun fromValue(value: Int): Proto3Status? = when (value) {
0 -> UNSPECIFIED
1 -> ACTIVE
2 -> INACTIVE
else -> null
}
}
}
}
// Proto3 identity handling
val message = SomeMessage.Builder()
.status(Proto3Status.UNSPECIFIED) // This won't be encoded (identity value)
.build()
val encoded = message.encode() // status field not present in wire format
val decoded = SomeMessage.ADAPTER.decode(encoded) // status defaults to UNSPECIFIEDWire supports annotations for enum constants to control code generation:
/**
* Annotation for enum constants
*/
@Target(AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
annotation class WireEnumConstant(
/** Override the constant name in generated code */
val declaredName: String = ""
)value property insteadWire ensures enum behavior is consistent across all supported platforms:
toString() and comparisonInstall with Tessl CLI
npx tessl i tessl/maven-com-squareup-wire--wire-runtime