Skip to content

feat: [CHA-2958] typed error hierarchy + waitForTask#65

Open
mogita wants to merge 5 commits into
mainfrom
feat/cha-2958-error-handling
Open

feat: [CHA-2958] typed error hierarchy + waitForTask#65
mogita wants to merge 5 commits into
mainfrom
feat/cha-2958-error-handling

Conversation

@mogita
Copy link
Copy Markdown
Contributor

@mogita mogita commented May 28, 2026

Summary

  • Four new CHECKED subclasses of StreamException:
    • StreamApiException — HTTP 4xx/5xx envelope (statusCode, code, exceptionFields, unrecoverable, rawResponseBody, moreInfo, details)
    • StreamRateLimitException — HTTP 429 + parsed Retry-After as Duration
    • StreamTransportException — IO failures with classified errorType enum, cause preserved
    • StreamTaskException — failed async task with ErrorResult fields
  • APIError parser now surfaces the two previously-dropped fields: unrecoverable and details.
  • Retry-After parser handles RFC 7231 §7.1.3 integer + IMF-fixdate HTTP-date; graceful on missing / unparseable.
  • client.waitForTask(taskId) customer-facing helper (replaces the test-only ChatTestBase.waitForTask).

No breaking API changes — all new types are subclasses of the existing checked StreamException. catch (StreamException) still works; throws StreamException still compiles.

Notable

  • StreamTaskException.getStackTraceText() instead of getStackTrace() to avoid clobbering Throwable.getStackTrace().
  • 5 integration tests migrated from the deleted ChatTestBase.waitForTask to client.waitForTask. Old test helper silently swallowed status="failed"; the new helper throws StreamTaskException.

Test plan

  • ./gradlew build --info
  • ./gradlew test --no-daemon
  • ./gradlew spotlessCheck

Add StreamApiException, StreamRateLimitException, StreamTransportException,
and StreamTaskException as checked subclasses of the existing StreamException.
Carries the previously-dropped APIError fields (unrecoverable, details) and
parses the Retry-After header (RFC 7231: integer seconds + HTTP-date) on 429.
Transport-layer IO failures are now classified into a per-error-type enum and
the cause chain is preserved at every wrap point.

Adds client.waitForTask(taskId) as a customer-facing main-source helper that
polls until terminal. Failed tasks throw StreamTaskException populated from
ErrorResult; the wait elapsing throws StreamTransportException(errorType=
timeout). Removes the duplicate test-only waitForTask helper from ChatTestBase
and updates the 5 callers.

No breaking API changes: subclasses extend the existing checked StreamException,
getResponseData() continues to return a populated mirror on API errors, and
existing throws StreamException declarations continue to compile.
@mogita mogita changed the title [CHA-2958] Typed error hierarchy + waitForTask feat: [CHA-2958] typed error hierarchy + waitForTask May 28, 2026
Auto-formatting via `./gradlew spotlessApply` over the CHA-2958 files.
No behavior change.
…heck

Two test-only fixes for CI on JDK 21:
1. `request()` helper now declares `throws StreamException` (StreamRequest's
   constructor is checked since CHA-2958's reshape).
2. `instanceof RuntimeException` against a statically-typed `StreamException`
   is an unconditional-pattern compile error on JDK 21; use
   `RuntimeException.class.isInstance(e)` to keep the runtime assertion.
CI sets `STREAM_BASE_URL: ${{ vars.STREAM_BASE_URL }}`, which renders to an
empty string when the GitHub variable is not configured. With the previous
`env.getOrDefault(..., System.getProperty(...))` shape, an empty env value
won and overrode any `io.getstream.url` system property set by tests, so
unit tests fell through to the real Stream API using the real credentials
also injected via env. Tests in `StreamWaitForTaskTest` hit the live
`get-task/t1` endpoint and got `StreamApiException: Can't find task with id`.

Now the env var falls through to the system property when it is null or
blank, so MockWebServer-backed tests work and any future deployment that
leaves STREAM_BASE_URL empty falls back to the SDK default base URL
instead of broken empty URLs.
CI sets STREAM_BASE_URL to the real Stream API URL, and the Properties-based
StreamHTTPClient constructor gives env vars priority over system properties.
The previous setUp set `io.getstream.url` via System.setProperty and then
called `new StreamSDKClient(System.getProperties())`, which still read the
real-URL env var and pointed every request at the live API. The mocked task
JSONs were never consumed and the SDK got back real "Can't find task with
id" responses.

Switched setUp to the credentials-only StreamHTTPClient constructor (which
does not read env vars) and used reflection to force the private `baseUrl`
field onto the MockWebServer URL. The SDK client is then wrapped via
`new StreamSDKClient(StreamHTTPClient)`. Removes the system-property dance
and the env precedence problem entirely.
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