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: code-generation for Services with Struct response types #407

Merged
merged 2 commits into from
Nov 27, 2021

Conversation

boukeversteegh
Copy link
Collaborator

@boukeversteegh boukeversteegh commented Nov 22, 2021

Should fix #403

Could you have a try? @mkmarek -- and thanks for reporting with a reproducing proto file.

You can switch to this version temporarily by changing the version in your package.json to:

"ts-proto": "https://github.com/stephenh/ts-proto/tarball/fix/grpc-struct"

@mkmarek
Copy link

mkmarek commented Nov 22, 2021

Thanks for the swift response @boukeversteegh !

I tested it on the https://github.com/mkmarek/ts-proto-bug repo and it worked like a charm.

So I tried to use it on the project where I got the error originally and noticed we're using a bit different configuration there. We use it with this parameter --ts_proto_opt=outputServices=grpc-js (since we're using it with grpc-js) and that still outputs the wrong thing.

Example:

export const ServiceService = {
foo: {
        path: '/pkg.Service/Foo',
        requestStream: false,
        responseStream: false,
        requestSerialize: (value: FooRequest) =>
          Buffer.from(FooRequest.encode(value).finish()),
        requestDeserialize: (value: Buffer) => FooRequest.decode(value),

       // ↓↓↓↓↓
        responseSerialize: (value: {[key: string]: any} | undefined) =>
          Buffer.from({[key: string]: any} | undefined.encode(value).finish()),
        responseDeserialize: (value: Buffer) => {[key: string]: any} | undefined.decode(value),
      }
}

I updated https://github.com/mkmarek/ts-proto-bug with the --ts_proto_opt=outputServices=grpc-js parameter.

Copy link
Owner

@stephenh stephenh left a comment

Choose a reason for hiding this comment

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

Lgtm with @mkmarek 's caveat that we probably need another "keepValueTypes" (I assume) somewhere.

@boukeversteegh could you add an RPC that returns a struct to this file:

https://github.com/stephenh/ts-proto/blob/main/integration/grpc-js/simple.proto#L27

As part of the fix, to prevent future regressions?

@@ -573,7 +573,7 @@ export function requestType(ctx: Context, methodDesc: MethodDescriptorProto): Co
}

export function responseType(ctx: Context, methodDesc: MethodDescriptorProto): Code {
return messageToTypeName(ctx, methodDesc.outputType);
return messageToTypeName(ctx, methodDesc.outputType, { keepValueType: true });
Copy link
Owner

Choose a reason for hiding this comment

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

Ah that's subtle; thanks @boukeversteegh !

@mkmarek
Copy link

mkmarek commented Nov 22, 2021

Looked at the codebase a bit and adding the same { keepValueType: true } to

return code`${messageToTypeName(ctx, typeName)}.encode(value).finish()`;

return code`${messageToTypeName(ctx, typeName)}.decode(value)`;

Seems to do the trick

@boukeversteegh
Copy link
Collaborator Author

Lgtm with @mkmarek 's caveat that we probably need another "keepValueTypes" (I assume) somewhere.

@boukeversteegh could you add an RPC that returns a struct to this file:

https://github.com/stephenh/ts-proto/blob/main/integration/grpc-js/simple.proto#L27

As part of the fix, to prevent future regressions?

Added that case, and fixed some issues exposed by it as well.

I'll also add a case for Value, because I suspect it will also break.

@boukeversteegh boukeversteegh changed the title Fix code-generation for Services with Struct response types Fix: code-generation for Services with Struct response types Nov 25, 2021
@boukeversteegh boukeversteegh changed the title Fix: code-generation for Services with Struct response types fix: code-generation for Services with Struct response types Nov 25, 2021
Copy link
Owner

@stephenh stephenh left a comment

Choose a reason for hiding this comment

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

Nice! I really like where you put the unwrap methods on each type:

Value.unwrap(Value.decode(reader, reader.uint32()));

I think let's merge this as-is; just as a curiosity, seeing ^ made me wonder, @boukeversteegh wdyt about having Value/Struct/List.decode return the already-unwrapped values? And similarly, Value/Struct/List.encode could accept the not-yet-wrapped values (or maybe if we're really fancy, encode could accept a union of either Value | ...the unwrapped values...?

Just thinking that might be neat for users b/c it would make the wrapper/value types "fade away" to be mostly serialization. Well, although I guess our message representation is already doing that, i.e. if you have a Struct inside another message, it already looks like "just JS", and it's only the encode/decode methods that want/need to know about the wrapped Struct type.

And so my musing of changing decode to return the already-unwrapped type is probably not something that users would use/notice that often anyway, b/c they're more likely interacting with a value type through some message that uses it. So maybe it's better to stay consistent and have encode/decode keep using the wrapper/message types, just like all of the other encode/decode methods do.

@boukeversteegh
Copy link
Collaborator Author

boukeversteegh commented Nov 27, 2021

... just as a curiosity, seeing ^ made me wonder, @boukeversteegh wdyt about having Value/Struct/List.decode return the already-unwrapped values? And similarly, Value/Struct/List.encode could accept the not-yet-wrapped values (or maybe if we're really fancy, encode could accept a union of either Value | ...the unwrapped values...? Just thinking that might be neat for users b/c it would make the wrapper/value types "fade away" to be mostly serialization. Well, although I guess our message representation is already doing that, i.e. if you have a Struct inside another message, it already looks like "just JS", and it's only the encode/decode methods that want/need to know about the wrapped Struct type.

And so my musing of changing decode to return the already-unwrapped type is probably not something that users would use/notice that often anyway, b/c they're more likely interacting with a value type through some message that uses it. So maybe it's better to stay consistent and have encode/decode keep using the wrapper/message types, just like all of the other encode/decode methods do.

I had the exact same train of thought, and ended up with the same tentative conclusion, to keep the signatures of the encode/decode methods the same as the others.

Two other reasons why I felt it might be better to keep the wrapping separate from the encoding:

  • Loss of ability to work with the wrapped objects, such as the ability to manually provide a wrapped object {stringValue: "hello"} and encode it. This may be useful when dealing with proto agents or generators that don't properly (un)wrap the values.
  • I think it may make the plugin code less straight forward to understand. I.e. to know where wrapping/unwrapping happens, and the interfaces/method signatures will be inconsistent. During implementation, it was quite convenient to know that all methods on the interfaces always work with the message instances, and not with native types.

I think let's merge this as-is;

Great, thanks for your review and support. I will go ahead and merge!

@boukeversteegh boukeversteegh merged commit f041fa1 into main Nov 27, 2021
@boukeversteegh boukeversteegh deleted the fix/grpc-struct branch November 27, 2021 11:48
stephenh pushed a commit that referenced this pull request Nov 27, 2021
## [1.90.1](v1.90.0...v1.90.1) (2021-11-27)

### Bug Fixes

* code-generation for Services with Struct response types ([#407](#407)) ([f041fa1](f041fa1))
@stephenh
Copy link
Owner

🎉 This PR is included in version 1.90.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

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

Successfully merging this pull request may close these issues.

Invalid typescript syntax generated for google.protobuf.Struct RPC replies in v1.88.0
3 participants