diff --git a/packages/grpc-transport/src/grpc-transport.ts b/packages/grpc-transport/src/grpc-transport.ts index 9ec47ed1..2dc6bb34 100644 --- a/packages/grpc-transport/src/grpc-transport.ts +++ b/packages/grpc-transport/src/grpc-transport.ts @@ -81,6 +81,8 @@ export class GrpcTransport implements RpcTransport { } if (err) { const e = new RpcError(err.message, GrpcStatus[err.code], metadataFromGrpc(err.metadata)); + e.methodName = method.name; + e.serviceName = method.service.typeName; defHeader.rejectPending(e); defMessage.rejectPending(e); defStatus.rejectPending(e); @@ -105,6 +107,8 @@ export class GrpcTransport implements RpcTransport { // we require that the status is an error status. if (defMessage.state === DeferredState.PENDING && val.code === GrpcStatus.OK) { const e = new RpcError('expected error status', GrpcStatus[GrpcStatus.DATA_LOSS]); + e.methodName = method.name; + e.serviceName = method.service.typeName; defMessage.rejectPending(e); defStatus.rejectPending(e); defTrailer.rejectPending(e); @@ -152,6 +156,8 @@ export class GrpcTransport implements RpcTransport { gCall.addListener('error', err => { const e = isServiceError(err) ? new RpcError(err.message, GrpcStatus[err.code], metadataFromGrpc(err.metadata)) : new RpcError(err.message); + e.methodName = method.name; + e.serviceName = method.service.typeName; defHeader.rejectPending(e); if (!outStream.closed) { outStream.notifyError(e); @@ -210,6 +216,8 @@ export class GrpcTransport implements RpcTransport { } if (err) { const e = new RpcError(err.message, GrpcStatus[err.code], metadataFromGrpc(err.metadata)); + e.methodName = method.name; + e.serviceName = method.service.typeName; defHeader.rejectPending(e); defMessage.rejectPending(e); defStatus.rejectPending(e); @@ -273,6 +281,8 @@ export class GrpcTransport implements RpcTransport { gCall.addListener('error', err => { const e = isServiceError(err) ? new RpcError(err.message, GrpcStatus[err.code], metadataFromGrpc(err.metadata)) : new RpcError(err.message); + e.methodName = method.name; + e.serviceName = method.service.typeName; defHeader.rejectPending(e); if (!outStream.closed) { outStream.notifyError(e); diff --git a/packages/grpcweb-transport/spec/grpc-web-transport.spec.ts b/packages/grpcweb-transport/spec/grpc-web-transport.spec.ts index d67f21aa..3afc4ee8 100644 --- a/packages/grpcweb-transport/spec/grpc-web-transport.spec.ts +++ b/packages/grpcweb-transport/spec/grpc-web-transport.spec.ts @@ -80,8 +80,23 @@ describe('GrpcWebFetchTransport', () => { describe('clientStreaming()', function () { it('throws because it is not supported in grpc-web', async () => { const transport = new GrpcWebFetchTransport({ baseUrl: '' }); + const methodInfo: MethodInfo = { + service: { + typeName: "test.Service", + methods: [], + options: {}, + }, + name: "clientStreaming", + I: RequestMessage, + O: ResponseMessage, + localName: "clientStreaming", + idempotency: undefined, + clientStreaming: true, + serverStreaming: false, + options: {}, + }; try { - await transport.clientStreaming(); + await transport.clientStreaming(methodInfo); fail('this should not be implemented'); } catch (e) { expect(e).toBeInstanceOf(RpcError); @@ -93,8 +108,23 @@ describe('GrpcWebFetchTransport', () => { describe('duplex()', function () { it('throws because it is not supported in grpc-web', async () => { const transport = new GrpcWebFetchTransport({ baseUrl: '' }); + const methodInfo: MethodInfo = { + service: { + typeName: "test.Service", + methods: [], + options: {}, + }, + name: "duplex", + I: RequestMessage, + O: ResponseMessage, + localName: "duplex", + idempotency: undefined, + clientStreaming: true, + serverStreaming: true, + options: {}, + }; try { - await transport.duplex(); + await transport.duplex(methodInfo); fail('this should not be implemented'); } catch (e) { expect(e).toBeInstanceOf(RpcError); diff --git a/packages/grpcweb-transport/src/grpc-web-transport.ts b/packages/grpcweb-transport/src/grpc-web-transport.ts index 630d1264..3234be37 100644 --- a/packages/grpcweb-transport/src/grpc-web-transport.ts +++ b/packages/grpcweb-transport/src/grpc-web-transport.ts @@ -70,12 +70,18 @@ export class GrpcWebFetchTransport implements RpcTransport { } - clientStreaming(/*method: MethodInfo, options: RpcOptions*/): ClientStreamingCall { - throw new RpcError('Client streaming is not supported by grpc-web', GrpcStatusCode[GrpcStatusCode.UNIMPLEMENTED]); + clientStreaming(method: MethodInfo/*, options: RpcOptions*/): ClientStreamingCall { + const e = new RpcError('Client streaming is not supported by grpc-web', GrpcStatusCode[GrpcStatusCode.UNIMPLEMENTED]); + e.methodName = method.name; + e.serviceName = method.service.typeName; + throw e; } - duplex(/*method: MethodInfo, options: RpcOptions*/): DuplexStreamingCall { - throw new RpcError('Duplex streaming is not supported by grpc-web', GrpcStatusCode[GrpcStatusCode.UNIMPLEMENTED]); + duplex(method: MethodInfo/*, options: RpcOptions*/): DuplexStreamingCall { + const e = new RpcError('Duplex streaming is not supported by grpc-web', GrpcStatusCode[GrpcStatusCode.UNIMPLEMENTED]); + e.methodName = method.name; + e.serviceName = method.service.typeName; + throw e; } @@ -158,6 +164,8 @@ export class GrpcWebFetchTransport implements RpcTransport { else // RpcErrors are thrown by us, everything else is an internal error error = new RpcError(reason instanceof Error ? reason.message : "" + reason, GrpcStatusCode[GrpcStatusCode.INTERNAL]); + error.methodName = method.name; + error.serviceName = method.service.typeName; defHeader.rejectPending(error); responseStream.notifyError(error) defStatus.rejectPending(error); @@ -264,6 +272,8 @@ export class GrpcWebFetchTransport implements RpcTransport { else // RpcErrors are thrown by us, everything else is an internal error error = new RpcError(reason instanceof Error ? reason.message : "" + reason, GrpcStatusCode[GrpcStatusCode.INTERNAL]); + error.methodName = method.name; + error.serviceName = method.service.typeName; defHeader.rejectPending(error); defMessage.rejectPending(error); defStatus.rejectPending(error); diff --git a/packages/runtime-angular/src/lib/twirp-transport.service.ts b/packages/runtime-angular/src/lib/twirp-transport.service.ts index e5706d85..d1609654 100644 --- a/packages/runtime-angular/src/lib/twirp-transport.service.ts +++ b/packages/runtime-angular/src/lib/twirp-transport.service.ts @@ -120,6 +120,8 @@ export class TwirpTransport implements RpcTransport { // RpcErrors are thrown by us, everything else is an internal error let error = reason instanceof RpcError ? reason : new RpcError(reason instanceof Error ? reason.message : reason, TwirpErrorCode[TwirpErrorCode.internal]); + error.methodName = method.name; + error.serviceName = method.service.typeName; defHeader.rejectPending(error); defMessage.rejectPending(error); defStatus.rejectPending(error); @@ -168,16 +170,25 @@ export class TwirpTransport implements RpcTransport { } - clientStreaming(/*service: ServiceInfo, method: MethodInfo, options: RpcOptions*/): ClientStreamingCall { - throw new RpcError('Client streaming is not supported by Twirp', TwirpErrorCode[TwirpErrorCode.unimplemented]); + clientStreaming(method: MethodInfo/*, options: RpcOptions*/): ClientStreamingCall { + const e = new RpcError('Client streaming is not supported by Twirp', TwirpErrorCode[TwirpErrorCode.unimplemented]); + e.methodName = method.name; + e.serviceName = method.service.typeName; + throw e; } - duplex(/*service: ServiceInfo, method: MethodInfo, options: RpcOptions*/): DuplexStreamingCall { - throw new RpcError('Duplex streaming is not supported by Twirp', TwirpErrorCode[TwirpErrorCode.unimplemented]); + duplex(method: MethodInfo/*, options: RpcOptions*/): DuplexStreamingCall { + const e = new RpcError('Duplex streaming is not supported by Twirp', TwirpErrorCode[TwirpErrorCode.unimplemented]); + e.methodName = method.name; + e.serviceName = method.service.typeName; + throw e; } - serverStreaming(/*service: ServiceInfo, method: MethodInfo, input: I, options?: RpcOptions*/): ServerStreamingCall { - throw new RpcError('Server streaming is not supported by Twirp', TwirpErrorCode[TwirpErrorCode.unimplemented]); + serverStreaming(method: MethodInfo/*, input: I, options?: RpcOptions*/): ServerStreamingCall { + const e = new RpcError('Server streaming is not supported by Twirp', TwirpErrorCode[TwirpErrorCode.unimplemented]); + e.methodName = method.name; + e.serviceName = method.service.typeName; + throw e; } } diff --git a/packages/runtime-rpc/spec/rpc-error.spec.ts b/packages/runtime-rpc/spec/rpc-error.spec.ts index 4d0f32a9..7a090794 100644 --- a/packages/runtime-rpc/spec/rpc-error.spec.ts +++ b/packages/runtime-rpc/spec/rpc-error.spec.ts @@ -38,6 +38,9 @@ describe('RpcError', () => { foo: "bar" }); expect(err.toString()).toBe('RpcError: msg\n\nCode: cde\n\nMeta:\n x: y\n foo: bar'); + err.methodName = "Qux"; + err.serviceName = "Foo.Bar.Baz"; + expect(err.toString()).toBe('RpcError: msg\n\nCode: cde\nMethod: Foo.Bar.Baz/Qux\n\nMeta:\n x: y\n foo: bar'); }); diff --git a/packages/runtime-rpc/src/rpc-error.ts b/packages/runtime-rpc/src/rpc-error.ts index 98ab5251..2bf917ea 100644 --- a/packages/runtime-rpc/src/rpc-error.ts +++ b/packages/runtime-rpc/src/rpc-error.ts @@ -22,6 +22,24 @@ export class RpcError extends Error { */ meta: RpcMetadata; + /** + * The name of the RPC method that was called as declared in .proto + */ + methodName?: string; + + /** + * The name of the RPC service that was called as declared in .proto + * + * It will be in the form of: + * - package name + * - dot "." + * - service name + * + * If the service was declared without a package, the package name and dot + * are omitted. + */ + serviceName?: string; + name = 'RpcError'; constructor(message: string, code = 'UNKNOWN', meta?: RpcMetadata) { @@ -39,6 +57,9 @@ export class RpcError extends Error { l.push(''); l.push('Code: ' + this.code); } + if (this.serviceName && this.methodName) { + l.push('Method: ' + this.serviceName + '/' + this.methodName) + } let m = Object.entries(this.meta); if (m.length) { l.push(''); diff --git a/packages/twirp-transport/src/twirp-transport.ts b/packages/twirp-transport/src/twirp-transport.ts index f7befc2c..bb3376b3 100644 --- a/packages/twirp-transport/src/twirp-transport.ts +++ b/packages/twirp-transport/src/twirp-transport.ts @@ -112,6 +112,8 @@ export class TwirpFetchTransport implements RpcTransport { // RpcErrors are thrown by us, everything else is an internal error let error = reason instanceof RpcError ? reason : new RpcError(reason instanceof Error ? reason.message : reason, TwirpErrorCode[TwirpErrorCode.internal]); + error.methodName = method.name; + error.serviceName = method.service.typeName; defHeader.rejectPending(error); defMessage.rejectPending(error); defStatus.rejectPending(error); @@ -161,16 +163,25 @@ export class TwirpFetchTransport implements RpcTransport { } - clientStreaming(/*service: ServiceInfo, method: MethodInfo, options: RpcOptions*/): ClientStreamingCall { - throw new RpcError('Client streaming is not supported by Twirp', TwirpErrorCode[TwirpErrorCode.unimplemented]); + clientStreaming(method: MethodInfo/*, options: RpcOptions*/): ClientStreamingCall { + const e = new RpcError('Client streaming is not supported by Twirp', TwirpErrorCode[TwirpErrorCode.unimplemented]); + e.methodName = method.name; + e.serviceName = method.service.typeName; + throw e; } - duplex(/*service: ServiceInfo, method: MethodInfo, options: RpcOptions*/): DuplexStreamingCall { - throw new RpcError('Duplex streaming is not supported by Twirp', TwirpErrorCode[TwirpErrorCode.unimplemented]); + duplex(method: MethodInfo/*, options: RpcOptions*/): DuplexStreamingCall { + const e = new RpcError('Duplex streaming is not supported by Twirp', TwirpErrorCode[TwirpErrorCode.unimplemented]); + e.methodName = method.name; + e.serviceName = method.service.typeName; + throw e; } - serverStreaming(/*service: ServiceInfo, method: MethodInfo, input: I, options?: RpcOptions*/): ServerStreamingCall { - throw new RpcError('Server streaming is not supported by Twirp', TwirpErrorCode[TwirpErrorCode.unimplemented]); + serverStreaming(method: MethodInfo/*, input: I, options?: RpcOptions*/): ServerStreamingCall { + const e = new RpcError('Server streaming is not supported by Twirp', TwirpErrorCode[TwirpErrorCode.unimplemented]); + e.methodName = method.name; + e.serviceName = method.service.typeName; + throw e; } }