Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: Add ability to generate a generic Rpc interface and client implementation #1061

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,10 @@ Generated code will be placed in the Gradle build directory.
(Note that this only uses the grpc-web runtime, you don't need to use any of their generated code, i.e. the ts-proto output replaces their `ts-protoc-gen` output.)

You'll need to add the `@improbable-eng/grpc-web` and a transport to your project's `package.json`; see the `integration/grpc-web` directory for a working example. Also see [#504](https://github.com/stephenh/ts-proto/issues/504) for integrating with [grpc-web-devtools](https://github.com/SafetyCulture/grpc-web-devtools).
- With `--ts_proto_opt=outputClientImpl=generic`, the client implementations, i.e. `FooServiceClientImpl`, will leverage an Rpc client which makes no assumptions about the format or method of transport. The Rpc interface instead gets a reference to the Protobuf request message instance, as well as `MessageType` instances for the request and response in order to serialize/deserialize those as the Rpc instance sees fit.

Implies `--ts_proto_opt=outputTypeRegistry=true`, as the generated `MessageType` interface is used in the `Rpc` definition.

- With `--ts_proto_opt=returnObservable=true`, the return type of service methods will be `Observable<T>` instead of `Promise<T>`.

- With`--ts_proto_opt=addGrpcMetadata=true`, the last argument of service methods will accept the grpc `Metadata` type, which contains additional information with the call (i.e. access tokens/etc.).
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {
BasicServiceClientImpl,
BasicServiceServiceName,
GetBasicRequest,
GetBasicResponse,
} from './generic-client';

describe('generic-client with asyncIterable and before/after', () => {
let rpc = {
request: jest.fn(),
clientStreamingRequest: jest.fn(),
serverStreamingRequest: jest.fn(),
bidirectionalStreamingRequest: jest.fn(),
};
const expectedResponse = GetBasicResponse.fromPartial({ resp: 'Rowan' });
const request = GetBasicRequest.fromPartial({ name: 'Finn' });
beforeEach(() => {
jest.clearAllMocks();
});

let beforeRequest = jest.fn();
let afterResponse = jest.fn();

it('correctly invokes afterResponse and clientStreamingRequest methods on Rpc', () => {
rpc.clientStreamingRequest.mockReturnValue(Promise.resolve(expectedResponse));

const client = new BasicServiceClientImpl({...rpc, beforeRequest: beforeRequest, afterResponse: afterResponse});
const asyncIterable = (async function*() {}())
const promise = client.ClientStreaming(asyncIterable);

expect(rpc.clientStreamingRequest).toHaveBeenCalledWith(BasicServiceServiceName, "ClientStreaming", asyncIterable, GetBasicRequest, GetBasicResponse);
return promise.then((res) => {
expect(res).toBe(expectedResponse);
expect(afterResponse).toHaveBeenCalledWith(BasicServiceServiceName, "ClientStreaming", expectedResponse);
});
});
it('correctly invokes beforeRequest and serverStreamingRequest methods on Rpc', () => {
const asyncIterable = (async function*() {}())
rpc.serverStreamingRequest.mockReturnValue(asyncIterable);

const client = new BasicServiceClientImpl({...rpc, beforeRequest: beforeRequest, afterResponse: afterResponse});

const resp = client.ServerStreaming(request);

expect(beforeRequest).toHaveBeenCalledWith(BasicServiceServiceName, "ServerStreaming", request);
expect(rpc.serverStreamingRequest).toHaveBeenCalledWith(BasicServiceServiceName, "ServerStreaming", request, GetBasicRequest, GetBasicResponse);
expect(resp).toBe(asyncIterable);
});
it('correctly invokes bidirectionalStreamingRequest on Rpc', () => {
const response = (async function*() {}())
rpc.bidirectionalStreamingRequest.mockReturnValue(response);

const client = new BasicServiceClientImpl({...rpc, beforeRequest: beforeRequest, afterResponse: afterResponse});

const req= (async function*() {}())
const resp = client.BidiStreaming(req)

expect(rpc.bidirectionalStreamingRequest).toHaveBeenCalledWith(BasicServiceServiceName, "BidiStreaming", req, GetBasicRequest, GetBasicResponse);
expect(resp).toBe(response);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
syntax = "proto3";
package basic;

message GetBasicRequest {
string name = 1;
}

message GetBasicResponse {
string resp = 1;
}

service BasicService {
rpc Unary (GetBasicRequest) returns (GetBasicResponse) {}
rpc ServerStreaming (GetBasicRequest) returns (stream GetBasicResponse) {}
rpc ClientStreaming (stream GetBasicRequest) returns (GetBasicResponse) {}
rpc BidiStreaming (stream GetBasicRequest) returns (stream GetBasicResponse) {}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
rpcBeforeRequest=true,rpcAfterResponse=true,outputClientImpl=generic,useAsyncIterable=true

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading