CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-squareup-wire--wire-runtime

Runtime support library for Wire-generated Protocol Buffer classes in Kotlin multiplatform applications

Pending
Overview
Eval results
Files

enum-support.mddocs/

Enum Support

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.

Capabilities

WireEnum Interface

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 ACTIVE

EnumAdapter Abstract Class

Abstract 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
        }
    }
}

Unknown Enum Value Handling

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()
}

Proto2 vs Proto3 Enum Behavior

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 UNSPECIFIED

Enum Annotations

Wire 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 = ""
)

Performance Characteristics

  • Encoding: Enums are encoded as varints, making small values (0-127) very efficient
  • Decoding: Direct integer-to-enum mapping with O(1) lookups
  • Memory: No boxing overhead for enum values
  • Type Safety: Compile-time checking prevents invalid enum assignments

Best Practices

  1. Always include a zero value for proto3 enums (required by spec)
  2. Use meaningful names for enum constants that describe their purpose
  3. Handle unknown values gracefully in production code
  4. Don't rely on enum ordinal values - use the value property instead
  5. Consider backwards compatibility when adding new enum values
  6. Use consistent naming between proto file and generated Kotlin enums

Cross-Platform Consistency

Wire ensures enum behavior is consistent across all supported platforms:

  • JVM: Full enum class support with proper toString() and comparison
  • JavaScript: Enum-like objects with integer values
  • Native: Efficient enum representations for each target platform
  • Wire Format: Identical binary encoding across all platforms

Install with Tessl CLI

npx tessl i tessl/maven-com-squareup-wire--wire-runtime

docs

any-message.md

enum-support.md

field-annotations.md

index.md

message-framework.md

proto-adapters.md

protobuf-io.md

time-types.md

tile.json