grape-swagger 2.0.2 → 2.1.0

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: 892c461e0945d50d3b923b7ee89f22aa1ab429f286f2f0fc4de76fe16098eb6f
4
- data.tar.gz: 6c702b542c93dd5a3c0c459bd1135471bc112da3d1d7a48a882b0f2416542f22
3
+ metadata.gz: 8fbf69e7e7bc7f3a836c574fe91df74abb10aab05c778cee716398d76afeace6
4
+ data.tar.gz: 5ab4bc60eafa3ad32959a040aed645943121912037a738278d88fb649390a0fa
5
5
  SHA512:
6
- metadata.gz: 66d718bc68a0e596eefb35d0cab1334754da8df8b3be13ba2114e0dac1930270d228c441d6fc8933aaef6379c7bee99d18a60e159aa747715e466922c5bca433
7
- data.tar.gz: a380f3d1ac174ff023e2874e09d336f287bba09d6187e055477ee5d6841cedea2a910fa5f04ca38b3be848356d1a6cf085f9183e37b85a8b2cc8418b5e9616f0
6
+ metadata.gz: 8bcb10791418aa79af266d503f7fd5130157bb7971d7acb094964981d9ee8635fe6bcf72e212b81f22a25f2032a002fba99b97a7d078d8d35a908f0117fae379
7
+ data.tar.gz: d7843b9aaf70d83a9d0de3dc7f075b8f3df2e4cedda4a1c519cbc4109dc9da1cb1de82c9966fcf6a0083ebbe22a89c13654fbd9fe9e1156af4103e8bc70a344e
data/CHANGELOG.md CHANGED
@@ -9,6 +9,27 @@
9
9
  * Your contribution here.
10
10
 
11
11
 
12
+ ### 2.1.0 (May 14, 2024)
13
+
14
+ #### Features
15
+
16
+ * [#927](https://github.com/ruby-grape/grape-swagger/pull/927): Set default parameter location based on consumes - [@spaceraccoon](https://github.com/spaceraccoon)
17
+ * [#929](https://github.com/ruby-grape/grape-swagger/pull/929): Set query parameter for array of primitive types - [@spaceraccoon](https://github.com/spaceraccoon)
18
+
19
+ #### Fixes
20
+
21
+ * [#926](https://github.com/ruby-grape/grape-swagger/pull/926): Refactor route and namespace combination logic - [@numbata](https://github.com/numbata)
22
+
23
+
24
+ ### 2.0.3 (April 26, 2024)
25
+
26
+ #### Fixes
27
+
28
+ * [#922](https://github.com/ruby-grape/grape-swagger/pull/922): Force request body to be an schema object - [@numbata](https://github.com/numbata)
29
+ * [#923](https://github.com/ruby-grape/grape-swagger/pull/923): Enabled schema definitions for body parameters in DELETE requests - [@numbata](https://github.com/numbata)
30
+ * [#924](https://github.com/ruby-grape/grape-swagger/pull/924): fix: Use mount_path to narrow down urls_for - [@chibicco](https://github.com/chibicco)
31
+
32
+
12
33
  ### 2.0.2 (Februar 2, 2024)
13
34
 
14
35
  #### Fixes
@@ -72,7 +93,6 @@
72
93
  * [#846](https://github.com/ruby-grape/grape-swagger/pull/846): Refactor oapi fetch task [@Vachman](https://github.com/Vachman)
73
94
  * [#850](https://github.com/ruby-grape/grape-swagger/pull/850): Fix value of enum to be Array [@takahashim](https://github.com/takahashim)
74
95
 
75
-
76
96
  ### 1.4.3 (January 5, 2022)
77
97
 
78
98
  #### Fixes
data/UPGRADING.md CHANGED
@@ -1,5 +1,14 @@
1
1
  ## Upgrading Grape-swagger
2
2
 
3
+ ### Upgrading to >= x.y.z
4
+
5
+ - Grape-swagger now documents array parameters within an object schema in Swagger. This aligns with grape's JSON structure requirements and ensures the documentation is correct.
6
+ - Previously, arrays were documented as standalone arrays, which could be incorrect based on grape's expectations.
7
+ - Check your API documentation and update your code or tests that use the old array format.
8
+
9
+ Attention: This update may require you to make changes to ensure your API integrations continue to work correctly.
10
+ For detailed reasons behind this update, refer to GitHub issue #666.
11
+
3
12
  ### Upgrading to >= 1.5.0
4
13
 
5
14
  - The names generated for body parameter definitions and their references has changed. It'll now include the HTTP action as well as any path parameters.
@@ -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
@@ -18,8 +18,6 @@ module GrapeSwagger
18
18
 
19
19
  params_to_move = movable_params(params)
20
20
 
21
- return (params + correct_array_param(params_to_move)) if should_correct_array?(params_to_move)
22
-
23
21
  params << parent_definition_of_params(params_to_move, path, route)
24
22
 
25
23
  params
@@ -27,21 +25,11 @@ module GrapeSwagger
27
25
 
28
26
  private
29
27
 
30
- def should_correct_array?(param)
31
- param.length == 1 && param.first[:in] == 'body' && param.first[:type] == 'array'
32
- end
33
-
34
- def correct_array_param(param)
35
- param.first[:schema] = { type: param.first.delete(:type), items: param.first.delete(:items) }
36
-
37
- param
38
- end
39
-
40
28
  def parent_definition_of_params(params, path, route)
41
29
  definition_name = OperationId.build(route, path)
42
- build_definition(definition_name, params)
30
+ # NOTE: Parent definition is always object
31
+ @definitions[definition_name] = object_type
43
32
  definition = @definitions[definition_name]
44
-
45
33
  move_params_to_new(definition, params)
46
34
 
47
35
  definition[:description] = route.description if route.try(:description)
@@ -53,6 +41,7 @@ module GrapeSwagger
53
41
  params, nested_params = params.partition { |x| !x[:name].to_s.include?('[') }
54
42
  params.each do |param|
55
43
  property = param[:name]
44
+
56
45
  param_properties, param_required = build_properties([param])
57
46
  add_properties_to_definition(definition, param_properties, param_required)
58
47
  related_nested_params, nested_params = nested_params.partition { |x| x[:name].start_with?("#{property}[") }
@@ -197,7 +186,7 @@ module GrapeSwagger
197
186
  end
198
187
 
199
188
  def move_methods
200
- [:post, :put, :patch, 'POST', 'PUT', 'PATCH']
189
+ [:delete, :post, :put, :patch, 'DELETE', 'POST', 'PUT', 'PATCH']
201
190
  end
202
191
 
203
192
  def includes_body_param?(params)
@@ -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
 
@@ -70,21 +70,17 @@ module GrapeSwagger
70
70
 
71
71
  def document_array_param(value_type, definitions)
72
72
  if value_type[:documentation].present?
73
- param_type = value_type[:documentation][:param_type]
74
73
  doc_type = value_type[:documentation][:type]
75
74
  type = DataType.mapping(doc_type) if doc_type && !DataType.request_primitive?(doc_type)
76
75
  collection_format = value_type[:documentation][:collectionFormat]
77
76
  end
78
77
 
79
- param_type ||= value_type[:param_type]
80
-
81
78
  array_items = parse_array_item(
82
79
  definitions,
83
80
  type,
84
81
  value_type
85
82
  )
86
83
 
87
- @parsed_param[:in] = param_type || 'formData'
88
84
  @parsed_param[:items] = array_items
89
85
  @parsed_param[:type] = 'array'
90
86
  @parsed_param[:collectionFormat] = collection_format if DataType.collections.include?(collection_format)
@@ -148,14 +144,20 @@ module GrapeSwagger
148
144
  @parsed_param[:example] = example if example
149
145
  end
150
146
 
151
- def param_type(value_type)
147
+ def param_type(value_type, consumes)
152
148
  param_type = value_type[:param_type] || value_type[:in]
153
- if value_type[:path].include?("{#{value_type[:param_name]}}")
149
+ if !value_type[:is_array] && value_type[:path].include?("{#{value_type[:param_name]}}")
154
150
  'path'
155
151
  elsif param_type
156
152
  param_type
157
153
  elsif %w[POST PUT PATCH].include?(value_type[:method])
158
- DataType.request_primitive?(value_type[:data_type]) ? 'formData' : 'body'
154
+ if consumes.include?('application/x-www-form-urlencoded') || consumes.include?('multipart/form-data')
155
+ 'formData'
156
+ else
157
+ 'body'
158
+ end
159
+ elsif value_type[:is_array] && !DataType.query_array_primitive?(value_type[:data_type])
160
+ 'formData'
159
161
  else
160
162
  'query'
161
163
  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)
@@ -63,7 +63,7 @@ module GrapeSwagger
63
63
  resource - if given only for that it would be generated (optional)'
64
64
  task validate: :environment do
65
65
  # :nocov:
66
- ENV['store'] = 'true'
66
+ ENV.store('store', 'true')
67
67
  ::Rake::Task['oapi:fetch'].invoke
68
68
  exit if error?
69
69
 
@@ -95,7 +95,7 @@ module GrapeSwagger
95
95
  def urls_for(api_class)
96
96
  api_class.routes
97
97
  .map(&:path)
98
- .select { |e| e.include?('doc') }
98
+ .grep(/#{GrapeSwagger::DocMethods.class_variable_get(:@@mount_path)}/)
99
99
  .reject { |e| e.include?(':name') }
100
100
  .map { |e| format_path(e) }
101
101
  .map { |e| [e, ENV.fetch('resource', nil)].join('/').chomp('/') }
@@ -108,7 +108,7 @@ module GrapeSwagger
108
108
  end
109
109
 
110
110
  def save_to_file?
111
- ENV['store'].present? && !error?
111
+ ENV.fetch('store', nil).present? && !error?
112
112
  end
113
113
 
114
114
  def error?
@@ -118,10 +118,10 @@ module GrapeSwagger
118
118
  def file(url)
119
119
  api_version = url.split('/').last
120
120
 
121
- name = if ENV['store'] == 'true' || ENV['store'].blank?
121
+ name = if ENV.fetch('store', nil) == 'true' || ENV.fetch('store', nil).blank?
122
122
  "swagger_doc_#{api_version}.json"
123
123
  else
124
- ENV['store'].sub('.json', "_#{api_version}.json")
124
+ ENV.fetch('store').sub('.json', "_#{api_version}.json")
125
125
  end
126
126
 
127
127
  File.join(Dir.getwd, name)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GrapeSwagger
4
- VERSION = '2.0.2'
4
+ VERSION = '2.1.0'
5
5
  end
data/lib/grape-swagger.rb CHANGED
@@ -24,7 +24,7 @@ module SwaggerRouting
24
24
  private
25
25
 
26
26
  def combine_routes(app, doc_klass)
27
- app.routes.each do |route|
27
+ app.routes.each_with_object({}) do |route, combined_routes|
28
28
  route_path = route.path
29
29
  route_match = route_path.split(/^.*?#{route.prefix}/).last
30
30
  next unless route_match
@@ -37,16 +37,16 @@ module SwaggerRouting
37
37
 
38
38
  resource = route_match.captures.first
39
39
  resource = '/' if resource.empty?
40
- @target_class.combined_routes[resource] ||= []
40
+ combined_routes[resource] ||= []
41
41
  next if doc_klass.hide_documentation_path && route.path.match(/#{doc_klass.mount_path}($|\/|\(\.)/)
42
42
 
43
- @target_class.combined_routes[resource].unshift route
43
+ combined_routes[resource] << route
44
44
  end
45
45
  end
46
46
 
47
- def determine_namespaced_routes(name, parent_route)
47
+ def determine_namespaced_routes(name, parent_route, routes)
48
48
  if parent_route.nil?
49
- @target_class.combined_routes.values.flatten
49
+ routes.values.flatten
50
50
  else
51
51
  parent_route.reject do |route|
52
52
  !route_path_start_with?(route, name) || !route_instance_variable_equals?(route, name)
@@ -54,14 +54,15 @@ module SwaggerRouting
54
54
  end
55
55
  end
56
56
 
57
- def combine_namespace_routes(namespaces)
57
+ def combine_namespace_routes(namespaces, routes)
58
+ combined_namespace_routes = {}
58
59
  # iterate over each single namespace
59
60
  namespaces.each_key do |name, _|
60
61
  # get the parent route for the namespace
61
62
  parent_route_name = extract_parent_route(name)
62
- parent_route = @target_class.combined_routes[parent_route_name]
63
+ parent_route = routes[parent_route_name]
63
64
  # fetch all routes that are within the current namespace
64
- namespace_routes = determine_namespaced_routes(name, parent_route)
65
+ namespace_routes = determine_namespaced_routes(name, parent_route, routes)
65
66
 
66
67
  # default case when not explicitly specified or nested == true
67
68
  standalone_namespaces = namespaces.reject do |_, ns|
@@ -76,12 +77,13 @@ module SwaggerRouting
76
77
  # rubocop:disable Style/Next
77
78
  if parent_standalone_namespaces.empty?
78
79
  # 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)
80
+ combined_namespace_routes[parent_route_name] ||= []
81
+ combined_namespace_routes[parent_route_name].push(*namespace_routes)
82
82
  end
83
83
  # rubocop:enable Style/Next
84
84
  end
85
+
86
+ combined_namespace_routes
85
87
  end
86
88
 
87
89
  def extract_parent_route(name)
@@ -110,7 +112,7 @@ module SwaggerRouting
110
112
  end
111
113
 
112
114
  module SwaggerDocumentationAdder
113
- attr_accessor :combined_namespaces, :combined_namespace_identifiers, :combined_routes, :combined_namespace_routes
115
+ attr_accessor :combined_namespaces, :combined_routes, :combined_namespace_routes
114
116
 
115
117
  include SwaggerRouting
116
118
 
@@ -127,20 +129,16 @@ module SwaggerDocumentationAdder
127
129
  documentation_class.setup(options)
128
130
  mount(documentation_class)
129
131
 
130
- @target_class.combined_routes = {}
131
- combine_routes(@target_class, documentation_class)
132
-
133
- @target_class.combined_namespaces = {}
134
- combine_namespaces(@target_class)
132
+ combined_routes = combine_routes(@target_class, documentation_class)
133
+ combined_namespaces = combine_namespaces(@target_class)
134
+ combined_namespace_routes = combine_namespace_routes(combined_namespaces, combined_routes)
135
+ exclusive_route_keys = combined_routes.keys - combined_namespaces.keys
136
+ @target_class.combined_namespace_routes = combined_namespace_routes.merge(
137
+ combined_routes.slice(*exclusive_route_keys)
138
+ )
139
+ @target_class.combined_routes = combined_routes
140
+ @target_class.combined_namespaces = combined_namespaces
135
141
 
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
142
  documentation_class
145
143
  end
146
144
 
@@ -151,17 +149,24 @@ module SwaggerDocumentationAdder
151
149
  end
152
150
 
153
151
  def combine_namespaces(app)
154
- app.endpoints.each do |endpoint|
152
+ combined_namespaces = {}
153
+ endpoints = app.endpoints.clone
154
+
155
+ while endpoints.any?
156
+ endpoint = endpoints.shift
157
+
158
+ endpoints.push(*endpoint.options[:app].endpoints) if endpoint.options[:app]
155
159
  ns = endpoint.namespace_stackable(:namespace).last
160
+ next unless ns
156
161
 
157
162
  # use the full namespace here (not the latest level only)
158
163
  # and strip leading slash
159
164
  mount_path = (endpoint.namespace_stackable(:mount_path) || []).join('/')
160
165
  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]
166
+ combined_namespaces[full_namespace] = ns
164
167
  end
168
+
169
+ combined_namespaces
165
170
  end
166
171
 
167
172
  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.2
4
+ version: 2.1.0
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-02-02 00:00:00.000000000 Z
12
+ date: 2024-05-14 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.5
106
+ rubygems_version: 3.5.9
107
107
  signing_key:
108
108
  specification_version: 4
109
109
  summary: Add auto generated documentation to your Grape API that can be displayed