-
Notifications
You must be signed in to change notification settings - Fork 459
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 FieldMask utilities to Message types #1505
Conversation
At quick glance you seem to have two distinct proposals in here (they don't seem connected in implementation), if that's the case, I'd suggest splitting this into two PRs so there can be easier discussion on them independently. It likely would help to also provide some examples of what usecases you are trying to enable. |
Hi @thomasvl, the protobuf mirroring feature has been deleted from this PR, so the current one is a proposal just for |
Handing off to Apple folks. Idle thought - Since |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So far we have not exposed any of the actual underlying proto definition in our generated code such as proto message name, field numbers or similar AFAIK. This would be diverting from this. Doesn't mean that I am against that but @thomasvl and @tbkka you did most of the work on SwiftProtobuf
and are probably better at judging this.
I am also wondering if this is something that other languages do provide.
8e5d1b3
to
eaa0666
Compare
I just saw the new implementation as a standalone decoder. That has the big advantage of not requiring any additional code generation, so it won't increase generated code size and can therefore be available by default everywhere. This is the right way to implement this functionality. The real question, then, is whether this new functionality is right for this particular library. As a rule, we do try to stick closely to the protobuf specification and precedents set by other implementations. So I have a couple of questions to help better understand how this proposal relates to other prior art:
Related: Your ProtobufMirror idea should be implementable as a new encoder, again without requiring any new code generation. |
@tbkka Thanks for your response.
The concept is the same in all the languages you've mentioned but when it comes to implementation, we would see different APIs based on the nature of each language. As I found in most of them, they use Descriptor and Reflection. Here are some examples:
Maybe, but AFAIK, it just helps us to tell the backend which fields should be excluded, when the client receives the response we have to replace somehow a bunch of values with those ones we've cached before. Here we need an API to set values by field numbers.
The |
I believe FieldMask usually also has helpers that work with copying things from one message to another message - "Copy just these fields". And by having it within the library it would have access to the field names data that currently isn't exposed. To really make these Apply methods useful would likely be figuring out how to include the field numbers in the generated files for folks. My initial thought would be an |
There are a number of inter-related features here that I have in mind. Since many of these can be implemented in terms of the others, it's worth thinking about them together to figure out how we want to provide them:
If we had APIs to query, set, and clear fields by name and by number, those could be used to implement the FieldMask utilities. They could also be used as building blocks for Descriptor-based utilities. I think those APIs could all be built using the techniques you've been exploring. (And coincidentally, in #1504, Max is proposing a different way to organize the traversal logic that might make the above more efficient.) |
To sum up, we'd better implement some utilities as extensions on the FieldMask and Messages. Here are some of those utils I found helpful: extension Message {
/// Returns a field mask with all fields of the message type.
static var fieldMask: Google_Protobuf_FieldMask
/// Builds a field mask from some particular field numebers of a message
/// - Parameter fieldNumbers: Field numbers of paths
/// - Returns: Field mask that include paths of corresponding field numbers
static func fieldMask(fieldNumbers: [Int]) -> Google_Protobuf_FieldMask
/// Builds a field mask by excluding some paths
/// - Parameter paths: Paths to be excluded
/// - Returns: Field mask that does not include the paths
static func fieldMask(excludedPaths paths: [String]) -> Google_Protobuf_FieldMask
/// Builds a field mask by reversing another mask
/// - Parameter mask: Mask to be reversed
/// - Returns: Field mask that does not include the `mask` paths
static func fieldMask(reverse mask: Google_Protobuf_FieldMask) -> Google_Protobuf_FieldMask
/// Clears masked fields and keep the other fields unchanged
/// - Parameter mask: Field mask which determines what fields shoud be cleared
mutating func mask(by mask: Google_Protobuf_FieldMask)
/// Clears all fields of a message except particular fields refered in mask
/// - Parameter mask: Fields should be ignored in clearance
mutating func mask(except mask: Google_Protobuf_FieldMask)
/// Overrides value of masked fields in original message with the input message
/// - Parameters:
/// - message: Message which overrides some fields of the original message
/// - mask: Field mask which determines what fields shoud be overriden
mutating func override(with message: Self, by mask: Google_Protobuf_FieldMask)
func masked(by mask: Google_Protobuf_FieldMask) -> Self
func masked(except mask: Google_Protobuf_FieldMask) -> Self
func overriden(with message: Self, by mask: Google_Protobuf_FieldMask) -> Self
}
extension Google_Protobuf_FieldMask {
func union(_ mask: Google_Protobuf_FieldMask) -> Self
func intersection(_ mask: Google_Protobuf_FieldMask) -> Self
} If you agree with these APIs, I could implement it. Please let me know if you have any other idea. @tbkka @thomasvl |
I haven't had a chance to really think about these or compare to other apis; but one general question that does come to mind: Would the I'm not sure which makes for a more swifty api, but maybe something to think about. They may actually make more sense to developers as convenience initializers? |
Can these all be implemented with the information we generate today? It seems like the name map might have everything you need for this? Or does this require something like #1504 first, in order to expose the field information in a more easily-digestible form? |
Yes it could be implemented already by |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Read back through everything to try and catch back up on the code.
Comments are fixed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks pretty good, kicked off the CI on the current state of things.
@tbkka can you take a look again to provide final decision since it is a new addition to the library.
@pouyayarandi looks like atleast one unittest test is failing. |
I changed my code a little bit, but I couldn't reproduce the failing test on my local machine and all tests passed. So it may fail again. Can you please re-run pipeline? @thomasvl |
It failed again 😢 |
So it's only failing on linux? You could try using Docker to use do a local linux build (can't say I have any experience with that). The failure seems to be around group merging? You could try making a temporary test case out of it to try and zero in on things. i.e. - make a test that just merges two messages with the fields in question, but doesn't use the FieldMask support, does that work as intended? Ensure the calculated mask has what you expect, etc. You can also tweak |
Bug fixed @thomasvl Problem was about |
Make sure you are synced up to |
I fixed the bug on another branch which I rebased with synced fork of your |
Hi @thomasvl, just for follow-up; do you have any updates? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good! The code is well-organized and easy to read. There are a few places where performance could be improved, but we can iterate on that later if that proves to be a problem for people.
I have just a few small suggestions:
- Inline a couple of trivial helpers
- A couple of small naming adjustments to clarify the public API.
It would also be good to update Documentation/API.md with information about this, but I think that should just be a separate PR.
Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift
Outdated
Show resolved
Hide resolved
Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift
Outdated
Show resolved
Hide resolved
Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift
Outdated
Show resolved
Hide resolved
Thank you @tbkka, all comments were fixed |
Merged! I'm looking forward to the next PR that updates Documentation/API.md with information about how people can use this. ;-) |
@pouyayarandi thanks again for all the work here! |
@pouyayarandi & @tbkka – Not sure why I didn't think of it before, but this morning I got wondering if with the new info, should any of the old initializers get revisited? Specifically –
|
There are two kinds of validation we might do and a couple of different places to do it:
We can do any of the above validation in a few different places:
I think that asserting/validating initializers are probably the least important here, though they might simplify some use cases. It's more important to validate when (de)serializing or masking. |
I was about to say I don't think we can do this one, but it just dawned on me, I guess the initializers can't do it either. The problem is we don't know the Message the proto names/JSON names are supposed to match, we'd need all new initializers that also take a |
People need to be able to build FieldMask protos without validating against a message type. (Because there are always going to be cases where the message type that's going to be masked isn't actually on the local system.) We must validate against a message type when we use the FieldMask against an actual message. I think in most cases, that's actually the right place to do that validation. |
Yea so I guess this work doesn't change anything. But what we could do is validated all the strings to ensure they are valid field names according to the proto language spec. |
Summary
This PR adds a bunch of utilities that enable message types to modify some of their fields (by Google well-known type, FieldMask). Here are added APIs:
Implementation
This feature is empowered by
_ProtoNameProviding
to map proto field numbers to names. This enables us to have the aforementioned inits of FieldMask. Also, for masking a message two Decoders have been implemented:GetPathDecoder
andSetPathDecoder
. The first one gets the value of a field by its path, the latter sets a value to a field of the message by the path. Inoverride
function, value will be captured by GetPathDecoder and then, it will be set by the SetPathDecoder.Use case
Assuming we have a caching system which prevent some fields of a response from recalculation. Backend gives us a proto message and tells that some of its fields should be cached (i.e. field number 1). Next time, when we are going to make a request, we evaluate our client-side's cache and send the cached field numbers in the header of the request (i.e. we send back 1 as a field number that its value is already cached). As a result, backend response with leaving those fields empty (or filled with default values). And in client-side, we need to modify those field numbers with cached values.
For this purpose, first of all, we can store the original backend response somewhere, and in the rest of requests we can override the responses with the cached proto message:
Testing
Unit tests have been added for the new feature, and all tests have passed.
Considerations
a.b
refers to fieldb
of the fielda
in the message.