Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add "onError" hook to generated clients #102

Merged
merged 13 commits into from
Nov 29, 2024
Merged
101 changes: 77 additions & 24 deletions languages/dart/dart-client/lib/request.dart
Original file line number Diff line number Diff line change
Expand Up @@ -126,26 +126,36 @@ Future<T> parsedArriRequest<T, E extends Exception>(
HttpMethod method = HttpMethod.post,
Map<String, dynamic>? params,
FutureOr<Map<String, String>> Function()? headers,
Function(Object)? onError,
String? clientVersion,
required T Function(String) parser,
}) async {
final result = await arriRequest(
url,
httpClient: httpClient,
method: method,
params: params,
headers: headers,
clientVersion: clientVersion,
);
if (result.statusCode >= 200 && result.statusCode <= 299) {
return parser(utf8.decode(result.bodyBytes));
final http.Response result;

try {
result = await arriRequest(
url,
httpClient: httpClient,
method: method,
params: params,
headers: headers,
clientVersion: clientVersion,
);
if (result.statusCode >= 200 && result.statusCode <= 299) {
return parser(utf8.decode(result.bodyBytes));
}
} catch (err) {
onError?.call(err);
rethrow;
}
throw ArriError.fromResponse(result);
final err = ArriError.fromResponse(result);
onError?.call(err);
throw err;
}

/// Perform a raw HTTP request to an Arri RPC server. This function does not thrown an error. Instead it returns a request result
/// in which both value and the error can be null.
Future<ArriRequestResult<T>> parsedArriRequestSafe<T>(
Future<ArriResult<T>> parsedArriRequestSafe<T>(
String url, {
http.Client? httpClient,
HttpMethod httpMethod = HttpMethod.get,
Expand All @@ -164,22 +174,65 @@ Future<ArriRequestResult<T>> parsedArriRequestSafe<T>(
method: httpMethod,
httpClient: httpClient,
);
return ArriRequestResult(value: result);
return ArriResultOk(result);
} catch (err) {
return ArriRequestResult(error: err is ArriError ? err : null);
return ArriResultErr(
err is ArriError
? err
: ArriError(
code: 0,
message: err.toString(),
data: err,
),
);
}
}

/// Container for holding a request result or a request error
class ArriRequestResult<T> {
final T? value;
final ArriError? error;
const ArriRequestResult({this.value, this.error});
/// Container for holding a request data or a request error
sealed class ArriResult<T> {
bool get isOk;
bool get isErr;
T? get unwrap;
T unwrapOr(T fallback);
ArriError? get unwrapErr;
}

/// Abstract endpoint to use as a base for generated client route enums
abstract class ArriEndpoint {
final String path;
final HttpMethod method;
const ArriEndpoint({required this.path, required this.method});
class ArriResultOk<T> implements ArriResult<T> {
final T _data;
const ArriResultOk(this._data);

@override
bool get isOk => true;

@override
bool get isErr => false;

@override
T get unwrap => _data;

@override
T unwrapOr(T fallback) => _data;

@override
ArriError? get unwrapErr => null;
}

class ArriResultErr<T> implements ArriResult<T> {
final ArriError _err;
const ArriResultErr(this._err);

@override
bool get isErr => true;

@override
bool get isOk => false;

@override
T? get unwrap => null;

@override
ArriError get unwrapErr => _err;

@override
T unwrapOr(T fallback) => fallback;
}
1 change: 1 addition & 0 deletions languages/dart/dart-client/lib/ws.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Future<ArriWebsocketController<TServerMessage, TClientMessage>>
FutureOr<Map<String, String>> Function()? headers,
required TServerMessage Function(String msg) parser,
required String Function(TClientMessage msg) serializer,
Function(Object)? onError,
String? clientVersion,
}) async {
var finalUrl =
Expand Down
4 changes: 1 addition & 3 deletions languages/dart/dart-client/test/arri_client_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ main() {
test("invalid url", () async {
final response =
await parsedArriRequestSafe(nonExistentUrl, parser: (data) {});
if (response.error != null) {
expect(response.error!.code, equals(500));
}
expect(response.unwrapErr?.code, equals(0));
});

test('auto retry sse', () async {
Expand Down
30 changes: 25 additions & 5 deletions languages/dart/dart-codegen-reference/lib/reference_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,17 @@ class ExampleClient {
final http.Client? _httpClient;
final String _baseUrl;
final String _clientVersion = "20";
late final FutureOr<Map<String, String>> Function()? _headers;
final FutureOr<Map<String, String>> Function()? _headers;
final Function(Object)? _onError;
ExampleClient({
http.Client? httpClient,
required String baseUrl,
FutureOr<Map<String, String>> Function()? headers,
Function(Object)? onError,
}) : _httpClient = httpClient,
_baseUrl = baseUrl,
_headers = headers;
_headers = headers,
_onError = onError;

Future<NestedObject> sendObject(NestedObject params) async {
return parsedArriRequest(
Expand All @@ -27,28 +30,33 @@ class ExampleClient {
clientVersion: _clientVersion,
params: params.toJson(),
parser: (body) => NestedObject.fromJsonString(body),
onError: _onError,
);
}

ExampleClientBooksService get books => ExampleClientBooksService(
baseUrl: _baseUrl,
headers: _headers,
httpClient: _httpClient,
onError: _onError,
);
}

class ExampleClientBooksService {
final http.Client? _httpClient;
final String _baseUrl;
final String _clientVersion = "20";
late final FutureOr<Map<String, String>> Function()? _headers;
final FutureOr<Map<String, String>> Function()? _headers;
final Function(Object)? _onError;
ExampleClientBooksService({
http.Client? httpClient,
required String baseUrl,
FutureOr<Map<String, String>> Function()? headers,
Function(Object)? onError,
}) : _httpClient = httpClient,
_baseUrl = baseUrl,
_headers = headers;
_headers = headers,
_onError = onError;

/// Get a book
Future<Book> getBook(BookParams params) async {
Expand All @@ -60,6 +68,7 @@ class ExampleClientBooksService {
clientVersion: _clientVersion,
params: params.toJson(),
parser: (body) => Book.fromJsonString(body),
onError: _onError,
);
}

Expand All @@ -74,6 +83,7 @@ class ExampleClientBooksService {
clientVersion: _clientVersion,
params: params.toJson(),
parser: (body) => Book.fromJsonString(body),
onError: _onError,
);
}

Expand Down Expand Up @@ -103,7 +113,16 @@ class ExampleClientBooksService {
onMessage: onMessage,
onOpen: onOpen,
onClose: onClose,
onError: onError,
onError: onError != null && _onError != null
? (err, es) {
_onError?.call(onError);
return onError(err, es);
}
: onError != null
? onError
: _onError != null
? (err, _) => _onError?.call(err)
: null,
);
}

Expand All @@ -114,6 +133,7 @@ class ExampleClientBooksService {
clientVersion: _clientVersion,
parser: (msg) => Book.fromJsonString(msg),
serializer: (msg) => msg.toJsonString(),
onError: _onError,
);
}
}
Expand Down
29 changes: 19 additions & 10 deletions languages/dart/dart-codegen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,28 +69,37 @@ final service = MyClientUsersService(
);
```

#### Client / Service Options

| name | Type | description |
| ---------- | ------------------------------------------- | ------------------------------------------------------------- |
| httpClient | `http.Client` | Use this to pass in a custom `http.Client` instance |
| baseUrl | `String` | The base url for the backend server |
| headers | `FutureOr<Map<String, String>> Function()?` | A function that returns a Map of headers |
| onError | `Function(Object)?` | A hook that fires whenever any error is thrown by the client. |

### Using Arri Models

All generated models will be immutable. They will have access to the following features:

**Methods**:

- `Map<String, dynamic> toJson()`
- `String toJsonString()`
- `String toUrlQueryParams()`
- `copyWith()`
- `Map<String, dynamic> toJson()`
- `String toJsonString()`
- `String toUrlQueryParams()`
- `copyWith()`

**Factory Methods**:

- `empty()`
- `fromJson(Map<String, dynamic> input)`
- `fromJsonString(String input)`
- `empty()`
- `fromJson(Map<String, dynamic> input)`
- `fromJsonString(String input)`

**Overrides**:

- `==` operator (allows for deep equality checking)
- `hashMap` (allows for deep equality checking)
- `toString` (will print out all properties and values instead of `Instance of X`)
- `==` operator (allows for deep equality checking)
- `hashMap` (allows for deep equality checking)
- `toString` (will print out all properties and values instead of `Instance of X`)

This library was generated with [Nx](https://nx.dev).

Expand Down
8 changes: 6 additions & 2 deletions languages/dart/dart-codegen/src/_index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,14 +167,17 @@ class ${clientName} {
final http.Client? _httpClient;
final String _baseUrl;
final String _clientVersion = "${context.clientVersion ?? ""}";
late final FutureOr<Map<String, String>> Function()? _headers;
final FutureOr<Map<String, String>> Function()? _headers;
final Function(Object)? _onError;
${clientName}({
http.Client? httpClient,
required String baseUrl,
FutureOr<Map<String, String>> Function()? headers,
Function(Object)? onError,
}) : _httpClient = httpClient,
_baseUrl = baseUrl,
_headers = headers;
_headers = headers,
_onError = onError;

${rpcParts.join("\n\n")}

Expand All @@ -184,6 +187,7 @@ ${subServices
baseUrl: _baseUrl,
headers: _headers,
httpClient: _httpClient,
onError: _onError,
);`,
)
.join("\n\n")}
Expand Down
21 changes: 18 additions & 3 deletions languages/dart/dart-codegen/src/procedures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,16 @@ export function dartHttpRpcFromSchema(
onMessage: onMessage,
onOpen: onOpen,
onClose: onClose,
onError: onError,
onError: onError != null && _onError != null
? (err, es) {
_onError?.call(onError);
return onError(err, es);
}
: onError != null
? onError
: _onError != null
? (err, _) => _onError?.call(err)
: null,
);
}`;
}
Expand All @@ -87,6 +96,7 @@ export function dartHttpRpcFromSchema(
clientVersion: _clientVersion,
${paramsType ? "params: params.toJson()," : ""}
parser: (body) ${schema.response ? `=> ${responseType}.fromJsonString(body)` : "{}"},
onError: _onError,
);
}`;
}
Expand Down Expand Up @@ -120,6 +130,7 @@ export function dartWsRpcFromSchema(
clientVersion: _clientVersion,
parser: (msg) ${responseType ? `=> ${responseType}.fromJsonString(msg)` : "{}"},
serializer: (msg) ${paramsType ? "=> msg.toJsonString()" : '=> ""'},
onError: _onError,
);
}`;
}
Expand Down Expand Up @@ -180,14 +191,17 @@ export function dartServiceFromSchema(
final http.Client? _httpClient;
final String _baseUrl;
final String _clientVersion = "${context.clientVersion}";
late final FutureOr<Map<String, String>> Function()? _headers;
final FutureOr<Map<String, String>> Function()? _headers;
final Function(Object)? _onError;
${serviceName}({
http.Client? httpClient,
required String baseUrl,
FutureOr<Map<String, String>> Function()? headers,
Function(Object)? onError,
}) : _httpClient = httpClient,
_baseUrl = baseUrl,
_headers = headers;
_headers = headers,
_onError = onError;

${rpcParts.join("\n\n")}

Expand All @@ -197,6 +211,7 @@ export function dartServiceFromSchema(
baseUrl: _baseUrl,
headers: _headers,
httpClient: _httpClient,
onError: _onError,
);`,
)
.join("\n\n")}
Expand Down
Loading