When

annotation class When(val predicate: String)

Conditional field: only present on the wire when the predicate holds. The field must be nullable. Setting = null as the constructor default is conventional (so the data class can be constructed without naming the field when the predicate is false) but is not enforced — KSP cannot inspect default expression trees, so any rule the validator can't actually check is not part of the contract.

Grammar

The predicate language is deliberately narrow: the validator parses literal forms only — no && / ||, no !=, no field-to-field comparisons, no method calls. If a use case doesn't fit, model it with @UseCodec and a custom codec instead. Two forms are accepted:

1. Dotted Boolean path on a prior sibling

"<siblingField>" resolves to a sibling Boolean constructor parameter declared before the bound parameter; "<siblingField>.<property>" resolves to a Boolean- returning val on a sibling @JvmInline value class.

@ProtocolMessage
data class OptionalPayload(
val hasExtra: Boolean,
@When("hasExtra") val extra: Int? = null,
)

@When("flags.willFlag") val willTopic: String? = null

2. remaining <op> <int-literal> (reserved — not yet implemented)

"remaining <op> <int>" where <op> ∈ {>=, >, ==} gates the slot on the bounded decode buffer's remaining(). The identifier remaining is reserved/magic and does not refer to a sibling field. Reserved for a future release; until then, this grammar is documented but not parsed — using it today produces the standard "sibling not found" diagnostic.

Compound conditions: use a value-class getter

The grammar is intentionally minimal — no && / || / cross-field comparisons. For predicates that combine multiple flags, model the combined condition as a val property on a sibling @JvmInline value class and reference it through grammar 1's dotted form. The property is plain Kotlin: any expression that returns Boolean is fair game (bit tests on the inner scalar, comparisons against constants, two-flag conjunctions, …).

Example — a keyId field present only when a frame is both encrypted and carries a header extension:

@JvmInline
@ProtocolMessage
value class FrameFlags(val raw: UByte) {
val encrypted: Boolean get() = (raw.toInt() and 0x01) != 0
val hasHeaderExtension: Boolean get() = (raw.toInt() and 0x02) != 0
// Compound predicate composed in Kotlin, exposed as a single Boolean val:
val carriesKeyId: Boolean get() = encrypted && hasHeaderExtension
}

@ProtocolMessage
data class SecureFrame(
val flags: FrameFlags,
@When("flags.carriesKeyId") val keyId: UInt? = null,
// … rest of the frame
)

The validator only sees flags.carriesKeyId returning Boolean — the combined logic stays in code where it's testable and refactorable, and the predicate language stays narrow enough to keep the validator's diagnostics actionable.

Semantics

Encoder semantics: when the predicate is false, the entire slot is skipped on the wire (zero bytes written, including any @LengthPrefixed prefix). When the predicate is true and the field's value is null, encode throws EncodeException with field-path attribution.

Parameters

predicate

Grammar 1 ("siblingField" or "siblingField.property") today; grammar 2 ("remaining <op> <int>") reserved for a future release.

Properties

Link copied to clipboard