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

fix(grpc-js): Add support for impl type to server.addService #1556

Merged
merged 3 commits into from Sep 2, 2020

Conversation

slavovojacek
Copy link
Contributor

Adding support for ImplementationType to server.addService(...).

It seems like the original (non-js) grpc type definitions support this - https://github.com/grpc/grpc-node/blob/grpc%401.24.x/packages/grpc-native-core/index.d.ts#L266.

Not sure if there was a reason not to do it in grpc-js?

Thanks!

@linux-foundation-easycla
Copy link

linux-foundation-easycla bot commented Aug 31, 2020

CLA Check
The committers are authorized under a signed CLA.

Copy link
Member

@murgatroid99 murgatroid99 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The difference there was just an oversight. I'm fine with making this change.

UntypedHandleCall
> = UntypedServiceImplementation
>(
service: ServiceDefinition<ImplementationType>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might need to be Partial<ServiceDefinition<ImplementationType>>. A service definition is allowed to be missing some methods.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't think Partial<T> is needed here as ServiceDefinition<T> is defined in such a way that allows the service definition to be missing some methods?

export interface UntypedServiceImplementation {
  [name: string]: UntypedHandleCall;
}

export type ServiceDefinition<
  ImplementationType = UntypedServiceImplementation
> = {
  readonly [index in keyof ImplementationType]: MethodDefinition<any, any>;
};

But I may be wrong... 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, right, the problem might actually be the other way around: if the implementation object has methods that aren't part of the service definition, those keys will be considered missing in the service definition.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And I think the Partial<T> there solves that problem: the inferred ServiceDefinition<ImplementationType> type may have extra keys corresponding to implementation object methods that are not part of the service, and partial allows those to be omitted from the service definition object.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will action shortly – just bogged down with other work; sorry!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I looked into it, pushed new update – that's the best I can manage at this stage. Seems to be working.

Screenshot 2020-09-02 at 16 06 24

@slavovojacek
Copy link
Contributor Author

FYI – faced some issues related to microsoft/TypeScript#15300 with the original implementation.

Copy link
Member

@murgatroid99 murgatroid99 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I strongly prefer to avoid type assertions wherever possible, because they effectively invalidate the compiler's type checks.

implementation: UntypedServiceImplementation
addService<
ImplementationType extends {
readonly [index in keyof ImplementationType]: UntypedHandleCall;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that every member of ImplementationType is an UntypedHandleCall? If so, that is not accurate. Implementation objects are allowed to have other members that are not registered as gRPC method handlers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this just now.

@@ -161,7 +165,7 @@ export class Server {
throw new Error('addService() requires two objects as arguments');
}

const serviceKeys = Object.keys(service);
const serviceKeys = Object.keys(service) as Array<keyof ImplementationType>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type assertion is not exactly accurate. The service can have keys pointing to method descriptions that are not implemented in the implementation object.

And it's not clear that this provides a significant benefit in terms of narrowing the type, because another type assertion name as string is needed later, and that would be implicit without this type assertion here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted.

@@ -189,13 +193,13 @@ export class Server {
let impl;

if (implFn === undefined && typeof attrs.originalName === 'string') {
implFn = implementation[attrs.originalName];
implFn = implementation[attrs.originalName as keyof ImplementationType];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the previous point, this method may not be implemented in the implementation object, so this is not necessarily an accurate type assertion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted.

}

if (implFn !== undefined) {
impl = implFn.bind(implementation);
impl = implFn.bind(implementation as any);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this cast needed at all? It looks to me like bind accepts an unknown, so anything should work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted.

Copy link
Member

@murgatroid99 murgatroid99 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your contribution.

@murgatroid99 murgatroid99 merged commit e21f337 into grpc:master Sep 2, 2020
@murgatroid99
Copy link
Member

I'm sorry, but I'm going to have to revert this. It broke my code in a different PR, and that probably means that it has the potential to break other people's code.

@slavovojacek
Copy link
Contributor Author

slavovojacek commented Sep 3, 2020

Thanks for letting me know, I looked at the PR and the error, I do believe #1561 will fix that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants