grape-swagger 2.0.3 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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