0
# Enum Support
1
2
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.
3
4
## Capabilities
5
6
### WireEnum Interface
7
8
Interface that all generated enum types implement to provide their integer tag values.
9
10
```kotlin { .api }
11
/**
12
* Interface for generated enum values to help serialization and deserialization
13
*/
14
interface WireEnum {
15
/** The tag value of an enum constant */
16
val value: Int
17
}
18
```
19
20
**Usage Examples:**
21
22
```kotlin
23
// Generated enum implementing WireEnum
24
enum class Status(override val value: Int) : WireEnum {
25
UNKNOWN(0),
26
ACTIVE(1),
27
INACTIVE(2),
28
PENDING(3);
29
30
companion object {
31
fun fromValue(value: Int): Status? = when (value) {
32
0 -> UNKNOWN
33
1 -> ACTIVE
34
2 -> INACTIVE
35
3 -> PENDING
36
else -> null
37
}
38
}
39
}
40
41
// Usage
42
val status = Status.ACTIVE
43
println(status.value) // Prints: 1
44
45
// Wire format uses integer values
46
val encoded = StatusAdapter.encode(status) // Encodes as varint 1
47
val decoded = StatusAdapter.decode(encoded) // Decodes back to ACTIVE
48
```
49
50
### EnumAdapter Abstract Class
51
52
Abstract base class for enum adapters that converts enum values to and from their integer representations.
53
54
```kotlin { .api }
55
/**
56
* Abstract ProtoAdapter that converts enum values to and from integers
57
* @param E The enum type extending WireEnum
58
* @param type The Kotlin class of the enum
59
* @param syntax Proto syntax version (proto2 or proto3)
60
* @param identity Identity value for proto3 (typically the zero value)
61
*/
62
expect abstract class EnumAdapter<E : WireEnum> protected constructor(
63
type: KClass<E>,
64
syntax: Syntax,
65
identity: E?
66
) : ProtoAdapter<E> {
67
/** Size of encoded enum value (varint encoding) */
68
override fun encodedSize(value: E): Int
69
70
/** Encode enum as varint */
71
@Throws(IOException::class)
72
override fun encode(writer: ProtoWriter, value: E)
73
74
/** Encode enum as varint (reverse writer) */
75
@Throws(IOException::class)
76
override fun encode(writer: ReverseProtoWriter, value: E)
77
78
/** Decode varint to enum, throw exception for unknown values */
79
@Throws(IOException::class)
80
override fun decode(reader: ProtoReader): E
81
82
/** Return redacted enum (throws UnsupportedOperationException) */
83
override fun redact(value: E): E
84
85
/**
86
* Convert integer to enum value
87
* @param value Integer value from wire format
88
* @return Enum constant or null if unknown
89
*/
90
protected abstract fun fromValue(value: Int): E?
91
}
92
```
93
94
**Implementation Examples:**
95
96
```kotlin
97
// Generated enum adapter
98
object StatusAdapter : EnumAdapter<Status>(
99
Status::class,
100
Syntax.PROTO_2,
101
Status.UNKNOWN
102
) {
103
override fun fromValue(value: Int): Status? = Status.fromValue(value)
104
}
105
106
// Usage with messages
107
class UserMessage(
108
val name: String,
109
val status: Status,
110
unknownFields: ByteString = ByteString.EMPTY
111
) : Message<UserMessage, UserMessage.Builder>(ADAPTER, unknownFields) {
112
113
companion object {
114
@JvmField
115
val ADAPTER = object : ProtoAdapter<UserMessage>(
116
FieldEncoding.LENGTH_DELIMITED,
117
UserMessage::class,
118
null,
119
Syntax.PROTO_2
120
) {
121
override fun encodedSize(value: UserMessage): Int {
122
return ProtoAdapter.STRING.encodedSizeWithTag(1, value.name) +
123
StatusAdapter.encodedSizeWithTag(2, value.status)
124
}
125
126
override fun encode(writer: ProtoWriter, value: UserMessage) {
127
ProtoAdapter.STRING.encodeWithTag(writer, 1, value.name)
128
StatusAdapter.encodeWithTag(writer, 2, value.status)
129
}
130
131
override fun decode(reader: ProtoReader): UserMessage {
132
var name = ""
133
var status = Status.UNKNOWN
134
135
reader.forEachTag { tag ->
136
when (tag) {
137
1 -> name = ProtoAdapter.STRING.decode(reader)
138
2 -> status = StatusAdapter.decode(reader)
139
else -> reader.readUnknownField(tag)
140
}
141
}
142
143
return UserMessage(name, status)
144
}
145
146
override fun redact(value: UserMessage) = value
147
}
148
}
149
}
150
```
151
152
### Unknown Enum Value Handling
153
154
Wire handles unknown enum values by throwing `EnumConstantNotFoundException` for type safety.
155
156
```kotlin { .api }
157
// Exception for unknown enum values
158
class ProtoAdapter.EnumConstantNotFoundException(
159
value: Int,
160
type: KClass<*>?
161
) : IllegalArgumentException {
162
@JvmField
163
val value: Int
164
}
165
```
166
167
**Handling Unknown Enum Values:**
168
169
```kotlin
170
import com.squareup.wire.ProtoAdapter.EnumConstantNotFoundException
171
172
// Reading message with potentially unknown enum values
173
try {
174
val message = UserMessage.ADAPTER.decode(wireData)
175
println("Status: ${message.status}")
176
} catch (e: EnumConstantNotFoundException) {
177
println("Unknown enum value: ${e.value}")
178
// Handle unknown enum gracefully
179
// Option 1: Use default value
180
val defaultMessage = UserMessage("Unknown User", Status.UNKNOWN)
181
182
// Option 2: Skip processing this message
183
return
184
185
// Option 3: Store unknown value for later processing
186
// (requires custom adapter implementation)
187
}
188
189
// Custom adapter that preserves unknown enum values
190
object SafeStatusAdapter : ProtoAdapter<Status?>(
191
FieldEncoding.VARINT,
192
Status::class,
193
null,
194
Syntax.PROTO_2,
195
null
196
) {
197
override fun encodedSize(value: Status?): Int {
198
return if (value != null) {
199
ProtoWriter.varint32Size(value.value)
200
} else 0
201
}
202
203
override fun encode(writer: ProtoWriter, value: Status?) {
204
if (value != null) {
205
writer.writeVarint32(value.value)
206
}
207
}
208
209
override fun decode(reader: ProtoReader): Status? {
210
val intValue = reader.readVarint32()
211
return Status.fromValue(intValue) // Returns null for unknown values
212
}
213
214
override fun redact(value: Status?): Status? = throw UnsupportedOperationException()
215
}
216
```
217
218
### Proto2 vs Proto3 Enum Behavior
219
220
Enum handling differs between proto2 and proto3 syntax:
221
222
```kotlin
223
// Proto2 enum (closed set, unknown values cause exceptions)
224
enum class Proto2Status(override val value: Int) : WireEnum {
225
UNKNOWN(0),
226
ACTIVE(1),
227
INACTIVE(2);
228
229
companion object {
230
val ADAPTER = object : EnumAdapter<Proto2Status>(
231
Proto2Status::class,
232
Syntax.PROTO_2,
233
UNKNOWN
234
) {
235
override fun fromValue(value: Int): Proto2Status? = when (value) {
236
0 -> UNKNOWN
237
1 -> ACTIVE
238
2 -> INACTIVE
239
else -> null // Unknown values not allowed in proto2
240
}
241
}
242
}
243
}
244
245
// Proto3 enum (open set, identity value omitted)
246
enum class Proto3Status(override val value: Int) : WireEnum {
247
UNSPECIFIED(0), // Proto3 requires zero value
248
ACTIVE(1),
249
INACTIVE(2);
250
251
companion object {
252
val ADAPTER = object : EnumAdapter<Proto3Status>(
253
Proto3Status::class,
254
Syntax.PROTO_3,
255
UNSPECIFIED // Identity value omitted when encoding
256
) {
257
override fun fromValue(value: Int): Proto3Status? = when (value) {
258
0 -> UNSPECIFIED
259
1 -> ACTIVE
260
2 -> INACTIVE
261
else -> null
262
}
263
}
264
}
265
}
266
267
// Proto3 identity handling
268
val message = SomeMessage.Builder()
269
.status(Proto3Status.UNSPECIFIED) // This won't be encoded (identity value)
270
.build()
271
272
val encoded = message.encode() // status field not present in wire format
273
val decoded = SomeMessage.ADAPTER.decode(encoded) // status defaults to UNSPECIFIED
274
```
275
276
### Enum Annotations
277
278
Wire supports annotations for enum constants to control code generation:
279
280
```kotlin { .api }
281
/**
282
* Annotation for enum constants
283
*/
284
@Target(AnnotationTarget.FIELD)
285
@Retention(AnnotationRetention.RUNTIME)
286
annotation class WireEnumConstant(
287
/** Override the constant name in generated code */
288
val declaredName: String = ""
289
)
290
```
291
292
### Performance Characteristics
293
294
- **Encoding**: Enums are encoded as varints, making small values (0-127) very efficient
295
- **Decoding**: Direct integer-to-enum mapping with O(1) lookups
296
- **Memory**: No boxing overhead for enum values
297
- **Type Safety**: Compile-time checking prevents invalid enum assignments
298
299
### Best Practices
300
301
1. **Always include a zero value** for proto3 enums (required by spec)
302
2. **Use meaningful names** for enum constants that describe their purpose
303
3. **Handle unknown values gracefully** in production code
304
4. **Don't rely on enum ordinal values** - use the `value` property instead
305
5. **Consider backwards compatibility** when adding new enum values
306
6. **Use consistent naming** between proto file and generated Kotlin enums
307
308
### Cross-Platform Consistency
309
310
Wire ensures enum behavior is consistent across all supported platforms:
311
312
- **JVM**: Full enum class support with proper `toString()` and comparison
313
- **JavaScript**: Enum-like objects with integer values
314
- **Native**: Efficient enum representations for each target platform
315
- **Wire Format**: Identical binary encoding across all platforms