Skip to content

fix(api): decode unknown server enum values to UNKNOWN everywhere - #1027

Merged
swolfand merged 2 commits into
mainfrom
mike/unknown-enum-fallback
Oct 6, 2026
Merged

swolfand merged 2 commits into
mainfrom
mike/unknown-enum-fallback

Conversation

@swolfand

@swolfand swolfand commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

Why

Architecture review finding #11 says "Unknown server enums are handled five different ways." Before this PR they were:

  1. coerceInputValues plus a property default (most models).
  2. Hand-written fromSerializedValue surrogates (SignIn.Status, BiometricCredential.Platform and BiometricCredential.Status).
  3. SessionTaskKey.fromRaw.
  4. A nullable property that defaults to null.
  5. No fallback at all. OrganizationInvitation.Status had no UNKNOWN entry and no default, so a new invitation status from the server failed the whole invitations response. This is the same client-decode failure class fix(api): keep response body when client sync fails #998 dealt with.

Path 1 only works through ClerkApi.json and only for properties that have a default. Standalone decoding, lists, and any other Json instance still throw.

This is PR 2 of 3 for #11 and is stacked on #1025 (typed Strategy).

Scope

  • New UnknownFallbackEnumSerializer (internal). It is the documented single policy: an enum decoded from Clerk API responses maps any unrecognized wire value to its UNKNOWN entry, whatever the Json configuration or property default.
  • Applied to all 21 server enums:
    • SignIn.Status, SignUp.Status, Verification.Status, InstanceEnvironmentType, PreferredSignInStrategy
    • BiometricCredential.Platform, BiometricCredential.Status
    • Session.SessionStatus, SessionVerification.Status, SessionVerification.Level
    • OrganizationInvitation.Status, which gains an Unknown entry
    • all 10 billing enums
  • Left alone on purpose, because these values never come from the API:
    • BiometricCredentialPolicy (local store), ForPayerType (request parameter only), OrganizationSystemPermission (never decoded), SharedSessionSyncSnapshot.State (app-to-app payload).
    • SDK-side enums: CredentialType, ResultType, MfaType, VerificationType, IdTokenProvider, HostedAuthMode.
    • SessionTaskKey already falls back to UNKNOWN from a raw string.
  • The UNKNOWN entries of SignIn.Status, BiometricCredential.Platform and BiometricCredential.Status get @SerialName("unknown"), so they encode as "unknown" (matching their serializedValue) instead of "UNKNOWN".
  • Organization.getInvitations(status = OrganizationInvitation.Status.Unknown) returns a local ClerkResult failure (invitation_status_invalid) without calling Clerk. Unknown only exists for decoding, and sending "unknown" as a filter would be meaningless.
  • The existing fromSerializedValue surrogates stay. They also keep the raw value for round-tripping, which is a separate concern.
  • UnknownFallbackEnumSerializerTest:
    • Every server enum decodes "brand_new_value_from_the_server" to its fallback, both with a plain Json (no coerceInputValues) and with ClerkApi.json.
    • Every entry round-trips its exact wire name, including every UNKNOWN entry and PreferredSignInStrategy.
    • Lists and nullable positions fall back.
    • An OrganizationInvitation with an unrecognized status now decodes. Before this change it threw.
  • OrganizationGetInvitationsTest: getInvitations(status = Unknown) fails locally and never touches the API.

Serial names. The review notes that 228 of 246 @SerialName annotations repeat the snake_case naming. I did not remove them. JsonNamingStrategy.SnakeCase is set only on ClerkApi.json. Other Json instances that decode the same models have no naming strategy, and so would silently stop decoding renamed fields: BiometricCredentialLegacyStore, the native magic-link store, and test JSON. Removing the annotations safely needs a per-model fixture round-trip suite first. That belongs in its own PR and is deferred.

Blast Radius

  • Decoding: responses carrying a new enum value now decode to UNKNOWN instead of throwing, in every position and with every Json instance. Known values encode and decode exactly as before; the round-trip test covers every entry.
  • Source compatibility: OrganizationInvitation.Status gains an Unknown entry. A consumer's exhaustive when expression over that enum needs an extra branch. That is the price of not failing the whole response. All other enums already had UNKNOWN.
  • Public surface: each of these enums' companions gets a plugin-generated generatedSerializer(). Opting into @KeepGeneratedSerializer (an experimental API) stays inside the SDK through @OptIn.
  • Expected conflict with add renovate.json #6 (explicit API), which touches the same enum declarations.

Testing

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • API responses containing unrecognized status or type values now decode safely as UNKNOWN, rather than failing.
    • Requesting organization invitations with an unknown status now returns a clear error without making an API call. Other supported status filters remain available.

@coderabbitai

coderabbitai Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: clerk/clerk-android/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Team
  • Run ID: 25db67f2-e64b-485d-953b-6b72ada1c2a3
📥 Commits

Reviewing files that changed from the base of the PR and between f8d04f5 and 8ced4dc.

📒 Files selected for processing (19)
  • source/api/src/main/kotlin/com/clerk/api/billing/BillingMoneyAmount.kt
  • source/api/src/main/kotlin/com/clerk/api/billing/BillingPayment.kt
  • source/api/src/main/kotlin/com/clerk/api/billing/BillingPaymentMethod.kt
  • source/api/src/main/kotlin/com/clerk/api/billing/BillingPlan.kt
  • source/api/src/main/kotlin/com/clerk/api/billing/BillingStatement.kt
  • source/api/src/main/kotlin/com/clerk/api/billing/BillingSubscription.kt
  • source/api/src/main/kotlin/com/clerk/api/biometriccredential/BiometricCredential.kt
  • source/api/src/main/kotlin/com/clerk/api/network/model/environment/DisplayConfig.kt
  • source/api/src/main/kotlin/com/clerk/api/network/model/environment/InstanceEnvironmentType.kt
  • source/api/src/main/kotlin/com/clerk/api/network/model/verification/Verification.kt
  • source/api/src/main/kotlin/com/clerk/api/network/serialization/UnknownFallbackEnumSerializer.kt
  • source/api/src/main/kotlin/com/clerk/api/organizations/Organization.kt
  • source/api/src/main/kotlin/com/clerk/api/organizations/OrganizationInvitation.kt
  • source/api/src/main/kotlin/com/clerk/api/session/Session.kt
  • source/api/src/main/kotlin/com/clerk/api/session/SessionVerification.kt
  • source/api/src/main/kotlin/com/clerk/api/signin/SignIn.kt
  • source/api/src/main/kotlin/com/clerk/api/signup/SignUp.kt
  • source/api/src/test/java/com/clerk/api/network/serialization/UnknownFallbackEnumSerializerTest.kt
  • source/api/src/test/java/com/clerk/api/organizations/OrganizationGetInvitationsTest.kt

Included review availability: This review used your included allowance. 0 included reviews remain after this review. Your included PR review attempts over the past 7 days set your current allowance at 5 reviews per hour.


📝 Walkthrough

Walkthrough

API enum deserialization now maps unrecognized values to UNKNOWN fallbacks. Organization invitations add a decode-only Unknown status, and getInvitations returns a local validation failure when that status is used as a filter.

Changes

Unknown enum handling

Layer / File(s) Summary
Fallback serializer and enum adoption
source/api/src/main/kotlin/com/clerk/api/network/serialization/UnknownFallbackEnumSerializer.kt, source/api/src/main/kotlin/com/clerk/api/billing/*, source/api/src/main/kotlin/com/clerk/api/biometriccredential/*, source/api/src/main/kotlin/com/clerk/api/network/model/environment/*, source/api/src/main/kotlin/com/clerk/api/network/model/verification/*, source/api/src/main/kotlin/com/clerk/api/session/*, source/api/src/main/kotlin/com/clerk/api/signin/*, source/api/src/main/kotlin/com/clerk/api/signup/*, source/api/src/main/kotlin/com/clerk/api/organizations/OrganizationInvitation.kt, source/api/src/test/java/com/clerk/api/network/serialization/UnknownFallbackEnumSerializerTest.kt
Adds a serializer that encodes known descriptor names and returns the configured fallback for unknown names. API enums use this serializer while retaining their known serialized values. Tests cover unknown-value fallback, wire-name round trips, list and nullable values, and invitation decoding.
Unknown invitation status handling
source/api/src/main/kotlin/com/clerk/api/organizations/Organization.kt, source/api/src/test/java/com/clerk/api/organizations/OrganizationGetInvitationsTest.kt
getInvitations returns an invitation_status_invalid failure for Status.Unknown without accessing Clerk. The test checks the failure and confirms the Clerk API is not accessed.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix

Suggested reviewers: mikepitre

Merge Risk: ⚪ Minimal · up to 8ced4

The supplied evidence identifies no issue that needs resolution before merging.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 13 functions across 19 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: unknown server enum values now decode to UNKNOWN.
Description check ✅ Passed The description explains why and what changed, defines the scope and compatibility impact, and reports testing. It does not use the template’s exact headings and omits a dedicated “What to focus on” s…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@swolfand
swolfand force-pushed the mike/typed-strategies branch from f8cee6d to 8c146e8 Compare October 5, 2026 23:35
@swolfand
swolfand force-pushed the mike/unknown-enum-fallback branch from 3e2b563 to feed29c Compare October 5, 2026 23:43
@swolfand
swolfand force-pushed the mike/typed-strategies branch from 8c146e8 to 3c38a6f Compare October 6, 2026 18:01
@swolfand
swolfand force-pushed the mike/unknown-enum-fallback branch 2 times, most recently from f73037c to e253b2c Compare October 6, 2026 19:23
@swolfand
swolfand force-pushed the mike/typed-strategies branch 2 times, most recently from 9279665 to 851854a Compare October 6, 2026 22:57
Base automatically changed from mike/typed-strategies to main October 6, 2026 23:08
Server enums handled unrecognized values five different ways:
coerceInputValues with a property default, hand-written fromSerializedValue
surrogates, SessionTaskKey.fromRaw, nullable defaults, and no fallback at
all (OrganizationInvitation.Status failed the whole response).

Add UnknownFallbackEnumSerializer as the single policy: every enum decoded
from Clerk API responses keeps its plugin-generated serializer
(@KeepGeneratedSerializer, so @SerialName stays the only source of wire
names) and maps any unrecognized value to its UNKNOWN entry, independent of
the Json configuration or property defaults. This now also covers
PreferredSignInStrategy, and the UNKNOWN entries of SignIn.Status and
BiometricCredential.Platform/Status get @SerialName("unknown") so they
round-trip.

OrganizationInvitation.Status gains an Unknown entry for this. It only
exists for decoding, so Organization.getInvitations(status = Unknown)
returns a local failure instead of sending "unknown" as a filter.
@swolfand
swolfand force-pushed the mike/unknown-enum-fallback branch from e253b2c to 8ced4dc Compare October 6, 2026 23:15
@swolfand
swolfand merged commit 49d297c into main Oct 6, 2026
9 checks passed
@swolfand
swolfand deleted the mike/unknown-enum-fallback branch October 6, 2026 23:37
swolfand added a commit that referenced this pull request Oct 6, 2026
)

ClerkTelemetryEnvironment looked the wire name up with
InstanceEnvironmentType.serializer().descriptor.getElementName(ordinal).
Since #1027 that serializer is the unknown-fallback serializer, whose
descriptor is a primitive string with no elements, so the lookup threw
IllegalStateException whenever telemetry read the instance type.
Encode the value through the serializer instead, which works for any
serializer shape.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant