From 17de1dc3317bfcb8e8183c0cef56b34823c46ad3 Mon Sep 17 00:00:00 2001 From: Joshua Sosso Date: Tue, 8 Oct 2024 16:11:34 -0500 Subject: [PATCH 01/10] add general onError hook to ts client --- languages/ts/ts-client/src/sse.ts | 7 + .../src/referenceClient.ts | 154 +++-- languages/ts/ts-codegen/src/rpc.ts | 90 ++- languages/ts/ts-codegen/src/service.ts | 3 + tests/clients/ts/testClient.rpc.ts | 635 +++++++++++------- 5 files changed, 562 insertions(+), 327 deletions(-) diff --git a/languages/ts/ts-client/src/sse.ts b/languages/ts/ts-client/src/sse.ts index 51c92712..ba1aee85 100644 --- a/languages/ts/ts-client/src/sse.ts +++ b/languages/ts/ts-client/src/sse.ts @@ -42,6 +42,7 @@ export function arriSseRequest< >( opts: ArriRequestOpts, options: SseOptions, + rootOnError?: (err: unknown) => void, ): EventSourceController { let url = opts.url; let body: undefined | string; @@ -95,6 +96,9 @@ export function arriSseRequest< options.onRequest?.(context); }, onRequestError(context) { + if (rootOnError) { + rootOnError(context.error); + } options.onRequestError?.({ ...context, error: new ArriErrorInstance({ @@ -108,6 +112,9 @@ export function arriSseRequest< options.onResponse?.(context); }, async onResponseError(context) { + if (rootOnError) { + rootOnError(context.error); + } if (!options.onResponseError) { return; } diff --git a/languages/ts/ts-codegen-reference/src/referenceClient.ts b/languages/ts/ts-codegen-reference/src/referenceClient.ts index 36b13449..635a2054 100644 --- a/languages/ts/ts-codegen-reference/src/referenceClient.ts +++ b/languages/ts/ts-codegen-reference/src/referenceClient.ts @@ -36,29 +36,39 @@ export class ExampleClient { private readonly _headers: | HeaderMap | (() => HeaderMap | Promise); + private readonly _onError?: (err: unknown) => void; books: ExampleClientBooksService; constructor( options: { baseUrl?: string; headers?: HeaderMap | (() => HeaderMap | Promise); + onError?: (err: unknown) => void; } = {}, ) { this._baseUrl = options.baseUrl ?? ""; this._headers = options.headers ?? {}; + this._onError = options.onError; this.books = new ExampleClientBooksService(options); } async sendObject(params: NestedObject): Promise { - return arriRequest({ - url: `${this._baseUrl}/send-object`, - method: "post", - headers: this._headers, - params: params, - responseFromJson: $$NestedObject.fromJson, - responseFromString: $$NestedObject.fromJsonString, - serializer: $$NestedObject.toJsonString, - clientVersion: "20", - }); + try { + return arriRequest({ + url: `${this._baseUrl}/send-object`, + method: "post", + headers: this._headers, + params: params, + responseFromJson: $$NestedObject.fromJson, + responseFromString: $$NestedObject.fromJsonString, + serializer: $$NestedObject.toJsonString, + clientVersion: "20", + }); + } catch (err) { + if (this._onError) { + this._onError(err); + } + throw err; + } } } @@ -67,45 +77,62 @@ export class ExampleClientBooksService { private readonly _headers: | HeaderMap | (() => HeaderMap | Promise); + private readonly _onError?: (err: unknown) => void; constructor( options: { baseUrl?: string; headers?: HeaderMap | (() => HeaderMap | Promise); + onError?: (err: unknown) => void; } = {}, ) { this._baseUrl = options.baseUrl ?? ""; this._headers = options.headers ?? {}; + this._onError = options.onError; } /** * Get a book */ async getBook(params: BookParams): Promise { - return arriRequest({ - url: `${this._baseUrl}/books/get-book`, - method: "get", - headers: this._headers, - params: params, - responseFromJson: $$Book.fromJson, - responseFromString: $$Book.fromJsonString, - serializer: $$BookParams.toUrlQueryString, - clientVersion: "20", - }); + try { + return arriRequest({ + url: `${this._baseUrl}/books/get-book`, + method: "get", + headers: this._headers, + params: params, + responseFromJson: $$Book.fromJson, + responseFromString: $$Book.fromJsonString, + serializer: $$BookParams.toUrlQueryString, + clientVersion: "20", + }); + } catch (err) { + if (this._onError) { + this._onError(err); + } + throw err; + } } /** * Create a book * @deprecated */ async createBook(params: Book): Promise { - return arriRequest({ - url: `${this._baseUrl}/books/create-book`, - method: "post", - headers: this._headers, - params: params, - responseFromJson: $$Book.fromJson, - responseFromString: $$Book.fromJsonString, - serializer: $$Book.toJsonString, - clientVersion: "20", - }); + try { + return arriRequest({ + url: `${this._baseUrl}/books/create-book`, + method: "post", + headers: this._headers, + params: params, + responseFromJson: $$Book.fromJson, + responseFromString: $$Book.fromJsonString, + serializer: $$Book.toJsonString, + clientVersion: "20", + }); + } catch (err) { + if (this._onError) { + this._onError(err); + } + throw err; + } } /** * @deprecated @@ -114,36 +141,51 @@ export class ExampleClientBooksService { params: BookParams, options: SseOptions = {}, ): EventSourceController { - return arriSseRequest( - { - url: `${this._baseUrl}/books/watch-book`, - method: "get", - headers: this._headers, - params: params, - responseFromJson: $$Book.fromJson, - responseFromString: $$Book.fromJsonString, - serializer: $$BookParams.toUrlQueryString, - clientVersion: "20", - }, - options, - ); + try { + return arriSseRequest( + { + url: `${this._baseUrl}/books/watch-book`, + method: "get", + headers: this._headers, + params: params, + responseFromJson: $$Book.fromJson, + responseFromString: $$Book.fromJsonString, + serializer: $$BookParams.toUrlQueryString, + clientVersion: "20", + }, + options, + this._onError, + ); + } catch (err) { + if (this._onError) { + this._onError(err); + } + throw err; + } } async createConnection( options: WsOptions = {}, ): Promise> { - return arriWsRequest({ - url: `${this._baseUrl}/books/create-connection`, - headers: this._headers, - responseFromJson: $$Book.fromJson, - responseFromString: $$Book.fromJsonString, - serializer: $$BookParams.toJsonString, - onOpen: options.onOpen, - onClose: options.onClose, - onError: options.onError, - onConnectionError: options.onConnectionError, - onMessage: options.onMessage, - clientVersion: "20", - }); + try { + return arriWsRequest({ + url: `${this._baseUrl}/books/create-connection`, + headers: this._headers, + responseFromJson: $$Book.fromJson, + responseFromString: $$Book.fromJsonString, + serializer: $$BookParams.toJsonString, + onOpen: options.onOpen, + onClose: options.onClose, + onError: options.onError, + onConnectionError: options.onConnectionError, + onMessage: options.onMessage, + clientVersion: "20", + }); + } catch (err) { + if (this._onError) { + this._onError(err); + } + throw err; + } } } diff --git a/languages/ts/ts-codegen/src/rpc.ts b/languages/ts/ts-codegen/src/rpc.ts index 29c35872..a44e5562 100644 --- a/languages/ts/ts-codegen/src/rpc.ts +++ b/languages/ts/ts-codegen/src/rpc.ts @@ -43,8 +43,35 @@ export function httpRpcFromDefinition( description: def.description, isDeprecated: def.isDeprecated, })} ${key}(${params ? `params: ${params},` : ""} options: SseOptions<${response ?? "undefined"}> = {}): EventSourceController { - return arriSseRequest<${response ?? "undefined"}, ${params ?? "undefined"}>( - { + try { + return arriSseRequest<${response ?? "undefined"}, ${params ?? "undefined"}>( + { + url: \`\${this._baseUrl}${def.path}\`, + method: "${def.method.toLowerCase()}", + headers: this._headers, + ${params ? "params: params," : ""} + responseFromJson: ${response ? `$$${response}.fromJson` : "() => {}"}, + responseFromString: ${response ? `$$${response}.fromJsonString` : "() => {}"}, + serializer: ${params ? `$$${params}.${serializerMethod}` : "() => {}"}, + clientVersion: "${context.versionNumber}", + }, + options, + this._onError, + ); + } catch(err) { + if (this._onError) { + this._onError(err); + } + throw err; + } + }`; + } + return `${getJsDocComment({ + description: def.description, + isDeprecated: def.isDeprecated, + })} async ${key}(${params ? `params: ${params}` : ""}): Promise<${response ?? "undefined"}> { + try { + return arriRequest<${response ?? "undefined"}, ${params ?? "undefined"}>({ url: \`\${this._baseUrl}${def.path}\`, method: "${def.method.toLowerCase()}", headers: this._headers, @@ -53,25 +80,13 @@ export function httpRpcFromDefinition( responseFromString: ${response ? `$$${response}.fromJsonString` : "() => {}"}, serializer: ${params ? `$$${params}.${serializerMethod}` : "() => {}"}, clientVersion: "${context.versionNumber}", - }, - options, - ) - }`; - } - return `${getJsDocComment({ - description: def.description, - isDeprecated: def.isDeprecated, - })} async ${key}(${params ? `params: ${params}` : ""}): Promise<${response ?? "undefined"}> { - return arriRequest<${response ?? "undefined"}, ${params ?? "undefined"}>({ - url: \`\${this._baseUrl}${def.path}\`, - method: "${def.method.toLowerCase()}", - headers: this._headers, - ${params ? "params: params," : ""} - responseFromJson: ${response ? `$$${response}.fromJson` : "() => {}"}, - responseFromString: ${response ? `$$${response}.fromJsonString` : "() => {}"}, - serializer: ${params ? `$$${params}.${serializerMethod}` : "() => {}"}, - clientVersion: "${context.versionNumber}", - }); + }); + } catch (err) { + if (this._onError) { + this._onError(err); + } + throw err; + } }`; } @@ -91,19 +106,26 @@ export function wsRpcFromDefinition( description: def.description, isDeprecated: def.isDeprecated, })} async ${key}(options: WsOptions<${response ?? "undefined"}> = {}): Promise> { - return arriWsRequest<${params ?? "undefined"}, ${response ?? "undefined"}>({ - url: \`\${this._baseUrl}${def.path}\`, - headers: this._headers, - responseFromJson: ${response ? `$$${response}.fromJson` : "() => {}"}, - responseFromString: ${response ? `$$${response}.fromJsonString` : "() => {}"}, - serializer: ${params ? `$$${params}.toJsonString` : "() => {}"}, - onOpen: options.onOpen, - onClose: options.onClose, - onError: options.onError, - onConnectionError: options.onConnectionError, - onMessage: options.onMessage, - clientVersion: "${context.versionNumber}", - }); + try { + return arriWsRequest<${params ?? "undefined"}, ${response ?? "undefined"}>({ + url: \`\${this._baseUrl}${def.path}\`, + headers: this._headers, + responseFromJson: ${response ? `$$${response}.fromJson` : "() => {}"}, + responseFromString: ${response ? `$$${response}.fromJsonString` : "() => {}"}, + serializer: ${params ? `$$${params}.toJsonString` : "() => {}"}, + onOpen: options.onOpen, + onClose: options.onClose, + onError: options.onError, + onConnectionError: options.onConnectionError, + onMessage: options.onMessage, + clientVersion: "${context.versionNumber}", + }); + } catch (err) { + if (this._onError) { + this._onError(err); + } + throw err; + } }`; } diff --git a/languages/ts/ts-codegen/src/service.ts b/languages/ts/ts-codegen/src/service.ts index cfde448a..a5ee7b99 100644 --- a/languages/ts/ts-codegen/src/service.ts +++ b/languages/ts/ts-codegen/src/service.ts @@ -71,15 +71,18 @@ export function tsServiceFromDefinition( content: `export class ${serviceName} { private readonly _baseUrl: string; private readonly _headers: HeaderMap | (() => HeaderMap | Promise); + private readonly _onError?: (err: unknown) => void; ${subServices.map((service) => ` ${service.key}: ${service.name};`).join("\n")} constructor( options: { baseUrl?: string; headers?: HeaderMap | (() => HeaderMap | Promise); + onError?: (err: unknown) => void; } = {}, ) { this._baseUrl = options.baseUrl ?? ""; this._headers = options.headers ?? {}; + this._onError = options.onError; ${subServices.map((service) => ` this.${service.key} = new ${service.name}(options);`).join("\n")} } ${rpcParts.map((rpc) => ` ${rpc}`).join("\n")} diff --git a/tests/clients/ts/testClient.rpc.ts b/tests/clients/ts/testClient.rpc.ts index 522e4c05..8d0658b5 100644 --- a/tests/clients/ts/testClient.rpc.ts +++ b/tests/clients/ts/testClient.rpc.ts @@ -35,16 +35,19 @@ export class TestClient { private readonly _headers: | HeaderMap | (() => HeaderMap | Promise); + private readonly _onError?: (err: unknown) => void; tests: TestClientTestsService; users: TestClientUsersService; constructor( options: { baseUrl?: string; headers?: HeaderMap | (() => HeaderMap | Promise); + onError?: (err: unknown) => void; } = {}, ) { this._baseUrl = options.baseUrl ?? ""; this._headers = options.headers ?? {}; + this._onError = options.onError; this.tests = new TestClientTestsService(options); this.users = new TestClientUsersService(options); } @@ -55,183 +58,273 @@ export class TestClientTestsService { private readonly _headers: | HeaderMap | (() => HeaderMap | Promise); + private readonly _onError?: (err: unknown) => void; constructor( options: { baseUrl?: string; headers?: HeaderMap | (() => HeaderMap | Promise); + onError?: (err: unknown) => void; } = {}, ) { this._baseUrl = options.baseUrl ?? ""; this._headers = options.headers ?? {}; + this._onError = options.onError; } async emptyParamsGetRequest(): Promise { - return arriRequest({ - url: `${this._baseUrl}/rpcs/tests/empty-params-get-request`, - method: "get", - headers: this._headers, + try { + return arriRequest({ + url: `${this._baseUrl}/rpcs/tests/empty-params-get-request`, + method: "get", + headers: this._headers, - responseFromJson: $$DefaultPayload.fromJson, - responseFromString: $$DefaultPayload.fromJsonString, - serializer: () => {}, - clientVersion: "10", - }); + responseFromJson: $$DefaultPayload.fromJson, + responseFromString: $$DefaultPayload.fromJsonString, + serializer: () => {}, + clientVersion: "10", + }); + } catch (err) { + if (this._onError) { + this._onError(err); + } + throw err; + } } async emptyParamsPostRequest(): Promise { - return arriRequest({ - url: `${this._baseUrl}/rpcs/tests/empty-params-post-request`, - method: "post", - headers: this._headers, + try { + return arriRequest({ + url: `${this._baseUrl}/rpcs/tests/empty-params-post-request`, + method: "post", + headers: this._headers, - responseFromJson: $$DefaultPayload.fromJson, - responseFromString: $$DefaultPayload.fromJsonString, - serializer: () => {}, - clientVersion: "10", - }); + responseFromJson: $$DefaultPayload.fromJson, + responseFromString: $$DefaultPayload.fromJsonString, + serializer: () => {}, + clientVersion: "10", + }); + } catch (err) { + if (this._onError) { + this._onError(err); + } + throw err; + } } async emptyResponseGetRequest(params: DefaultPayload): Promise { - return arriRequest({ - url: `${this._baseUrl}/rpcs/tests/empty-response-get-request`, - method: "get", - headers: this._headers, - params: params, - responseFromJson: () => {}, - responseFromString: () => {}, - serializer: $$DefaultPayload.toUrlQueryString, - clientVersion: "10", - }); + try { + return arriRequest({ + url: `${this._baseUrl}/rpcs/tests/empty-response-get-request`, + method: "get", + headers: this._headers, + params: params, + responseFromJson: () => {}, + responseFromString: () => {}, + serializer: $$DefaultPayload.toUrlQueryString, + clientVersion: "10", + }); + } catch (err) { + if (this._onError) { + this._onError(err); + } + throw err; + } } async emptyResponsePostRequest(params: DefaultPayload): Promise { - return arriRequest({ - url: `${this._baseUrl}/rpcs/tests/empty-response-post-request`, - method: "post", - headers: this._headers, - params: params, - responseFromJson: () => {}, - responseFromString: () => {}, - serializer: $$DefaultPayload.toJsonString, - clientVersion: "10", - }); + try { + return arriRequest({ + url: `${this._baseUrl}/rpcs/tests/empty-response-post-request`, + method: "post", + headers: this._headers, + params: params, + responseFromJson: () => {}, + responseFromString: () => {}, + serializer: $$DefaultPayload.toJsonString, + clientVersion: "10", + }); + } catch (err) { + if (this._onError) { + this._onError(err); + } + throw err; + } } /** * If the target language supports it. Generated code should mark this procedure as deprecated. * @deprecated */ async deprecatedRpc(params: DeprecatedRpcParams): Promise { - return arriRequest({ - url: `${this._baseUrl}/rpcs/tests/deprecated-rpc`, - method: "post", - headers: this._headers, - params: params, - responseFromJson: () => {}, - responseFromString: () => {}, - serializer: $$DeprecatedRpcParams.toJsonString, - clientVersion: "10", - }); + try { + return arriRequest({ + url: `${this._baseUrl}/rpcs/tests/deprecated-rpc`, + method: "post", + headers: this._headers, + params: params, + responseFromJson: () => {}, + responseFromString: () => {}, + serializer: $$DeprecatedRpcParams.toJsonString, + clientVersion: "10", + }); + } catch (err) { + if (this._onError) { + this._onError(err); + } + throw err; + } } async sendError(params: SendErrorParams): Promise { - return arriRequest({ - url: `${this._baseUrl}/rpcs/tests/send-error`, - method: "post", - headers: this._headers, - params: params, - responseFromJson: () => {}, - responseFromString: () => {}, - serializer: $$SendErrorParams.toJsonString, - clientVersion: "10", - }); + try { + return arriRequest({ + url: `${this._baseUrl}/rpcs/tests/send-error`, + method: "post", + headers: this._headers, + params: params, + responseFromJson: () => {}, + responseFromString: () => {}, + serializer: $$SendErrorParams.toJsonString, + clientVersion: "10", + }); + } catch (err) { + if (this._onError) { + this._onError(err); + } + throw err; + } } async sendObject( params: ObjectWithEveryType, ): Promise { - return arriRequest({ - url: `${this._baseUrl}/rpcs/tests/send-object`, - method: "post", - headers: this._headers, - params: params, - responseFromJson: $$ObjectWithEveryType.fromJson, - responseFromString: $$ObjectWithEveryType.fromJsonString, - serializer: $$ObjectWithEveryType.toJsonString, - clientVersion: "10", - }); + try { + return arriRequest({ + url: `${this._baseUrl}/rpcs/tests/send-object`, + method: "post", + headers: this._headers, + params: params, + responseFromJson: $$ObjectWithEveryType.fromJson, + responseFromString: $$ObjectWithEveryType.fromJsonString, + serializer: $$ObjectWithEveryType.toJsonString, + clientVersion: "10", + }); + } catch (err) { + if (this._onError) { + this._onError(err); + } + throw err; + } } async sendObjectWithNullableFields( params: ObjectWithEveryNullableType, ): Promise { - return arriRequest< - ObjectWithEveryNullableType, - ObjectWithEveryNullableType - >({ - url: `${this._baseUrl}/rpcs/tests/send-object-with-nullable-fields`, - method: "post", - headers: this._headers, - params: params, - responseFromJson: $$ObjectWithEveryNullableType.fromJson, - responseFromString: $$ObjectWithEveryNullableType.fromJsonString, - serializer: $$ObjectWithEveryNullableType.toJsonString, - clientVersion: "10", - }); + try { + return arriRequest< + ObjectWithEveryNullableType, + ObjectWithEveryNullableType + >({ + url: `${this._baseUrl}/rpcs/tests/send-object-with-nullable-fields`, + method: "post", + headers: this._headers, + params: params, + responseFromJson: $$ObjectWithEveryNullableType.fromJson, + responseFromString: + $$ObjectWithEveryNullableType.fromJsonString, + serializer: $$ObjectWithEveryNullableType.toJsonString, + clientVersion: "10", + }); + } catch (err) { + if (this._onError) { + this._onError(err); + } + throw err; + } } async sendPartialObject( params: ObjectWithEveryOptionalType, ): Promise { - return arriRequest< - ObjectWithEveryOptionalType, - ObjectWithEveryOptionalType - >({ - url: `${this._baseUrl}/rpcs/tests/send-partial-object`, - method: "post", - headers: this._headers, - params: params, - responseFromJson: $$ObjectWithEveryOptionalType.fromJson, - responseFromString: $$ObjectWithEveryOptionalType.fromJsonString, - serializer: $$ObjectWithEveryOptionalType.toJsonString, - clientVersion: "10", - }); + try { + return arriRequest< + ObjectWithEveryOptionalType, + ObjectWithEveryOptionalType + >({ + url: `${this._baseUrl}/rpcs/tests/send-partial-object`, + method: "post", + headers: this._headers, + params: params, + responseFromJson: $$ObjectWithEveryOptionalType.fromJson, + responseFromString: + $$ObjectWithEveryOptionalType.fromJsonString, + serializer: $$ObjectWithEveryOptionalType.toJsonString, + clientVersion: "10", + }); + } catch (err) { + if (this._onError) { + this._onError(err); + } + throw err; + } } async sendRecursiveObject( params: RecursiveObject, ): Promise { - return arriRequest({ - url: `${this._baseUrl}/rpcs/tests/send-recursive-object`, - method: "post", - headers: this._headers, - params: params, - responseFromJson: $$RecursiveObject.fromJson, - responseFromString: $$RecursiveObject.fromJsonString, - serializer: $$RecursiveObject.toJsonString, - clientVersion: "10", - }); + try { + return arriRequest({ + url: `${this._baseUrl}/rpcs/tests/send-recursive-object`, + method: "post", + headers: this._headers, + params: params, + responseFromJson: $$RecursiveObject.fromJson, + responseFromString: $$RecursiveObject.fromJsonString, + serializer: $$RecursiveObject.toJsonString, + clientVersion: "10", + }); + } catch (err) { + if (this._onError) { + this._onError(err); + } + throw err; + } } async sendRecursiveUnion(params: RecursiveUnion): Promise { - return arriRequest({ - url: `${this._baseUrl}/rpcs/tests/send-recursive-union`, - method: "post", - headers: this._headers, - params: params, - responseFromJson: $$RecursiveUnion.fromJson, - responseFromString: $$RecursiveUnion.fromJsonString, - serializer: $$RecursiveUnion.toJsonString, - clientVersion: "10", - }); + try { + return arriRequest({ + url: `${this._baseUrl}/rpcs/tests/send-recursive-union`, + method: "post", + headers: this._headers, + params: params, + responseFromJson: $$RecursiveUnion.fromJson, + responseFromString: $$RecursiveUnion.fromJsonString, + serializer: $$RecursiveUnion.toJsonString, + clientVersion: "10", + }); + } catch (err) { + if (this._onError) { + this._onError(err); + } + throw err; + } } streamAutoReconnect( params: AutoReconnectParams, options: SseOptions = {}, ): EventSourceController { - return arriSseRequest( - { - url: `${this._baseUrl}/rpcs/tests/stream-auto-reconnect`, - method: "get", - headers: this._headers, - params: params, - responseFromJson: $$AutoReconnectResponse.fromJson, - responseFromString: $$AutoReconnectResponse.fromJsonString, - serializer: $$AutoReconnectParams.toUrlQueryString, - clientVersion: "10", - }, - options, - ); + try { + return arriSseRequest( + { + url: `${this._baseUrl}/rpcs/tests/stream-auto-reconnect`, + method: "get", + headers: this._headers, + params: params, + responseFromJson: $$AutoReconnectResponse.fromJson, + responseFromString: $$AutoReconnectResponse.fromJsonString, + serializer: $$AutoReconnectParams.toUrlQueryString, + clientVersion: "10", + }, + options, + this._onError, + ); + } catch (err) { + if (this._onError) { + this._onError(err); + } + throw err; + } } /** * This route will always return an error. The client should automatically retry with exponential backoff. @@ -240,23 +333,33 @@ export class TestClientTestsService { params: StreamConnectionErrorTestParams, options: SseOptions = {}, ): EventSourceController { - return arriSseRequest< - StreamConnectionErrorTestResponse, - StreamConnectionErrorTestParams - >( - { - url: `${this._baseUrl}/rpcs/tests/stream-connection-error-test`, - method: "get", - headers: this._headers, - params: params, - responseFromJson: $$StreamConnectionErrorTestResponse.fromJson, - responseFromString: - $$StreamConnectionErrorTestResponse.fromJsonString, - serializer: $$StreamConnectionErrorTestParams.toUrlQueryString, - clientVersion: "10", - }, - options, - ); + try { + return arriSseRequest< + StreamConnectionErrorTestResponse, + StreamConnectionErrorTestParams + >( + { + url: `${this._baseUrl}/rpcs/tests/stream-connection-error-test`, + method: "get", + headers: this._headers, + params: params, + responseFromJson: + $$StreamConnectionErrorTestResponse.fromJson, + responseFromString: + $$StreamConnectionErrorTestResponse.fromJsonString, + serializer: + $$StreamConnectionErrorTestParams.toUrlQueryString, + clientVersion: "10", + }, + options, + this._onError, + ); + } catch (err) { + if (this._onError) { + this._onError(err); + } + throw err; + } } /** * Test to ensure that the client can handle receiving streams of large objects. When objects are large messages will sometimes get sent in chunks. Meaning you have to handle receiving a partial message @@ -264,59 +367,84 @@ export class TestClientTestsService { streamLargeObjects( options: SseOptions = {}, ): EventSourceController { - return arriSseRequest( - { - url: `${this._baseUrl}/rpcs/tests/stream-large-objects`, - method: "get", - headers: this._headers, + try { + return arriSseRequest( + { + url: `${this._baseUrl}/rpcs/tests/stream-large-objects`, + method: "get", + headers: this._headers, - responseFromJson: $$StreamLargeObjectsResponse.fromJson, - responseFromString: $$StreamLargeObjectsResponse.fromJsonString, - serializer: () => {}, - clientVersion: "10", - }, - options, - ); + responseFromJson: $$StreamLargeObjectsResponse.fromJson, + responseFromString: + $$StreamLargeObjectsResponse.fromJsonString, + serializer: () => {}, + clientVersion: "10", + }, + options, + this._onError, + ); + } catch (err) { + if (this._onError) { + this._onError(err); + } + throw err; + } } streamMessages( params: ChatMessageParams, options: SseOptions = {}, ): EventSourceController { - return arriSseRequest( - { - url: `${this._baseUrl}/rpcs/tests/stream-messages`, - method: "get", - headers: this._headers, - params: params, - responseFromJson: $$ChatMessage.fromJson, - responseFromString: $$ChatMessage.fromJsonString, - serializer: $$ChatMessageParams.toUrlQueryString, - clientVersion: "10", - }, - options, - ); + try { + return arriSseRequest( + { + url: `${this._baseUrl}/rpcs/tests/stream-messages`, + method: "get", + headers: this._headers, + params: params, + responseFromJson: $$ChatMessage.fromJson, + responseFromString: $$ChatMessage.fromJsonString, + serializer: $$ChatMessageParams.toUrlQueryString, + clientVersion: "10", + }, + options, + this._onError, + ); + } catch (err) { + if (this._onError) { + this._onError(err); + } + throw err; + } } streamRetryWithNewCredentials( options: SseOptions = {}, ): EventSourceController { - return arriSseRequest< - TestsStreamRetryWithNewCredentialsResponse, - undefined - >( - { - url: `${this._baseUrl}/rpcs/tests/stream-retry-with-new-credentials`, - method: "get", - headers: this._headers, + try { + return arriSseRequest< + TestsStreamRetryWithNewCredentialsResponse, + undefined + >( + { + url: `${this._baseUrl}/rpcs/tests/stream-retry-with-new-credentials`, + method: "get", + headers: this._headers, - responseFromJson: - $$TestsStreamRetryWithNewCredentialsResponse.fromJson, - responseFromString: - $$TestsStreamRetryWithNewCredentialsResponse.fromJsonString, - serializer: () => {}, - clientVersion: "10", - }, - options, - ); + responseFromJson: + $$TestsStreamRetryWithNewCredentialsResponse.fromJson, + responseFromString: + $$TestsStreamRetryWithNewCredentialsResponse.fromJsonString, + serializer: () => {}, + clientVersion: "10", + }, + options, + this._onError, + ); + } catch (err) { + if (this._onError) { + this._onError(err); + } + throw err; + } } /** * When the client receives the 'done' event, it should close the connection and NOT reconnect @@ -324,53 +452,75 @@ export class TestClientTestsService { streamTenEventsThenEnd( options: SseOptions = {}, ): EventSourceController { - return arriSseRequest( - { - url: `${this._baseUrl}/rpcs/tests/stream-ten-events-then-end`, - method: "get", - headers: this._headers, + try { + return arriSseRequest( + { + url: `${this._baseUrl}/rpcs/tests/stream-ten-events-then-end`, + method: "get", + headers: this._headers, - responseFromJson: $$ChatMessage.fromJson, - responseFromString: $$ChatMessage.fromJsonString, - serializer: () => {}, - clientVersion: "10", - }, - options, - ); + responseFromJson: $$ChatMessage.fromJson, + responseFromString: $$ChatMessage.fromJsonString, + serializer: () => {}, + clientVersion: "10", + }, + options, + this._onError, + ); + } catch (err) { + if (this._onError) { + this._onError(err); + } + throw err; + } } async websocketRpc( options: WsOptions = {}, ): Promise> { - return arriWsRequest({ - url: `${this._baseUrl}/rpcs/tests/websocket-rpc`, - headers: this._headers, - responseFromJson: $$WsMessageResponse.fromJson, - responseFromString: $$WsMessageResponse.fromJsonString, - serializer: $$WsMessageParams.toJsonString, - onOpen: options.onOpen, - onClose: options.onClose, - onError: options.onError, - onConnectionError: options.onConnectionError, - onMessage: options.onMessage, - clientVersion: "10", - }); + try { + return arriWsRequest({ + url: `${this._baseUrl}/rpcs/tests/websocket-rpc`, + headers: this._headers, + responseFromJson: $$WsMessageResponse.fromJson, + responseFromString: $$WsMessageResponse.fromJsonString, + serializer: $$WsMessageParams.toJsonString, + onOpen: options.onOpen, + onClose: options.onClose, + onError: options.onError, + onConnectionError: options.onConnectionError, + onMessage: options.onMessage, + clientVersion: "10", + }); + } catch (err) { + if (this._onError) { + this._onError(err); + } + throw err; + } } async websocketRpcSendTenLargeMessages( options: WsOptions = {}, ): Promise> { - return arriWsRequest({ - url: `${this._baseUrl}/rpcs/tests/websocket-rpc-send-ten-large-messages`, - headers: this._headers, - responseFromJson: $$StreamLargeObjectsResponse.fromJson, - responseFromString: $$StreamLargeObjectsResponse.fromJsonString, - serializer: () => {}, - onOpen: options.onOpen, - onClose: options.onClose, - onError: options.onError, - onConnectionError: options.onConnectionError, - onMessage: options.onMessage, - clientVersion: "10", - }); + try { + return arriWsRequest({ + url: `${this._baseUrl}/rpcs/tests/websocket-rpc-send-ten-large-messages`, + headers: this._headers, + responseFromJson: $$StreamLargeObjectsResponse.fromJson, + responseFromString: $$StreamLargeObjectsResponse.fromJsonString, + serializer: () => {}, + onOpen: options.onOpen, + onClose: options.onClose, + onError: options.onError, + onConnectionError: options.onConnectionError, + onMessage: options.onMessage, + clientVersion: "10", + }); + } catch (err) { + if (this._onError) { + this._onError(err); + } + throw err; + } } } @@ -379,33 +529,44 @@ export class TestClientUsersService { private readonly _headers: | HeaderMap | (() => HeaderMap | Promise); + private readonly _onError?: (err: unknown) => void; constructor( options: { baseUrl?: string; headers?: HeaderMap | (() => HeaderMap | Promise); + onError?: (err: unknown) => void; } = {}, ) { this._baseUrl = options.baseUrl ?? ""; this._headers = options.headers ?? {}; + this._onError = options.onError; } watchUser( params: UsersWatchUserParams, options: SseOptions = {}, ): EventSourceController { - return arriSseRequest( - { - url: `${this._baseUrl}/rpcs/users/watch-user`, - method: "get", - headers: this._headers, - params: params, - responseFromJson: $$UsersWatchUserResponse.fromJson, - responseFromString: $$UsersWatchUserResponse.fromJsonString, - serializer: $$UsersWatchUserParams.toUrlQueryString, - clientVersion: "10", - }, - options, - ); + try { + return arriSseRequest( + { + url: `${this._baseUrl}/rpcs/users/watch-user`, + method: "get", + headers: this._headers, + params: params, + responseFromJson: $$UsersWatchUserResponse.fromJson, + responseFromString: $$UsersWatchUserResponse.fromJsonString, + serializer: $$UsersWatchUserParams.toUrlQueryString, + clientVersion: "10", + }, + options, + this._onError, + ); + } catch (err) { + if (this._onError) { + this._onError(err); + } + throw err; + } } } From 2ee09d279cf4c312c951421fa5dc329ea4be413d Mon Sep 17 00:00:00 2001 From: Joshua Sosso Date: Tue, 8 Oct 2024 16:45:10 -0500 Subject: [PATCH 02/10] add onError hook to dart client --- languages/dart/dart-client/lib/request.dart | 32 +++-- languages/dart/dart-client/lib/ws.dart | 1 + .../lib/reference_client.dart | 30 ++++- languages/dart/dart-codegen/src/_index.ts | 8 +- languages/dart/dart-codegen/src/procedures.ts | 21 +++- tests/clients/dart/lib/test_client.rpc.dart | 113 ++++++++++++++++-- tests/clients/dart/test/test_client_test.dart | 15 +++ 7 files changed, 186 insertions(+), 34 deletions(-) diff --git a/languages/dart/dart-client/lib/request.dart b/languages/dart/dart-client/lib/request.dart index 1e2bdf1a..cbd311fd 100644 --- a/languages/dart/dart-client/lib/request.dart +++ b/languages/dart/dart-client/lib/request.dart @@ -126,21 +126,31 @@ Future parsedArriRequest( HttpMethod method = HttpMethod.post, Map? params, FutureOr> 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 diff --git a/languages/dart/dart-client/lib/ws.dart b/languages/dart/dart-client/lib/ws.dart index fba9158b..1fe4d31f 100644 --- a/languages/dart/dart-client/lib/ws.dart +++ b/languages/dart/dart-client/lib/ws.dart @@ -10,6 +10,7 @@ Future> FutureOr> Function()? headers, required TServerMessage Function(String msg) parser, required String Function(TClientMessage msg) serializer, + Function(Object)? onError, String? clientVersion, }) async { var finalUrl = diff --git a/languages/dart/dart-codegen-reference/lib/reference_client.dart b/languages/dart/dart-codegen-reference/lib/reference_client.dart index 0b7c773d..57003e31 100644 --- a/languages/dart/dart-codegen-reference/lib/reference_client.dart +++ b/languages/dart/dart-codegen-reference/lib/reference_client.dart @@ -9,14 +9,17 @@ class ExampleClient { final http.Client? _httpClient; final String _baseUrl; final String _clientVersion = "20"; - late final FutureOr> Function()? _headers; + final FutureOr> Function()? _headers; + final Function(Object)? _onError; ExampleClient({ http.Client? httpClient, required String baseUrl, FutureOr> Function()? headers, + Function(Object)? onError, }) : _httpClient = httpClient, _baseUrl = baseUrl, - _headers = headers; + _headers = headers, + _onError = onError; Future sendObject(NestedObject params) async { return parsedArriRequest( @@ -27,6 +30,7 @@ class ExampleClient { clientVersion: _clientVersion, params: params.toJson(), parser: (body) => NestedObject.fromJsonString(body), + onError: _onError, ); } @@ -34,6 +38,7 @@ class ExampleClient { baseUrl: _baseUrl, headers: _headers, httpClient: _httpClient, + onError: _onError, ); } @@ -41,14 +46,17 @@ class ExampleClientBooksService { final http.Client? _httpClient; final String _baseUrl; final String _clientVersion = "20"; - late final FutureOr> Function()? _headers; + final FutureOr> Function()? _headers; + final Function(Object)? _onError; ExampleClientBooksService({ http.Client? httpClient, required String baseUrl, FutureOr> Function()? headers, + Function(Object)? onError, }) : _httpClient = httpClient, _baseUrl = baseUrl, - _headers = headers; + _headers = headers, + _onError = onError; /// Get a book Future getBook(BookParams params) async { @@ -60,6 +68,7 @@ class ExampleClientBooksService { clientVersion: _clientVersion, params: params.toJson(), parser: (body) => Book.fromJsonString(body), + onError: _onError, ); } @@ -74,6 +83,7 @@ class ExampleClientBooksService { clientVersion: _clientVersion, params: params.toJson(), parser: (body) => Book.fromJsonString(body), + onError: _onError, ); } @@ -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, ); } @@ -114,6 +133,7 @@ class ExampleClientBooksService { clientVersion: _clientVersion, parser: (msg) => Book.fromJsonString(msg), serializer: (msg) => msg.toJsonString(), + onError: _onError, ); } } diff --git a/languages/dart/dart-codegen/src/_index.ts b/languages/dart/dart-codegen/src/_index.ts index 92613fa8..8edb6dc3 100644 --- a/languages/dart/dart-codegen/src/_index.ts +++ b/languages/dart/dart-codegen/src/_index.ts @@ -167,14 +167,17 @@ class ${clientName} { final http.Client? _httpClient; final String _baseUrl; final String _clientVersion = "${context.clientVersion ?? ""}"; - late final FutureOr> Function()? _headers; + final FutureOr> Function()? _headers; + final Function(Object)? _onError; ${clientName}({ http.Client? httpClient, required String baseUrl, FutureOr> Function()? headers, + Function(Object)? onError, }) : _httpClient = httpClient, _baseUrl = baseUrl, - _headers = headers; + _headers = headers, + _onError = onError; ${rpcParts.join("\n\n")} @@ -184,6 +187,7 @@ ${subServices baseUrl: _baseUrl, headers: _headers, httpClient: _httpClient, + onError: _onError, );`, ) .join("\n\n")} diff --git a/languages/dart/dart-codegen/src/procedures.ts b/languages/dart/dart-codegen/src/procedures.ts index 93301a17..27fa8e16 100644 --- a/languages/dart/dart-codegen/src/procedures.ts +++ b/languages/dart/dart-codegen/src/procedures.ts @@ -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, ); }`; } @@ -87,6 +96,7 @@ export function dartHttpRpcFromSchema( clientVersion: _clientVersion, ${paramsType ? "params: params.toJson()," : ""} parser: (body) ${schema.response ? `=> ${responseType}.fromJsonString(body)` : "{}"}, + onError: _onError, ); }`; } @@ -120,6 +130,7 @@ export function dartWsRpcFromSchema( clientVersion: _clientVersion, parser: (msg) ${responseType ? `=> ${responseType}.fromJsonString(msg)` : "{}"}, serializer: (msg) ${paramsType ? "=> msg.toJsonString()" : '=> ""'}, + onError: _onError, ); }`; } @@ -180,14 +191,17 @@ export function dartServiceFromSchema( final http.Client? _httpClient; final String _baseUrl; final String _clientVersion = "${context.clientVersion}"; - late final FutureOr> Function()? _headers; + final FutureOr> Function()? _headers; + final Function(Object)? _onError; ${serviceName}({ http.Client? httpClient, required String baseUrl, FutureOr> Function()? headers, + Function(Object)? onError, }) : _httpClient = httpClient, _baseUrl = baseUrl, - _headers = headers; + _headers = headers, + _onError = onError; ${rpcParts.join("\n\n")} @@ -197,6 +211,7 @@ export function dartServiceFromSchema( baseUrl: _baseUrl, headers: _headers, httpClient: _httpClient, + onError: _onError, );`, ) .join("\n\n")} diff --git a/tests/clients/dart/lib/test_client.rpc.dart b/tests/clients/dart/lib/test_client.rpc.dart index c25ff4a6..68a7b057 100644 --- a/tests/clients/dart/lib/test_client.rpc.dart +++ b/tests/clients/dart/lib/test_client.rpc.dart @@ -9,25 +9,30 @@ class TestClient { final http.Client? _httpClient; final String _baseUrl; final String _clientVersion = "10"; - late final FutureOr> Function()? _headers; + final FutureOr> Function()? _headers; + final Function(Object)? _onError; TestClient({ http.Client? httpClient, required String baseUrl, FutureOr> Function()? headers, + Function(Object)? onError, }) : _httpClient = httpClient, _baseUrl = baseUrl, - _headers = headers; + _headers = headers, + _onError = onError; TestClientTestsService get tests => TestClientTestsService( baseUrl: _baseUrl, headers: _headers, httpClient: _httpClient, + onError: _onError, ); TestClientUsersService get users => TestClientUsersService( baseUrl: _baseUrl, headers: _headers, httpClient: _httpClient, + onError: _onError, ); } @@ -35,14 +40,17 @@ class TestClientTestsService { final http.Client? _httpClient; final String _baseUrl; final String _clientVersion = "10"; - late final FutureOr> Function()? _headers; + final FutureOr> Function()? _headers; + final Function(Object)? _onError; TestClientTestsService({ http.Client? httpClient, required String baseUrl, FutureOr> Function()? headers, + Function(Object)? onError, }) : _httpClient = httpClient, _baseUrl = baseUrl, - _headers = headers; + _headers = headers, + _onError = onError; Future emptyParamsGetRequest() async { return parsedArriRequest( @@ -52,6 +60,7 @@ class TestClientTestsService { headers: _headers, clientVersion: _clientVersion, parser: (body) => DefaultPayload.fromJsonString(body), + onError: _onError, ); } @@ -63,6 +72,7 @@ class TestClientTestsService { headers: _headers, clientVersion: _clientVersion, parser: (body) => DefaultPayload.fromJsonString(body), + onError: _onError, ); } @@ -75,6 +85,7 @@ class TestClientTestsService { clientVersion: _clientVersion, params: params.toJson(), parser: (body) {}, + onError: _onError, ); } @@ -87,6 +98,7 @@ class TestClientTestsService { clientVersion: _clientVersion, params: params.toJson(), parser: (body) {}, + onError: _onError, ); } @@ -101,6 +113,7 @@ class TestClientTestsService { clientVersion: _clientVersion, params: params.toJson(), parser: (body) {}, + onError: _onError, ); } @@ -113,6 +126,7 @@ class TestClientTestsService { clientVersion: _clientVersion, params: params.toJson(), parser: (body) {}, + onError: _onError, ); } @@ -125,6 +139,7 @@ class TestClientTestsService { clientVersion: _clientVersion, params: params.toJson(), parser: (body) => ObjectWithEveryType.fromJsonString(body), + onError: _onError, ); } @@ -138,6 +153,7 @@ class TestClientTestsService { clientVersion: _clientVersion, params: params.toJson(), parser: (body) => ObjectWithEveryNullableType.fromJsonString(body), + onError: _onError, ); } @@ -151,6 +167,7 @@ class TestClientTestsService { clientVersion: _clientVersion, params: params.toJson(), parser: (body) => ObjectWithEveryOptionalType.fromJsonString(body), + onError: _onError, ); } @@ -163,6 +180,7 @@ class TestClientTestsService { clientVersion: _clientVersion, params: params.toJson(), parser: (body) => RecursiveObject.fromJsonString(body), + onError: _onError, ); } @@ -175,6 +193,7 @@ class TestClientTestsService { clientVersion: _clientVersion, params: params.toJson(), parser: (body) => RecursiveUnion.fromJsonString(body), + onError: _onError, ); } @@ -208,7 +227,16 @@ class TestClientTestsService { 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, ); } @@ -244,7 +272,16 @@ class TestClientTestsService { 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, ); } @@ -277,7 +314,16 @@ class TestClientTestsService { 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, ); } @@ -309,7 +355,16 @@ class TestClientTestsService { 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, ); } @@ -345,7 +400,16 @@ class TestClientTestsService { 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, ); } @@ -376,7 +440,16 @@ class TestClientTestsService { 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, ); } @@ -388,6 +461,7 @@ class TestClientTestsService { clientVersion: _clientVersion, parser: (msg) => WsMessageResponse.fromJsonString(msg), serializer: (msg) => msg.toJsonString(), + onError: _onError, ); } @@ -399,6 +473,7 @@ class TestClientTestsService { clientVersion: _clientVersion, parser: (msg) => StreamLargeObjectsResponse.fromJsonString(msg), serializer: (msg) => "", + onError: _onError, ); } } @@ -407,14 +482,17 @@ class TestClientUsersService { final http.Client? _httpClient; final String _baseUrl; final String _clientVersion = "10"; - late final FutureOr> Function()? _headers; + final FutureOr> Function()? _headers; + final Function(Object)? _onError; TestClientUsersService({ http.Client? httpClient, required String baseUrl, FutureOr> Function()? headers, + Function(Object)? onError, }) : _httpClient = httpClient, _baseUrl = baseUrl, - _headers = headers; + _headers = headers, + _onError = onError; EventSource watchUser( UsersWatchUserParams params, { @@ -446,7 +524,16 @@ class TestClientUsersService { 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, ); } } diff --git a/tests/clients/dart/test/test_client_test.dart b/tests/clients/dart/test/test_client_test.dart index 6d7e0e98..73b4a6ff 100644 --- a/tests/clients/dart/test/test_client_test.dart +++ b/tests/clients/dart/test/test_client_test.dart @@ -277,6 +277,21 @@ Future main() async { equals(true)); }); + test("onError hook fires", () async { + bool onErrFired = false; + final customClient = TestClient( + baseUrl: baseUrl, + onError: (err) { + onErrFired = true; + expect(err is ArriError, equals(true)); + }, + ); + try { + await customClient.tests.sendObject(input); + } catch (_) {} + expect(onErrFired, equals(true)); + }); + test("[SSE] supports server sent events", () async { int messageCount = 0; final eventSource = client.tests.streamMessages( From 184059b0a7ea68f084a17198998ba5d0a93d4295 Mon Sep 17 00:00:00 2001 From: Joshua Sosso Date: Tue, 8 Oct 2024 17:01:48 -0500 Subject: [PATCH 03/10] fix issue with ts onError hook add test to ensure onError hook fires in generated ts clients --- languages/ts/ts-client/src/request.ts | 8 +- languages/ts/ts-client/src/sse.ts | 9 +- languages/ts/ts-client/src/ws.test.ts | 2 + languages/ts/ts-client/src/ws.ts | 1 - .../src/referenceClient.test.ts | 2 + .../src/referenceClient.ts | 152 ++--- languages/ts/ts-codegen/src/rpc.ts | 92 +-- tests/clients/ts/testClient.rpc.ts | 644 +++++++----------- tests/clients/ts/testClient.test.ts | 17 + 9 files changed, 382 insertions(+), 545 deletions(-) diff --git a/languages/ts/ts-client/src/request.ts b/languages/ts/ts-client/src/request.ts index 1cc89eae..6f97b12c 100644 --- a/languages/ts/ts-client/src/request.ts +++ b/languages/ts/ts-client/src/request.ts @@ -15,6 +15,7 @@ export interface ArriRequestOpts< params?: TParams; responseFromJson: (input: Record) => TType; responseFromString: (input: string) => TType; + onError?: (err: unknown) => void; serializer: ( input: TParams, ) => TParams extends undefined ? undefined : string; @@ -54,10 +55,11 @@ export async function arriRequest< return opts.responseFromJson(result); } catch (err) { const error = err as any as FetchError; + let arriError: ArriErrorInstance; if (isArriError(error.data)) { - throw new ArriErrorInstance(error.data); + arriError = new ArriErrorInstance(error.data); } else { - throw new ArriErrorInstance({ + arriError = new ArriErrorInstance({ code: error.statusCode ?? 500, message: error.statusMessage ?? @@ -67,6 +69,8 @@ export async function arriRequest< stack: error.stack, }); } + if (opts.onError) opts.onError(arriError); + throw arriError; } } diff --git a/languages/ts/ts-client/src/sse.ts b/languages/ts/ts-client/src/sse.ts index ba1aee85..9761796a 100644 --- a/languages/ts/ts-client/src/sse.ts +++ b/languages/ts/ts-client/src/sse.ts @@ -42,7 +42,6 @@ export function arriSseRequest< >( opts: ArriRequestOpts, options: SseOptions, - rootOnError?: (err: unknown) => void, ): EventSourceController { let url = opts.url; let body: undefined | string; @@ -96,8 +95,8 @@ export function arriSseRequest< options.onRequest?.(context); }, onRequestError(context) { - if (rootOnError) { - rootOnError(context.error); + if (opts.onError) { + opts.onError(context.error); } options.onRequestError?.({ ...context, @@ -112,8 +111,8 @@ export function arriSseRequest< options.onResponse?.(context); }, async onResponseError(context) { - if (rootOnError) { - rootOnError(context.error); + if (opts.onError) { + opts.onError(context.error); } if (!options.onResponseError) { return; diff --git a/languages/ts/ts-client/src/ws.test.ts b/languages/ts/ts-client/src/ws.test.ts index 806e0979..75f3869a 100644 --- a/languages/ts/ts-client/src/ws.test.ts +++ b/languages/ts/ts-client/src/ws.test.ts @@ -1,3 +1,5 @@ +import { expect, test } from "vitest"; + import { parsedWsResponse } from "./ws"; test("Parse WS Response", () => { diff --git a/languages/ts/ts-client/src/ws.ts b/languages/ts/ts-client/src/ws.ts index 0d247744..0c9eb09f 100644 --- a/languages/ts/ts-client/src/ws.ts +++ b/languages/ts/ts-client/src/ws.ts @@ -87,7 +87,6 @@ export async function arriWsRequest< } catch (err) { console.error(err); if (opts.onConnectionError) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument opts.onConnectionError(err as any); } return arriWsRequest(opts, retryCount + 1); diff --git a/languages/ts/ts-codegen-reference/src/referenceClient.test.ts b/languages/ts/ts-codegen-reference/src/referenceClient.test.ts index 5667b66f..3896a076 100644 --- a/languages/ts/ts-codegen-reference/src/referenceClient.test.ts +++ b/languages/ts/ts-codegen-reference/src/referenceClient.test.ts @@ -1,6 +1,8 @@ import fs from "node:fs"; import path from "node:path"; +import { describe, expect, test } from "vitest"; + import { $$Book, $$NestedObject, diff --git a/languages/ts/ts-codegen-reference/src/referenceClient.ts b/languages/ts/ts-codegen-reference/src/referenceClient.ts index 635a2054..4b79b4e4 100644 --- a/languages/ts/ts-codegen-reference/src/referenceClient.ts +++ b/languages/ts/ts-codegen-reference/src/referenceClient.ts @@ -52,23 +52,17 @@ export class ExampleClient { } async sendObject(params: NestedObject): Promise { - try { - return arriRequest({ - url: `${this._baseUrl}/send-object`, - method: "post", - headers: this._headers, - params: params, - responseFromJson: $$NestedObject.fromJson, - responseFromString: $$NestedObject.fromJsonString, - serializer: $$NestedObject.toJsonString, - clientVersion: "20", - }); - } catch (err) { - if (this._onError) { - this._onError(err); - } - throw err; - } + return arriRequest({ + url: `${this._baseUrl}/send-object`, + method: "post", + headers: this._headers, + onError: this._onError, + params: params, + responseFromJson: $$NestedObject.fromJson, + responseFromString: $$NestedObject.fromJsonString, + serializer: $$NestedObject.toJsonString, + clientVersion: "20", + }); } } @@ -93,46 +87,34 @@ export class ExampleClientBooksService { * Get a book */ async getBook(params: BookParams): Promise { - try { - return arriRequest({ - url: `${this._baseUrl}/books/get-book`, - method: "get", - headers: this._headers, - params: params, - responseFromJson: $$Book.fromJson, - responseFromString: $$Book.fromJsonString, - serializer: $$BookParams.toUrlQueryString, - clientVersion: "20", - }); - } catch (err) { - if (this._onError) { - this._onError(err); - } - throw err; - } + return arriRequest({ + url: `${this._baseUrl}/books/get-book`, + method: "get", + headers: this._headers, + onError: this._onError, + params: params, + responseFromJson: $$Book.fromJson, + responseFromString: $$Book.fromJsonString, + serializer: $$BookParams.toUrlQueryString, + clientVersion: "20", + }); } /** * Create a book * @deprecated */ async createBook(params: Book): Promise { - try { - return arriRequest({ - url: `${this._baseUrl}/books/create-book`, - method: "post", - headers: this._headers, - params: params, - responseFromJson: $$Book.fromJson, - responseFromString: $$Book.fromJsonString, - serializer: $$Book.toJsonString, - clientVersion: "20", - }); - } catch (err) { - if (this._onError) { - this._onError(err); - } - throw err; - } + return arriRequest({ + url: `${this._baseUrl}/books/create-book`, + method: "post", + headers: this._headers, + onError: this._onError, + params: params, + responseFromJson: $$Book.fromJson, + responseFromString: $$Book.fromJsonString, + serializer: $$Book.toJsonString, + clientVersion: "20", + }); } /** * @deprecated @@ -141,51 +123,37 @@ export class ExampleClientBooksService { params: BookParams, options: SseOptions = {}, ): EventSourceController { - try { - return arriSseRequest( - { - url: `${this._baseUrl}/books/watch-book`, - method: "get", - headers: this._headers, - params: params, - responseFromJson: $$Book.fromJson, - responseFromString: $$Book.fromJsonString, - serializer: $$BookParams.toUrlQueryString, - clientVersion: "20", - }, - options, - this._onError, - ); - } catch (err) { - if (this._onError) { - this._onError(err); - } - throw err; - } - } - async createConnection( - options: WsOptions = {}, - ): Promise> { - try { - return arriWsRequest({ - url: `${this._baseUrl}/books/create-connection`, + return arriSseRequest( + { + url: `${this._baseUrl}/books/watch-book`, + method: "get", headers: this._headers, + onError: this._onError, + params: params, responseFromJson: $$Book.fromJson, responseFromString: $$Book.fromJsonString, - serializer: $$BookParams.toJsonString, - onOpen: options.onOpen, - onClose: options.onClose, - onError: options.onError, - onConnectionError: options.onConnectionError, - onMessage: options.onMessage, + serializer: $$BookParams.toUrlQueryString, clientVersion: "20", - }); - } catch (err) { - if (this._onError) { - this._onError(err); - } - throw err; - } + }, + options, + ); + } + async createConnection( + options: WsOptions = {}, + ): Promise> { + return arriWsRequest({ + url: `${this._baseUrl}/books/create-connection`, + headers: this._headers, + responseFromJson: $$Book.fromJson, + responseFromString: $$Book.fromJsonString, + serializer: $$BookParams.toJsonString, + onOpen: options.onOpen, + onClose: options.onClose, + onError: options.onError, + onConnectionError: options.onConnectionError, + onMessage: options.onMessage, + clientVersion: "20", + }); } } diff --git a/languages/ts/ts-codegen/src/rpc.ts b/languages/ts/ts-codegen/src/rpc.ts index a44e5562..292f3d0a 100644 --- a/languages/ts/ts-codegen/src/rpc.ts +++ b/languages/ts/ts-codegen/src/rpc.ts @@ -43,50 +43,37 @@ export function httpRpcFromDefinition( description: def.description, isDeprecated: def.isDeprecated, })} ${key}(${params ? `params: ${params},` : ""} options: SseOptions<${response ?? "undefined"}> = {}): EventSourceController { - try { - return arriSseRequest<${response ?? "undefined"}, ${params ?? "undefined"}>( - { - url: \`\${this._baseUrl}${def.path}\`, - method: "${def.method.toLowerCase()}", - headers: this._headers, - ${params ? "params: params," : ""} - responseFromJson: ${response ? `$$${response}.fromJson` : "() => {}"}, - responseFromString: ${response ? `$$${response}.fromJsonString` : "() => {}"}, - serializer: ${params ? `$$${params}.${serializerMethod}` : "() => {}"}, - clientVersion: "${context.versionNumber}", - }, - options, - this._onError, - ); - } catch(err) { - if (this._onError) { - this._onError(err); - } - throw err; - } - }`; - } - return `${getJsDocComment({ - description: def.description, - isDeprecated: def.isDeprecated, - })} async ${key}(${params ? `params: ${params}` : ""}): Promise<${response ?? "undefined"}> { - try { - return arriRequest<${response ?? "undefined"}, ${params ?? "undefined"}>({ + return arriSseRequest<${response ?? "undefined"}, ${params ?? "undefined"}>( + { url: \`\${this._baseUrl}${def.path}\`, method: "${def.method.toLowerCase()}", headers: this._headers, + onError: this._onError, ${params ? "params: params," : ""} responseFromJson: ${response ? `$$${response}.fromJson` : "() => {}"}, responseFromString: ${response ? `$$${response}.fromJsonString` : "() => {}"}, serializer: ${params ? `$$${params}.${serializerMethod}` : "() => {}"}, clientVersion: "${context.versionNumber}", - }); - } catch (err) { - if (this._onError) { - this._onError(err); - } - throw err; - } + }, + options, + ); + }`; + } + return `${getJsDocComment({ + description: def.description, + isDeprecated: def.isDeprecated, + })} async ${key}(${params ? `params: ${params}` : ""}): Promise<${response ?? "undefined"}> { + return arriRequest<${response ?? "undefined"}, ${params ?? "undefined"}>({ + url: \`\${this._baseUrl}${def.path}\`, + method: "${def.method.toLowerCase()}", + headers: this._headers, + onError: this._onError, + ${params ? "params: params," : ""} + responseFromJson: ${response ? `$$${response}.fromJson` : "() => {}"}, + responseFromString: ${response ? `$$${response}.fromJsonString` : "() => {}"}, + serializer: ${params ? `$$${params}.${serializerMethod}` : "() => {}"}, + clientVersion: "${context.versionNumber}", + }); }`; } @@ -106,26 +93,19 @@ export function wsRpcFromDefinition( description: def.description, isDeprecated: def.isDeprecated, })} async ${key}(options: WsOptions<${response ?? "undefined"}> = {}): Promise> { - try { - return arriWsRequest<${params ?? "undefined"}, ${response ?? "undefined"}>({ - url: \`\${this._baseUrl}${def.path}\`, - headers: this._headers, - responseFromJson: ${response ? `$$${response}.fromJson` : "() => {}"}, - responseFromString: ${response ? `$$${response}.fromJsonString` : "() => {}"}, - serializer: ${params ? `$$${params}.toJsonString` : "() => {}"}, - onOpen: options.onOpen, - onClose: options.onClose, - onError: options.onError, - onConnectionError: options.onConnectionError, - onMessage: options.onMessage, - clientVersion: "${context.versionNumber}", - }); - } catch (err) { - if (this._onError) { - this._onError(err); - } - throw err; - } + return arriWsRequest<${params ?? "undefined"}, ${response ?? "undefined"}>({ + url: \`\${this._baseUrl}${def.path}\`, + headers: this._headers, + responseFromJson: ${response ? `$$${response}.fromJson` : "() => {}"}, + responseFromString: ${response ? `$$${response}.fromJsonString` : "() => {}"}, + serializer: ${params ? `$$${params}.toJsonString` : "() => {}"}, + onOpen: options.onOpen, + onClose: options.onClose, + onError: options.onError, + onConnectionError: options.onConnectionError, + onMessage: options.onMessage, + clientVersion: "${context.versionNumber}", + }); }`; } diff --git a/tests/clients/ts/testClient.rpc.ts b/tests/clients/ts/testClient.rpc.ts index 8d0658b5..41468c02 100644 --- a/tests/clients/ts/testClient.rpc.ts +++ b/tests/clients/ts/testClient.rpc.ts @@ -72,259 +72,184 @@ export class TestClientTestsService { this._onError = options.onError; } async emptyParamsGetRequest(): Promise { - try { - return arriRequest({ - url: `${this._baseUrl}/rpcs/tests/empty-params-get-request`, - method: "get", - headers: this._headers, + return arriRequest({ + url: `${this._baseUrl}/rpcs/tests/empty-params-get-request`, + method: "get", + headers: this._headers, + onError: this._onError, - responseFromJson: $$DefaultPayload.fromJson, - responseFromString: $$DefaultPayload.fromJsonString, - serializer: () => {}, - clientVersion: "10", - }); - } catch (err) { - if (this._onError) { - this._onError(err); - } - throw err; - } + responseFromJson: $$DefaultPayload.fromJson, + responseFromString: $$DefaultPayload.fromJsonString, + serializer: () => {}, + clientVersion: "10", + }); } async emptyParamsPostRequest(): Promise { - try { - return arriRequest({ - url: `${this._baseUrl}/rpcs/tests/empty-params-post-request`, - method: "post", - headers: this._headers, + return arriRequest({ + url: `${this._baseUrl}/rpcs/tests/empty-params-post-request`, + method: "post", + headers: this._headers, + onError: this._onError, - responseFromJson: $$DefaultPayload.fromJson, - responseFromString: $$DefaultPayload.fromJsonString, - serializer: () => {}, - clientVersion: "10", - }); - } catch (err) { - if (this._onError) { - this._onError(err); - } - throw err; - } + responseFromJson: $$DefaultPayload.fromJson, + responseFromString: $$DefaultPayload.fromJsonString, + serializer: () => {}, + clientVersion: "10", + }); } async emptyResponseGetRequest(params: DefaultPayload): Promise { - try { - return arriRequest({ - url: `${this._baseUrl}/rpcs/tests/empty-response-get-request`, - method: "get", - headers: this._headers, - params: params, - responseFromJson: () => {}, - responseFromString: () => {}, - serializer: $$DefaultPayload.toUrlQueryString, - clientVersion: "10", - }); - } catch (err) { - if (this._onError) { - this._onError(err); - } - throw err; - } + return arriRequest({ + url: `${this._baseUrl}/rpcs/tests/empty-response-get-request`, + method: "get", + headers: this._headers, + onError: this._onError, + params: params, + responseFromJson: () => {}, + responseFromString: () => {}, + serializer: $$DefaultPayload.toUrlQueryString, + clientVersion: "10", + }); } async emptyResponsePostRequest(params: DefaultPayload): Promise { - try { - return arriRequest({ - url: `${this._baseUrl}/rpcs/tests/empty-response-post-request`, - method: "post", - headers: this._headers, - params: params, - responseFromJson: () => {}, - responseFromString: () => {}, - serializer: $$DefaultPayload.toJsonString, - clientVersion: "10", - }); - } catch (err) { - if (this._onError) { - this._onError(err); - } - throw err; - } + return arriRequest({ + url: `${this._baseUrl}/rpcs/tests/empty-response-post-request`, + method: "post", + headers: this._headers, + onError: this._onError, + params: params, + responseFromJson: () => {}, + responseFromString: () => {}, + serializer: $$DefaultPayload.toJsonString, + clientVersion: "10", + }); } /** * If the target language supports it. Generated code should mark this procedure as deprecated. * @deprecated */ async deprecatedRpc(params: DeprecatedRpcParams): Promise { - try { - return arriRequest({ - url: `${this._baseUrl}/rpcs/tests/deprecated-rpc`, - method: "post", - headers: this._headers, - params: params, - responseFromJson: () => {}, - responseFromString: () => {}, - serializer: $$DeprecatedRpcParams.toJsonString, - clientVersion: "10", - }); - } catch (err) { - if (this._onError) { - this._onError(err); - } - throw err; - } + return arriRequest({ + url: `${this._baseUrl}/rpcs/tests/deprecated-rpc`, + method: "post", + headers: this._headers, + onError: this._onError, + params: params, + responseFromJson: () => {}, + responseFromString: () => {}, + serializer: $$DeprecatedRpcParams.toJsonString, + clientVersion: "10", + }); } async sendError(params: SendErrorParams): Promise { - try { - return arriRequest({ - url: `${this._baseUrl}/rpcs/tests/send-error`, - method: "post", - headers: this._headers, - params: params, - responseFromJson: () => {}, - responseFromString: () => {}, - serializer: $$SendErrorParams.toJsonString, - clientVersion: "10", - }); - } catch (err) { - if (this._onError) { - this._onError(err); - } - throw err; - } + return arriRequest({ + url: `${this._baseUrl}/rpcs/tests/send-error`, + method: "post", + headers: this._headers, + onError: this._onError, + params: params, + responseFromJson: () => {}, + responseFromString: () => {}, + serializer: $$SendErrorParams.toJsonString, + clientVersion: "10", + }); } async sendObject( params: ObjectWithEveryType, ): Promise { - try { - return arriRequest({ - url: `${this._baseUrl}/rpcs/tests/send-object`, - method: "post", - headers: this._headers, - params: params, - responseFromJson: $$ObjectWithEveryType.fromJson, - responseFromString: $$ObjectWithEveryType.fromJsonString, - serializer: $$ObjectWithEveryType.toJsonString, - clientVersion: "10", - }); - } catch (err) { - if (this._onError) { - this._onError(err); - } - throw err; - } + return arriRequest({ + url: `${this._baseUrl}/rpcs/tests/send-object`, + method: "post", + headers: this._headers, + onError: this._onError, + params: params, + responseFromJson: $$ObjectWithEveryType.fromJson, + responseFromString: $$ObjectWithEveryType.fromJsonString, + serializer: $$ObjectWithEveryType.toJsonString, + clientVersion: "10", + }); } async sendObjectWithNullableFields( params: ObjectWithEveryNullableType, ): Promise { - try { - return arriRequest< - ObjectWithEveryNullableType, - ObjectWithEveryNullableType - >({ - url: `${this._baseUrl}/rpcs/tests/send-object-with-nullable-fields`, - method: "post", - headers: this._headers, - params: params, - responseFromJson: $$ObjectWithEveryNullableType.fromJson, - responseFromString: - $$ObjectWithEveryNullableType.fromJsonString, - serializer: $$ObjectWithEveryNullableType.toJsonString, - clientVersion: "10", - }); - } catch (err) { - if (this._onError) { - this._onError(err); - } - throw err; - } + return arriRequest< + ObjectWithEveryNullableType, + ObjectWithEveryNullableType + >({ + url: `${this._baseUrl}/rpcs/tests/send-object-with-nullable-fields`, + method: "post", + headers: this._headers, + onError: this._onError, + params: params, + responseFromJson: $$ObjectWithEveryNullableType.fromJson, + responseFromString: $$ObjectWithEveryNullableType.fromJsonString, + serializer: $$ObjectWithEveryNullableType.toJsonString, + clientVersion: "10", + }); } async sendPartialObject( params: ObjectWithEveryOptionalType, ): Promise { - try { - return arriRequest< - ObjectWithEveryOptionalType, - ObjectWithEveryOptionalType - >({ - url: `${this._baseUrl}/rpcs/tests/send-partial-object`, - method: "post", - headers: this._headers, - params: params, - responseFromJson: $$ObjectWithEveryOptionalType.fromJson, - responseFromString: - $$ObjectWithEveryOptionalType.fromJsonString, - serializer: $$ObjectWithEveryOptionalType.toJsonString, - clientVersion: "10", - }); - } catch (err) { - if (this._onError) { - this._onError(err); - } - throw err; - } + return arriRequest< + ObjectWithEveryOptionalType, + ObjectWithEveryOptionalType + >({ + url: `${this._baseUrl}/rpcs/tests/send-partial-object`, + method: "post", + headers: this._headers, + onError: this._onError, + params: params, + responseFromJson: $$ObjectWithEveryOptionalType.fromJson, + responseFromString: $$ObjectWithEveryOptionalType.fromJsonString, + serializer: $$ObjectWithEveryOptionalType.toJsonString, + clientVersion: "10", + }); } async sendRecursiveObject( params: RecursiveObject, ): Promise { - try { - return arriRequest({ - url: `${this._baseUrl}/rpcs/tests/send-recursive-object`, - method: "post", - headers: this._headers, - params: params, - responseFromJson: $$RecursiveObject.fromJson, - responseFromString: $$RecursiveObject.fromJsonString, - serializer: $$RecursiveObject.toJsonString, - clientVersion: "10", - }); - } catch (err) { - if (this._onError) { - this._onError(err); - } - throw err; - } + return arriRequest({ + url: `${this._baseUrl}/rpcs/tests/send-recursive-object`, + method: "post", + headers: this._headers, + onError: this._onError, + params: params, + responseFromJson: $$RecursiveObject.fromJson, + responseFromString: $$RecursiveObject.fromJsonString, + serializer: $$RecursiveObject.toJsonString, + clientVersion: "10", + }); } async sendRecursiveUnion(params: RecursiveUnion): Promise { - try { - return arriRequest({ - url: `${this._baseUrl}/rpcs/tests/send-recursive-union`, - method: "post", - headers: this._headers, - params: params, - responseFromJson: $$RecursiveUnion.fromJson, - responseFromString: $$RecursiveUnion.fromJsonString, - serializer: $$RecursiveUnion.toJsonString, - clientVersion: "10", - }); - } catch (err) { - if (this._onError) { - this._onError(err); - } - throw err; - } + return arriRequest({ + url: `${this._baseUrl}/rpcs/tests/send-recursive-union`, + method: "post", + headers: this._headers, + onError: this._onError, + params: params, + responseFromJson: $$RecursiveUnion.fromJson, + responseFromString: $$RecursiveUnion.fromJsonString, + serializer: $$RecursiveUnion.toJsonString, + clientVersion: "10", + }); } streamAutoReconnect( params: AutoReconnectParams, options: SseOptions = {}, ): EventSourceController { - try { - return arriSseRequest( - { - url: `${this._baseUrl}/rpcs/tests/stream-auto-reconnect`, - method: "get", - headers: this._headers, - params: params, - responseFromJson: $$AutoReconnectResponse.fromJson, - responseFromString: $$AutoReconnectResponse.fromJsonString, - serializer: $$AutoReconnectParams.toUrlQueryString, - clientVersion: "10", - }, - options, - this._onError, - ); - } catch (err) { - if (this._onError) { - this._onError(err); - } - throw err; - } + return arriSseRequest( + { + url: `${this._baseUrl}/rpcs/tests/stream-auto-reconnect`, + method: "get", + headers: this._headers, + onError: this._onError, + params: params, + responseFromJson: $$AutoReconnectResponse.fromJson, + responseFromString: $$AutoReconnectResponse.fromJsonString, + serializer: $$AutoReconnectParams.toUrlQueryString, + clientVersion: "10", + }, + options, + ); } /** * This route will always return an error. The client should automatically retry with exponential backoff. @@ -333,33 +258,24 @@ export class TestClientTestsService { params: StreamConnectionErrorTestParams, options: SseOptions = {}, ): EventSourceController { - try { - return arriSseRequest< - StreamConnectionErrorTestResponse, - StreamConnectionErrorTestParams - >( - { - url: `${this._baseUrl}/rpcs/tests/stream-connection-error-test`, - method: "get", - headers: this._headers, - params: params, - responseFromJson: - $$StreamConnectionErrorTestResponse.fromJson, - responseFromString: - $$StreamConnectionErrorTestResponse.fromJsonString, - serializer: - $$StreamConnectionErrorTestParams.toUrlQueryString, - clientVersion: "10", - }, - options, - this._onError, - ); - } catch (err) { - if (this._onError) { - this._onError(err); - } - throw err; - } + return arriSseRequest< + StreamConnectionErrorTestResponse, + StreamConnectionErrorTestParams + >( + { + url: `${this._baseUrl}/rpcs/tests/stream-connection-error-test`, + method: "get", + headers: this._headers, + onError: this._onError, + params: params, + responseFromJson: $$StreamConnectionErrorTestResponse.fromJson, + responseFromString: + $$StreamConnectionErrorTestResponse.fromJsonString, + serializer: $$StreamConnectionErrorTestParams.toUrlQueryString, + clientVersion: "10", + }, + options, + ); } /** * Test to ensure that the client can handle receiving streams of large objects. When objects are large messages will sometimes get sent in chunks. Meaning you have to handle receiving a partial message @@ -367,84 +283,62 @@ export class TestClientTestsService { streamLargeObjects( options: SseOptions = {}, ): EventSourceController { - try { - return arriSseRequest( - { - url: `${this._baseUrl}/rpcs/tests/stream-large-objects`, - method: "get", - headers: this._headers, + return arriSseRequest( + { + url: `${this._baseUrl}/rpcs/tests/stream-large-objects`, + method: "get", + headers: this._headers, + onError: this._onError, - responseFromJson: $$StreamLargeObjectsResponse.fromJson, - responseFromString: - $$StreamLargeObjectsResponse.fromJsonString, - serializer: () => {}, - clientVersion: "10", - }, - options, - this._onError, - ); - } catch (err) { - if (this._onError) { - this._onError(err); - } - throw err; - } + responseFromJson: $$StreamLargeObjectsResponse.fromJson, + responseFromString: $$StreamLargeObjectsResponse.fromJsonString, + serializer: () => {}, + clientVersion: "10", + }, + options, + ); } streamMessages( params: ChatMessageParams, options: SseOptions = {}, ): EventSourceController { - try { - return arriSseRequest( - { - url: `${this._baseUrl}/rpcs/tests/stream-messages`, - method: "get", - headers: this._headers, - params: params, - responseFromJson: $$ChatMessage.fromJson, - responseFromString: $$ChatMessage.fromJsonString, - serializer: $$ChatMessageParams.toUrlQueryString, - clientVersion: "10", - }, - options, - this._onError, - ); - } catch (err) { - if (this._onError) { - this._onError(err); - } - throw err; - } + return arriSseRequest( + { + url: `${this._baseUrl}/rpcs/tests/stream-messages`, + method: "get", + headers: this._headers, + onError: this._onError, + params: params, + responseFromJson: $$ChatMessage.fromJson, + responseFromString: $$ChatMessage.fromJsonString, + serializer: $$ChatMessageParams.toUrlQueryString, + clientVersion: "10", + }, + options, + ); } streamRetryWithNewCredentials( options: SseOptions = {}, ): EventSourceController { - try { - return arriSseRequest< - TestsStreamRetryWithNewCredentialsResponse, - undefined - >( - { - url: `${this._baseUrl}/rpcs/tests/stream-retry-with-new-credentials`, - method: "get", - headers: this._headers, + return arriSseRequest< + TestsStreamRetryWithNewCredentialsResponse, + undefined + >( + { + url: `${this._baseUrl}/rpcs/tests/stream-retry-with-new-credentials`, + method: "get", + headers: this._headers, + onError: this._onError, - responseFromJson: - $$TestsStreamRetryWithNewCredentialsResponse.fromJson, - responseFromString: - $$TestsStreamRetryWithNewCredentialsResponse.fromJsonString, - serializer: () => {}, - clientVersion: "10", - }, - options, - this._onError, - ); - } catch (err) { - if (this._onError) { - this._onError(err); - } - throw err; - } + responseFromJson: + $$TestsStreamRetryWithNewCredentialsResponse.fromJson, + responseFromString: + $$TestsStreamRetryWithNewCredentialsResponse.fromJsonString, + serializer: () => {}, + clientVersion: "10", + }, + options, + ); } /** * When the client receives the 'done' event, it should close the connection and NOT reconnect @@ -452,75 +346,54 @@ export class TestClientTestsService { streamTenEventsThenEnd( options: SseOptions = {}, ): EventSourceController { - try { - return arriSseRequest( - { - url: `${this._baseUrl}/rpcs/tests/stream-ten-events-then-end`, - method: "get", - headers: this._headers, + return arriSseRequest( + { + url: `${this._baseUrl}/rpcs/tests/stream-ten-events-then-end`, + method: "get", + headers: this._headers, + onError: this._onError, - responseFromJson: $$ChatMessage.fromJson, - responseFromString: $$ChatMessage.fromJsonString, - serializer: () => {}, - clientVersion: "10", - }, - options, - this._onError, - ); - } catch (err) { - if (this._onError) { - this._onError(err); - } - throw err; - } + responseFromJson: $$ChatMessage.fromJson, + responseFromString: $$ChatMessage.fromJsonString, + serializer: () => {}, + clientVersion: "10", + }, + options, + ); } async websocketRpc( options: WsOptions = {}, ): Promise> { - try { - return arriWsRequest({ - url: `${this._baseUrl}/rpcs/tests/websocket-rpc`, - headers: this._headers, - responseFromJson: $$WsMessageResponse.fromJson, - responseFromString: $$WsMessageResponse.fromJsonString, - serializer: $$WsMessageParams.toJsonString, - onOpen: options.onOpen, - onClose: options.onClose, - onError: options.onError, - onConnectionError: options.onConnectionError, - onMessage: options.onMessage, - clientVersion: "10", - }); - } catch (err) { - if (this._onError) { - this._onError(err); - } - throw err; - } + return arriWsRequest({ + url: `${this._baseUrl}/rpcs/tests/websocket-rpc`, + headers: this._headers, + responseFromJson: $$WsMessageResponse.fromJson, + responseFromString: $$WsMessageResponse.fromJsonString, + serializer: $$WsMessageParams.toJsonString, + onOpen: options.onOpen, + onClose: options.onClose, + onError: options.onError, + onConnectionError: options.onConnectionError, + onMessage: options.onMessage, + clientVersion: "10", + }); } async websocketRpcSendTenLargeMessages( options: WsOptions = {}, ): Promise> { - try { - return arriWsRequest({ - url: `${this._baseUrl}/rpcs/tests/websocket-rpc-send-ten-large-messages`, - headers: this._headers, - responseFromJson: $$StreamLargeObjectsResponse.fromJson, - responseFromString: $$StreamLargeObjectsResponse.fromJsonString, - serializer: () => {}, - onOpen: options.onOpen, - onClose: options.onClose, - onError: options.onError, - onConnectionError: options.onConnectionError, - onMessage: options.onMessage, - clientVersion: "10", - }); - } catch (err) { - if (this._onError) { - this._onError(err); - } - throw err; - } + return arriWsRequest({ + url: `${this._baseUrl}/rpcs/tests/websocket-rpc-send-ten-large-messages`, + headers: this._headers, + responseFromJson: $$StreamLargeObjectsResponse.fromJson, + responseFromString: $$StreamLargeObjectsResponse.fromJsonString, + serializer: () => {}, + onOpen: options.onOpen, + onClose: options.onClose, + onError: options.onError, + onConnectionError: options.onConnectionError, + onMessage: options.onMessage, + clientVersion: "10", + }); } } @@ -546,27 +419,20 @@ export class TestClientUsersService { params: UsersWatchUserParams, options: SseOptions = {}, ): EventSourceController { - try { - return arriSseRequest( - { - url: `${this._baseUrl}/rpcs/users/watch-user`, - method: "get", - headers: this._headers, - params: params, - responseFromJson: $$UsersWatchUserResponse.fromJson, - responseFromString: $$UsersWatchUserResponse.fromJsonString, - serializer: $$UsersWatchUserParams.toUrlQueryString, - clientVersion: "10", - }, - options, - this._onError, - ); - } catch (err) { - if (this._onError) { - this._onError(err); - } - throw err; - } + return arriSseRequest( + { + url: `${this._baseUrl}/rpcs/users/watch-user`, + method: "get", + headers: this._headers, + onError: this._onError, + params: params, + responseFromJson: $$UsersWatchUserResponse.fromJson, + responseFromString: $$UsersWatchUserResponse.fromJsonString, + serializer: $$UsersWatchUserParams.toUrlQueryString, + clientVersion: "10", + }, + options, + ); } } diff --git a/tests/clients/ts/testClient.test.ts b/tests/clients/ts/testClient.test.ts index 52d4cdeb..401b1361 100644 --- a/tests/clients/ts/testClient.test.ts +++ b/tests/clients/ts/testClient.test.ts @@ -283,6 +283,23 @@ test("can send/receive recursive unions", async () => { expect(result).toStrictEqual(payload); }); +test("onError hook fires properly", async () => { + let onErrorFired = false; + const customClient = new TestClient({ + baseUrl, + onError(err) { + onErrorFired = true; + expect(err instanceof ArriErrorInstance).toBe(true); + }, + }); + try { + await customClient.tests.sendObject(input); + } catch (_) { + // do nothing + } + expect(onErrorFired).toBe(true); +}); + test("[SSE] supports server sent events", async () => { let wasConnected = false; let receivedMessageCount = 0; From fbaf807390f8d3939dd8da4807758f5eba6df807 Mon Sep 17 00:00:00 2001 From: Joshua Sosso Date: Fri, 29 Nov 2024 13:56:48 -0600 Subject: [PATCH 04/10] improve dart result types --- languages/dart/dart-client/lib/request.dart | 69 +++++++++++++++---- .../dart-client/test/arri_client_test.dart | 4 +- 2 files changed, 57 insertions(+), 16 deletions(-) diff --git a/languages/dart/dart-client/lib/request.dart b/languages/dart/dart-client/lib/request.dart index cbd311fd..89d1ce42 100644 --- a/languages/dart/dart-client/lib/request.dart +++ b/languages/dart/dart-client/lib/request.dart @@ -155,7 +155,7 @@ Future parsedArriRequest( /// 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> parsedArriRequestSafe( +Future> parsedArriRequestSafe( String url, { http.Client? httpClient, HttpMethod httpMethod = HttpMethod.get, @@ -174,22 +174,65 @@ Future> parsedArriRequestSafe( 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 { - 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 { + 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 implements ArriResult { + 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 implements ArriResult { + 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; } diff --git a/languages/dart/dart-client/test/arri_client_test.dart b/languages/dart/dart-client/test/arri_client_test.dart index 7419b957..963dee38 100644 --- a/languages/dart/dart-client/test/arri_client_test.dart +++ b/languages/dart/dart-client/test/arri_client_test.dart @@ -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 { From 3938233f8383e5c7d3303ee2369481223bacb7b2 Mon Sep 17 00:00:00 2001 From: Joshua Sosso Date: Fri, 29 Nov 2024 13:57:02 -0600 Subject: [PATCH 05/10] add onError hooks to kotlin client --- .../src/main/kotlin/ExampleClient.kt | 128 ++++++++++-------- languages/kotlin/kotlin-codegen/src/_index.ts | 2 + .../kotlin/kotlin-codegen/src/procedures.ts | 29 ++-- 3 files changed, 94 insertions(+), 65 deletions(-) diff --git a/languages/kotlin/kotlin-codegen-reference/src/main/kotlin/ExampleClient.kt b/languages/kotlin/kotlin-codegen-reference/src/main/kotlin/ExampleClient.kt index 73b72c12..397c5607 100644 --- a/languages/kotlin/kotlin-codegen-reference/src/main/kotlin/ExampleClient.kt +++ b/languages/kotlin/kotlin-codegen-reference/src/main/kotlin/ExampleClient.kt @@ -37,33 +37,40 @@ class ExampleClient( private val httpClient: HttpClient, private val baseUrl: String, private val headers: headersFn, + private val onError: ((err: Exception) -> Unit) = {}, ) { suspend fun sendObject(params: NestedObject): NestedObject { - val response = __prepareRequest( - client = httpClient, - url = "$baseUrl/send-object", - method = HttpMethod.Post, - params = params, - headers = headers?.invoke(), - ).execute() - if (response.headers["Content-Type"] != "application/json") { - throw ExampleClientError( - code = 0, - errorMessage = "Expected server to return Content-Type \"application/json\". Got \"${response.headers["Content-Type"]}\"", - data = JsonPrimitive(response.bodyAsText()), - stack = null, - ) - } - if (response.status.value in 200..299) { - return NestedObject.fromJson(response.bodyAsText()) + try { + val response = __prepareRequest( + client = httpClient, + url = "$baseUrl/send-object", + method = HttpMethod.Post, + params = params, + headers = headers?.invoke(), + ).execute() + if (response.headers["Content-Type"] != "application/json") { + throw ExampleClientError( + code = 0, + errorMessage = "Expected server to return Content-Type \"application/json\". Got \"${response.headers["Content-Type"]}\"", + data = JsonPrimitive(response.bodyAsText()), + stack = null, + ) + } + if (response.status.value in 200..299) { + return NestedObject.fromJson(response.bodyAsText()) + } + throw ExampleClientError.fromJson(response.bodyAsText()) + } catch (e: Exception) { + onError(e) + throw e } - throw ExampleClientError.fromJson(response.bodyAsText()) } val books: ExampleClientBooksService = ExampleClientBooksService( httpClient = httpClient, baseUrl = baseUrl, headers = headers, + onError = onError, ) } @@ -71,30 +78,37 @@ class ExampleClientBooksService( private val httpClient: HttpClient, private val baseUrl: String, private val headers: headersFn, + private val onError: ((err: Exception) -> Unit) = {}, ) { /** * Get a book */ suspend fun getBook(params: BookParams): Book { - val response = __prepareRequest( - client = httpClient, - url = "$baseUrl/books/get-book", - method = HttpMethod.Get, - params = params, - headers = headers?.invoke(), - ).execute() - if (response.headers["Content-Type"] != "application/json") { - throw ExampleClientError( - code = 0, - errorMessage = "Expected server to return Content-Type \"application/json\". Got \"${response.headers["Content-Type"]}\"", - data = JsonPrimitive(response.bodyAsText()), - stack = null, - ) - } - if (response.status.value in 200..299) { - return Book.fromJson(response.bodyAsText()) + try { + val response = __prepareRequest( + client = httpClient, + url = "$baseUrl/books/get-book", + method = HttpMethod.Get, + params = params, + headers = headers?.invoke(), + ).execute() + if (response.headers["Content-Type"] != "application/json") { + throw ExampleClientError( + code = 0, + errorMessage = "Expected server to return Content-Type \"application/json\". Got \"${response.headers["Content-Type"]}\"", + data = JsonPrimitive(response.bodyAsText()), + stack = null, + ) + } + if (response.status.value in 200..299) { + return Book.fromJson(response.bodyAsText()) + } + throw ExampleClientError.fromJson(response.bodyAsText()) + + } catch (e: Exception) { + onError(e) + throw e } - throw ExampleClientError.fromJson(response.bodyAsText()) } /** @@ -102,25 +116,31 @@ class ExampleClientBooksService( */ @Deprecated(message = "This method was marked as deprecated by the server") suspend fun createBook(params: Book): Book { - val response = __prepareRequest( - client = httpClient, - url = "$baseUrl/books/create-book", - method = HttpMethod.Post, - params = params, - headers = headers?.invoke(), - ).execute() - if (response.headers["Content-Type"] != "application/json") { - throw ExampleClientError( - code = 0, - errorMessage = "Expected server to return Content-Type \"application/json\". Got \"${response.headers["Content-Type"]}\"", - data = JsonPrimitive(response.bodyAsText()), - stack = null, - ) - } - if (response.status.value in 200..299) { - return Book.fromJson(response.bodyAsText()) + try { + val response = __prepareRequest( + client = httpClient, + url = "$baseUrl/books/create-book", + method = HttpMethod.Post, + params = params, + headers = headers?.invoke(), + ).execute() + if (response.headers["Content-Type"] != "application/json") { + throw ExampleClientError( + code = 0, + errorMessage = "Expected server to return Content-Type \"application/json\". Got \"${response.headers["Content-Type"]}\"", + data = JsonPrimitive(response.bodyAsText()), + stack = null, + ) + } + if (response.status.value in 200..299) { + return Book.fromJson(response.bodyAsText()) + } + throw ExampleClientError.fromJson(response.bodyAsText()) + + } catch (e: Exception) { + onError(e) + throw e } - throw ExampleClientError.fromJson(response.bodyAsText()) } @Deprecated(message = "This method was marked as deprecated by the server") diff --git a/languages/kotlin/kotlin-codegen/src/_index.ts b/languages/kotlin/kotlin-codegen/src/_index.ts index cac46b4c..aa9397bb 100644 --- a/languages/kotlin/kotlin-codegen/src/_index.ts +++ b/languages/kotlin/kotlin-codegen/src/_index.ts @@ -115,6 +115,7 @@ export function kotlinClientFromAppDefinition( httpClient = httpClient, baseUrl = baseUrl, headers = headers, + onError = onError, )`); if (subService.content) { subServiceParts.push(subService.content); @@ -146,6 +147,7 @@ class ${clientName}( private val httpClient: HttpClient, private val baseUrl: String, private val headers: headersFn, + private val onError: ((err: Exception) -> Unit) = {}, ) { ${procedureParts.join("\n\n ")} } diff --git a/languages/kotlin/kotlin-codegen/src/procedures.ts b/languages/kotlin/kotlin-codegen/src/procedures.ts index 45bd8293..902e6b98 100644 --- a/languages/kotlin/kotlin-codegen/src/procedures.ts +++ b/languages/kotlin/kotlin-codegen/src/procedures.ts @@ -94,18 +94,23 @@ export function kotlinHttpRpcFromSchema( ) }`; return `${codeComment}suspend fun ${name}(${params ? `params: ${params}` : ""}): ${response ?? "Unit"} { - val response = __prepareRequest( - client = httpClient, - url = "$baseUrl${schema.path}", - method = HttpMethod.${pascalCase(schema.method, { normalize: true })}, - params = ${params ? "params" : null}, - headers = headers?.invoke(), - ).execute() - ${response ? headingCheck : ""} - if (response.status.value in 200..299) { - return ${response ? `${response}.fromJson(response.bodyAsText())` : ""} + try { + val response = __prepareRequest( + client = httpClient, + url = "$baseUrl${schema.path}", + method = HttpMethod.${pascalCase(schema.method, { normalize: true })}, + params = ${params ? "params" : null}, + headers = headers?.invoke(), + ).execute() + ${response ? headingCheck : ""} + if (response.status.value in 200..299) { + return ${response ? `${response}.fromJson(response.bodyAsText())` : ""} + } + throw ${context.clientName}Error.fromJson(response.bodyAsText()) + } catch (e: Exception) { + onError(e) + throw e } - throw ${context.clientName}Error.fromJson(response.bodyAsText()) }`; } @@ -140,6 +145,7 @@ export function kotlinServiceFromSchema( httpClient = httpClient, baseUrl = baseUrl, headers = headers, + onError = onError, )`); if (subService.content) { subServiceParts.push(subService.content); @@ -163,6 +169,7 @@ export function kotlinServiceFromSchema( private val httpClient: HttpClient, private val baseUrl: String, private val headers: headersFn, + private val onError: ((err: Exception) -> Unit) = {}, ) { ${procedureParts.join("\n\n ")} } From c7ff112e619b86d6fc3c68e1852079d84e3c3322 Mon Sep 17 00:00:00 2001 From: Joshua Sosso Date: Fri, 29 Nov 2024 14:07:32 -0600 Subject: [PATCH 06/10] add error hooks to kotlin sse implementation --- .../src/main/kotlin/ExampleClient.kt | 52 ++++++++++++------- languages/kotlin/kotlin-codegen/src/_index.ts | 51 +++++++++++------- .../kotlin/kotlin-codegen/src/procedures.ts | 1 + 3 files changed, 66 insertions(+), 38 deletions(-) diff --git a/languages/kotlin/kotlin-codegen-reference/src/main/kotlin/ExampleClient.kt b/languages/kotlin/kotlin-codegen-reference/src/main/kotlin/ExampleClient.kt index 397c5607..8976efd6 100644 --- a/languages/kotlin/kotlin-codegen-reference/src/main/kotlin/ExampleClient.kt +++ b/languages/kotlin/kotlin-codegen-reference/src/main/kotlin/ExampleClient.kt @@ -167,6 +167,7 @@ class ExampleClientBooksService( bufferCapacity = bufferCapacity, onOpen = onOpen, onClose = onClose, + onError = onError, onRequestError = onRequestError, onResponseError = onResponseError, onData = { str -> @@ -1997,8 +1998,9 @@ private suspend fun __handleSseRequest( onOpen: ((response: HttpResponse) -> Unit) = {}, onClose: (() -> Unit) = {}, onData: ((data: String) -> Unit) = {}, - onRequestError: ((error: Exception) -> Unit) = {}, - onResponseError: ((error: ExampleClientError) -> Unit) = {}, + onError: ((err: Exception) -> Unit) = {}, + onRequestError: ((err: Exception) -> Unit) = {}, + onResponseError: ((err: ExampleClientError) -> Unit) = {}, bufferCapacity: Int, ) { val finalHeaders = headers?.invoke() ?: mutableMapOf() @@ -2033,18 +2035,18 @@ private suspend fun __handleSseRequest( if (httpResponse.status.value !in 200..299) { try { if (httpResponse.headers["Content-Type"] == "application/json") { - onResponseError( - ExampleClientError.fromJson(httpResponse.bodyAsText()) - ) + val err = ExampleClientError.fromJson(httpResponse.bodyAsText()) + onError(err) + onResponseError(err) } else { - onResponseError( - ExampleClientError( - code = httpResponse.status.value, - errorMessage = httpResponse.status.description, - data = JsonPrimitive(httpResponse.bodyAsText()), - stack = null, - ) + val err = ExampleClientError( + code = httpResponse.status.value, + errorMessage = httpResponse.status.description, + data = JsonPrimitive(httpResponse.bodyAsText()), + stack = null, ) + onError(err) + onResponseError(err) } } catch (e: CancellationException) { onClose() @@ -2064,19 +2066,21 @@ private suspend fun __handleSseRequest( onOpen = onOpen, onClose = onClose, onData = onData, + onError = onError, + onRequestError = onRequestError, onResponseError = onResponseError, ) } if (httpResponse.headers["Content-Type"] != "text/event-stream") { try { - onResponseError( - ExampleClientError( - code = 0, - errorMessage = "Expected server to return Content-Type \"text/event-stream\". Got \"${httpResponse.headers["Content-Type"]}\"", - data = JsonPrimitive(httpResponse.bodyAsText()), - stack = null, - ) + val err = ExampleClientError( + code = 0, + errorMessage = "Expected server to return Content-Type \"text/event-stream\". Got \"${httpResponse.headers["Content-Type"]}\"", + data = JsonPrimitive(httpResponse.bodyAsText()), + stack = null, ) + onError(err) + onResponseError(err) } catch (e: CancellationException) { httpResponse.cancel() return@execute @@ -2094,6 +2098,8 @@ private suspend fun __handleSseRequest( onOpen = onOpen, onClose = onClose, onData = onData, + onError = onError, + onRequestError = onRequestError, onResponseError = onResponseError, ) } @@ -2145,10 +2151,13 @@ private suspend fun __handleSseRequest( onOpen = onOpen, onClose = onClose, onData = onData, + onError = onError, + onRequestError = onRequestError, onResponseError = onResponseError, ) } } catch (e: java.net.ConnectException) { + onError(e) onRequestError(e) return __handleSseRequest( httpClient = httpClient, @@ -2163,9 +2172,12 @@ private suspend fun __handleSseRequest( onOpen = onOpen, onClose = onClose, onData = onData, + onError = onError, + onRequestError = onRequestError, onResponseError = onResponseError, ) } catch (e: Exception) { + onError(e) onRequestError(e) return __handleSseRequest( httpClient = httpClient, @@ -2180,6 +2192,8 @@ private suspend fun __handleSseRequest( onOpen = onOpen, onClose = onClose, onData = onData, + onError = onError, + onRequestError = onRequestError, onResponseError = onResponseError, ) } diff --git a/languages/kotlin/kotlin-codegen/src/_index.ts b/languages/kotlin/kotlin-codegen/src/_index.ts index aa9397bb..e9ce5eef 100644 --- a/languages/kotlin/kotlin-codegen/src/_index.ts +++ b/languages/kotlin/kotlin-codegen/src/_index.ts @@ -520,8 +520,9 @@ private suspend fun __handleSseRequest( onOpen: ((response: HttpResponse) -> Unit) = {}, onClose: (() -> Unit) = {}, onData: ((data: String) -> Unit) = {}, - onRequestError: ((error: Exception) -> Unit) = {}, - onResponseError: ((error: ${clientName}Error) -> Unit) = {}, + onError: ((err: Exception) -> Unit) = {}, + onRequestError: ((err: Exception) -> Unit) = {}, + onResponseError: ((err: ${clientName}Error) -> Unit) = {}, bufferCapacity: Int, ) { val finalHeaders = headers?.invoke() ?: mutableMapOf() @@ -556,18 +557,18 @@ private suspend fun __handleSseRequest( if (httpResponse.status.value !in 200..299) { try { if (httpResponse.headers["Content-Type"] == "application/json") { - onResponseError( - ${clientName}Error.fromJson(httpResponse.bodyAsText()) - ) + val err = ${clientName}Error.fromJson(httpResponse.bodyAsText()) + onError(err) + onResponseError(err) } else { - onResponseError( - ${clientName}Error( - code = httpResponse.status.value, - errorMessage = httpResponse.status.description, - data = JsonPrimitive(httpResponse.bodyAsText()), - stack = null, - ) + val err = ${clientName}Error( + code = httpResponse.status.value, + errorMessage = httpResponse.status.description, + data = JsonPrimitive(httpResponse.bodyAsText()), + stack = null, ) + onError(err) + onResponseError(err) } } catch (e: CancellationException) { onClose() @@ -587,19 +588,21 @@ private suspend fun __handleSseRequest( onOpen = onOpen, onClose = onClose, onData = onData, + onError = onError, + onRequestError = onRequestError, onResponseError = onResponseError, ) } if (httpResponse.headers["Content-Type"] != "text/event-stream") { try { - onResponseError( - ${clientName}Error( - code = 0, - errorMessage = "Expected server to return Content-Type \\"text/event-stream\\". Got \\"\${httpResponse.headers["Content-Type"]}\\"", - data = JsonPrimitive(httpResponse.bodyAsText()), - stack = null, - ) + val err = ${clientName}Error( + code = 0, + errorMessage = "Expected server to return Content-Type \\"text/event-stream\\". Got \\"\${httpResponse.headers["Content-Type"]}\\"", + data = JsonPrimitive(httpResponse.bodyAsText()), + stack = null, ) + onError(err) + onResponseError(err) } catch (e: CancellationException) { httpResponse.cancel() return@execute @@ -617,6 +620,8 @@ private suspend fun __handleSseRequest( onOpen = onOpen, onClose = onClose, onData = onData, + onError = onError, + onRequestError = onRequestError, onResponseError = onResponseError, ) } @@ -668,10 +673,13 @@ private suspend fun __handleSseRequest( onOpen = onOpen, onClose = onClose, onData = onData, + onError = onError, + onRequestError = onRequestError, onResponseError = onResponseError, ) } } catch (e: java.net.ConnectException) { + onError(e) onRequestError(e) return __handleSseRequest( httpClient = httpClient, @@ -686,9 +694,12 @@ private suspend fun __handleSseRequest( onOpen = onOpen, onClose = onClose, onData = onData, + onError = onError, + onRequestError = onRequestError, onResponseError = onResponseError, ) } catch (e: Exception) { + onError(e) onRequestError(e) return __handleSseRequest( httpClient = httpClient, @@ -703,6 +714,8 @@ private suspend fun __handleSseRequest( onOpen = onOpen, onClose = onClose, onData = onData, + onError = onError, + onRequestError = onRequestError, onResponseError = onResponseError, ) } diff --git a/languages/kotlin/kotlin-codegen/src/procedures.ts b/languages/kotlin/kotlin-codegen/src/procedures.ts index 902e6b98..185b12ad 100644 --- a/languages/kotlin/kotlin-codegen/src/procedures.ts +++ b/languages/kotlin/kotlin-codegen/src/procedures.ts @@ -76,6 +76,7 @@ export function kotlinHttpRpcFromSchema( bufferCapacity = bufferCapacity, onOpen = onOpen, onClose = onClose, + onError = onError, onRequestError = onRequestError, onResponseError = onResponseError, onData = { str -> From 63fee2e04598162e81081c23c8377b5ce9ad1002 Mon Sep 17 00:00:00 2001 From: Joshua Sosso Date: Fri, 29 Nov 2024 14:24:37 -0600 Subject: [PATCH 07/10] add error hooks to swift client --- .../Sources/ArriClient/ArriClient.swift | 78 ++-- .../SwiftCodegenReference.swift | 22 +- .../swift/swift-codegen/src/procedures.ts | 11 +- tests/clients/dart/lib/test_client.rpc.dart | 2 + .../kotlin/src/main/kotlin/TestClient.rpc.kt | 414 +++++++++++------- .../clients/swift/Sources/TestClient.g.swift | 60 ++- tests/clients/ts/testClient.rpc.ts | 2 + 7 files changed, 364 insertions(+), 225 deletions(-) diff --git a/languages/swift/swift-client/Sources/ArriClient/ArriClient.swift b/languages/swift/swift-client/Sources/ArriClient/ArriClient.swift index c03e4783..e50c7252 100644 --- a/languages/swift/swift-client/Sources/ArriClient/ArriClient.swift +++ b/languages/swift/swift-client/Sources/ArriClient/ArriClient.swift @@ -18,44 +18,50 @@ public func parsedArriHttpRequest Dictionary, clientVersion: String, params: TParams, - timeoutSeconds: Int64 = 60 + timeoutSeconds: Int64 = 60, + onError: (Error) -> Void ) async throws -> TResponse { - var parsedURL = URLComponents(string: url) - if parsedURL == nil { - throw ArriRequestError.invalidUrl - } - var finalHeaders = headers() - if !clientVersion.isEmpty { - finalHeaders["client-version"] = clientVersion - } - var finalBody: String? - switch method { - case "GET": - if !(params is EmptyArriModel) { - parsedURL!.queryItems = params.toURLQueryParts() - } - break; - default: - if !(params is EmptyArriModel) { - finalHeaders["Content-Type"] = "application/json" - finalBody = params.toJSONString() - } - break; - } - let request = ArriHTTPRequest(url: parsedURL!.url!, method: method, headers: finalHeaders, body: finalBody) - let response = try await delegate.handleHTTPRequest(request: request) - if response.statusCode >= 200 && response.statusCode < 300 { - let result = TResponse.init(JSONData: response.body ?? Data()) - return result - } - var error = ArriResponseError(JSONData: response.body ?? Data()) - if error.code == 0 { - error.code = response.statusCode - } - if error.message.isEmpty { - error.message = response.statusMessage ?? "Unknown error" + do { + var parsedURL = URLComponents(string: url) + if parsedURL == nil { + throw ArriRequestError.invalidUrl + } + var finalHeaders = headers() + if !clientVersion.isEmpty { + finalHeaders["client-version"] = clientVersion + } + var finalBody: String? + switch method { + case "GET": + if !(params is EmptyArriModel) { + parsedURL!.queryItems = params.toURLQueryParts() + } + break; + default: + if !(params is EmptyArriModel) { + finalHeaders["Content-Type"] = "application/json" + finalBody = params.toJSONString() + } + break; + } + let request = ArriHTTPRequest(url: parsedURL!.url!, method: method, headers: finalHeaders, body: finalBody) + let response = try await delegate.handleHTTPRequest(request: request) + if response.statusCode >= 200 && response.statusCode < 300 { + let result = TResponse.init(JSONData: response.body ?? Data()) + return result + } + var error = ArriResponseError(JSONData: response.body ?? Data()) + if error.code == 0 { + error.code = response.statusCode + } + if error.message.isEmpty { + error.message = response.statusMessage ?? "Unknown error" + } + throw error + } catch (let e) { + onError(e) + throw e } - throw error } public enum ArriRequestError: Error { diff --git a/languages/swift/swift-codegen-reference/Sources/SwiftCodegenReference/SwiftCodegenReference.swift b/languages/swift/swift-codegen-reference/Sources/SwiftCodegenReference/SwiftCodegenReference.swift index 046906e1..86493963 100644 --- a/languages/swift/swift-codegen-reference/Sources/SwiftCodegenReference/SwiftCodegenReference.swift +++ b/languages/swift/swift-codegen-reference/Sources/SwiftCodegenReference/SwiftCodegenReference.swift @@ -6,20 +6,24 @@ public class ExampleClient { let baseURL: String let delegate: ArriRequestDelegate let headers: () -> Dictionary + let onError: (Error) -> Void public let books: ExampleClientBooksService public init( baseURL: String, delegate: ArriRequestDelegate, - headers: @escaping () -> Dictionary + headers: @escaping () -> Dictionary, + onError: @escaping ((Error) -> Void) = { _ -> Void in } ) { self.baseURL = baseURL self.delegate = delegate self.headers = headers + self.onError = onError self.books = ExampleClientBooksService( baseURL: baseURL, delegate: delegate, - headers: headers + headers: headers, + onError: onError ) } @@ -30,7 +34,8 @@ public class ExampleClient { method: "POST", headers: self.headers, clientVersion: "20", - params: params + params: params, + onError: onError ) return result } @@ -41,15 +46,18 @@ public class ExampleClientBooksService { let baseURL: String let delegate: ArriRequestDelegate let headers: () -> Dictionary + let onError: (Error) -> Void public init( baseURL: String, delegate: ArriRequestDelegate, - headers: @escaping () -> Dictionary + headers: @escaping () -> Dictionary, + onError: @escaping ((Error) -> Void) = { _ -> Void in } ) { self.baseURL = baseURL self.delegate = delegate self.headers = headers + self.onError = onError } /// Get a book public func getBook(_ params: BookParams) async throws -> Book { @@ -59,7 +67,8 @@ public class ExampleClientBooksService { method: "GET", headers: self.headers, clientVersion: "20", - params: params + params: params, + onError: onError ) return result } @@ -72,7 +81,8 @@ public class ExampleClientBooksService { method: "POST", headers: self.headers, clientVersion: "20", - params: params + params: params, + onError: onError ) return result } diff --git a/languages/swift/swift-codegen/src/procedures.ts b/languages/swift/swift-codegen/src/procedures.ts index 5620c95f..1b566d4e 100644 --- a/languages/swift/swift-codegen/src/procedures.ts +++ b/languages/swift/swift-codegen/src/procedures.ts @@ -75,7 +75,8 @@ export function swiftHttpProcedureFromSchema( method: "${schema.method.toUpperCase()}", headers: self.headers, clientVersion: "${context.clientVersion}", - ${params ? `params: params` : "params: EmptyArriModel()"} + ${params ? `params: params` : "params: EmptyArriModel()"}, + onError: onError ) ${response ? `return result` : ""} }`; @@ -175,21 +176,25 @@ public class ${serviceName} { let baseURL: String let delegate: ArriRequestDelegate let headers: () -> Dictionary + let onError: (Error) -> Void ${services.map((service) => ` public let ${service.key}: ${service.typeName}`).join("\n")} public init( baseURL: String, delegate: ArriRequestDelegate, - headers: @escaping () -> Dictionary + headers: @escaping () -> Dictionary, + onError: @escaping ((Error) -> Void) = { _ -> Void in } ) { self.baseURL = baseURL self.delegate = delegate self.headers = headers + self.onError = onError ${services .map( (service) => ` self.${service.key} = ${service.typeName}( baseURL: baseURL, delegate: delegate, - headers: headers + headers: headers, + onError: onError )`, ) .join("\n")} diff --git a/tests/clients/dart/lib/test_client.rpc.dart b/tests/clients/dart/lib/test_client.rpc.dart index 5f90cc32..c0d1c8d3 100644 --- a/tests/clients/dart/lib/test_client.rpc.dart +++ b/tests/clients/dart/lib/test_client.rpc.dart @@ -167,6 +167,7 @@ class TestClientTestsService { clientVersion: _clientVersion, params: params.toJson(), parser: (body) => ObjectWithPascalCaseKeys.fromJsonString(body), + onError: _onError, ); } @@ -180,6 +181,7 @@ class TestClientTestsService { clientVersion: _clientVersion, params: params.toJson(), parser: (body) => ObjectWithSnakeCaseKeys.fromJsonString(body), + onError: _onError, ); } diff --git a/tests/clients/kotlin/src/main/kotlin/TestClient.rpc.kt b/tests/clients/kotlin/src/main/kotlin/TestClient.rpc.kt index db8c32c9..9c68a743 100644 --- a/tests/clients/kotlin/src/main/kotlin/TestClient.rpc.kt +++ b/tests/clients/kotlin/src/main/kotlin/TestClient.rpc.kt @@ -36,17 +36,20 @@ class TestClient( private val httpClient: HttpClient, private val baseUrl: String, private val headers: headersFn, + private val onError: ((err: Exception) -> Unit) = {}, ) { val tests: TestClientTestsService = TestClientTestsService( httpClient = httpClient, baseUrl = baseUrl, headers = headers, + onError = onError, ) val users: TestClientUsersService = TestClientUsersService( httpClient = httpClient, baseUrl = baseUrl, headers = headers, + onError = onError, ) } @@ -54,16 +57,18 @@ class TestClientTestsService( private val httpClient: HttpClient, private val baseUrl: String, private val headers: headersFn, + private val onError: ((err: Exception) -> Unit) = {}, ) { suspend fun emptyParamsGetRequest(): DefaultPayload { - val response = __prepareRequest( - client = httpClient, - url = "$baseUrl/rpcs/tests/empty-params-get-request", - method = HttpMethod.Get, - params = null, - headers = headers?.invoke(), - ).execute() - if (response.headers["Content-Type"] != "application/json") { + try { + val response = __prepareRequest( + client = httpClient, + url = "$baseUrl/rpcs/tests/empty-params-get-request", + method = HttpMethod.Get, + params = null, + headers = headers?.invoke(), + ).execute() + if (response.headers["Content-Type"] != "application/json") { throw TestClientError( code = 0, errorMessage = "Expected server to return Content-Type \"application/json\". Got \"${response.headers["Content-Type"]}\"", @@ -71,21 +76,26 @@ class TestClientTestsService( stack = null, ) } - if (response.status.value in 200..299) { - return DefaultPayload.fromJson(response.bodyAsText()) + if (response.status.value in 200..299) { + return DefaultPayload.fromJson(response.bodyAsText()) + } + throw TestClientError.fromJson(response.bodyAsText()) + } catch (e: Exception) { + onError(e) + throw e } - throw TestClientError.fromJson(response.bodyAsText()) } suspend fun emptyParamsPostRequest(): DefaultPayload { - val response = __prepareRequest( - client = httpClient, - url = "$baseUrl/rpcs/tests/empty-params-post-request", - method = HttpMethod.Post, - params = null, - headers = headers?.invoke(), - ).execute() - if (response.headers["Content-Type"] != "application/json") { + try { + val response = __prepareRequest( + client = httpClient, + url = "$baseUrl/rpcs/tests/empty-params-post-request", + method = HttpMethod.Post, + params = null, + headers = headers?.invoke(), + ).execute() + if (response.headers["Content-Type"] != "application/json") { throw TestClientError( code = 0, errorMessage = "Expected server to return Content-Type \"application/json\". Got \"${response.headers["Content-Type"]}\"", @@ -93,40 +103,54 @@ class TestClientTestsService( stack = null, ) } - if (response.status.value in 200..299) { - return DefaultPayload.fromJson(response.bodyAsText()) + if (response.status.value in 200..299) { + return DefaultPayload.fromJson(response.bodyAsText()) + } + throw TestClientError.fromJson(response.bodyAsText()) + } catch (e: Exception) { + onError(e) + throw e } - throw TestClientError.fromJson(response.bodyAsText()) } suspend fun emptyResponseGetRequest(params: DefaultPayload): Unit { - val response = __prepareRequest( - client = httpClient, - url = "$baseUrl/rpcs/tests/empty-response-get-request", - method = HttpMethod.Get, - params = params, - headers = headers?.invoke(), - ).execute() - - if (response.status.value in 200..299) { - return + try { + val response = __prepareRequest( + client = httpClient, + url = "$baseUrl/rpcs/tests/empty-response-get-request", + method = HttpMethod.Get, + params = params, + headers = headers?.invoke(), + ).execute() + + if (response.status.value in 200..299) { + return + } + throw TestClientError.fromJson(response.bodyAsText()) + } catch (e: Exception) { + onError(e) + throw e } - throw TestClientError.fromJson(response.bodyAsText()) } suspend fun emptyResponsePostRequest(params: DefaultPayload): Unit { - val response = __prepareRequest( - client = httpClient, - url = "$baseUrl/rpcs/tests/empty-response-post-request", - method = HttpMethod.Post, - params = params, - headers = headers?.invoke(), - ).execute() - - if (response.status.value in 200..299) { - return + try { + val response = __prepareRequest( + client = httpClient, + url = "$baseUrl/rpcs/tests/empty-response-post-request", + method = HttpMethod.Post, + params = params, + headers = headers?.invoke(), + ).execute() + + if (response.status.value in 200..299) { + return + } + throw TestClientError.fromJson(response.bodyAsText()) + } catch (e: Exception) { + onError(e) + throw e } - throw TestClientError.fromJson(response.bodyAsText()) } /** @@ -134,44 +158,55 @@ class TestClientTestsService( */ @Deprecated(message = "This method was marked as deprecated by the server") suspend fun deprecatedRpc(params: DeprecatedRpcParams): Unit { - val response = __prepareRequest( - client = httpClient, - url = "$baseUrl/rpcs/tests/deprecated-rpc", - method = HttpMethod.Post, - params = params, - headers = headers?.invoke(), - ).execute() - - if (response.status.value in 200..299) { - return + try { + val response = __prepareRequest( + client = httpClient, + url = "$baseUrl/rpcs/tests/deprecated-rpc", + method = HttpMethod.Post, + params = params, + headers = headers?.invoke(), + ).execute() + + if (response.status.value in 200..299) { + return + } + throw TestClientError.fromJson(response.bodyAsText()) + } catch (e: Exception) { + onError(e) + throw e } - throw TestClientError.fromJson(response.bodyAsText()) } suspend fun sendError(params: SendErrorParams): Unit { - val response = __prepareRequest( - client = httpClient, - url = "$baseUrl/rpcs/tests/send-error", - method = HttpMethod.Post, - params = params, - headers = headers?.invoke(), - ).execute() - - if (response.status.value in 200..299) { - return + try { + val response = __prepareRequest( + client = httpClient, + url = "$baseUrl/rpcs/tests/send-error", + method = HttpMethod.Post, + params = params, + headers = headers?.invoke(), + ).execute() + + if (response.status.value in 200..299) { + return + } + throw TestClientError.fromJson(response.bodyAsText()) + } catch (e: Exception) { + onError(e) + throw e } - throw TestClientError.fromJson(response.bodyAsText()) } suspend fun sendObject(params: ObjectWithEveryType): ObjectWithEveryType { - val response = __prepareRequest( - client = httpClient, - url = "$baseUrl/rpcs/tests/send-object", - method = HttpMethod.Post, - params = params, - headers = headers?.invoke(), - ).execute() - if (response.headers["Content-Type"] != "application/json") { + try { + val response = __prepareRequest( + client = httpClient, + url = "$baseUrl/rpcs/tests/send-object", + method = HttpMethod.Post, + params = params, + headers = headers?.invoke(), + ).execute() + if (response.headers["Content-Type"] != "application/json") { throw TestClientError( code = 0, errorMessage = "Expected server to return Content-Type \"application/json\". Got \"${response.headers["Content-Type"]}\"", @@ -179,21 +214,26 @@ suspend fun deprecatedRpc(params: DeprecatedRpcParams): Unit { stack = null, ) } - if (response.status.value in 200..299) { - return ObjectWithEveryType.fromJson(response.bodyAsText()) + if (response.status.value in 200..299) { + return ObjectWithEveryType.fromJson(response.bodyAsText()) + } + throw TestClientError.fromJson(response.bodyAsText()) + } catch (e: Exception) { + onError(e) + throw e } - throw TestClientError.fromJson(response.bodyAsText()) } suspend fun sendObjectWithNullableFields(params: ObjectWithEveryNullableType): ObjectWithEveryNullableType { - val response = __prepareRequest( - client = httpClient, - url = "$baseUrl/rpcs/tests/send-object-with-nullable-fields", - method = HttpMethod.Post, - params = params, - headers = headers?.invoke(), - ).execute() - if (response.headers["Content-Type"] != "application/json") { + try { + val response = __prepareRequest( + client = httpClient, + url = "$baseUrl/rpcs/tests/send-object-with-nullable-fields", + method = HttpMethod.Post, + params = params, + headers = headers?.invoke(), + ).execute() + if (response.headers["Content-Type"] != "application/json") { throw TestClientError( code = 0, errorMessage = "Expected server to return Content-Type \"application/json\". Got \"${response.headers["Content-Type"]}\"", @@ -201,21 +241,26 @@ suspend fun deprecatedRpc(params: DeprecatedRpcParams): Unit { stack = null, ) } - if (response.status.value in 200..299) { - return ObjectWithEveryNullableType.fromJson(response.bodyAsText()) + if (response.status.value in 200..299) { + return ObjectWithEveryNullableType.fromJson(response.bodyAsText()) + } + throw TestClientError.fromJson(response.bodyAsText()) + } catch (e: Exception) { + onError(e) + throw e } - throw TestClientError.fromJson(response.bodyAsText()) } suspend fun sendObjectWithPascalCaseKeys(params: ObjectWithPascalCaseKeys): ObjectWithPascalCaseKeys { - val response = __prepareRequest( - client = httpClient, - url = "$baseUrl/rpcs/tests/send-object-with-pascal-case-keys", - method = HttpMethod.Post, - params = params, - headers = headers?.invoke(), - ).execute() - if (response.headers["Content-Type"] != "application/json") { + try { + val response = __prepareRequest( + client = httpClient, + url = "$baseUrl/rpcs/tests/send-object-with-pascal-case-keys", + method = HttpMethod.Post, + params = params, + headers = headers?.invoke(), + ).execute() + if (response.headers["Content-Type"] != "application/json") { throw TestClientError( code = 0, errorMessage = "Expected server to return Content-Type \"application/json\". Got \"${response.headers["Content-Type"]}\"", @@ -223,21 +268,26 @@ suspend fun deprecatedRpc(params: DeprecatedRpcParams): Unit { stack = null, ) } - if (response.status.value in 200..299) { - return ObjectWithPascalCaseKeys.fromJson(response.bodyAsText()) + if (response.status.value in 200..299) { + return ObjectWithPascalCaseKeys.fromJson(response.bodyAsText()) + } + throw TestClientError.fromJson(response.bodyAsText()) + } catch (e: Exception) { + onError(e) + throw e } - throw TestClientError.fromJson(response.bodyAsText()) } suspend fun sendObjectWithSnakeCaseKeys(params: ObjectWithSnakeCaseKeys): ObjectWithSnakeCaseKeys { - val response = __prepareRequest( - client = httpClient, - url = "$baseUrl/rpcs/tests/send-object-with-snake-case-keys", - method = HttpMethod.Post, - params = params, - headers = headers?.invoke(), - ).execute() - if (response.headers["Content-Type"] != "application/json") { + try { + val response = __prepareRequest( + client = httpClient, + url = "$baseUrl/rpcs/tests/send-object-with-snake-case-keys", + method = HttpMethod.Post, + params = params, + headers = headers?.invoke(), + ).execute() + if (response.headers["Content-Type"] != "application/json") { throw TestClientError( code = 0, errorMessage = "Expected server to return Content-Type \"application/json\". Got \"${response.headers["Content-Type"]}\"", @@ -245,21 +295,26 @@ suspend fun deprecatedRpc(params: DeprecatedRpcParams): Unit { stack = null, ) } - if (response.status.value in 200..299) { - return ObjectWithSnakeCaseKeys.fromJson(response.bodyAsText()) + if (response.status.value in 200..299) { + return ObjectWithSnakeCaseKeys.fromJson(response.bodyAsText()) + } + throw TestClientError.fromJson(response.bodyAsText()) + } catch (e: Exception) { + onError(e) + throw e } - throw TestClientError.fromJson(response.bodyAsText()) } suspend fun sendPartialObject(params: ObjectWithEveryOptionalType): ObjectWithEveryOptionalType { - val response = __prepareRequest( - client = httpClient, - url = "$baseUrl/rpcs/tests/send-partial-object", - method = HttpMethod.Post, - params = params, - headers = headers?.invoke(), - ).execute() - if (response.headers["Content-Type"] != "application/json") { + try { + val response = __prepareRequest( + client = httpClient, + url = "$baseUrl/rpcs/tests/send-partial-object", + method = HttpMethod.Post, + params = params, + headers = headers?.invoke(), + ).execute() + if (response.headers["Content-Type"] != "application/json") { throw TestClientError( code = 0, errorMessage = "Expected server to return Content-Type \"application/json\". Got \"${response.headers["Content-Type"]}\"", @@ -267,21 +322,26 @@ suspend fun deprecatedRpc(params: DeprecatedRpcParams): Unit { stack = null, ) } - if (response.status.value in 200..299) { - return ObjectWithEveryOptionalType.fromJson(response.bodyAsText()) + if (response.status.value in 200..299) { + return ObjectWithEveryOptionalType.fromJson(response.bodyAsText()) + } + throw TestClientError.fromJson(response.bodyAsText()) + } catch (e: Exception) { + onError(e) + throw e } - throw TestClientError.fromJson(response.bodyAsText()) } suspend fun sendRecursiveObject(params: RecursiveObject): RecursiveObject { - val response = __prepareRequest( - client = httpClient, - url = "$baseUrl/rpcs/tests/send-recursive-object", - method = HttpMethod.Post, - params = params, - headers = headers?.invoke(), - ).execute() - if (response.headers["Content-Type"] != "application/json") { + try { + val response = __prepareRequest( + client = httpClient, + url = "$baseUrl/rpcs/tests/send-recursive-object", + method = HttpMethod.Post, + params = params, + headers = headers?.invoke(), + ).execute() + if (response.headers["Content-Type"] != "application/json") { throw TestClientError( code = 0, errorMessage = "Expected server to return Content-Type \"application/json\". Got \"${response.headers["Content-Type"]}\"", @@ -289,21 +349,26 @@ suspend fun deprecatedRpc(params: DeprecatedRpcParams): Unit { stack = null, ) } - if (response.status.value in 200..299) { - return RecursiveObject.fromJson(response.bodyAsText()) + if (response.status.value in 200..299) { + return RecursiveObject.fromJson(response.bodyAsText()) + } + throw TestClientError.fromJson(response.bodyAsText()) + } catch (e: Exception) { + onError(e) + throw e } - throw TestClientError.fromJson(response.bodyAsText()) } suspend fun sendRecursiveUnion(params: RecursiveUnion): RecursiveUnion { - val response = __prepareRequest( - client = httpClient, - url = "$baseUrl/rpcs/tests/send-recursive-union", - method = HttpMethod.Post, - params = params, - headers = headers?.invoke(), - ).execute() - if (response.headers["Content-Type"] != "application/json") { + try { + val response = __prepareRequest( + client = httpClient, + url = "$baseUrl/rpcs/tests/send-recursive-union", + method = HttpMethod.Post, + params = params, + headers = headers?.invoke(), + ).execute() + if (response.headers["Content-Type"] != "application/json") { throw TestClientError( code = 0, errorMessage = "Expected server to return Content-Type \"application/json\". Got \"${response.headers["Content-Type"]}\"", @@ -311,10 +376,14 @@ suspend fun deprecatedRpc(params: DeprecatedRpcParams): Unit { stack = null, ) } - if (response.status.value in 200..299) { - return RecursiveUnion.fromJson(response.bodyAsText()) + if (response.status.value in 200..299) { + return RecursiveUnion.fromJson(response.bodyAsText()) + } + throw TestClientError.fromJson(response.bodyAsText()) + } catch (e: Exception) { + onError(e) + throw e } - throw TestClientError.fromJson(response.bodyAsText()) } suspend fun streamAutoReconnect( @@ -340,6 +409,7 @@ suspend fun deprecatedRpc(params: DeprecatedRpcParams): Unit { bufferCapacity = bufferCapacity, onOpen = onOpen, onClose = onClose, + onError = onError, onRequestError = onRequestError, onResponseError = onResponseError, onData = { str -> @@ -375,6 +445,7 @@ suspend fun streamConnectionErrorTest( bufferCapacity = bufferCapacity, onOpen = onOpen, onClose = onClose, + onError = onError, onRequestError = onRequestError, onResponseError = onResponseError, onData = { str -> @@ -410,6 +481,7 @@ suspend fun streamLargeObjects( bufferCapacity = bufferCapacity, onOpen = onOpen, onClose = onClose, + onError = onError, onRequestError = onRequestError, onResponseError = onResponseError, onData = { str -> @@ -442,6 +514,7 @@ suspend fun streamLargeObjects( bufferCapacity = bufferCapacity, onOpen = onOpen, onClose = onClose, + onError = onError, onRequestError = onRequestError, onResponseError = onResponseError, onData = { str -> @@ -474,6 +547,7 @@ suspend fun streamLargeObjects( bufferCapacity = bufferCapacity, onOpen = onOpen, onClose = onClose, + onError = onError, onRequestError = onRequestError, onResponseError = onResponseError, onData = { str -> @@ -509,6 +583,7 @@ suspend fun streamTenEventsThenEnd( bufferCapacity = bufferCapacity, onOpen = onOpen, onClose = onClose, + onError = onError, onRequestError = onRequestError, onResponseError = onResponseError, onData = { str -> @@ -525,6 +600,7 @@ class TestClientUsersService( private val httpClient: HttpClient, private val baseUrl: String, private val headers: headersFn, + private val onError: ((err: Exception) -> Unit) = {}, ) { suspend fun watchUser( params: UsersWatchUserParams, @@ -549,6 +625,7 @@ class TestClientUsersService( bufferCapacity = bufferCapacity, onOpen = onOpen, onClose = onClose, + onError = onError, onRequestError = onRequestError, onResponseError = onResponseError, onData = { str -> @@ -6214,8 +6291,9 @@ private suspend fun __handleSseRequest( onOpen: ((response: HttpResponse) -> Unit) = {}, onClose: (() -> Unit) = {}, onData: ((data: String) -> Unit) = {}, - onRequestError: ((error: Exception) -> Unit) = {}, - onResponseError: ((error: TestClientError) -> Unit) = {}, + onError: ((err: Exception) -> Unit) = {}, + onRequestError: ((err: Exception) -> Unit) = {}, + onResponseError: ((err: TestClientError) -> Unit) = {}, bufferCapacity: Int, ) { val finalHeaders = headers?.invoke() ?: mutableMapOf() @@ -6250,18 +6328,18 @@ private suspend fun __handleSseRequest( if (httpResponse.status.value !in 200..299) { try { if (httpResponse.headers["Content-Type"] == "application/json") { - onResponseError( - TestClientError.fromJson(httpResponse.bodyAsText()) - ) + val err = TestClientError.fromJson(httpResponse.bodyAsText()) + onError(err) + onResponseError(err) } else { - onResponseError( - TestClientError( - code = httpResponse.status.value, - errorMessage = httpResponse.status.description, - data = JsonPrimitive(httpResponse.bodyAsText()), - stack = null, - ) + val err = TestClientError( + code = httpResponse.status.value, + errorMessage = httpResponse.status.description, + data = JsonPrimitive(httpResponse.bodyAsText()), + stack = null, ) + onError(err) + onResponseError(err) } } catch (e: CancellationException) { onClose() @@ -6281,19 +6359,21 @@ private suspend fun __handleSseRequest( onOpen = onOpen, onClose = onClose, onData = onData, + onError = onError, + onRequestError = onRequestError, onResponseError = onResponseError, ) } if (httpResponse.headers["Content-Type"] != "text/event-stream") { try { - onResponseError( - TestClientError( - code = 0, - errorMessage = "Expected server to return Content-Type \"text/event-stream\". Got \"${httpResponse.headers["Content-Type"]}\"", - data = JsonPrimitive(httpResponse.bodyAsText()), - stack = null, - ) + val err = TestClientError( + code = 0, + errorMessage = "Expected server to return Content-Type \"text/event-stream\". Got \"${httpResponse.headers["Content-Type"]}\"", + data = JsonPrimitive(httpResponse.bodyAsText()), + stack = null, ) + onError(err) + onResponseError(err) } catch (e: CancellationException) { httpResponse.cancel() return@execute @@ -6311,6 +6391,8 @@ private suspend fun __handleSseRequest( onOpen = onOpen, onClose = onClose, onData = onData, + onError = onError, + onRequestError = onRequestError, onResponseError = onResponseError, ) } @@ -6362,10 +6444,13 @@ private suspend fun __handleSseRequest( onOpen = onOpen, onClose = onClose, onData = onData, + onError = onError, + onRequestError = onRequestError, onResponseError = onResponseError, ) } } catch (e: java.net.ConnectException) { + onError(e) onRequestError(e) return __handleSseRequest( httpClient = httpClient, @@ -6380,9 +6465,12 @@ private suspend fun __handleSseRequest( onOpen = onOpen, onClose = onClose, onData = onData, + onError = onError, + onRequestError = onRequestError, onResponseError = onResponseError, ) } catch (e: Exception) { + onError(e) onRequestError(e) return __handleSseRequest( httpClient = httpClient, @@ -6397,6 +6485,8 @@ private suspend fun __handleSseRequest( onOpen = onOpen, onClose = onClose, onData = onData, + onError = onError, + onRequestError = onRequestError, onResponseError = onResponseError, ) } diff --git a/tests/clients/swift/Sources/TestClient.g.swift b/tests/clients/swift/Sources/TestClient.g.swift index e5a895d2..1f202e0f 100644 --- a/tests/clients/swift/Sources/TestClient.g.swift +++ b/tests/clients/swift/Sources/TestClient.g.swift @@ -6,25 +6,30 @@ public class TestClient { let baseURL: String let delegate: ArriRequestDelegate let headers: () -> Dictionary + let onError: (Error) -> Void public let tests: TestClientTestsService public let users: TestClientUsersService public init( baseURL: String, delegate: ArriRequestDelegate, - headers: @escaping () -> Dictionary + headers: @escaping () -> Dictionary, + onError: @escaping ((Error) -> Void) = { _ -> Void in } ) { self.baseURL = baseURL self.delegate = delegate self.headers = headers + self.onError = onError self.tests = TestClientTestsService( baseURL: baseURL, delegate: delegate, - headers: headers + headers: headers, + onError: onError ) self.users = TestClientUsersService( baseURL: baseURL, delegate: delegate, - headers: headers + headers: headers, + onError: onError ) } @@ -36,15 +41,18 @@ public class TestClientTestsService { let baseURL: String let delegate: ArriRequestDelegate let headers: () -> Dictionary + let onError: (Error) -> Void public init( baseURL: String, delegate: ArriRequestDelegate, - headers: @escaping () -> Dictionary + headers: @escaping () -> Dictionary, + onError: @escaping ((Error) -> Void) = { _ -> Void in } ) { self.baseURL = baseURL self.delegate = delegate self.headers = headers + self.onError = onError } public func emptyParamsGetRequest() async throws -> DefaultPayload { @@ -54,7 +62,8 @@ public class TestClientTestsService { method: "GET", headers: self.headers, clientVersion: "10", - params: EmptyArriModel() + params: EmptyArriModel(), + onError: onError ) return result } @@ -65,7 +74,8 @@ public class TestClientTestsService { method: "POST", headers: self.headers, clientVersion: "10", - params: EmptyArriModel() + params: EmptyArriModel(), + onError: onError ) return result } @@ -76,7 +86,8 @@ public class TestClientTestsService { method: "GET", headers: self.headers, clientVersion: "10", - params: params + params: params, + onError: onError ) } @@ -87,7 +98,8 @@ public class TestClientTestsService { method: "POST", headers: self.headers, clientVersion: "10", - params: params + params: params, + onError: onError ) } @@ -100,7 +112,8 @@ public class TestClientTestsService { method: "POST", headers: self.headers, clientVersion: "10", - params: params + params: params, + onError: onError ) } @@ -111,7 +124,8 @@ public class TestClientTestsService { method: "POST", headers: self.headers, clientVersion: "10", - params: params + params: params, + onError: onError ) } @@ -122,7 +136,8 @@ public class TestClientTestsService { method: "POST", headers: self.headers, clientVersion: "10", - params: params + params: params, + onError: onError ) return result } @@ -133,7 +148,8 @@ public class TestClientTestsService { method: "POST", headers: self.headers, clientVersion: "10", - params: params + params: params, + onError: onError ) return result } @@ -144,7 +160,8 @@ public class TestClientTestsService { method: "POST", headers: self.headers, clientVersion: "10", - params: params + params: params, + onError: onError ) return result } @@ -155,7 +172,8 @@ public class TestClientTestsService { method: "POST", headers: self.headers, clientVersion: "10", - params: params + params: params, + onError: onError ) return result } @@ -166,7 +184,8 @@ public class TestClientTestsService { method: "POST", headers: self.headers, clientVersion: "10", - params: params + params: params, + onError: onError ) return result } @@ -177,7 +196,8 @@ public class TestClientTestsService { method: "POST", headers: self.headers, clientVersion: "10", - params: params + params: params, + onError: onError ) return result } @@ -188,7 +208,8 @@ public class TestClientTestsService { method: "POST", headers: self.headers, clientVersion: "10", - params: params + params: params, + onError: onError ) return result } @@ -294,15 +315,18 @@ public class TestClientUsersService { let baseURL: String let delegate: ArriRequestDelegate let headers: () -> Dictionary + let onError: (Error) -> Void public init( baseURL: String, delegate: ArriRequestDelegate, - headers: @escaping () -> Dictionary + headers: @escaping () -> Dictionary, + onError: @escaping ((Error) -> Void) = { _ -> Void in } ) { self.baseURL = baseURL self.delegate = delegate self.headers = headers + self.onError = onError } public func watchUser(_ params: UsersWatchUserParams, options: EventSourceOptions) -> Task<(), Never> { diff --git a/tests/clients/ts/testClient.rpc.ts b/tests/clients/ts/testClient.rpc.ts index d853ef7f..871a9fe4 100644 --- a/tests/clients/ts/testClient.rpc.ts +++ b/tests/clients/ts/testClient.rpc.ts @@ -190,6 +190,7 @@ export class TestClientTestsService { url: `${this._baseUrl}/rpcs/tests/send-object-with-pascal-case-keys`, method: "post", headers: this._headers, + onError: this._onError, params: params, responseFromJson: $$ObjectWithPascalCaseKeys.fromJson, responseFromString: $$ObjectWithPascalCaseKeys.fromJsonString, @@ -204,6 +205,7 @@ export class TestClientTestsService { url: `${this._baseUrl}/rpcs/tests/send-object-with-snake-case-keys`, method: "post", headers: this._headers, + onError: this._onError, params: params, responseFromJson: $$ObjectWithSnakeCaseKeys.fromJson, responseFromString: $$ObjectWithSnakeCaseKeys.fromJsonString, From 3e9c396fc2e96c6f1ca671f6bc8654a6e933bde4 Mon Sep 17 00:00:00 2001 From: Joshua Sosso Date: Fri, 29 Nov 2024 14:30:28 -0600 Subject: [PATCH 08/10] test onError hooks (swift and ts) --- .../clients/swift/Tests/TestClientTests.swift | 21 ++++++++++++------- tests/clients/ts/testClient.test.ts | 11 +++++++--- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/tests/clients/swift/Tests/TestClientTests.swift b/tests/clients/swift/Tests/TestClientTests.swift index 50f42dc2..c5d706f9 100644 --- a/tests/clients/swift/Tests/TestClientTests.swift +++ b/tests/clients/swift/Tests/TestClientTests.swift @@ -3,6 +3,7 @@ import ArriClient @testable import TestClientSwift final class TestSwiftClientTests: XCTestCase { + let baseUrl = "http://localhost:2020" let client = TestClient( baseURL: "http://localhost:2020", delegate: DefaultRequestDelegate(), @@ -12,13 +13,7 @@ final class TestSwiftClientTests: XCTestCase { return headers } ) - let unauthenticatedClient = TestClient( - baseURL: "http://localhost:2020", - delegate: DefaultRequestDelegate(), - headers: { - return Dictionary() - } - ) + let testDate = Date(timeIntervalSince1970: 500000) func testSendObject() async throws { let input = ObjectWithEveryType( @@ -180,6 +175,17 @@ final class TestSwiftClientTests: XCTestCase { } func testSendUnauthenticatedRequest() async { + var firedOnError = false + let unauthenticatedClient = TestClient( + baseURL: baseUrl, + delegate: DefaultRequestDelegate(), + headers: { + return Dictionary() + }, + onError: { _ in + firedOnError = true + } + ) var didError = false do { let _ = try await unauthenticatedClient.tests.sendObject(ObjectWithEveryType()) @@ -192,6 +198,7 @@ final class TestSwiftClientTests: XCTestCase { } } XCTAssert(didError) + XCTAssert(firedOnError) } func testRpcWithNoParams() async throws { diff --git a/tests/clients/ts/testClient.test.ts b/tests/clients/ts/testClient.test.ts index 5873819c..f49f0255 100644 --- a/tests/clients/ts/testClient.test.ts +++ b/tests/clients/ts/testClient.test.ts @@ -31,9 +31,6 @@ const client = new TestClient({ baseUrl, headers, }); -const unauthenticatedClient = new TestClient({ - baseUrl, -}); test("route request", async () => { const result = await ofetch("/routes/hello-world", { @@ -173,6 +170,13 @@ test("returns error if sending nothing when RPC expects body", async () => { } }); test("unauthenticated RPC request returns a 401 error", async () => { + let firedOnErr = false; + const unauthenticatedClient = new TestClient({ + baseUrl, + onError(_) { + firedOnErr = true; + }, + }); try { await unauthenticatedClient.tests.sendObject(input); expect(true).toBe(false); @@ -182,6 +186,7 @@ test("unauthenticated RPC request returns a 401 error", async () => { expect(err.code).toBe(401); } } + expect(firedOnErr).toBe(true); }); test("can use async functions for headers", async () => { const _client = new TestClient({ From 8b6153f777e31d3fb791ee107d4b705764a6a668 Mon Sep 17 00:00:00 2001 From: Joshua Sosso Date: Fri, 29 Nov 2024 14:34:22 -0600 Subject: [PATCH 09/10] add integration tests for onError hook (dart and kotlin) --- tests/clients/dart/test/test_client_test.dart | 9 ++++++++- tests/clients/kotlin/src/main/kotlin/Main.kt | 16 +++++++++++----- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/tests/clients/dart/test/test_client_test.dart b/tests/clients/dart/test/test_client_test.dart index ed6320ed..97433998 100644 --- a/tests/clients/dart/test/test_client_test.dart +++ b/tests/clients/dart/test/test_client_test.dart @@ -13,7 +13,6 @@ const baseUrl = "http://127.0.0.1:2020"; Future main() async { final client = TestClient(baseUrl: baseUrl, headers: () => {"x-test-header": 'test'}); - final unauthenticatedClient = TestClient(baseUrl: "http://127.0.0.1:2020"); final httpClient = HttpClient(context: SecurityContext(withTrustedRoots: true)); final ioClient = IOClient(httpClient); @@ -147,6 +146,13 @@ Future main() async { expect(result.uint64, equals(input.uint64)); }); test("unauthenticated RPC requests return a 401 error", () async { + bool firedOnErr = false; + final unauthenticatedClient = TestClient( + baseUrl: baseUrl, + onError: (_) { + firedOnErr = true; + }, + ); try { await unauthenticatedClient.tests.sendObject(input); expect(false, equals(true)); @@ -157,6 +163,7 @@ Future main() async { } expect(false, equals(true)); } + expect(firedOnErr, equals(true)); }); test("can send/receive objects with partial fields", () async { final input = ObjectWithEveryOptionalType( diff --git a/tests/clients/kotlin/src/main/kotlin/Main.kt b/tests/clients/kotlin/src/main/kotlin/Main.kt index 442c3613..5fd6ada1 100644 --- a/tests/clients/kotlin/src/main/kotlin/Main.kt +++ b/tests/clients/kotlin/src/main/kotlin/Main.kt @@ -17,11 +17,7 @@ fun main() { mutableMapOf(Pair("x-test-header", "12345")) } ) - val unauthenticatedClient = TestClient( - httpClient = httpClient, - baseUrl = "http://localhost:2020", - null - ) + val targetDate = Instant.parse("2002-02-02T08:02:00.000Z") runBlocking { @@ -115,6 +111,15 @@ fun main() { } runBlocking { + var didCallOnErr = false + val unauthenticatedClient = TestClient( + httpClient = httpClient, + baseUrl = "http://localhost:2020", + null, + onError = { _ -> + didCallOnErr = true + } + ) val tag = "UNAUTHENTICATED REQUEST RETURNS ERROR" try { unauthenticatedClient.tests.sendObject(objectInput) @@ -125,6 +130,7 @@ fun main() { // this should never be reached expect(tag, input = false, result = true) } + expect(tag, input = didCallOnErr, result = true) } runBlocking { From 18b8c3c067542f49c2af7c87f84f58b78e358c8c Mon Sep 17 00:00:00 2001 From: Joshua Sosso Date: Fri, 29 Nov 2024 14:49:59 -0600 Subject: [PATCH 10/10] document error hooks --- languages/dart/dart-codegen/README.md | 29 +++++++++++++++-------- languages/kotlin/kotlin-codegen/README.md | 27 ++++++++++++++------- languages/swift/swift-codegen/README.md | 22 ++++++++++------- languages/ts/ts-codegen/README.md | 4 ++++ 4 files changed, 54 insertions(+), 28 deletions(-) diff --git a/languages/dart/dart-codegen/README.md b/languages/dart/dart-codegen/README.md index 74d32e68..2d3ef8c7 100644 --- a/languages/dart/dart-codegen/README.md +++ b/languages/dart/dart-codegen/README.md @@ -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> 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 toJson()` -- `String toJsonString()` -- `String toUrlQueryParams()` -- `copyWith()` +- `Map toJson()` +- `String toJsonString()` +- `String toUrlQueryParams()` +- `copyWith()` **Factory Methods**: -- `empty()` -- `fromJson(Map input)` -- `fromJsonString(String input)` +- `empty()` +- `fromJson(Map 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). diff --git a/languages/kotlin/kotlin-codegen/README.md b/languages/kotlin/kotlin-codegen/README.md index db190e04..32a5d11b 100644 --- a/languages/kotlin/kotlin-codegen/README.md +++ b/languages/kotlin/kotlin-codegen/README.md @@ -30,8 +30,8 @@ export default defineConfig({ The generated code relies on the following dependencies: -- [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization) -- [ktor client](https://ktor.io/docs/client-dependencies.html) +- [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization) +- [ktor client](https://ktor.io/docs/client-dependencies.html) ## Using the Generated Code @@ -72,6 +72,15 @@ val service = MyClientUsersService( ) ``` +#### Client / Service Options + +| Name | Type | Description | +| ------------------ | -------------------------------------- | ---------------------------------------------------------------- | +| httpClient | `HttpClient` | An instance of ktor HttpClient | +| baseUrl | `String` | The base URL of the API server | +| headers | `(() -> MutableMap?)?` | A function that returns a map of http headers | +| onError (Optional) | `((err: Exception) -> Unit)` | A hook that fires whenever any exception is thrown by the client | + ### Calling Procedures #### Standard HTTP Procedures @@ -149,19 +158,19 @@ All generated models will be data classes. They will have access to the followin **Methods**: -- `toJson(): String` -- `toUrlQueryParams(): String` +- `toJson(): String` +- `toUrlQueryParams(): String` **Factory Methods**: -- `new()` -- `fromJson(input: String)` -- `fromJsonElement(input: JsonElement, instancePath: String)` +- `new()` +- `fromJson(input: String)` +- `fromJsonElement(input: JsonElement, instancePath: String)` **Other Notes** -- All Enums will have a `serialValue` property. -- Discriminator schemas are converted to sealed classes +- All Enums will have a `serialValue` property. +- Discriminator schemas are converted to sealed classes ## Development diff --git a/languages/swift/swift-codegen/README.md b/languages/swift/swift-codegen/README.md index 97919e83..e1d0e3f3 100644 --- a/languages/swift/swift-codegen/README.md +++ b/languages/swift/swift-codegen/README.md @@ -61,9 +61,13 @@ then add `ArriClient` as a dependency to your target let client = MyClient( baseURL: "https://example.com", delegate: DefaultRequestDelegate(), - headers { + headers: { var headers: Dictionary = Dictionary() return headers + }, + // optional + onError: { err in + // do something } ) @@ -156,14 +160,14 @@ await task.result #### Available Event Source Options -- `onMessage` - Closure that fires whenever a "message" event is received from the server. This is the only required option. -- `onRequest` - Closure that fires when a request has been created but has not been executed yet. -- `onRequestError` - Closure that fires when there was an error in creating the request (i.e. a malformed URL), or if we were unable to connect to the server. (i.e a `connectionRefused` error) -- `onResponse` - Closure that fires when we receive a response from the server -- `onResponseError` - Closure that fires when the server has not responded with status code from `200` - `299` or the `Content-Type` header does not contain `text/event-stream` -- `onClose` - Closure that fires when the EventSource is closed. (This will only fire if the EventSource was already able successfully receive a response from the server.) -- `maxRetryCount` - Limit the number of times that the EventSource tries to reconnect to the server. When set to `nil` it will retry indefinitely. (Default is `nil`) -- `maxRetryInterval` - Set the max delay time between retries in milliseconds. Default is `30000`. +- `onMessage` - Closure that fires whenever a "message" event is received from the server. This is the only required option. +- `onRequest` - Closure that fires when a request has been created but has not been executed yet. +- `onRequestError` - Closure that fires when there was an error in creating the request (i.e. a malformed URL), or if we were unable to connect to the server. (i.e a `connectionRefused` error) +- `onResponse` - Closure that fires when we receive a response from the server +- `onResponseError` - Closure that fires when the server has not responded with status code from `200` - `299` or the `Content-Type` header does not contain `text/event-stream` +- `onClose` - Closure that fires when the EventSource is closed. (This will only fire if the EventSource was already able successfully receive a response from the server.) +- `maxRetryCount` - Limit the number of times that the EventSource tries to reconnect to the server. When set to `nil` it will retry indefinitely. (Default is `nil`) +- `maxRetryInterval` - Set the max delay time between retries in milliseconds. Default is `30000`. ## Additional Notes diff --git a/languages/ts/ts-codegen/README.md b/languages/ts/ts-codegen/README.md index c354878c..7a96b97d 100644 --- a/languages/ts/ts-codegen/README.md +++ b/languages/ts/ts-codegen/README.md @@ -53,6 +53,10 @@ const client = new MyClient({ Authorization: "", }; }, + // optional + onError: (err) => { + // do something + }, }); await client.myProcedure();