-
Notifications
You must be signed in to change notification settings - Fork 100
Add support for "oneof" keyword #211
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
Conversation
These commits add support for the
I've added some rough tests that should probably be filled out, particularly for each |
/cc @liveh2o @abrandoned |
Looks fine to me. I don't fully understand the applications of such a field, but understanding is not a prerequisite for maintaining parity with the spec. 😉 |
I'm not in love with the current DSL, specifically the package test;
message FullName {
optional string first_name = 1;
optional string last_name = 2;
}
message OneofTest {
oneof SingleIdentifier {
FullName full_name = 1;
string email = 2;
string username = 3;
}
} module Test
class FullName < ::Protobuf::Message; end
class OneofTest < ::Protobuf::Message; end
class FullName
optional :string, :first_name, 1
optional :string, :last_name, 2
end
class OneofTest
set_oneof_names :SingleIdentifier
optional ::Test::FullName, :full_name, 1, :oneof => :SingleIdentifier
optional :string, :email, 2, :oneof => :SingleIdentifier
optional :string, :username, 3, :oneof => :SingleIdentifier
end
end Any ideas @liveh2o or look fine to you? |
FYI added |
@localshred @liveh2o, for the DSL, what about using a Ruby block to mimick a similar structure? oneof :SingleIdentifier do
optional ::Test::FullName, :full_name, 1
optional :string, :email, 2
optional :string, :username, 3
end The implementation in def oneof(identifier, &block)
@current_oneof = identifier
yield
ensure
@current_oneof = nil
end
def define_field(*args)
if @current_oneof
# set options correctly
end
end |
@zachmargolis are nested |
@pascallouisperez AFAIK no, there are no nested |
I like the symmetry the block DSL has with the Protocol Buffers definition, but I'm not sure it communicates intent unless it also yields something to the block. The scope of the block also doesn't affect anything. @localshred: why do we need to capture the oneof names as well as define them in the options block? Is that part of the spec? |
@liveh2o we need the name because we store which fields belong to the one of so that we can clear the other fields when "one of" them is set. # using OneofTest example from above:
test = OneofTest.new
test.full_name = FullName.new(:first_name => "Billy", :last_name => "Blanks") # sets the full_name field
test.email = "billy@blanks.com" # sets email field, _and_ unsets full_name field If we don't specify which fields the oneof type are associated with we don't know how to unset the other associated fields when one of them is set. We could remove the The block form looks nicer, but since you don't hand-roll most (all?) definitions it just feels a little overkill, and doesn't actually solve the problem. A block DSL that might work: oneof :SingleIdentifier do |oneof_identifier|
optional ::Test::FullName, :full_name, 1, :oneof => oneof_identifier
optional :string, :email, 2, :oneof => oneof_identifier
optional :string, :username, 3, :oneof => oneof_identifier
end But, I'm not sure why at that point. I guess you could combine the oneof :SingleIdentifier, [:full_name, :email, :username] do
optional ::Test::FullName, :full_name, 1, :oneof => oneof_identifier
optional :string, :email, 2, :oneof => oneof_identifier
optional :string, :username, 3, :oneof => oneof_identifier
end This last form feels like the best of both worlds. Probably. |
Solid points. I like the last idea. Perhaps we could use @zachmargolis' idea of using the block to set some context that oneof :SingleIdentifier, [:full_name, :email, :username] do
optional ::Test::FullName, :full_name, 1
optional :string, :email, 2
optional :string, :username, 3
end The only thing that is kind of odd is that this DSL reuses the |
Actually, maybe this makes more sense: oneof :SingleIdentifier, [:full_name, :email, :username]
optional ::Test::FullName, :full_name, 1
optional :string, :email, 2
optional :string, :username, 3 This let's us define fields without any customization, but still specify which fields should be considered part of a Oneof set. |
Oh, good point, I did not realize that fields inside And I don't think we need to list the fields included in the oneof :SingleIdentifier do
field ::Test::FullName, :full_name, 1
field :string, :email, 2
field :string, :username, 3
end def oneof(identifier, &block)
@current_oneof_identifier = identifier
@current_oneof_fields = Set.new
yield
ensure
@current_oneof_fields.each do |field|
field.oneof_identifier = @current_oneof_identifier
field.oneof_siblings = @current_oneof_fields - field
# other oneof configuration
end
@current_oneof_identifier = nil
@current_oneof_fields = nil
end
def field(*args)
raise "must be inside 'oneof'" unless @current_oneof_identifier
optional(*args)
end
def define_field(*args)
field = # ... same as before
if @current_oneof_identifier
@current_oneof_fields << field
end
end |
I like that idea, but if we go that route, then I'd expect def oneof(identifier, &block)
@current_oneof_identifier = identifier
@current_oneof_fields = Set.new
yield
@current_oneof_fields.each do |field|
field.oneof_identifier = @current_oneof_identifier
field.oneof_siblings = @current_oneof_fields - field
# other oneof configuration
end
ensure
@current_oneof_identifier = nil
@current_oneof_fields = nil
end
def oneof_field(*args)
raise "must be inside 'oneof'" unless @current_oneof_identifier
@current_oneof_fields << args.first # Or however we handle this
optional(*args)
end
def define_field(*args)
# ... same as before
end |
I guess I'm struggling to understand what problem you're trying to solve. All of these solutions are aimed at removing the need for the |
Ahh, now understanding that oneof fields are optional implicitly. Did not realize that. Re-analyzing my assumptions... |
The spec is definitely written in a confusing way: "...at most one field can be set at the same time." That implies that none of the fields could be sent and the message would still be valid, but doesn't state it explicitly. I originally read it as "at least one", which made it even more confusing 😄. |
Not sure what spec you're reading, but they actually do state it explicitly in the 2nd paragraph:
|
Well, I was selectively reading your excerpt, of course 😉. |
what is the status of this PR? |
Hi! Here at Square we'd love this to land. I'm not familiar with the code, but did the grunt work of rebasing: https://github.com/pascallouisperez/protobuf/tree/pascal/bj-pb-2.6-rebased Can you help from there? |
Conflicts: Rakefile lib/protobuf/field/message_field.rb lib/protobuf/generators/extension_generator.rb lib/protobuf/generators/field_generator.rb lib/protobuf/generators/message_generator.rb lib/protobuf/message/fields.rb spec/lib/protobuf/generators/field_generator_spec.rb
@clarkds14 @pascallouisperez hey guys, thanks for the ping. I'm revising the DSL, almost have it updated. Should be able to merge this by the end of the week methinks. Will keep you guys posted. |
@clarkds14 @pascallouisperez @localshred I'd love to see this land too. 👍 Did you come to some conclusion on the internal API? |
Just another friendly ping about this one. We'd love to see it at AngelList as well. Any help needed with respect to the DSL? |
Thanks for the pings guys, should be able to merge this in tonight or tomorrow. @BBonifield @kmontag @clarkds14 @pascallouisperez |
… name to the optional field options hash
…is the :oneof option
So, after investigating there really isn't any need for a DSL in the generated proto, the only thing you need is the require 'protobuf/message'
module Test
class FullName < ::Protobuf::Message; end
class OneofTest < ::Protobuf::Message; end
class FullName
optional :string, :first_name, 1
optional :string, :last_name, 2
end
class OneofTest
optional ::Test::FullName, :full_name, 1, :oneof => :SingleIdentifier
optional :string, :email, 2, :oneof => :SingleIdentifier
optional :string, :username, 3, :oneof => :SingleIdentifier
end
end |
Ready to merge if anyone wants a final pass. |
|
Almost one year since last comment. Any plans on support for |
@apbarrero We do plan to. It might take a bit, though. If you want to use this as a template and implement it, that would be super awesome!! |
Protobuf 2.6 was released today. We should add support for
oneof
keyword field definitions.