Skip to content

Optional scalar fields still use zero-value sentinels (SPEC Optional Fields debt) #436

Description

@jeremy

Summary

SPEC.md's Optional Fields rule states:

Fields not listed in the required array of the OpenAPI schema must be
nullable or optional in the language's type system. Sentinel values (empty
string, 0, etc.) are not acceptable substitutes for absence.

The rich-text *_attachments follow-up (#433) fixed this for optional arrays
in Kotlin (List<T>? = null instead of = emptyList()), and a generated-output
guard now pins it. But optional non-array scalars still collapse to
zero-value sentinels
, violating the rule:

  • Kotlin emits optional non-nullable scalars with zero-value defaults:
    val completed: Boolean = false, val commentsCount: Int = 0,
    val byteSize: Long = 0L, val position: Int = 0. An absent optional boolean
    decodes to false, indistinguishable from a present false; an absent int → 0.
  • Go optional non-pointer scalars collapse absence on decode too: an
    absent field and an explicit zero both unmarshal to the zero value, so they are
    indistinguishable. Only Go pointer fields (*T) preserve the distinction.
    (omitempty additionally drops zero-values on re-encode.) SPEC.md §10.
  • Ruby / Swift decode faithfully but omit nil on re-encode (pre-existing encoder
    behavior, documented in SPEC.md §10).

Scope

Pre-existing, SDK-wide debt broader than the #433 array fix (which was
deliberately array-scoped). The Kotlin scalar case is the clearest static-type
violation: fixing it means optional scalars become Boolean? = null / Int? = null
in the generator — another API-breaking change, and a cascade across hand-written
code + tests similar to the array fix.

Why filed now

rubric-audit.json criterion 1B.4 ("Optional Fields") is set to pass: false
referencing this issue, because the criterion is not fully met while optional
scalars still use sentinels. This issue tracks closing that gap (or an explicit
decision to accept the scalar zero-value trade-off and document it as the encode-only
edge already is).

Acceptance

  • Optional scalar fields preserve absence in Kotlin's static types (and any other
    SDK found to collapse them on decode), or
  • A documented, agreed decision recording the residual encode-only collapses as
    acceptable, with 1B.4 updated to pass: true + that rationale.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions