Skip to content

refactor(storage)!: split the Iceberg exceptions into a client and an api base - #1699

Open
spydon wants to merge 1 commit into
breaking/supabase-exception-basefrom
breaking/iceberg-exception-base
Open

refactor(storage)!: split the Iceberg exceptions into a client and an api base#1699
spydon wants to merge 1 commit into
breaking/supabase-exception-basefrom
breaking/iceberg-exception-base

Conversation

@spydon

@spydon spydon commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Stacked on #1644, which introduces SupabaseException and SupabaseApiException. Review that one first; this PR only touches the Iceberg exceptions. Under the v3 umbrella #1278.

Follows up on @Vinzent03's question about why Iceberg was left out. It should not have been: it was the closest fit of anything not yet migrated, and it carried the same status code sentinel that #1644 removed from FunctionsFetchException.

What

IcebergException used 0 as the status code when a request never reached the catalog:

final class IcebergNetworkException extends IcebergException {
  const IcebergNetworkException(super.message, {super.details})
    : super(statusCode: 0);
}

So a caller reading statusCode had to know that 0 meant "no response" rather than a real status. The sealed hierarchy now splits the same way as auth, storage and functions:

IcebergException                      message, errorCode, code, details
├── IcebergNetworkException           no status, the request never went out
└── IcebergApiException               + statusCode   <- SupabaseApiException
    ├── IcebergNotFoundException            (404)
    ├── IcebergConflictException            (409)
    ├── IcebergAuthenticationTimeoutException (419)
    ├── IcebergCommitStateUnknownException
    ├── IcebergServerException              (5xx)
    └── IcebergUnknownException

IcebergApiException is itself sealed, so the six concrete subtypes stay closed and unchanged, and exhaustive switches over the hierarchy compile with exactly the same set of cases as before.

Breaking changes

Before After
IcebergException.type errorCode, inherited from SupabaseException
IcebergException.statusCode IcebergApiException.statusCode; gone from the network case
IcebergException.statusCode == 0 catch IcebergNetworkException, or check is SupabaseApiException
IcebergException.fromResponse IcebergApiException.fromResponse

message, code and details keep their names. code is still the Iceberg numeric error code, which is unrelated to errorCode, the error type string such as NoSuchTableException.

type becomes errorCode because that is exactly what SupabaseException.errorCode is for, the same way StorageException.error became errorCode in #1644. The wire format is untouched: fromResponse still reads the type field out of the response body.

// Before
try {
  await catalog.loadTable(id);
} on IcebergException catch (error) {
  if (error.statusCode == 0) {
    // the request never went out
  }
  print(error.type);
}

// After
try {
  await catalog.loadTable(id);
} on IcebergNetworkException catch (error) {
  // the request never went out
  print(error.details);
} on IcebergApiException catch (error) {
  print('${error.statusCode}: ${error.errorCode}');
}

An Iceberg failure is now also reachable through the shared types, so a single catch covers it alongside any other service:

try {
  await catalog.loadTable(id);
} on SupabaseApiException catch (error) {
  print('${error.statusCode}: ${error.message}');
}

Testing

dart analyze, dcm analyze and dart format are clean. The storage_client suite passes, including two new tests: one asserting that every subtype fromResponse can build is a SupabaseApiException while IcebergNetworkException is not, and one asserting that a network failure keeps the originating error in details. The existing error tests were updated for the renamed field and the moved factory.

The capability matrix symbol, drift and schema checks pass with IcebergApiException registered.

MIGRATION.md gains a section for the split, next to the one #1644 adds.

@spydon
spydon requested a review from a team as a code owner August 12, 2026 15:23
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7663a651-6da1-4f49-91e7-33d1757628c7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@spydon spydon changed the title breaking(storage)!: split the Iceberg exceptions into a client and an api base refactor(storage)!: split the Iceberg exceptions into a client and an api base Aug 12, 2026
@spydon
spydon force-pushed the breaking/supabase-exception-base branch from dd6f916 to 2476ef4 Compare August 12, 2026 15:30
…api base

`IcebergException` was the last exception hierarchy still using `0` as its
status code when a request never reached the catalog, so callers had to know
that `statusCode == 0` meant "no response" rather than a real status. It now
extends `SupabaseException` and splits the way the other packages do:

- `IcebergNetworkException` covers the transport case and reports no status.
- `IcebergApiException` is a new sealed base for everything the catalog
  answered, mixes in `SupabaseApiException`, and carries a non-nullable
  `statusCode`. The six concrete response subtypes now extend it and are
  otherwise unchanged.

`type` becomes the inherited `errorCode`, since it is the Iceberg error type
such as `NoSuchTableException`. `message`, `code` and `details` keep their
names, and `fromResponse` moves to `IcebergApiException` because it only ever
builds response backed subtypes.

Exhaustive switches over the hierarchy still compile with the same cases: the
new base is sealed and no concrete subtype changed.
@spydon
spydon force-pushed the breaking/iceberg-exception-base branch from 62e61c8 to c51e27b Compare August 12, 2026 15:30
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