[OpenAPI]: Extend @response / @request tag syntax to accept serializer options#299
Conversation
15170bc to
bae4515
Compare
| key, value = part.split(":", 2).map(&:strip) | ||
| next unless key | ||
|
|
||
| if value.nil? | ||
| hash[key.to_sym] = nil | ||
| elsif value.start_with?(":") | ||
| hash[key.to_sym] = value[1..].to_sym | ||
| elsif value.start_with?('"') && value.end_with?('"') | ||
| hash[key.to_sym] = value[1..-2] | ||
| elsif value == "true" | ||
| hash[key.to_sym] = true | ||
| elsif value == "false" | ||
| hash[key.to_sym] = false | ||
| else | ||
| hash[key.to_sym] = value | ||
| end |
There was a problem hiding this comment.
Can we substitute this logic with YAML parsing? For example:
| key, value = part.split(":", 2).map(&:strip) | |
| next unless key | |
| if value.nil? | |
| hash[key.to_sym] = nil | |
| elsif value.start_with?(":") | |
| hash[key.to_sym] = value[1..].to_sym | |
| elsif value.start_with?('"') && value.end_with?('"') | |
| hash[key.to_sym] = value[1..-2] | |
| elsif value == "true" | |
| hash[key.to_sym] = true | |
| elsif value == "false" | |
| hash[key.to_sym] = false | |
| else | |
| hash[key.to_sym] = value | |
| end | |
| option = YAML.load(part) | |
| return nil unless option.is_a?(Hash) # TODO: handle the nil response in `OpenAPI::Parser` | |
| hash.merge!(option.transform_keys!(&:to_sym)) |
There was a problem hiding this comment.
Okay, I didn't knew about YAML.load ... 😅 make things much simpler. Thanks.
| elsif response_data.nil? | ||
| node.responses[status] = nil | ||
| else | ||
| response_data, serializer_options = Rage::OpenAPI.__parse_serializer_options(response_data) |
There was a problem hiding this comment.
If you check the existing parsers, you'll see that each of them internally calls the similar __try_parse_collection call.
This creates a fair amount of duplication, but this is by design - parsers are intended to be independent systems that receive raw response tags and decide what to do with them.
That being said, let's move this call inside the Blueprinter parser. It's currently the only one using these options, which will also allow us to leave out the ** signature updates for parsers that don't support options. This will also allow the parsers to correctly issue a warning if someone passes options to the parser that doesn't support them.
| end | ||
|
|
||
| # @private | ||
| def self.__parse_serializer_options(str) |
There was a problem hiding this comment.
What about args instead of options?
| def self.__parse_serializer_options(str) | |
| def self.__parse_serializer_args(str) |
…r options - Updated the tag parser(Rage::OpenAPI) to recognise an options hash after the serializer class name. - The parsed options are forwarded as-is to whatever serializer parser from it is invoked, so this change is generic - Alba and any future serializer can use the same syntax. - The tag parser should is tolerant for unknown options i.e it ignores and don't raise so existing tags stay valid.
bae4515 to
27d1726
Compare
|
Hi @rsamoilov can you review the changes again whenever you're free. Thank you! Here's a quick summary:
|
rsamoilov
left a comment
There was a problem hiding this comment.
Hey @Abishekcs,
This looks really nice.
I left a couple of minor suggestions - one to add a YARD comment and another to update the name of the variable.
There're also multiple statements not covered with tests. Let's either remove them if the situations they guard against can't happen or add tests to cover them.
| _, clean_inner, options = __parse_serializer_args(inner) | ||
| if options.any? | ||
| [is_collection, clean_inner, options] |
There was a problem hiding this comment.
| _, clean_inner, options = __parse_serializer_args(inner) | |
| if options.any? | |
| [is_collection, clean_inner, options] | |
| _, clean_inner, args = __parse_serializer_args(inner) | |
| if args.any? | |
| [is_collection, clean_inner, args] |
| # @private | ||
| def self.__parse_serializer_args(str) |
There was a problem hiding this comment.
| # @private | |
| def self.__parse_serializer_args(str) | |
| # @private | |
| # @return [Array<Boolean, String, Hash>] a tuple of (is_collection, serializer, args) | |
| def self.__parse_serializer_args(str) |
| else | ||
| [is_collection, clean_inner, {}] |
There was a problem hiding this comment.
This branch is not covered with tests.
There was a problem hiding this comment.
It's covered by this two test (Line 94) and (Line 99) cases:
context "with a collection using square brackets without options" do
let(:str) { "[UserBlueprint]" }
it { is_expected.to eq([true, "UserBlueprint", {}]) }
end
context "with a collection using Array syntax without options" do
let(:str) { "Array<UserBlueprint>" }
it { is_expected.to eq([true, "UserBlueprint", {}]) }
end|
|
||
| # @private | ||
| def self.__parse_keywords(str) | ||
| return {} if str.nil? || str.empty? |
There was a problem hiding this comment.
The str.empty? clause is not covered with tests.
There was a problem hiding this comment.
This is the test case (Line 148):
context "with empty string" do
let(:str) { "" }
it { is_expected.to eq({}) }
endWhile poking around I noticed removing str.empty? clause didn't break the test context "with empty string" because "".split(",") returns an empty array [], so each_with_object({}) just returns {} naturally without the block ever running.
So str.empty? is technically redundant, but I think keeping it will be nice since it's not immediately obvious that "".split(",").each_with_object({}) returns {} , the explicit check makes the intent clearer for anyone reading the code later.
There was a problem hiding this comment.
Does this make sense😅 ? or should i remove str.empty?
|
|
||
| str.split(",").each_with_object({}) do |part, hash| | ||
| option = YAML.load(part) | ||
| return nil unless option.is_a?(Hash) |
There was a problem hiding this comment.
This condition is not covered with tests.
There was a problem hiding this comment.
Added a new test case for this (Line 183)
context "with an invalid option (not a key value pair)" do
let(:str) { "extended" }
it { is_expected.to be_nil }
end
I have to check this one to be sure. Thanks for the review. |
- Adds a test for when `YAML.load` returns a non-Hash value (e.g. `UserBlueprint(extended)`) confirming `__parse_keywords` returns `nil` instead of crashing.
b75861e to
14b6422
Compare
What this PR does
Extends the tag parser to recognize serializer options like (Few Examples) :
a)
UserBlueprintb)
UserBlueprint(view: :extended)c)
UserBlueprint(view: :extended, root: :user)d)
Array<UserBlueprint>e)
Array<UserBlueprint(view: :extended, root: :user)... .
As, recmoneded in the issue description changes have been made in a way so Alba and any future serializer can use the same syntax.
References
AI usage
test cases. I also went through all the test cases that was generated by the AI just to be sure it's okay.