Skip to content

Add a public StreamApiException constructor#215

Merged
sierpinskid merged 1 commit into
GetStream:developfrom
harlan:feature/public-streamapiexception-ctor
Jun 30, 2026
Merged

Add a public StreamApiException constructor#215
sierpinskid merged 1 commit into
GetStream:developfrom
harlan:feature/public-streamapiexception-ctor

Conversation

@harlan

@harlan harlan commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Summary

StreamApiException is a public, catch-and-branch type — integrators are expected to catch it, inspect StatusCode/Code, and branch via the public StreamApiExceptionExtensions.Is* helpers (IsNoAccessToChannelsError, IsRateLimitExceededError, etc.). But its only constructor is internal and takes the internal APIErrorInternalDTO, so integrators can't construct one to unit-test their own error handling.

This means there's no supported way to write a test like "when a channel-hydration call throws a 403 / code 70, my code drops the channel from the local list instead of rethrowing" — you can't produce the exception to feed your handler without reaching into SDK internals (e.g. an InternalsVisibleTo patch against a vendored copy).

This PR adds a public constructor over the type's already-public fields. APIErrorInternalDTO stays internal.

Change

public StreamApiException(
    int statusCode,
    int code,
    string errorMessage = null,
    string moreInfo = null,
    string duration = null,
    IReadOnlyDictionary<string, string> exceptionFields = null)

Both this and the existing DTO constructor funnel through a single private constructor and a shared BuildMessage helper, so the Exception.Message format is identical regardless of how the exception is built (keeps log/crash-report grouping consistent). Every parameter maps 1:1 to an existing public property; nothing internal is exposed.

Why this shape

  • A plain public constructor (vs. a ForTesting-style factory) is idiomatic for an Exception subclass and reads naturally at call sites. Hand-constructing a representative API error is legitimately useful beyond tests (fakes, mocks, replay tooling).
  • This matches the signature suggested in the support thread; APIErrorInternalDTO is kept internal as requested.

Integrator impact (before → after)

Before — only possible by granting InternalsVisibleTo to the SDK and using the internal DTO:

// Requires internal access to both StreamApiException's ctor and APIErrorInternalDTO
private static StreamApiException NoAccessToChannelsException()
    => new(new APIErrorInternalDTO
    {
        StatusCode = StreamApiException.NoAccessToChannelsErrorHttpStatusCode, // 403
        Code       = StreamApiException.NoAccessToChannelsErrorStreamCode,     // 70
        Message    = "channels match your query but cannot be returned",
    });

After — public API only:

private static StreamApiException NoAccessToChannelsException()
    => new(
        StreamApiException.NoAccessToChannelsErrorHttpStatusCode, // 403
        StreamApiException.NoAccessToChannelsErrorStreamCode,     // 70
        "channels match your query but cannot be returned");

Testing

  • Verified the refactor produces byte-identical Exception.Message output to the previous inline code across all error shapes (null message/moreInfo, null/empty/multi-entry exceptionFields).
  • Existing CI runtime test suite covers the rest.

Changelog

Added an entry under Unreleased / Features.

StreamApiException is a public, catch-and-branch type (consumers inspect
StatusCode/Code and branch via the StreamApiExceptionExtensions.Is* helpers),
but its only constructor was internal and took the internal APIErrorInternalDTO,
so integrators could not construct one to unit-test their own error handling
(e.g. simulating a 403 / code 70 'no access to channels' response).

Add a public constructor over the type's already-public fields. Both it and the
existing DTO constructor funnel through one private constructor and a shared
BuildMessage helper, so the Exception.Message format is identical however the
exception is built. APIErrorInternalDTO stays internal.
@sierpinskid sierpinskid self-requested a review June 30, 2026 08:13
@sierpinskid

Copy link
Copy Markdown
Member

All tests passed:
image

@sierpinskid

Copy link
Copy Markdown
Member

Thank you for you contribution!

@sierpinskid sierpinskid merged commit 70be709 into GetStream:develop Jun 30, 2026
0 of 17 checks passed
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