Skip to content

feat: expose structured API errors via RequestError#15

Merged
smoratino-apogea merged 1 commit into
mainfrom
feature/request-error
May 14, 2026
Merged

feat: expose structured API errors via RequestError#15
smoratino-apogea merged 1 commit into
mainfrom
feature/request-error

Conversation

@smoratino-apogea

Copy link
Copy Markdown
Contributor

feat: Expose structured API errors via RequestError

Summary

Introduces a new RequestError class that replaces the generic Error thrown by EngineServicesClient on non-2xx HTTP responses. It parses the structured JSON body the platform returns ({ message, code?, details? }) and exposes each field as a typed property, so callers can react to specific failure conditions without string-matching an error message.

Motivation

The platform API already returns machine-readable error codes (e.g. LIMIT_EXCEEDED) in its response body. Before this change, all HTTP failures were surfaced as a plain Error whose message was a concatenated status line — forcing consumers to do fragile substring searches to distinguish a 401 from a quota error. This PR makes that contract explicit and testable.

Changes

src/core/request-error.ts (new)

  • RequestError extends Error with readonly properties:
    • status: number — HTTP status code.
    • code?: string — machine-readable error code from the response body (e.g. 'LIMIT_EXCEEDED').
    • details?: unknown — arbitrary details object from the response body.
    • body: string — raw response body for diagnostics.
  • Parses JSON bodies defensively; non-JSON bodies (proxy HTML pages, plain text) fall back to "<statusText> (<status>)" as the message with no code or details.
  • Exported from the package root (src/index.ts).

src/core/client.ts

  • Replaced throw new Error(...) in the HTTP failure path with throw new RequestError(status, statusText, body).

src/cli/commands/publish.ts

  • Replaced brittle message.includes('401') / message.includes('403') checks with instanceof RequestError + err.status / err.code guards.
  • Specific handling for LIMIT_EXCEEDED (prints the API message verbatim), 401 (login hint), 403 (permission denied), and a generic fallback.
  • Network errors (ECONNREFUSED, fetch) are still caught separately through the outer else branch.

src/cli/templates/test/src/main.ts

  • Replaced err.message.includes("4") (heuristic 4xx detection) with a proper check on err.status using the new structured shape (status >= 400 && status < 500).

Tests

File Coverage
src/core/request-error.test.ts (new) 7 unit tests covering: structured body parsing, partial body (message only), non-JSON fallback, empty body fallback, non-object JSON fallback, ignored non-string fields, instanceof Error / instanceof RequestError identity
src/core/client.test.ts Added integration-level test asserting rejects.toMatchObject({ name: 'RequestError', status, code, message })

Breaking changes

None. RequestError is a subclass of Error, so any existing catch (err) that only reads err.message continues to work. The new class is purely additive.

Changeset

minor — new public export RequestError.

@smoratino-apogea smoratino-apogea merged commit b598e3c into main May 14, 2026
1 check passed
@agviegas agviegas deleted the feature/request-error branch June 4, 2026 21:52
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.

2 participants