Skip to content

SDK decoders still fail closed on values IAPKit can add #330

Description

@hyochan

#321 fixed six SDK parsers that threw on an environment or clientPayload.format
value IAPKit can legitimately add, and documented the rule at /docs/kit-compatibility:
optional metadata degrades, it never fails a purchase the store already confirmed.

It fixed them by hand, one parser at a time. Two surfaces still fail closed, and
both are places the next contributor would land on by default rather than by mistake.

Suggested order: §1 first — it removes the failure mode from the generator, so no
future SDK has to remember the rule.


1. Generated decoders throw on an unknown enum value

The hand-written parsers degrade; the generated ones do not. Nothing routes through
them from IAPKit-controlled data today — #321 mapped around them — so this is latent,
not live. It stops being latent the moment someone decodes a response with the
generated type, which is the obvious thing to reach for.

Two changes, neither of which adds an enum member, so neither breaks an exhaustive
when / switch in consumer code:

a. Degrade to the neutral member when the enum already declares one.
IapkitPurchaseState has UNKNOWN and IapStore has Unknown, yet the generated
fromJson throws anyway — packages/gql/codegen/plugins/kotlin.ts:230,
dart.ts:187, csharp.ts:322. Return the neutral member instead, and keep the throw
only for enums that have none.

b. Drop an optional object whose required enum field cannot decode.
clientPayload is optional but its format is a required IapkitClientPayloadFormat,
which has no neutral member. Emit the owner-field decode so an unreadable format
yields null for the whole payload — exactly what the hand-written parsers already do.
With (b), IapkitClientPayloadFormat does not need an Unknown member at all.

Known wrong today:

  • libraries/flutter_inapp_purchase/lib/types.dart — throws ArgumentError for
    state, store, and client-payload format.
  • libraries/godot-iap/addons/godot-iap/types.gd — assigns the raw string into an
    enum-typed variable, a GDScript runtime type error.
  • packages/google/.../Types.kt and the kmp mirror — a missing or non-string
    format silently defaults to Toml, so a JSON body would be parsed as TOML.
  • Add a per-language case asserting an unknown value degrades instead of throwing.

Scope: the Kotlin, Dart, C#, GDScript and TypeScript plugins under
packages/gql/codegen/plugins/, then bun run generate.


2. One unknown platform empties the whole MAUI KitApi response

KitSubscription.Platform (IapPlatform) and KitProduct.Platform
(KitProductPlatform) are the only wire fields on the KitApi records typed as enums.
Everything else IAPKit could extend — State, Type, BillingPeriod, Kind,
Format — is already string.

Both converters throw on an unrecognised value: IapPlatformJsonConverter.Read in
libraries/maui-iap/src/OpenIap.Maui/Types.cs ends in throw new JsonException(...)
with no Unknown member to land on, and KitProductPlatform uses the stock
JsonStringEnumConverter. Because the body is decoded in one
parsed.Deserialize<T>() in KitApi.cs, a single unknown platform empties the entire
entitlements or products response rather than dropping one row.

IAPKit deploys from main and its verify surface already carries horizon and
amazon, so widening platform is plausible.

  • Decide the shape. The clean fix changes a public property type
    (IapPlatform → string, matching State on the same record) in a released
    package. The non-breaking alternative is a KitApi-local tolerant converter.
  • Decode subscriptions, products and offers element by element so one
    unreadable row cannot empty the array.
  • Types.cs is generated — fix in KitApi.cs or the codegen plugin, never by hand.

Context: #321, and /docs/kit-compatibility for the policy both sections enforce.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions