grape-swagger 2.0.3 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c36235d1ec48b00075496c2d59151a87e42f2b7f34c876db913504a7706c1a70
4
- data.tar.gz: 0d7aac2f325422f53218257e4cb8e2bef51e73e424a53d88b244c3073c614656
3
+ metadata.gz: d07c4678df57d48124ef1afed6e8d60c0cedc0d5f1ea198b4c1ce2e6b3e30f2d
4
+ data.tar.gz: 35e02858f204c33f403a6b583c1c2ced2c3d66caaa94613d30de4c227ed96266
5
5
  SHA512:
6
- metadata.gz: 13d4c18cf80ce43aedde8a3347be17ebda40fd6e2b56496459226f350e273b31cd6c74d79d75b68e2461d47dd6f0aeaec3080709aebbcf5b6beb9b8162f949bb
7
- data.tar.gz: 949ebd6da1fcdb3c398e6070a035f50a9e211ed27a93694b275f5e09a9d1266c675f04f7b1e9583329689fcc9a38fd50cdac8dd6c1de87c486016e63faacce57
6
+ metadata.gz: 35c4824daa66db7842b786b580981a18223c1cb7c199a4a8dd754b2cc310fc876d8b7bb8dad659f5a92f554d4c8e306e960e739a9764b6a6c6c8bb004a87d37c
7
+ data.tar.gz: 2b4c8c3e19b0d9ee56a66655598b8293470f18a3815f0cc8c6e5bf003647f8ba46774017bcc5a5b2db7e947aafd75f90e15ad2ef46d6ec683bfc8d868a9205b9
data/CHANGELOG.md CHANGED
@@ -1,12 +1,19 @@
1
- ### next
1
+ ### 2.1.1 (Sep 21, 2024)
2
+
3
+ #### Fixes
4
+
5
+ * [#940](https://github.com/ruby-grape/grape-swagger/pull/940): Grape 2.2.0 compatibility - [@padde](https://github.com/padde)
6
+
7
+ ### 2.1.0 (May 14, 2024)
2
8
 
3
9
  #### Features
4
10
 
5
- * Your contribution here.
11
+ * [#927](https://github.com/ruby-grape/grape-swagger/pull/927): Set default parameter location based on consumes - [@spaceraccoon](https://github.com/spaceraccoon)
12
+ * [#929](https://github.com/ruby-grape/grape-swagger/pull/929): Set query parameter for array of primitive types - [@spaceraccoon](https://github.com/spaceraccoon)
6
13
 
7
14
  #### Fixes
8
15
 
9
- * Your contribution here.
16
+ * [#926](https://github.com/ruby-grape/grape-swagger/pull/926): Refactor route and namespace combination logic - [@numbata](https://github.com/numbata)
10
17
 
11
18
 
12
19
  ### 2.0.3 (April 26, 2024)
@@ -15,8 +15,8 @@ Gem::Specification.new do |s|
15
15
  s.metadata['rubygems_mfa_required'] = 'true'
16
16
 
17
17
  s.required_ruby_version = '>= 3.0'
18
- s.add_runtime_dependency 'grape', '>= 1.7', '< 3.0'
19
- s.add_runtime_dependency 'rack-test', '~> 2'
18
+ s.add_dependency 'grape', '>= 1.7', '< 3.0'
19
+ s.add_dependency 'rack-test', '~> 2'
20
20
 
21
21
  s.files = Dir['lib/**/*', '*.md', 'LICENSE.txt', 'grape-swagger.gemspec']
22
22
  s.require_paths = ['lib']
@@ -75,6 +75,14 @@ module GrapeSwagger
75
75
  PRIMITIVE_MAPPINGS.keys.map(&:downcase)
76
76
  end
77
77
 
78
+ def query_array_primitive?(type)
79
+ query_array_primitives.include?(type.to_s.downcase)
80
+ end
81
+
82
+ def query_array_primitives
83
+ primitives << 'string'
84
+ end
85
+
78
86
  def mapping(value)
79
87
  PRIMITIVE_MAPPINGS[value] || 'string'
80
88
  end
@@ -4,7 +4,7 @@ module GrapeSwagger
4
4
  module DocMethods
5
5
  class ParseParams
6
6
  class << self
7
- def call(param, settings, path, route, definitions)
7
+ def call(param, settings, path, route, definitions, consumes) # rubocop:disable Metrics/ParameterLists
8
8
  method = route.request_method
9
9
  additional_documentation = settings.fetch(:documentation, {})
10
10
  settings.merge!(additional_documentation)
@@ -14,7 +14,7 @@ module GrapeSwagger
14
14
 
15
15
  # required properties
16
16
  @parsed_param = {
17
- in: param_type(value_type),
17
+ in: param_type(value_type, consumes),
18
18
  name: settings[:full_name] || param
19
19
  }
20
20
 
@@ -25,6 +25,7 @@ module GrapeSwagger
25
25
  document_default_value(settings) unless value_type[:is_array]
26
26
  document_range_values(settings) unless value_type[:is_array]
27
27
  document_required(settings)
28
+ document_length_limits(value_type)
28
29
  document_additional_properties(definitions, settings) unless value_type[:is_array]
29
30
  document_add_extensions(settings)
30
31
  document_example(settings)
@@ -70,21 +71,17 @@ module GrapeSwagger
70
71
 
71
72
  def document_array_param(value_type, definitions)
72
73
  if value_type[:documentation].present?
73
- param_type = value_type[:documentation][:param_type] || value_type[:documentation][:in]
74
74
  doc_type = value_type[:documentation][:type]
75
75
  type = DataType.mapping(doc_type) if doc_type && !DataType.request_primitive?(doc_type)
76
76
  collection_format = value_type[:documentation][:collectionFormat]
77
77
  end
78
78
 
79
- param_type ||= value_type[:param_type]
80
-
81
79
  array_items = parse_array_item(
82
80
  definitions,
83
81
  type,
84
82
  value_type
85
83
  )
86
84
 
87
- @parsed_param[:in] = param_type || 'formData'
88
85
  @parsed_param[:items] = array_items
89
86
  @parsed_param[:type] = 'array'
90
87
  @parsed_param[:collectionFormat] = collection_format if DataType.collections.include?(collection_format)
@@ -148,19 +145,35 @@ module GrapeSwagger
148
145
  @parsed_param[:example] = example if example
149
146
  end
150
147
 
151
- def param_type(value_type)
148
+ def param_type(value_type, consumes)
152
149
  param_type = value_type[:param_type] || value_type[:in]
153
- if value_type[:path].include?("{#{value_type[:param_name]}}")
150
+ if !value_type[:is_array] && value_type[:path].include?("{#{value_type[:param_name]}}")
154
151
  'path'
155
152
  elsif param_type
156
153
  param_type
157
154
  elsif %w[POST PUT PATCH].include?(value_type[:method])
158
- DataType.request_primitive?(value_type[:data_type]) ? 'formData' : 'body'
155
+ if consumes.include?('application/x-www-form-urlencoded') || consumes.include?('multipart/form-data')
156
+ 'formData'
157
+ else
158
+ 'body'
159
+ end
160
+ elsif value_type[:is_array] && !DataType.query_array_primitive?(value_type[:data_type])
161
+ 'formData'
159
162
  else
160
163
  'query'
161
164
  end
162
165
  end
163
166
 
167
+ def document_length_limits(value_type)
168
+ if value_type[:is_array]
169
+ @parsed_param[:minItems] = value_type[:min_length] if value_type.key?(:min_length)
170
+ @parsed_param[:maxItems] = value_type[:max_length] if value_type.key?(:max_length)
171
+ else
172
+ @parsed_param[:minLength] = value_type[:min_length] if value_type.key?(:min_length)
173
+ @parsed_param[:maxLength] = value_type[:max_length] if value_type.key?(:max_length)
174
+ end
175
+ end
176
+
164
177
  def parse_enum_or_range_values(values)
165
178
  case values
166
179
  when Proc
@@ -7,7 +7,7 @@ module GrapeSwagger
7
7
  def call(*args)
8
8
  return ['application/json'] unless args.flatten.present?
9
9
 
10
- args.flatten.map { |x| Grape::ContentTypes::CONTENT_TYPES[x] || x }.uniq
10
+ args.flatten.map { |x| GrapeSwagger::CONTENT_TYPE_DEFAULTS[x] || x }.uniq
11
11
  end
12
12
  end
13
13
  end
@@ -11,8 +11,8 @@ module Grape
11
11
 
12
12
  if content_types.empty?
13
13
  formats = [target_class.format, target_class.default_format].compact.uniq
14
- formats = Grape::Formatter.formatters(**{}).keys if formats.empty?
15
- content_types = Grape::ContentTypes::CONTENT_TYPES.select do |content_type, _mime_type|
14
+ formats = GrapeSwagger::FORMATTER_DEFAULTS.keys if formats.empty?
15
+ content_types = GrapeSwagger::CONTENT_TYPE_DEFAULTS.select do |content_type, _mime_type|
16
16
  formats.include? content_type
17
17
  end.values
18
18
  end
@@ -119,7 +119,7 @@ module Grape
119
119
  method[:description] = description_object(route)
120
120
  method[:produces] = produces_object(route, options[:produces] || options[:format])
121
121
  method[:consumes] = consumes_object(route, options[:consumes] || options[:format])
122
- method[:parameters] = params_object(route, options, path)
122
+ method[:parameters] = params_object(route, options, path, method[:consumes])
123
123
  method[:security] = security_object(route)
124
124
  method[:responses] = response_object(route, options)
125
125
  method[:tags] = route.options.fetch(:tags, tag_object(route, path))
@@ -175,7 +175,7 @@ module Grape
175
175
  GrapeSwagger::DocMethods::ProducesConsumes.call(route.settings.dig(:description, :consumes) || format)
176
176
  end
177
177
 
178
- def params_object(route, options, path)
178
+ def params_object(route, options, path, consumes)
179
179
  parameters = build_request_params(route, options).each_with_object([]) do |(param, value), memo|
180
180
  next if hidden_parameter?(value)
181
181
 
@@ -187,7 +187,7 @@ module Grape
187
187
  elsif value[:type]
188
188
  expose_params(value[:type])
189
189
  end
190
- memo << GrapeSwagger::DocMethods::ParseParams.call(param, value, path, route, @definitions)
190
+ memo << GrapeSwagger::DocMethods::ParseParams.call(param, value, path, route, @definitions, consumes)
191
191
  end
192
192
 
193
193
  if GrapeSwagger::DocMethods::MoveParams.can_be_moved?(route.request_method, parameters)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GrapeSwagger
4
- VERSION = '2.0.3'
4
+ VERSION = '2.1.1'
5
5
  end
data/lib/grape-swagger.rb CHANGED
@@ -18,13 +18,31 @@ module GrapeSwagger
18
18
  end
19
19
  end
20
20
  autoload :Rake, 'grape-swagger/rake/oapi_tasks'
21
+
22
+ # Copied from https://github.com/ruby-grape/grape/blob/v2.2.0/lib/grape/formatter.rb
23
+ FORMATTER_DEFAULTS = {
24
+ json: Grape::Formatter::Json,
25
+ jsonapi: Grape::Formatter::Json,
26
+ serializable_hash: Grape::Formatter::SerializableHash,
27
+ txt: Grape::Formatter::Txt,
28
+ xml: Grape::Formatter::Xml
29
+ }.freeze
30
+
31
+ # Copied from https://github.com/ruby-grape/grape/blob/v2.2.0/lib/grape/content_types.rb
32
+ CONTENT_TYPE_DEFAULTS = {
33
+ xml: 'application/xml',
34
+ serializable_hash: 'application/json',
35
+ json: 'application/json',
36
+ binary: 'application/octet-stream',
37
+ txt: 'text/plain'
38
+ }.freeze
21
39
  end
22
40
 
23
41
  module SwaggerRouting
24
42
  private
25
43
 
26
44
  def combine_routes(app, doc_klass)
27
- app.routes.each do |route|
45
+ app.routes.each_with_object({}) do |route, combined_routes|
28
46
  route_path = route.path
29
47
  route_match = route_path.split(/^.*?#{route.prefix}/).last
30
48
  next unless route_match
@@ -37,31 +55,30 @@ module SwaggerRouting
37
55
 
38
56
  resource = route_match.captures.first
39
57
  resource = '/' if resource.empty?
40
- @target_class.combined_routes[resource] ||= []
58
+ combined_routes[resource] ||= []
41
59
  next if doc_klass.hide_documentation_path && route.path.match(/#{doc_klass.mount_path}($|\/|\(\.)/)
42
60
 
43
- @target_class.combined_routes[resource] << route
61
+ combined_routes[resource] << route
44
62
  end
45
63
  end
46
64
 
47
- def determine_namespaced_routes(name, parent_route)
48
- if parent_route.nil?
49
- @target_class.combined_routes.values.flatten
50
- else
51
- parent_route.reject do |route|
52
- !route_path_start_with?(route, name) || !route_instance_variable_equals?(route, name)
53
- end
65
+ def determine_namespaced_routes(name, parent_route, routes)
66
+ return routes.values.flatten if parent_route.nil?
67
+
68
+ parent_route.select do |route|
69
+ route_path_start_with?(route, name) || route_namespace_equals?(route, name)
54
70
  end
55
71
  end
56
72
 
57
- def combine_namespace_routes(namespaces)
73
+ def combine_namespace_routes(namespaces, routes)
74
+ combined_namespace_routes = {}
58
75
  # iterate over each single namespace
59
76
  namespaces.each_key do |name, _|
60
77
  # get the parent route for the namespace
61
78
  parent_route_name = extract_parent_route(name)
62
- parent_route = @target_class.combined_routes[parent_route_name]
79
+ parent_route = routes[parent_route_name]
63
80
  # fetch all routes that are within the current namespace
64
- namespace_routes = determine_namespaced_routes(name, parent_route)
81
+ namespace_routes = determine_namespaced_routes(name, parent_route, routes)
65
82
 
66
83
  # default case when not explicitly specified or nested == true
67
84
  standalone_namespaces = namespaces.reject do |_, ns|
@@ -76,12 +93,13 @@ module SwaggerRouting
76
93
  # rubocop:disable Style/Next
77
94
  if parent_standalone_namespaces.empty?
78
95
  # default option, append namespace methods to parent route
79
- parent_route = @target_class.combined_namespace_routes.key?(parent_route_name)
80
- @target_class.combined_namespace_routes[parent_route_name] = [] unless parent_route
81
- @target_class.combined_namespace_routes[parent_route_name].push(*namespace_routes)
96
+ combined_namespace_routes[parent_route_name] ||= []
97
+ combined_namespace_routes[parent_route_name].push(*namespace_routes)
82
98
  end
83
99
  # rubocop:enable Style/Next
84
100
  end
101
+
102
+ combined_namespace_routes
85
103
  end
86
104
 
87
105
  def extract_parent_route(name)
@@ -92,25 +110,32 @@ module SwaggerRouting
92
110
  matches.nil? ? route_name : matches[0].delete('/')
93
111
  end
94
112
 
95
- def route_instance_variable(route)
96
- route.instance_variable_get(:@options)[:namespace]
97
- end
113
+ def route_namespace_equals?(route, name)
114
+ patterns = Enumerator.new do |yielder|
115
+ yielder << "/#{name}"
116
+ yielder << "/:version/#{name}"
117
+ end
98
118
 
99
- def route_instance_variable_equals?(route, name)
100
- route_instance_variable(route) == "/#{name}" ||
101
- route_instance_variable(route) == "/:version/#{name}"
119
+ patterns.any? { |p| route.namespace == p }
102
120
  end
103
121
 
104
122
  def route_path_start_with?(route, name)
105
- route_prefix = route.prefix ? "/#{route.prefix}/#{name}" : "/#{name}"
106
- route_versioned_prefix = route.prefix ? "/#{route.prefix}/:version/#{name}" : "/:version/#{name}"
123
+ patterns = Enumerator.new do |yielder|
124
+ if route.prefix
125
+ yielder << "/#{route.prefix}/#{name}"
126
+ yielder << "/#{route.prefix}/:version/#{name}"
127
+ else
128
+ yielder << "/#{name}"
129
+ yielder << "/:version/#{name}"
130
+ end
131
+ end
107
132
 
108
- route.path.start_with?(route_prefix, route_versioned_prefix)
133
+ patterns.any? { |p| route.path.start_with?(p) }
109
134
  end
110
135
  end
111
136
 
112
137
  module SwaggerDocumentationAdder
113
- attr_accessor :combined_namespaces, :combined_namespace_identifiers, :combined_routes, :combined_namespace_routes
138
+ attr_accessor :combined_namespaces, :combined_routes, :combined_namespace_routes
114
139
 
115
140
  include SwaggerRouting
116
141
 
@@ -127,20 +152,16 @@ module SwaggerDocumentationAdder
127
152
  documentation_class.setup(options)
128
153
  mount(documentation_class)
129
154
 
130
- @target_class.combined_routes = {}
131
- combine_routes(@target_class, documentation_class)
155
+ combined_routes = combine_routes(@target_class, documentation_class)
156
+ combined_namespaces = combine_namespaces(@target_class)
157
+ combined_namespace_routes = combine_namespace_routes(combined_namespaces, combined_routes)
158
+ exclusive_route_keys = combined_routes.keys - combined_namespaces.keys
159
+ @target_class.combined_namespace_routes = combined_namespace_routes.merge(
160
+ combined_routes.slice(*exclusive_route_keys)
161
+ )
162
+ @target_class.combined_routes = combined_routes
163
+ @target_class.combined_namespaces = combined_namespaces
132
164
 
133
- @target_class.combined_namespaces = {}
134
- combine_namespaces(@target_class)
135
-
136
- @target_class.combined_namespace_routes = {}
137
- @target_class.combined_namespace_identifiers = {}
138
- combine_namespace_routes(@target_class.combined_namespaces)
139
-
140
- exclusive_route_keys = @target_class.combined_routes.keys - @target_class.combined_namespaces.keys
141
- exclusive_route_keys.each do |key|
142
- @target_class.combined_namespace_routes[key] = @target_class.combined_routes[key]
143
- end
144
165
  documentation_class
145
166
  end
146
167
 
@@ -151,17 +172,24 @@ module SwaggerDocumentationAdder
151
172
  end
152
173
 
153
174
  def combine_namespaces(app)
154
- app.endpoints.each do |endpoint|
175
+ combined_namespaces = {}
176
+ endpoints = app.endpoints.clone
177
+
178
+ while endpoints.any?
179
+ endpoint = endpoints.shift
180
+
181
+ endpoints.push(*endpoint.options[:app].endpoints) if endpoint.options[:app]
155
182
  ns = endpoint.namespace_stackable(:namespace).last
183
+ next unless ns
156
184
 
157
185
  # use the full namespace here (not the latest level only)
158
186
  # and strip leading slash
159
187
  mount_path = (endpoint.namespace_stackable(:mount_path) || []).join('/')
160
188
  full_namespace = (mount_path + endpoint.namespace).sub(/\/{2,}/, '/').sub(/^\//, '')
161
- @target_class.combined_namespaces[full_namespace] = ns if ns
162
-
163
- combine_namespaces(endpoint.options[:app]) if endpoint.options[:app]
189
+ combined_namespaces[full_namespace] = ns
164
190
  end
191
+
192
+ combined_namespaces
165
193
  end
166
194
 
167
195
  def create_documentation_class
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grape-swagger
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.3
4
+ version: 2.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - LeFnord
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2024-04-26 00:00:00.000000000 Z
12
+ date: 2024-09-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: grape
@@ -103,7 +103,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
103
103
  - !ruby/object:Gem::Version
104
104
  version: '0'
105
105
  requirements: []
106
- rubygems_version: 3.5.9
106
+ rubygems_version: 3.3.7
107
107
  signing_key:
108
108
  specification_version: 4
109
109
  summary: Add auto generated documentation to your Grape API that can be displayed