brainstem 1.4.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +77 -0
- data/README.md +119 -0
- data/docs/api_doc_generator.markdown +45 -4
- data/docs/brainstem_executable.markdown +1 -1
- data/docs/oas_2_docgen.png +0 -0
- data/docs/oas_2_docgen_ascii.txt +78 -0
- data/lib/brainstem/api_docs.rb +23 -9
- data/lib/brainstem/api_docs/abstract_collection.rb +0 -13
- data/lib/brainstem/api_docs/atlas.rb +0 -14
- data/lib/brainstem/api_docs/builder.rb +0 -14
- data/lib/brainstem/api_docs/controller.rb +7 -16
- data/lib/brainstem/api_docs/controller_collection.rb +0 -3
- data/lib/brainstem/api_docs/endpoint.rb +73 -19
- data/lib/brainstem/api_docs/endpoint_collection.rb +0 -7
- data/lib/brainstem/api_docs/formatters/abstract_formatter.rb +0 -2
- data/lib/brainstem/api_docs/formatters/markdown/controller_formatter.rb +1 -9
- data/lib/brainstem/api_docs/formatters/markdown/endpoint_collection_formatter.rb +1 -9
- data/lib/brainstem/api_docs/formatters/markdown/endpoint_formatter.rb +39 -24
- data/lib/brainstem/api_docs/formatters/markdown/helper.rb +0 -13
- data/lib/brainstem/api_docs/formatters/markdown/presenter_formatter.rb +22 -35
- data/lib/brainstem/api_docs/formatters/open_api_specification/helper.rb +66 -0
- data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/controller_formatter.rb +57 -0
- data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/param_definitions_formatter.rb +311 -0
- data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/response_definitions_formatter.rb +197 -0
- data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint_collection_formatter.rb +60 -0
- data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint_formatter.rb +162 -0
- data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/info_formatter.rb +126 -0
- data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/presenter_formatter.rb +132 -0
- data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/security_definitions_formatter.rb +99 -0
- data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/tags_formatter.rb +123 -0
- data/lib/brainstem/api_docs/introspectors/abstract_introspector.rb +0 -7
- data/lib/brainstem/api_docs/introspectors/rails_introspector.rb +1 -20
- data/lib/brainstem/api_docs/presenter.rb +21 -27
- data/lib/brainstem/api_docs/presenter_collection.rb +1 -11
- data/lib/brainstem/api_docs/resolver.rb +1 -8
- data/lib/brainstem/api_docs/sinks/abstract_sink.rb +0 -4
- data/lib/brainstem/api_docs/sinks/controller_presenter_multifile_sink.rb +0 -9
- data/lib/brainstem/api_docs/sinks/open_api_specification_sink.rb +234 -0
- data/lib/brainstem/api_docs/sinks/stdout_sink.rb +0 -5
- data/lib/brainstem/cli.rb +0 -13
- data/lib/brainstem/cli/abstract_command.rb +0 -7
- data/lib/brainstem/cli/generate_api_docs_command.rb +48 -24
- data/lib/brainstem/concerns/controller_dsl.rb +288 -145
- data/lib/brainstem/concerns/formattable.rb +0 -5
- data/lib/brainstem/concerns/optional.rb +0 -1
- data/lib/brainstem/concerns/presenter_dsl.rb +2 -21
- data/lib/brainstem/dsl/configuration.rb +0 -11
- data/lib/brainstem/presenter.rb +0 -4
- data/lib/brainstem/version.rb +1 -1
- data/spec/brainstem/api_docs/abstract_collection_spec.rb +0 -11
- data/spec/brainstem/api_docs/atlas_spec.rb +0 -6
- data/spec/brainstem/api_docs/builder_spec.rb +0 -4
- data/spec/brainstem/api_docs/controller_collection_spec.rb +0 -2
- data/spec/brainstem/api_docs/controller_spec.rb +29 -18
- data/spec/brainstem/api_docs/endpoint_collection_spec.rb +0 -6
- data/spec/brainstem/api_docs/endpoint_spec.rb +343 -13
- data/spec/brainstem/api_docs/formatters/abstract_formatter_spec.rb +0 -2
- data/spec/brainstem/api_docs/formatters/markdown/controller_formatter_spec.rb +0 -1
- data/spec/brainstem/api_docs/formatters/markdown/endpoint_collection_formatter_spec.rb +0 -5
- data/spec/brainstem/api_docs/formatters/markdown/endpoint_formatter_spec.rb +94 -8
- data/spec/brainstem/api_docs/formatters/markdown/helper_spec.rb +0 -8
- data/spec/brainstem/api_docs/formatters/markdown/presenter_formatter_spec.rb +0 -7
- data/spec/brainstem/api_docs/formatters/open_api_specification/helper_spec.rb +210 -0
- data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/controller_formatter_spec.rb +81 -0
- data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/param_definitions_formatter_spec.rb +672 -0
- data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/response_definitions_formatter_spec.rb +335 -0
- data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint_collection_formatter_spec.rb +59 -0
- data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint_formatter_spec.rb +308 -0
- data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/info_formatter_spec.rb +89 -0
- data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/presenter_formatter_spec.rb +430 -0
- data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/security_definitions_formatter_spec.rb +190 -0
- data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/tags_formatter_spec.rb +217 -0
- data/spec/brainstem/api_docs/introspectors/abstract_introspector_spec.rb +0 -2
- data/spec/brainstem/api_docs/introspectors/rails_introspector_spec.rb +0 -2
- data/spec/brainstem/api_docs/presenter_collection_spec.rb +0 -2
- data/spec/brainstem/api_docs/presenter_spec.rb +58 -18
- data/spec/brainstem/api_docs/resolver_spec.rb +0 -1
- data/spec/brainstem/api_docs/sinks/controller_presenter_multifile_sink_spec.rb +0 -2
- data/spec/brainstem/api_docs/sinks/open_api_specification_sink_spec.rb +371 -0
- data/spec/brainstem/api_docs_spec.rb +2 -0
- data/spec/brainstem/cli/abstract_command_spec.rb +0 -4
- data/spec/brainstem/cli/generate_api_docs_command_spec.rb +53 -2
- data/spec/brainstem/concerns/controller_dsl_spec.rb +430 -64
- data/spec/brainstem/concerns/presenter_dsl_spec.rb +0 -20
- data/spec/brainstem/preloader_spec.rb +0 -7
- data/spec/brainstem/presenter_spec.rb +0 -1
- data/spec/dummy/rails.rb +0 -1
- data/spec/spec_helpers/db.rb +0 -1
- 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
|
data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/controller_formatter.rb
ADDED
@@ -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)
|