brainstem 1.4.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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)