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

Add serde support for google.protobuf.Any #18

Closed
wants to merge 2 commits into from

Conversation

XAMPPRocky
Copy link
Contributor

@XAMPPRocky XAMPPRocky commented Nov 22, 2021

This PR adds serde support for google.protobuf.Any. This one is a bit more complex than google.protobuf.Valuebecause in order to support the inline layout for objects we need to have an additional marshalling step, where we transcode the value into a google.protobuf.Value in order to be able to determine what the type is, and how to encode/decode it.

The only alternative to this approach that I'm aware of, would be to change Any to accept a generic parameter T, but that would defeat the purposes of it being dynamic.

closes #2
depends on #16

@tustvold
Copy link
Collaborator

Would you be able to rebase this on to current main 🙏

Copy link
Collaborator

@tustvold tustvold left a comment

Choose a reason for hiding this comment

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

Had a look at the last commit in this PR, and I think the implementation doesn't match my understanding of how Any behaves w.r.t JSON. This could well be my understanding that is wrong though 😅

pbjson-types/src/any.rs Outdated Show resolved Hide resolved

const NAME: &str = "google.protobuf.Any";

let value = <crate::Value as prost::Message>::decode(self.value.clone()).map_err(|_| {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't this perform a match on self.type and decode to the corresponding well known type if it matches? This seems to be assuming all google.protobuf.Any payloads are encoded as google.protobuf.Value?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

decode to the corresponding well known type if it matches?

I'm not sure that I follow. If we only decoded matching "well-known" values, then this type can't be used for most projects. For example, in the project I want to use this library in, we're using the data-plane-api which uses google.protobuf.Any to store typed configs, those aren't going to be known to this project, so that makes decoding those impossible.

This seems to be assuming all google.protobuf.Any payloads are encoded as google.protobuf.Value?

Almost correct, it assumes that any payload can be decoded as a google.protobuf.Value, which to my understanding (which could be wrong), matches any protobuf value.

Copy link
Collaborator

@tustvold tustvold Nov 27, 2021

Choose a reason for hiding this comment

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

If we only decoded matching "well-known" values, then this type can't be used for most projects.

Yes, this is a major limitation of protobuf.Any and by extension protobuf as a whole. A payload cannot be decoded without knowledge of the schema, in fact the wire format has no concept of the name of a field at all see here.

so that makes decoding those impossible.

If the client understands the types it can decode the value field to the concrete message type, we do this in IOx for operations.

If you wish to handle any protobuf payload, you will need some channel to get the message descriptors (e.g. gRPC reflection) and then a client library that can use these descriptors to decode the messages, but I'm not sure of a Rust implementation of protobuf reflection...

which to my understanding (which could be wrong), matches any protobuf value.

The wire encoding of a protobuf message is tightly coupled to its schema, whilst any message with the same layout of fields and types could theoretically be decoded as protobuf.Value, it would only be correct to decode to a payload that was an actual encoded protobuf.Value.

Edit: I also found this with some googling that might be useful

};
}
AnyField::Unknown(key) => {
if map.contains_key(&key) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Surely this should be an error?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What do you mean? It does return an error?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I meant any unknown field should be an error, regardless of if it already exists in the map?

pbjson-types/src/any.rs Outdated Show resolved Hide resolved
return Err(serde::de::Error::duplicate_field(VALUE_FIELD));
}

value = if let Ok(bytes) =
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this should be inspecting the type_url to work out what the value payload is and how to treat it, I think this assumes that anything that is valid base64 is a bytes payload?

@XAMPPRocky
Copy link
Contributor Author

A payload cannot be decoded without knowledge of the schema, in fact the wire format has no concept of the name of a field at all see here.

I think you're mixing concepts here, just because a protobuf message doesn't describe the schema of that generated message, doesn't mean that the protobuf wire format itself isn't self-describing. You can decode a Protobuf message without knowing the schema, as detailed in "Message structure" in your link. It's just less useful. I've worked with actual non-self-describing formats, and protobuf isn't one of them.

That being said, it does seem like protobuf.Value doesn't encompass all of the types in the protobuf wire format (primarily integer types, and bytes). So I'm just going to close this PR, because this project won't ever be useful to me compared to writing an Any implementation directly in the project, which using this project would prevent.

@XAMPPRocky XAMPPRocky closed this Nov 27, 2021
@XAMPPRocky XAMPPRocky deleted the add-any branch November 27, 2021 18:08
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.

Correct serialization of google::protobuf::Any
2 participants