Skip to content

Thread the API error code through to typed AppErrors (replaces string-sniffing) #94

Description

@Adron

Discovered while implementing #33, and it affects every feature module — filing it rather than
smuggling a repo-wide refactor into an unrelated PR.

The problem

Every InterlinedList API error comes back as:

{ "error": "<human message>", "code": "<snake_case_code>" }

The app throws the code away. ErrorDto in :core:network models only { "error": ... }, and
safeApiCall maps HTTP status → AppError, with the AppError subclasses in :core:common
carrying just a message.

Two concrete costs:

  1. Distinct failures collapse. :feature:ai had to define its own module-local
    AiError/AiResult (see AI: status, gating and quota plumbing (:feature:ai scaffold) #4 / PR feat(ai): :feature:ai scaffold — status, gating, quota and preview→confirm (#4) #92) purely because the shared AppError cannot tell
    quota_exceeded from rate_limited — both are HTTP 429 — nor no_provider_configured from a
    generic conflict. Every future module with meaningful error codes will face the same fork.
  2. safeApiCall currently string-sniffs. It decides SubscriptionRequired with a
    message.contains("subscription") heuristic. That is a user-visible behaviour hanging off
    English prose in a server response — it breaks the day the copy is reworded.

What to do

  • Add code to ErrorDto.
  • Widen AppError (or add a typed code alongside the message) so call sites can branch on the
    server's own vocabulary.
  • Replace the contains("subscription") heuristic with the real code.
  • Fold :feature:ai's module-local AiError back onto the shared type once it can express
    quota_exceeded / rate_limited (+ Retry-After) / no_provider_configured.

Why it is filed rather than done

It touches every AppError construction site across all feature modules, so it needs a quiet
moment with no parallel branches in flight — otherwise it conflicts with everything. Best done
after the current parity wave merges.

Observed codes so far: bad_request (rejected viewingPreference), unauthorized
(GET /api/auth/accounts to a bearer token), plus quota_exceeded, rate_limited and
no_provider_configured documented at https://interlinedlist.com/help/api/ai-integration.

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

    Labels

    P2Completeness / settings surface

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions