Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,29 @@ example to hide it in an import.
Nothing about the wire format changes. The `X-Client-Info` header still identifies this client as
`gotrue-dart`, and the `gotrue_meta_security` field in captcha payloads is unchanged.

### `FunctionException` is now a sealed class

`FunctionException` is now a `sealed class`, enabling compile-time exhaustive
pattern matching over all Edge Function failure modes (`FunctionsFetchException`,
`FunctionsApiException`, `FunctionsRelayException`).

```dart
try {
await supabase.functions.invoke('hello');
} on FunctionException catch (error) {
final message = switch (error) {
FunctionsFetchException() => 'Network/transport failure',
FunctionsRelayException() => 'Relay error',
FunctionsApiException() => 'Function returned ${error.statusCode}',
};
}
```

Because `sealed` classes are abstract, external code can no longer directly
instantiate a bare `FunctionException` or subclass/implement it. Use one of the
specific subtypes (`FunctionsFetchException`, `FunctionsApiException`,
`FunctionsRelayException`) instead.

### `RealtimeClient.connectionState` is now typed

`RealtimeClient` used to expose the socket state twice: a typed `connState` field and a stringly
Expand Down
12 changes: 8 additions & 4 deletions packages/functions_client/lib/src/types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,14 @@ class FunctionResponse {
/// The response body, or the originating error when no response was received,
/// is available in [details].
///
/// A plain [FunctionException] is a failure the client raised on its own, such
/// as a request that never reached the function. A failure the function
/// answered with is a [FunctionsApiException].
class FunctionException extends SupabaseException {
/// Use pattern matching over the specific subtypes:
/// - [FunctionsFetchException]: The request could not be sent (e.g. network
/// failure).
/// - [FunctionsApiException]: The Edge Function answered with a non-2xx status
/// code.
/// - [FunctionsRelayException]: The Supabase relay returned an error
/// (`x-relay-error`).
sealed class FunctionException extends SupabaseException {
final dynamic details;

const FunctionException({
Expand Down
18 changes: 18 additions & 0 deletions packages/functions_client/test/functions_dart_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,24 @@ void main() {
);
});

test('exhaustive switch over FunctionException subtypes', () {
String describeError(FunctionException exception) {
return switch (exception) {
FunctionsFetchException(:final message) => 'fetch: $message',
FunctionsRelayException(:final statusCode) => 'relay: $statusCode',
FunctionsApiException(:final statusCode) => 'api: $statusCode',
};
}

const fetchError = FunctionsFetchException(message: 'Connection failed');
const relayError = FunctionsRelayException(statusCode: 500);
const apiError = FunctionsApiException(statusCode: 400);

expect(describeError(fetchError), 'fetch: Connection failed');
expect(describeError(relayError), 'relay: 500');
expect(describeError(apiError), 'api: 400');
});

test(
'error response with a streaming content type exposes the body',
() async {
Expand Down
Loading