Skip to content

Commit 41bca66

Browse files
authored
Refactor of request params parsing process (#947)
* wip * Form params parser. First step * Cleanup mess a bit * Fix specs * Let rubocop be happy * Fixes after rubocop autocorrection * Cleanup a little * Fix spec * Add requst param parser regestry * Refactor param parsers * Fix rubocop offenses
1 parent 8576390 commit 41bca66

File tree

12 files changed

+194
-95
lines changed

12 files changed

+194
-95
lines changed

lib/grape-swagger.rb

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,27 @@
1010

1111
require 'grape-swagger/doc_methods'
1212
require 'grape-swagger/model_parsers'
13+
require 'grape-swagger/request_param_parser_registry'
1314

1415
module GrapeSwagger
1516
class << self
1617
def model_parsers
1718
@model_parsers ||= GrapeSwagger::ModelParsers.new
1819
end
20+
21+
def request_param_parsers
22+
@request_param_parsers ||= GrapeSwagger::RequestParamParserRegistry.new
23+
end
1924
end
2025
autoload :Rake, 'grape-swagger/rake/oapi_tasks'
2126

2227
# Copied from https://github.com/ruby-grape/grape/blob/v2.2.0/lib/grape/formatter.rb
2328
FORMATTER_DEFAULTS = {
29+
xml: Grape::Formatter::Xml,
30+
serializable_hash: Grape::Formatter::SerializableHash,
2431
json: Grape::Formatter::Json,
2532
jsonapi: Grape::Formatter::Json,
26-
serializable_hash: Grape::Formatter::SerializableHash,
27-
txt: Grape::Formatter::Txt,
28-
xml: Grape::Formatter::Xml
33+
txt: Grape::Formatter::Txt
2934
}.freeze
3035

3136
# Copied from https://github.com/ruby-grape/grape/blob/v2.2.0/lib/grape/content_types.rb

lib/grape-swagger/doc_methods.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
require 'grape-swagger/doc_methods/tag_name_description'
1212
require 'grape-swagger/doc_methods/parse_params'
1313
require 'grape-swagger/doc_methods/move_params'
14-
require 'grape-swagger/doc_methods/headers'
1514
require 'grape-swagger/doc_methods/build_model_definition'
1615
require 'grape-swagger/doc_methods/version'
1716

lib/grape-swagger/doc_methods/format_data.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class << self
77
def to_format(parameters)
88
parameters.reject { |parameter| parameter[:in] == 'body' }.each do |b|
99
related_parameters = parameters.select do |p|
10-
p[:name] != b[:name] && p[:name].to_s.start_with?("#{b[:name].to_s.gsub(/\[\]\z/, '')}[")
10+
p[:name] != b[:name] && p[:name].start_with?("#{b[:name].to_s.gsub(/\[\]\z/, '')}[")
1111
end
1212
parameters.reject! { |p| p[:name] == b[:name] } if move_down(b, related_parameters)
1313
end

lib/grape-swagger/doc_methods/headers.rb

Lines changed: 0 additions & 20 deletions
This file was deleted.

lib/grape-swagger/doc_methods/move_params.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ def object_type
163163
end
164164

165165
def prepare_nested_names(property, params)
166-
params.each { |x| x[:name] = x[:name].sub(property, '').sub('[', '').sub(']', '') }
166+
params.each { |x| x[:name] = x[:name].sub(property.to_s, '').sub('[', '').sub(']', '') }
167167
end
168168

169169
def unify!(params)

lib/grape-swagger/endpoint.rb

Lines changed: 12 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
require 'active_support'
44
require 'active_support/core_ext/string/inflections'
5-
require 'grape-swagger/endpoint/params_parser'
5+
require_relative 'request_param_parsers/headers'
6+
require_relative 'request_param_parsers/route'
7+
require_relative 'request_param_parsers/body'
68

79
module Grape
810
class Endpoint # rubocop:disable Metrics/ClassLength
@@ -12,9 +14,7 @@ def content_types_for(target_class)
1214
if content_types.empty?
1315
formats = [target_class.format, target_class.default_format].compact.uniq
1416
formats = GrapeSwagger::FORMATTER_DEFAULTS.keys if formats.empty?
15-
content_types = GrapeSwagger::CONTENT_TYPE_DEFAULTS.select do |content_type, _mime_type| # rubocop:disable Style/HashSlice
16-
formats.include? content_type
17-
end.values
17+
content_types = formats.filter_map { |f| GrapeSwagger::CONTENT_TYPE_DEFAULTS[f] }
1818
end
1919

2020
content_types.uniq
@@ -383,45 +383,15 @@ def build_file_response(memo)
383383
end
384384

385385
def build_request_params(route, settings)
386-
required = merge_params(route)
387-
required = GrapeSwagger::DocMethods::Headers.parse(route) + required unless route.headers.nil?
388-
389-
default_type(required)
390-
391-
request_params = GrapeSwagger::Endpoint::ParamsParser.parse_request_params(required, settings, self)
392-
393-
request_params.empty? ? required : request_params
394-
end
395-
396-
def merge_params(route)
397-
path_params = get_path_params(route.app&.inheritable_setting&.namespace_stackable)
398-
param_keys = route.params.keys
399-
400-
# Merge path params options into route params
401-
route_params = route.params
402-
route_params.each_key do |key|
403-
path = path_params[key] || {}
404-
params = route_params[key]
405-
params = {} unless params.is_a? Hash
406-
route_params[key] = path.merge(params)
407-
end
408-
409-
route_params.delete_if { |key| key.is_a?(String) && param_keys.include?(key.to_sym) }.to_a
410-
end
411-
412-
# Iterates over namespaces recursively
413-
# to build a hash of path params with options, including type
414-
def get_path_params(stackable_values)
415-
params = {}
416-
return param unless stackable_values
417-
return params unless stackable_values.is_a? Grape::Util::StackableValues
418-
419-
stackable_values&.new_values&.dig(:namespace)&.each do |namespace| # rubocop:disable Style/SafeNavigationChainLength
420-
space = namespace.space.to_s.gsub(':', '')
421-
params[space] = namespace.options || {}
386+
GrapeSwagger.request_param_parsers.each_with_object({}) do |parser_klass, accum|
387+
params = parser_klass.parse(
388+
route,
389+
accum,
390+
settings,
391+
self
392+
)
393+
accum.merge!(params.stringify_keys)
422394
end
423-
inherited_params = get_path_params(stackable_values.inherited_values)
424-
inherited_params.merge(params)
425395
end
426396

427397
def default_type(params)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# frozen_string_literal: true
2+
3+
require_relative 'request_param_parsers/headers'
4+
require_relative 'request_param_parsers/route'
5+
require_relative 'request_param_parsers/body'
6+
7+
module GrapeSwagger
8+
class RequestParamParserRegistry
9+
DEFAULT_PARSERS = [
10+
GrapeSwagger::RequestParamParsers::Headers,
11+
GrapeSwagger::RequestParamParsers::Route,
12+
GrapeSwagger::RequestParamParsers::Body
13+
].freeze
14+
15+
include Enumerable
16+
17+
def initialize
18+
@parsers = DEFAULT_PARSERS.dup
19+
end
20+
21+
def register(klass)
22+
remove_parser(klass)
23+
@parsers << klass
24+
end
25+
26+
def insert_before(before_klass, klass)
27+
remove_parser(klass)
28+
insert_at = @parsers.index(before_klass) || @parsers.size
29+
@parsers.insert(insert_at, klass)
30+
end
31+
32+
def insert_after(after_klass, klass)
33+
remove_parser(klass)
34+
insert_at = @parsers.index(after_klass)
35+
@parsers.insert(insert_at ? insert_at + 1 : @parsers.size, klass)
36+
end
37+
38+
def each(&)
39+
@parsers.each(&)
40+
end
41+
42+
private
43+
44+
def remove_parser(klass)
45+
@parsers.reject! { |k| k == klass }
46+
end
47+
end
48+
end

lib/grape-swagger/endpoint/params_parser.rb renamed to lib/grape-swagger/request_param_parsers/body.rb

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,26 @@
11
# frozen_string_literal: true
22

33
module GrapeSwagger
4-
module Endpoint
5-
class ParamsParser
6-
attr_reader :params, :settings, :endpoint
4+
module RequestParamParsers
5+
class Body
6+
attr_reader :route, :params, :settings, :endpoint
77

8-
def self.parse_request_params(params, settings, endpoint)
9-
new(params, settings, endpoint).parse_request_params
8+
def self.parse(route, params, settings, endpoint)
9+
new(route, params, settings, endpoint).parse
1010
end
1111

12-
def initialize(params, settings, endpoint)
12+
def initialize(_route, params, settings, endpoint)
1313
@params = params
1414
@settings = settings
1515
@endpoint = endpoint
1616
end
1717

18-
def parse_request_params
18+
def parse
1919
public_params.each_with_object({}) do |(name, options), memo|
2020
name = name.to_s
21-
param_type = options[:type]
22-
param_type = param_type.to_s unless param_type.nil?
21+
param_type = options[:type]&.to_s
2322

24-
if param_type_is_array?(param_type)
23+
if array_param?(param_type)
2524
options[:is_array] = true
2625
name += '[]' if array_use_braces?
2726
end
@@ -36,8 +35,8 @@ def array_use_braces?
3635
@array_use_braces ||= settings[:array_use_braces] && !includes_body_param?
3736
end
3837

39-
def param_type_is_array?(param_type)
40-
return false unless param_type
38+
def array_param?(param_type)
39+
return false if param_type.nil?
4140
return true if param_type == 'Array'
4241

4342
param_types = param_type.match(/\[(.*)\]$/)
@@ -48,11 +47,10 @@ def param_type_is_array?(param_type)
4847
end
4948

5049
def public_params
51-
params.select { |param| public_parameter?(param) }
50+
params.select { |_key, param| public_parameter?(param) }
5251
end
5352

54-
def public_parameter?(param)
55-
param_options = param.last
53+
def public_parameter?(param_options)
5654
return true unless param_options.key?(:documentation) && !param_options[:required]
5755

5856
param_hidden = param_options[:documentation].fetch(:hidden, false)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# frozen_string_literal: true
2+
3+
module GrapeSwagger
4+
module RequestParamParsers
5+
class Headers
6+
attr_reader :route
7+
8+
def self.parse(route, params, settings, endpoint)
9+
new(route, params, settings, endpoint).parse
10+
end
11+
12+
def initialize(route, _params, _settings, _endpoint)
13+
@route = route
14+
end
15+
16+
def parse
17+
return {} unless route.headers
18+
19+
route.headers.each_with_object({}) do |(name, definition), accum|
20+
# Extract the description from any key type (string or symbol)
21+
description = definition[:description] || definition['description']
22+
doc = { desc: description, in: 'header' }
23+
24+
header_attrs = definition.symbolize_keys.except(:description, 'description')
25+
header_attrs[:type] = definition[:type].titleize if definition[:type]
26+
header_attrs[:documentation] = doc
27+
28+
accum[name] = header_attrs
29+
end
30+
end
31+
end
32+
end
33+
end
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# frozen_string_literal: true
2+
3+
module GrapeSwagger
4+
module RequestParamParsers
5+
class Route
6+
DEFAULT_PARAM_TYPE = { required: true, type: 'Integer' }.freeze
7+
8+
attr_reader :route
9+
10+
def self.parse(route, params, settings, endpoint)
11+
new(route, params, settings, endpoint).parse
12+
end
13+
14+
def initialize(route, _params, _settings, _endpoint)
15+
@route = route
16+
end
17+
18+
def parse
19+
stackable_values = route.app&.inheritable_setting&.namespace_stackable
20+
21+
path_params = build_path_params(stackable_values)
22+
23+
fulfill_params(path_params)
24+
end
25+
26+
private
27+
28+
def build_path_params(stackable_values)
29+
params = {}
30+
31+
while stackable_values.is_a?(Grape::Util::StackableValues)
32+
params.merge!(fetch_inherited_params(stackable_values))
33+
stackable_values = stackable_values.inherited_values
34+
end
35+
36+
params
37+
end
38+
39+
def fetch_inherited_params(stackable_values)
40+
return {} unless stackable_values.new_values
41+
42+
namespaces = stackable_values.new_values[:namespace] || []
43+
44+
namespaces.each_with_object({}) do |namespace, params|
45+
space = namespace.space.to_s.gsub(':', '')
46+
params[space] = namespace.options || {}
47+
end
48+
end
49+
50+
def fulfill_params(path_params)
51+
# Merge path params options into route params
52+
route.params.each_with_object({}) do |(param, definition), accum|
53+
# The route.params hash includes both parametrized params (with a string as a key)
54+
# and well-defined params from body/query (with a symbol as a key).
55+
# We avoid overriding well-defined params with parametrized ones.
56+
key = param.is_a?(String) ? param.to_sym : param
57+
next if param.is_a?(String) && accum.key?(key)
58+
59+
defined_options = definition.is_a?(Hash) ? definition : {}
60+
value = (path_params[param] || {}).merge(defined_options)
61+
accum[key] = value.empty? ? DEFAULT_PARAM_TYPE : value
62+
end
63+
end
64+
end
65+
end
66+
end

spec/lib/endpoint_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@
4747
end
4848

4949
describe 'parse_request_params' do
50-
let(:subject) { GrapeSwagger::Endpoint::ParamsParser }
50+
let(:subject) { GrapeSwagger::RequestParamParsers::Body }
5151
before do
52-
subject.send(:parse_request_params, params, {}, nil)
52+
subject.parse(nil, params, {}, nil)
5353
end
5454

5555
context 'when params do not contain an array' do

0 commit comments

Comments
 (0)