brainstem 1.4.1 → 2.0.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.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +77 -0
  3. data/README.md +119 -0
  4. data/docs/api_doc_generator.markdown +45 -4
  5. data/docs/brainstem_executable.markdown +1 -1
  6. data/docs/oas_2_docgen.png +0 -0
  7. data/docs/oas_2_docgen_ascii.txt +78 -0
  8. data/lib/brainstem/api_docs.rb +23 -9
  9. data/lib/brainstem/api_docs/abstract_collection.rb +0 -13
  10. data/lib/brainstem/api_docs/atlas.rb +0 -14
  11. data/lib/brainstem/api_docs/builder.rb +0 -14
  12. data/lib/brainstem/api_docs/controller.rb +7 -16
  13. data/lib/brainstem/api_docs/controller_collection.rb +0 -3
  14. data/lib/brainstem/api_docs/endpoint.rb +73 -19
  15. data/lib/brainstem/api_docs/endpoint_collection.rb +0 -7
  16. data/lib/brainstem/api_docs/formatters/abstract_formatter.rb +0 -2
  17. data/lib/brainstem/api_docs/formatters/markdown/controller_formatter.rb +1 -9
  18. data/lib/brainstem/api_docs/formatters/markdown/endpoint_collection_formatter.rb +1 -9
  19. data/lib/brainstem/api_docs/formatters/markdown/endpoint_formatter.rb +39 -24
  20. data/lib/brainstem/api_docs/formatters/markdown/helper.rb +0 -13
  21. data/lib/brainstem/api_docs/formatters/markdown/presenter_formatter.rb +22 -35
  22. data/lib/brainstem/api_docs/formatters/open_api_specification/helper.rb +66 -0
  23. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/controller_formatter.rb +57 -0
  24. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/param_definitions_formatter.rb +311 -0
  25. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/response_definitions_formatter.rb +197 -0
  26. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint_collection_formatter.rb +60 -0
  27. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint_formatter.rb +162 -0
  28. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/info_formatter.rb +126 -0
  29. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/presenter_formatter.rb +132 -0
  30. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/security_definitions_formatter.rb +99 -0
  31. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/tags_formatter.rb +123 -0
  32. data/lib/brainstem/api_docs/introspectors/abstract_introspector.rb +0 -7
  33. data/lib/brainstem/api_docs/introspectors/rails_introspector.rb +1 -20
  34. data/lib/brainstem/api_docs/presenter.rb +21 -27
  35. data/lib/brainstem/api_docs/presenter_collection.rb +1 -11
  36. data/lib/brainstem/api_docs/resolver.rb +1 -8
  37. data/lib/brainstem/api_docs/sinks/abstract_sink.rb +0 -4
  38. data/lib/brainstem/api_docs/sinks/controller_presenter_multifile_sink.rb +0 -9
  39. data/lib/brainstem/api_docs/sinks/open_api_specification_sink.rb +234 -0
  40. data/lib/brainstem/api_docs/sinks/stdout_sink.rb +0 -5
  41. data/lib/brainstem/cli.rb +0 -13
  42. data/lib/brainstem/cli/abstract_command.rb +0 -7
  43. data/lib/brainstem/cli/generate_api_docs_command.rb +48 -24
  44. data/lib/brainstem/concerns/controller_dsl.rb +288 -145
  45. data/lib/brainstem/concerns/formattable.rb +0 -5
  46. data/lib/brainstem/concerns/optional.rb +0 -1
  47. data/lib/brainstem/concerns/presenter_dsl.rb +2 -21
  48. data/lib/brainstem/dsl/configuration.rb +0 -11
  49. data/lib/brainstem/presenter.rb +0 -4
  50. data/lib/brainstem/version.rb +1 -1
  51. data/spec/brainstem/api_docs/abstract_collection_spec.rb +0 -11
  52. data/spec/brainstem/api_docs/atlas_spec.rb +0 -6
  53. data/spec/brainstem/api_docs/builder_spec.rb +0 -4
  54. data/spec/brainstem/api_docs/controller_collection_spec.rb +0 -2
  55. data/spec/brainstem/api_docs/controller_spec.rb +29 -18
  56. data/spec/brainstem/api_docs/endpoint_collection_spec.rb +0 -6
  57. data/spec/brainstem/api_docs/endpoint_spec.rb +343 -13
  58. data/spec/brainstem/api_docs/formatters/abstract_formatter_spec.rb +0 -2
  59. data/spec/brainstem/api_docs/formatters/markdown/controller_formatter_spec.rb +0 -1
  60. data/spec/brainstem/api_docs/formatters/markdown/endpoint_collection_formatter_spec.rb +0 -5
  61. data/spec/brainstem/api_docs/formatters/markdown/endpoint_formatter_spec.rb +94 -8
  62. data/spec/brainstem/api_docs/formatters/markdown/helper_spec.rb +0 -8
  63. data/spec/brainstem/api_docs/formatters/markdown/presenter_formatter_spec.rb +0 -7
  64. data/spec/brainstem/api_docs/formatters/open_api_specification/helper_spec.rb +210 -0
  65. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/controller_formatter_spec.rb +81 -0
  66. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/param_definitions_formatter_spec.rb +672 -0
  67. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/response_definitions_formatter_spec.rb +335 -0
  68. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint_collection_formatter_spec.rb +59 -0
  69. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint_formatter_spec.rb +308 -0
  70. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/info_formatter_spec.rb +89 -0
  71. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/presenter_formatter_spec.rb +430 -0
  72. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/security_definitions_formatter_spec.rb +190 -0
  73. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/tags_formatter_spec.rb +217 -0
  74. data/spec/brainstem/api_docs/introspectors/abstract_introspector_spec.rb +0 -2
  75. data/spec/brainstem/api_docs/introspectors/rails_introspector_spec.rb +0 -2
  76. data/spec/brainstem/api_docs/presenter_collection_spec.rb +0 -2
  77. data/spec/brainstem/api_docs/presenter_spec.rb +58 -18
  78. data/spec/brainstem/api_docs/resolver_spec.rb +0 -1
  79. data/spec/brainstem/api_docs/sinks/controller_presenter_multifile_sink_spec.rb +0 -2
  80. data/spec/brainstem/api_docs/sinks/open_api_specification_sink_spec.rb +371 -0
  81. data/spec/brainstem/api_docs_spec.rb +2 -0
  82. data/spec/brainstem/cli/abstract_command_spec.rb +0 -4
  83. data/spec/brainstem/cli/generate_api_docs_command_spec.rb +53 -2
  84. data/spec/brainstem/concerns/controller_dsl_spec.rb +430 -64
  85. data/spec/brainstem/concerns/presenter_dsl_spec.rb +0 -20
  86. data/spec/brainstem/preloader_spec.rb +0 -7
  87. data/spec/brainstem/presenter_spec.rb +0 -1
  88. data/spec/dummy/rails.rb +0 -1
  89. data/spec/spec_helpers/db.rb +0 -1
  90. metadata +37 -2
@@ -0,0 +1,66 @@
1
+ module Brainstem
2
+ module ApiDocs
3
+ module Formatters
4
+ module OpenApiSpecification
5
+ module Helper
6
+ def presenter_title(presenter)
7
+ presenter.contextual_documentation(:title).presence ||
8
+ presenter.target_class.underscore.singularize.titleize.strip
9
+ end
10
+
11
+ def format_http_method(endpoint)
12
+ endpoint.http_methods.first.downcase
13
+ end
14
+
15
+ def format_tag_name(name)
16
+ return name if name.blank?
17
+
18
+ name.underscore.titleize.strip
19
+ end
20
+
21
+ def format_description(description)
22
+ return '' if description.blank?
23
+
24
+ desc = description.to_s.strip.tap { |desc| desc[0] = desc[0].upcase }
25
+ desc += "." unless desc =~ /\.\s*\z/
26
+ desc
27
+ end
28
+
29
+ def uncapitalize(description)
30
+ return '' if description.blank?
31
+
32
+ description.strip.tap { |desc| desc[0] = desc[0].downcase }
33
+ end
34
+
35
+ # TODO: multi nested
36
+ def type_and_format(type, item_type = nil)
37
+ result = case type.to_s.downcase
38
+ when 'array'
39
+ { 'type' => 'array', 'items' => { 'type' => item_type.presence || 'string' } }
40
+ else
41
+ TYPE_INFO[type.to_s]
42
+ end
43
+ result ? result.with_indifferent_access : nil
44
+ end
45
+
46
+ TYPE_INFO = {
47
+ 'string' => { 'type' => 'string' },
48
+ 'boolean' => { 'type' => 'boolean' },
49
+ 'integer' => { 'type' => 'integer', 'format' => 'int32' },
50
+ 'long' => { 'type' => 'integer', 'format' => 'int64' },
51
+ 'float' => { 'type' => 'number', 'format' => 'float' },
52
+ 'double' => { 'type' => 'number', 'format' => 'double' },
53
+ 'byte' => { 'type' => 'string', 'format' => 'byte' },
54
+ 'binary' => { 'type' => 'string', 'format' => 'binary' },
55
+ 'date' => { 'type' => 'string', 'format' => 'date' },
56
+ 'datetime' => { 'type' => 'string', 'format' => 'date-time' },
57
+ 'password' => { 'type' => 'string', 'format' => 'password' },
58
+ 'id' => { 'type' => 'integer', 'format' => 'int32' },
59
+ 'decimal' => { 'type' => 'number', 'format' => 'float' },
60
+ }
61
+ private_constant :TYPE_INFO
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,57 @@
1
+ require 'brainstem/api_docs/formatters/abstract_formatter'
2
+
3
+ module Brainstem
4
+ module ApiDocs
5
+ module Formatters
6
+ module OpenApiSpecification
7
+ module Version2
8
+ class ControllerFormatter < AbstractFormatter
9
+
10
+ #
11
+ # Declares the options that are permissable to set on this instance.
12
+ #
13
+ def valid_options
14
+ super | [
15
+ :include_actions
16
+ ]
17
+ end
18
+
19
+ attr_accessor :controller,
20
+ :include_actions,
21
+ :output
22
+
23
+ alias_method :include_actions?,
24
+ :include_actions
25
+
26
+ def initialize(controller, options = {})
27
+ self.controller = controller
28
+ self.output = {}
29
+ self.include_actions = true
30
+
31
+ super options
32
+ end
33
+
34
+ def call
35
+ return {} if controller.nodoc?
36
+
37
+ format_actions!
38
+ end
39
+
40
+ #####################################################################
41
+ private
42
+ #####################################################################
43
+
44
+ def format_actions!
45
+ return unless include_actions?
46
+
47
+ controller.valid_sorted_endpoints.formatted_as(:oas_v2)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ Brainstem::ApiDocs::FORMATTERS[:controller][:oas_v2] =
57
+ Brainstem::ApiDocs::Formatters::OpenApiSpecification::Version2::ControllerFormatter.method(:call)
@@ -0,0 +1,311 @@
1
+ require 'active_support/core_ext/hash/except'
2
+ require 'active_support/core_ext/hash/compact'
3
+ require 'active_support/inflector'
4
+ require 'brainstem/api_docs/formatters/abstract_formatter'
5
+ require 'brainstem/api_docs/formatters/open_api_specification/helper'
6
+ require 'brainstem/api_docs/formatters/markdown/helper'
7
+
8
+ #
9
+ # Responsible for formatting each endpoint.
10
+ #
11
+ module Brainstem
12
+ module ApiDocs
13
+ module Formatters
14
+ module OpenApiSpecification
15
+ module Version2
16
+ module Endpoint
17
+ class ParamDefinitionsFormatter < AbstractFormatter
18
+ include Helper
19
+ include ::Brainstem::ApiDocs::Formatters::Markdown::Helper
20
+
21
+ attr_reader :output
22
+
23
+ def initialize(endpoint)
24
+ @endpoint = endpoint
25
+ @http_method = format_http_method(endpoint)
26
+ @presenter = endpoint.presenter
27
+ @output = []
28
+ end
29
+
30
+ def call
31
+ format_path_params!
32
+
33
+ if endpoint.action == 'index'
34
+ format_pagination_params!
35
+ format_search_param!
36
+ format_only_param!
37
+ format_sort_order_params!
38
+ format_filter_params!
39
+ end
40
+
41
+ if http_method != 'delete'
42
+ format_optional_params!
43
+ format_include_params!
44
+ end
45
+
46
+ format_query_params!
47
+ format_body_params!
48
+
49
+ output
50
+ end
51
+
52
+ ################################################################################
53
+ private
54
+ ################################################################################
55
+
56
+ attr_reader :endpoint, :presenter, :http_method
57
+
58
+ def format_path_params!
59
+ path_params.each do |param|
60
+ model_name = param.match(/_id/) ? param.split('_id').first : 'model'
61
+
62
+ output << {
63
+ 'in' => 'path',
64
+ 'name' => param,
65
+ 'required' => true,
66
+ 'type' => 'integer',
67
+ 'description' => "The ID of the #{model_name.humanize}."
68
+ }
69
+ end
70
+ end
71
+
72
+ def path_params
73
+ endpoint.path
74
+ .gsub('(.:format)', '')
75
+ .scan(/(:(?<param>\w+))/)
76
+ .flatten
77
+ end
78
+
79
+ def nested_properties(param_config)
80
+ param_config.except(:_config)
81
+ end
82
+
83
+ def format_pagination_params!
84
+ output << format_query_param(:page, type: 'integer', default: 1)
85
+ output << format_query_param(:per_page, type: 'integer', default: 20, maximum: 200)
86
+ end
87
+
88
+ def format_search_param!
89
+ return if presenter.nil? || !presenter.searchable?
90
+
91
+ output << format_query_param(:search, type: 'string')
92
+ end
93
+
94
+ def format_only_param!
95
+ output << format_query_param(:only,
96
+ type: 'string',
97
+ info: <<-DESC.strip_heredoc
98
+ Allows you to request one or more resources directly by ID. Multiple IDs can be supplied
99
+ in a comma separated list, like `GET /api/v1/workspaces.json?only=5,6,7`.
100
+ DESC
101
+ )
102
+ end
103
+
104
+ def format_sort_order_params!
105
+ return if presenter.nil? || (valid_sort_orders = presenter.valid_sort_orders).empty?
106
+
107
+ sort_orders = valid_sort_orders.map { |sort_name, _|
108
+ [md_inline_code("#{sort_name}:asc"), md_inline_code("#{sort_name}:desc")]
109
+ }.flatten.sort
110
+
111
+ description = <<-DESC.strip_heredoc
112
+ Supply `order` with the name of a valid sort field for the endpoint and a direction.
113
+
114
+ Valid values: #{sort_orders.to_sentence}.
115
+ DESC
116
+
117
+ output << format_query_param('order',
118
+ info: description,
119
+ type: 'string',
120
+ default: presenter.default_sort_order
121
+ )
122
+ end
123
+
124
+ def format_filter_params!
125
+ return if presenter.nil?
126
+
127
+ presenter.valid_filters.each do |filter_name, filter_config|
128
+ output << format_query_param(filter_name, filter_config)
129
+ end
130
+ end
131
+
132
+ def format_optional_params!
133
+ return if presenter.nil? || (optional_field_names = presenter.optional_field_names).empty?
134
+
135
+ output << format_query_param('optional_fields',
136
+ info: 'Allows you to request one or more optional fields as an array',
137
+ type: 'array',
138
+ item_type: 'string',
139
+ items: optional_field_names
140
+ )
141
+ end
142
+
143
+ def format_include_params!
144
+ return if presenter.nil? || presenter.valid_associations.empty?
145
+
146
+ output << format_query_param('include',
147
+ type: 'string',
148
+ info: include_params_description
149
+ )
150
+ end
151
+
152
+ def include_params_description
153
+ result = "Any of the below associations can be included in your request by providing the `include` "\
154
+ "param, e.g. `include=association1,association2`.\n"
155
+
156
+ presenter.valid_associations
157
+ .sort_by { |_, association| association.name }
158
+ .each do |_, association|
159
+
160
+ text = md_inline_code(association.name)
161
+ text += " (#{ association.target_class.to_s })"
162
+
163
+ desc = format_description(association.description)
164
+ if association.options && association.options[:restrict_to_only]
165
+ desc += " Restricted to queries using the #{md_inline_code("only")} parameter."
166
+ end
167
+
168
+ result << md_li(text + (desc.present? ? " - #{desc}" : ''))
169
+ end
170
+
171
+ result
172
+ end
173
+
174
+ def format_query_params!
175
+ endpoint.params_configuration_tree.each do |param_name, param_config|
176
+ next if nested_properties(param_config).present?
177
+
178
+ output << format_query_param(param_name, param_config[:_config])
179
+ end
180
+
181
+ # Sort all query params by required attribute & alphabetically.
182
+ output.sort_by! { |param| [(param['required'] ? 0 : 1), param['name']] }
183
+ end
184
+
185
+ def format_query_param(param_name, param_config)
186
+ type_data = type_and_format(param_config[:type], param_config[:item_type])
187
+ if type_data.nil?
188
+ raise "Unknown Brainstem Param type encountered(#{param_config[:type]}) for param #{param_name}"
189
+ end
190
+
191
+ if param_config[:type].to_s == 'array'
192
+ type_data[:items].merge!(
193
+ {
194
+ 'type' => param_config[:item_type],
195
+ 'enum' => param_config[:items],
196
+ 'default' => param_config[:default],
197
+ }.compact
198
+ )
199
+ else
200
+ type_data.merge!(
201
+ 'default' => param_config[:default],
202
+ 'minimum' => param_config[:minimum],
203
+ 'maximum' => param_config[:maximum]
204
+ )
205
+ end
206
+
207
+ {
208
+ 'in' => 'query',
209
+ 'name' => param_name.to_s,
210
+ 'required' => param_config[:required],
211
+ 'description' => format_description(param_config[:info]).presence,
212
+ }.merge(type_data).compact
213
+ end
214
+
215
+ def format_body_params!
216
+ formatted_body_params = format_body_params
217
+ return if formatted_body_params.blank?
218
+
219
+ output << {
220
+ 'in' => 'body',
221
+ 'required' => true,
222
+ 'name' => 'body',
223
+ 'schema' => {
224
+ 'type' => 'object',
225
+ 'properties' => formatted_body_params
226
+ },
227
+ }
228
+ end
229
+
230
+ def format_body_params
231
+ ActiveSupport::HashWithIndifferentAccess.new.tap do |body_params|
232
+ endpoint.params_configuration_tree.each do |param_name, param_config|
233
+ next if nested_properties(param_config).blank?
234
+
235
+ body_params[param_name] = format_parent_param(param_name, param_config)
236
+ end
237
+ end
238
+ end
239
+
240
+ def format_parent_param(param_name, param_data)
241
+ param_config = param_data[:_config]
242
+ result = case param_config[:type]
243
+ when 'hash'
244
+ {
245
+ type: 'object',
246
+ title: param_name.to_s,
247
+ description: format_description(param_config[:info]),
248
+ properties: format_param_branch(nested_properties(param_data))
249
+ }
250
+ when 'array'
251
+ {
252
+ type: 'array',
253
+ title: param_name.to_s,
254
+ description: format_description(param_config[:info]),
255
+ items: {
256
+ type: 'object',
257
+ properties: format_param_branch(nested_properties(param_data))
258
+ }
259
+ }
260
+ else
261
+ raise "Unknown Brainstem body param encountered(#{param_config[:type]}) for field #{param_name}"
262
+ end
263
+
264
+ result.with_indifferent_access.reject { |_, v| v.blank? }
265
+ end
266
+
267
+ def format_param_branch(branch)
268
+ branch.inject(ActiveSupport::HashWithIndifferentAccess.new) do |buffer, (param_name, param_data)|
269
+ nested_properties = nested_properties(param_data)
270
+ param_config = param_data[:_config]
271
+
272
+ branch_schema = if nested_properties.present?
273
+ case param_config[:type].to_s
274
+ when 'hash'
275
+ { type: 'object', properties: format_param_branch(nested_properties) }
276
+ when 'array'
277
+ {
278
+ type: 'array',
279
+ items: { type: 'object', properties: format_param_branch(nested_properties) }
280
+ }
281
+ else
282
+ raise "Unknown Brainstem Param type encountered(#{param_config[:type]}) for param #{param_name}"
283
+ end
284
+ else
285
+ param_data = type_and_format(param_config[:type].to_s, param_config[:item_type])
286
+
287
+ if param_data.blank?
288
+ raise "Unknown Brainstem Param type encountered(#{param_config[:type]}) for param #{param_name}"
289
+ end
290
+
291
+ param_data
292
+ end
293
+
294
+ buffer[param_name.to_s] = {
295
+ title: param_name.to_s,
296
+ description: format_description(param_config[:info])
297
+ }.merge(branch_schema).reject { |_, v| v.blank? }
298
+
299
+ buffer
300
+ end
301
+ end
302
+ end
303
+ end
304
+ end
305
+ end
306
+ end
307
+ end
308
+ end
309
+
310
+ Brainstem::ApiDocs::FORMATTERS[:parameters][:oas_v2] =
311
+ Brainstem::ApiDocs::Formatters::OpenApiSpecification::Version2::Endpoint::ParamDefinitionsFormatter.method(:call)
@@ -0,0 +1,197 @@
1
+ require 'active_support/core_ext/hash/except'
2
+ require 'active_support/inflector'
3
+ require 'brainstem/api_docs/formatters/abstract_formatter'
4
+ require 'brainstem/api_docs/formatters/open_api_specification/helper'
5
+ require 'forwardable'
6
+
7
+ #
8
+ # Responsible for formatting a response for an endpoint.
9
+ #
10
+ module Brainstem
11
+ module ApiDocs
12
+ module Formatters
13
+ module OpenApiSpecification
14
+ module Version2
15
+ module Endpoint
16
+ class ResponseDefinitionsFormatter < AbstractFormatter
17
+ include Helper
18
+
19
+ attr_reader :output
20
+
21
+ def initialize(endpoint)
22
+ @endpoint = endpoint
23
+ @http_method = format_http_method(endpoint)
24
+ @presenter = endpoint.presenter
25
+ @model_name = presenter ? presenter_title(presenter) : "object"
26
+ @output = ActiveSupport::HashWithIndifferentAccess.new
27
+ end
28
+
29
+ def call
30
+ if endpoint.custom_response_configuration_tree.present?
31
+ format_custom_response!
32
+ elsif http_method == 'delete'
33
+ format_delete_response!
34
+ else
35
+ format_schema_response!
36
+ end
37
+ format_error_responses!
38
+
39
+ output
40
+ end
41
+
42
+ ################################################################################
43
+ private
44
+ ################################################################################
45
+
46
+ attr_reader :endpoint,
47
+ :presenter,
48
+ :model_name,
49
+ :http_method
50
+
51
+ def format_delete_response!
52
+ output.merge! '204' => { description: success_response_description }
53
+ end
54
+
55
+ def nested_properties(param_config)
56
+ param_config.except(:_config)
57
+ end
58
+
59
+ def success_response_description
60
+ case http_method
61
+ when 'post'
62
+ "#{model_name} has been created."
63
+ when 'put', 'patch'
64
+ "#{model_name} has been updated."
65
+ when 'delete'
66
+ "#{model_name} has been deleted."
67
+ else
68
+ "A list of #{model_name.pluralize} have been retrieved."
69
+ end
70
+ end
71
+
72
+ def format_schema_response!
73
+ return if presenter.nil?
74
+
75
+ brainstem_key = presenter.brainstem_keys.first
76
+ model_klass = presenter.target_class
77
+
78
+ output.merge! '200' => {
79
+ description: success_response_description,
80
+ schema: {
81
+ type: 'object',
82
+ properties: {
83
+ count: type_and_format('integer'),
84
+ meta: {
85
+ type: 'object',
86
+ properties: {
87
+ count: type_and_format('integer'),
88
+ page_count: type_and_format('integer'),
89
+ page_number: type_and_format('integer'),
90
+ page_size: type_and_format('integer'),
91
+ }
92
+ },
93
+ results: {
94
+ type: 'array',
95
+ items: {
96
+ type: 'object',
97
+ properties: {
98
+ key: type_and_format('string'),
99
+ id: type_and_format('string')
100
+ }
101
+ }
102
+ },
103
+ brainstem_key => {
104
+ type: 'object',
105
+ additionalProperties: {
106
+ '$ref' => "#/definitions/#{model_klass}"
107
+ }
108
+ }
109
+ }
110
+ }
111
+ }
112
+ end
113
+
114
+ def format_error_responses!
115
+ output.merge!(
116
+ '400' => { description: 'Bad Request', schema: { '$ref' => '#/definitions/Errors' } },
117
+ '401' => { description: 'Unauthorized request', schema: { '$ref' => '#/definitions/Errors' } },
118
+ '403' => { description: 'Forbidden request', schema: { '$ref' => '#/definitions/Errors' } },
119
+ '404' => { description: 'Page Not Found', schema: { '$ref' => '#/definitions/Errors' } },
120
+ '503' => { description: 'Service is unavailable', schema: { '$ref' => '#/definitions/Errors' } }
121
+ )
122
+ end
123
+
124
+ def format_custom_response!
125
+ output.merge! '200' => {
126
+ description: success_response_description,
127
+ schema: format_response(endpoint.custom_response_configuration_tree)
128
+ }
129
+ end
130
+
131
+ def format_response(response_tree)
132
+ response_config = response_tree[:_config]
133
+ response_branches = response_tree.except(:_config)
134
+
135
+ format_response_field(response_config, response_branches)
136
+ end
137
+
138
+ def format_response_field(field_config, field_branches)
139
+ if field_branches.present?
140
+ formed_nested_field(field_config, field_branches)
141
+ else
142
+ format_response_leaf(field_config)
143
+ end
144
+ end
145
+
146
+ def format_response_leaf(field_config)
147
+ field_data = type_and_format(field_config[:type], field_config[:item_type])
148
+
149
+ unless field_data
150
+ raise "Unknown Brainstem Field type encountered(#{field_config[:type]}) for field #{field_config[:name]}"
151
+ end
152
+
153
+ field_data.merge!(description: format_description(field_config[:info])) if field_config[:info].present?
154
+ field_data
155
+ end
156
+
157
+ def formed_nested_field(field_config, field_branches)
158
+ result = case field_config[:type]
159
+ when 'hash'
160
+ {
161
+ type: 'object',
162
+ description: format_description(field_config[:info]),
163
+ properties: format_response_branches(field_branches)
164
+ }
165
+ when 'array'
166
+ {
167
+ type: 'array',
168
+ description: format_description(field_config[:info]),
169
+ items: {
170
+ type: 'object',
171
+ properties: format_response_branches(field_branches)
172
+ }
173
+ }
174
+ end
175
+
176
+ result.with_indifferent_access.reject { |_, v| v.blank? }
177
+ end
178
+
179
+ def format_response_branches(branches)
180
+ branches.inject(ActiveSupport::HashWithIndifferentAccess.new) do |buffer, (field_name, field_config)|
181
+ config = field_config[:_config]
182
+ branches = field_config.except(:_config)
183
+
184
+ buffer[field_name.to_s] = format_response_field(config, branches)
185
+ buffer
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
195
+
196
+ Brainstem::ApiDocs::FORMATTERS[:response][:oas_v2] =
197
+ Brainstem::ApiDocs::Formatters::OpenApiSpecification::Version2::Endpoint::ResponseDefinitionsFormatter.method(:call)