pw_rpc: Optional invoke parameters

Invoke() is called with a request message on the Unary and
ServerStreaming method stubs. It should not be provided when invoking
either a ClientStream or a BidirectionalStream.

No-Docs-Update-Reason: Docs already demonstrated correct invoke()
Bug: b/194329554
Change-Id: Ib1b37970feba55d25adab6540015e1e81b41144a
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/61561
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
Pigweed-Auto-Submit: Jared Weinstein <jaredweinstein@google.com>
Reviewed-by: Alexei Frolov <frolv@google.com>
diff --git a/pw_rpc/ts/client_test.ts b/pw_rpc/ts/client_test.ts
index 40046bd..b82a50b 100644
--- a/pw_rpc/ts/client_test.ts
+++ b/pw_rpc/ts/client_test.ts
@@ -23,7 +23,7 @@
 
 import {Client} from './client';
 import {Channel, Method} from './descriptors';
-import {MethodStub} from './method';
+import {ServerStreamingMethodStub, UnaryMethodStub} from './method';
 import * as packets from './packets';
 
 const TEST_PROTO_PATH = 'pw_rpc/ts/test_protos-descriptor-set.proto.bin';
@@ -208,14 +208,15 @@
   }
 
   describe('Unary', () => {
-    let unaryStub: MethodStub;
+    let unaryStub: UnaryMethodStub;
     let request: any;
     let requestType: MessageCreator;
     let responseType: MessageCreator;
 
     beforeEach(async () => {
-      unaryStub = client.channel()?.methodStub(
-          'pw.rpc.test1.TheTestService.SomeUnary')!;
+      unaryStub =
+          client.channel()?.methodStub(
+              'pw.rpc.test1.TheTestService.SomeUnary')! as UnaryMethodStub;
       requestType = unaryStub.method.requestType;
       responseType = unaryStub.method.responseType;
       request = new requestType();
@@ -248,7 +249,7 @@
       request.setMagicNumber(5);
 
       let onNext = jasmine.createSpy();
-      const call = unaryStub.invoke(request, onNext, () => {}, () => {});
+      const call = unaryStub.invoke(request, onNext);
 
       expect(requests.length).toBeGreaterThan(0);
       requests = [];
@@ -259,11 +260,10 @@
     });
 
     it('nonblocking duplicate calls first is cancelled', () => {
-      const firstCall = unaryStub.invoke(request, () => {}, () => {}, () => {});
+      const firstCall = unaryStub.invoke(request);
       expect(firstCall.completed()).toBeFalse();
 
-      const secondCall =
-          unaryStub.invoke(request, () => {}, () => {}, () => {});
+      const secondCall = unaryStub.invoke(request);
       expect(firstCall.error).toEqual(Status.CANCELLED);
       expect(secondCall.completed()).toBeFalse();
     });
@@ -274,21 +274,23 @@
       };
 
       enqueueResponse(1, unaryStub.method, Status.OK);
-      const call = unaryStub.invoke(request, errorCallback, () => {}, () => {});
+      const call = unaryStub.invoke(request, errorCallback);
       expect(call.callbackException!.name).toEqual('Error');
       expect(call.callbackException!.message).toEqual('Something went wrong!');
     });
   })
 
   describe('ServerStreaming', () => {
-    let serverStreaming: MethodStub;
+    let serverStreaming: ServerStreamingMethodStub;
     let request: any;
     let requestType: any;
     let responseType: any;
 
     beforeEach(async () => {
-      serverStreaming = client.channel()?.methodStub(
-          'pw.rpc.test1.TheTestService.SomeServerStreaming')!;
+      serverStreaming =
+          client.channel()?.methodStub(
+              'pw.rpc.test1.TheTestService.SomeServerStreaming')! as
+          ServerStreamingMethodStub;
       requestType = serverStreaming.method.requestType;
       responseType = serverStreaming.method.responseType;
       request = new requestType();
@@ -336,7 +338,7 @@
       const onNext = jasmine.createSpy();
       const onCompleted = jasmine.createSpy();
       const onError = jasmine.createSpy();
-      let call = serverStreaming.invoke(request, onNext, () => {}, () => {});
+      let call = serverStreaming.invoke(request, onNext);
       expect(onNext).toHaveBeenCalledOnceWith(response);
 
       onNext.calls.reset();
diff --git a/pw_rpc/ts/docs.rst b/pw_rpc/ts/docs.rst
index 2d63f68..0be9d77 100644
--- a/pw_rpc/ts/docs.rst
+++ b/pw_rpc/ts/docs.rst
@@ -61,6 +61,8 @@
 
 Calling an RPC
 ==============
+``channel.methodStub()`` returns a general methodStub. It must be typecast
+before invoke can be called with the correct parameters.
 
 Unary RPC
 ---------
@@ -69,7 +71,8 @@
 .. code-block:: typescript
 
   unaryStub = client.channel()?.methodStub(
-      'pw.rpc.test1.TheTestService.SomeUnary')!;
+      'pw.rpc.test1.TheTestService.SomeUnary')!
+      as UnaryMethodStub;
   request = new unaryStub.method.requestType();
   request.setFooProperty('hello world');
   const call = unaryStub.invoke(request, (response) => {
@@ -84,7 +87,8 @@
 .. code-block:: typescript
 
   serverStreamRpc = client.channel()?.methodStub(
-      'pw.rpc.test1.TheTestService.SomeUnary')!;
+      'pw.rpc.test1.TheTestService.SomeUnary')!
+      as ServerStreamingMethodStub;
   const onNext = (response) => {console.log(response)};
   const call = serverStreamRpc.invoke(undefined, onNext);
 
diff --git a/pw_rpc/ts/method.ts b/pw_rpc/ts/method.ts
index c30f695..3ea4427 100644
--- a/pw_rpc/ts/method.ts
+++ b/pw_rpc/ts/method.ts
@@ -14,7 +14,7 @@
 
 import {Message} from 'google-protobuf';
 
-import {Call, Callback, ServerStreamingCall, UnaryCall} from './call';
+import {BidirectionalStreamingCall, Call, Callback, ClientStreamingCall, ServerStreamingCall, UnaryCall} from './call';
 import {Channel, Method, MethodType, Service} from './descriptors';
 import {PendingCalls, Rpc} from './rpc_classes';
 
@@ -32,7 +32,7 @@
   }
 }
 
-export class MethodStub {
+export abstract class MethodStub {
   readonly method: Method;
   readonly rpcs: PendingCalls;
   readonly rpc: Rpc;
@@ -44,22 +44,14 @@
     this.channel = channel;
     this.rpc = new Rpc(channel, method.service, method)
   }
-
-  invoke(
-      request?: Message,
-      onNext: Callback = () => {},
-      onCompleted: Callback = () => {},
-      onError: Callback = () => {}): UnaryCall {
-    throw Error('invoke() not implemented');
-  }
 }
 
-class UnaryMethodStub extends MethodStub {
+export class UnaryMethodStub extends MethodStub {
   // TODO(jaredweinstein): Add blocking invocation.
   // invokeBlocking(request) {...}
 
   invoke(
-      request?: Message,
+      request: Message,
       onNext: Callback = () => {},
       onCompleted: Callback = () => {},
       onError: Callback = () => {}): UnaryCall {
@@ -70,7 +62,7 @@
   }
 }
 
-class ServerStreamingMethodStub extends MethodStub {
+export class ServerStreamingMethodStub extends MethodStub {
   invoke(
       request?: Message,
       onNext: Callback = () => {},
@@ -85,20 +77,18 @@
 
 class ClientStreamingMethodStub extends MethodStub {
   invoke(
-      request: Message,
-      onNext: Callback,
-      onCompleted: Callback,
-      onError: Callback): Call {
+      onNext: Callback = () => {},
+      onCompleted: Callback = () => {},
+      onError: Callback = () => {}): ClientStreamingCall {
     throw Error('ClientStreaming invoke() not implemented');
   }
 }
 
 class BidirectionStreamingMethodStub extends MethodStub {
   invoke(
-      request: Message,
-      onNext: Callback,
-      onCompleted: Callback,
-      onError: Callback): Call {
+      onNext: Callback = () => {},
+      onCompleted: Callback = () => {},
+      onError: Callback = () => {}): BidirectionalStreamingCall {
     throw Error('BidirectionalStreaming invoke() not implemented');
   }
 }