brainstem 2.0.0 → 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 +4 -4
- data/CHANGELOG.md +147 -0
- data/Gemfile.lock +68 -39
- data/lib/brainstem/api_docs.rb +9 -4
- data/lib/brainstem/api_docs/atlas.rb +3 -3
- data/lib/brainstem/api_docs/controller.rb +12 -4
- data/lib/brainstem/api_docs/controller_collection.rb +11 -2
- data/lib/brainstem/api_docs/endpoint.rb +17 -7
- data/lib/brainstem/api_docs/endpoint_collection.rb +9 -1
- data/lib/brainstem/api_docs/formatters/open_api_specification/helper.rb +19 -16
- data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/param_definitions_formatter.rb +52 -80
- data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/response_definitions_formatter.rb +64 -84
- data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint_formatter.rb +1 -1
- data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/field_definitions/endpoint_param_formatter.rb +39 -0
- data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/field_definitions/presenter_field_formatter.rb +147 -0
- data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/field_definitions/response_field_formatter.rb +146 -0
- data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/presenter_formatter.rb +53 -55
- data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/tags_formatter.rb +1 -1
- data/lib/brainstem/api_docs/presenter.rb +16 -8
- data/lib/brainstem/api_docs/presenter_collection.rb +8 -5
- data/lib/brainstem/api_docs/sinks/open_api_specification_sink.rb +3 -1
- data/lib/brainstem/cli/generate_api_docs_command.rb +4 -0
- data/lib/brainstem/concerns/controller_dsl.rb +90 -20
- data/lib/brainstem/concerns/presenter_dsl.rb +16 -8
- data/lib/brainstem/dsl/association.rb +12 -0
- data/lib/brainstem/dsl/fields_block.rb +1 -1
- data/lib/brainstem/version.rb +1 -1
- data/spec/brainstem/api_docs/controller_spec.rb +127 -5
- data/spec/brainstem/api_docs/endpoint_spec.rb +489 -57
- data/spec/brainstem/api_docs/formatters/open_api_specification/helper_spec.rb +15 -4
- data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/param_definitions_formatter_spec.rb +112 -66
- data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/response_definitions_formatter_spec.rb +404 -32
- data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/field_definitions/endpoint_param_formatter_spec.rb +335 -0
- data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/field_definitions/presenter_field_formatter_spec.rb +237 -0
- data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/field_definitions/response_field_formatter_spec.rb +413 -0
- data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/presenter_formatter_spec.rb +116 -4
- data/spec/brainstem/api_docs/presenter_spec.rb +406 -24
- data/spec/brainstem/cli/generate_api_docs_command_spec.rb +8 -0
- data/spec/brainstem/concerns/controller_dsl_spec.rb +606 -45
- data/spec/brainstem/concerns/presenter_dsl_spec.rb +34 -2
- data/spec/brainstem/dsl/association_spec.rb +54 -3
- metadata +11 -2
@@ -7,6 +7,14 @@ module Brainstem
|
|
7
7
|
class EndpointCollection < AbstractCollection
|
8
8
|
include Concerns::Formattable
|
9
9
|
|
10
|
+
attr_accessor :include_internal
|
11
|
+
|
12
|
+
def valid_options
|
13
|
+
super | [
|
14
|
+
:include_internal
|
15
|
+
]
|
16
|
+
end
|
17
|
+
|
10
18
|
def find_from_route(route)
|
11
19
|
find do |endpoint|
|
12
20
|
endpoint.path == route[:path] &&
|
@@ -18,7 +26,7 @@ module Brainstem
|
|
18
26
|
alias_method :find_by_route, :find_from_route
|
19
27
|
|
20
28
|
def create_from_route(route, controller)
|
21
|
-
Endpoint.new(atlas) do |ep|
|
29
|
+
Endpoint.new(atlas, include_internal: self.include_internal) do |ep|
|
22
30
|
ep.path = route[:path]
|
23
31
|
ep.http_methods = route[:http_methods]
|
24
32
|
ep.controller = controller
|
@@ -18,7 +18,7 @@ module Brainstem
|
|
18
18
|
name.underscore.titleize.strip
|
19
19
|
end
|
20
20
|
|
21
|
-
def
|
21
|
+
def format_sentence(description)
|
22
22
|
return '' if description.blank?
|
23
23
|
|
24
24
|
desc = description.to_s.strip.tap { |desc| desc[0] = desc[0].upcase }
|
@@ -32,11 +32,10 @@ module Brainstem
|
|
32
32
|
description.strip.tap { |desc| desc[0] = desc[0].downcase }
|
33
33
|
end
|
34
34
|
|
35
|
-
# TODO: multi nested
|
36
35
|
def type_and_format(type, item_type = nil)
|
37
36
|
result = case type.to_s.downcase
|
38
37
|
when 'array'
|
39
|
-
{ 'type' => 'array', 'items' =>
|
38
|
+
{ 'type' => 'array', 'items' => type_and_format(item_type.presence || 'string') }
|
40
39
|
else
|
41
40
|
TYPE_INFO[type.to_s]
|
42
41
|
end
|
@@ -44,19 +43,23 @@ module Brainstem
|
|
44
43
|
end
|
45
44
|
|
46
45
|
TYPE_INFO = {
|
47
|
-
'string'
|
48
|
-
'boolean'
|
49
|
-
'integer'
|
50
|
-
'long'
|
51
|
-
'float'
|
52
|
-
'double'
|
53
|
-
'byte'
|
54
|
-
'binary'
|
55
|
-
'date'
|
56
|
-
'datetime'
|
57
|
-
'password'
|
58
|
-
'id'
|
59
|
-
'decimal'
|
46
|
+
'string' => { 'type' => 'string' },
|
47
|
+
'boolean' => { 'type' => 'boolean' },
|
48
|
+
'integer' => { 'type' => 'integer', 'format' => 'int32' },
|
49
|
+
'long' => { 'type' => 'integer', 'format' => 'int64' },
|
50
|
+
'float' => { 'type' => 'number', 'format' => 'float' },
|
51
|
+
'double' => { 'type' => 'number', 'format' => 'double' },
|
52
|
+
'byte' => { 'type' => 'string', 'format' => 'byte' },
|
53
|
+
'binary' => { 'type' => 'string', 'format' => 'binary' },
|
54
|
+
'date' => { 'type' => 'string', 'format' => 'date' },
|
55
|
+
'datetime' => { 'type' => 'string', 'format' => 'date-time' },
|
56
|
+
'password' => { 'type' => 'string', 'format' => 'password' },
|
57
|
+
'id' => { 'type' => 'integer', 'format' => 'int32' },
|
58
|
+
'decimal' => { 'type' => 'number', 'format' => 'float' },
|
59
|
+
'csv' => { 'type' => 'string', 'collectionFormat' => 'csv' },
|
60
|
+
'ssv' => { 'type' => 'string', 'collectionFormat' => 'ssv' },
|
61
|
+
'tsv' => { 'type' => 'string', 'collectionFormat' => 'tsv' },
|
62
|
+
'pipes' => { 'type' => 'string', 'collectionFormat' => 'pipes' },
|
60
63
|
}
|
61
64
|
private_constant :TYPE_INFO
|
62
65
|
end
|
@@ -3,6 +3,7 @@ require 'active_support/core_ext/hash/compact'
|
|
3
3
|
require 'active_support/inflector'
|
4
4
|
require 'brainstem/api_docs/formatters/abstract_formatter'
|
5
5
|
require 'brainstem/api_docs/formatters/open_api_specification/helper'
|
6
|
+
require 'brainstem/api_docs/formatters/open_api_specification/version_2/field_definitions/endpoint_param_formatter'
|
6
7
|
require 'brainstem/api_docs/formatters/markdown/helper'
|
7
8
|
|
8
9
|
#
|
@@ -30,17 +31,19 @@ module Brainstem
|
|
30
31
|
def call
|
31
32
|
format_path_params!
|
32
33
|
|
33
|
-
if
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
34
|
+
if presenter
|
35
|
+
if endpoint.action == 'index'
|
36
|
+
format_pagination_params!
|
37
|
+
format_search_param!
|
38
|
+
format_only_param!
|
39
|
+
format_sort_order_params!
|
40
|
+
format_filter_params!
|
41
|
+
end
|
40
42
|
|
41
|
-
|
42
|
-
|
43
|
-
|
43
|
+
if http_method != 'delete'
|
44
|
+
format_optional_params!
|
45
|
+
format_include_params!
|
46
|
+
end
|
44
47
|
end
|
45
48
|
|
46
49
|
format_query_params!
|
@@ -104,8 +107,12 @@ module Brainstem
|
|
104
107
|
def format_sort_order_params!
|
105
108
|
return if presenter.nil? || (valid_sort_orders = presenter.valid_sort_orders).empty?
|
106
109
|
|
107
|
-
sort_orders = valid_sort_orders.map { |sort_name,
|
108
|
-
[
|
110
|
+
sort_orders = valid_sort_orders.map { |sort_name, sort_config|
|
111
|
+
if sort_config[:direction]
|
112
|
+
[md_inline_code("#{sort_name}:asc"), md_inline_code("#{sort_name}:desc")]
|
113
|
+
else
|
114
|
+
md_inline_code(sort_name)
|
115
|
+
end
|
109
116
|
}.flatten.sort
|
110
117
|
|
111
118
|
description = <<-DESC.strip_heredoc
|
@@ -160,7 +167,7 @@ module Brainstem
|
|
160
167
|
text = md_inline_code(association.name)
|
161
168
|
text += " (#{ association.target_class.to_s })"
|
162
169
|
|
163
|
-
desc =
|
170
|
+
desc = format_sentence(association.description)
|
164
171
|
if association.options && association.options[:restrict_to_only]
|
165
172
|
desc += " Restricted to queries using the #{md_inline_code("only")} parameter."
|
166
173
|
end
|
@@ -208,13 +215,15 @@ module Brainstem
|
|
208
215
|
'in' => 'query',
|
209
216
|
'name' => param_name.to_s,
|
210
217
|
'required' => param_config[:required],
|
211
|
-
'description' =>
|
218
|
+
'description' => format_sentence(param_config[:info]).presence,
|
212
219
|
}.merge(type_data).compact
|
213
220
|
end
|
214
221
|
|
215
222
|
def format_body_params!
|
216
|
-
|
217
|
-
|
223
|
+
properties, additional_properties = split_properties(endpoint.params_configuration_tree)
|
224
|
+
formatted_body_params = format_field_properties(properties)
|
225
|
+
formatted_dynamic_key_params = format_field_properties(additional_properties)
|
226
|
+
return if formatted_body_params.blank? && formatted_dynamic_key_params.blank?
|
218
227
|
|
219
228
|
output << {
|
220
229
|
'in' => 'body',
|
@@ -222,82 +231,45 @@ module Brainstem
|
|
222
231
|
'name' => 'body',
|
223
232
|
'schema' => {
|
224
233
|
'type' => 'object',
|
225
|
-
'properties' => formatted_body_params
|
226
|
-
|
234
|
+
'properties' => formatted_body_params,
|
235
|
+
'additionalProperties' => formatted_dynamic_key_params,
|
236
|
+
}.with_indifferent_access.reject { |_, v| v.blank? },
|
227
237
|
}
|
228
238
|
end
|
229
239
|
|
230
|
-
def
|
231
|
-
|
232
|
-
|
233
|
-
|
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
|
-
}
|
240
|
+
def split_properties(field_properties)
|
241
|
+
split_properties = field_properties.each_with_object({ properties: {}, additional_properties: {} }) do |(field_name, field_config), acc|
|
242
|
+
if field_config[:_config][:dynamic_key]
|
243
|
+
acc[:additional_properties][field_name] = field_config
|
260
244
|
else
|
261
|
-
|
245
|
+
acc[:properties][field_name] = field_config
|
246
|
+
end
|
262
247
|
end
|
263
248
|
|
264
|
-
|
249
|
+
[split_properties[:properties], split_properties[:additional_properties]]
|
265
250
|
end
|
266
251
|
|
267
|
-
def
|
268
|
-
|
269
|
-
|
270
|
-
|
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
|
252
|
+
def format_field_properties(branches)
|
253
|
+
branches.inject(ActiveSupport::HashWithIndifferentAccess.new) do |buffer, (field_name, field_config)|
|
254
|
+
if dynamic_key_field?(field_config)
|
255
|
+
formatted_field('Dynamic Key Field', field_config)
|
284
256
|
else
|
285
|
-
|
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
|
257
|
+
buffer[field_name.to_s] = formatted_field(field_name, field_config) if nested_properties(field_config).present?
|
258
|
+
buffer
|
292
259
|
end
|
260
|
+
end
|
261
|
+
end
|
293
262
|
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
}.merge(branch_schema).reject { |_, v| v.blank? }
|
263
|
+
def dynamic_key_field?(param_config)
|
264
|
+
param_config[:_config][:dynamic_key].presence
|
265
|
+
end
|
298
266
|
|
299
|
-
|
300
|
-
|
267
|
+
def formatted_field(param_name, param_data)
|
268
|
+
Brainstem::ApiDocs::FORMATTERS[:endpoint_param][:oas_v2].call(
|
269
|
+
endpoint,
|
270
|
+
param_name,
|
271
|
+
param_data
|
272
|
+
)
|
301
273
|
end
|
302
274
|
end
|
303
275
|
end
|
@@ -2,6 +2,7 @@ require 'active_support/core_ext/hash/except'
|
|
2
2
|
require 'active_support/inflector'
|
3
3
|
require 'brainstem/api_docs/formatters/abstract_formatter'
|
4
4
|
require 'brainstem/api_docs/formatters/open_api_specification/helper'
|
5
|
+
require 'brainstem/api_docs/formatters/open_api_specification/version_2/field_definitions/response_field_formatter'
|
5
6
|
require 'forwardable'
|
6
7
|
|
7
8
|
#
|
@@ -72,41 +73,70 @@ module Brainstem
|
|
72
73
|
def format_schema_response!
|
73
74
|
return if presenter.nil?
|
74
75
|
|
75
|
-
brainstem_key = presenter.brainstem_keys.first
|
76
|
-
model_klass = presenter.target_class
|
77
|
-
|
78
76
|
output.merge! '200' => {
|
79
77
|
description: success_response_description,
|
80
78
|
schema: {
|
79
|
+
type: 'object',
|
80
|
+
properties: properties
|
81
|
+
}
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
def properties
|
86
|
+
brainstem_key = presenter.brainstem_keys.first
|
87
|
+
model_klass = presenter.target_class
|
88
|
+
|
89
|
+
{
|
90
|
+
count: type_and_format('integer'),
|
91
|
+
meta: {
|
81
92
|
type: 'object',
|
82
93
|
properties: {
|
83
94
|
count: type_and_format('integer'),
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
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
|
-
}
|
95
|
+
page_count: type_and_format('integer'),
|
96
|
+
page_number: type_and_format('integer'),
|
97
|
+
page_size: type_and_format('integer'),
|
98
|
+
}
|
99
|
+
},
|
100
|
+
results: {
|
101
|
+
type: 'array',
|
102
|
+
items: {
|
103
|
+
type: 'object',
|
104
|
+
properties: {
|
105
|
+
key: type_and_format('string'),
|
106
|
+
id: type_and_format('string')
|
108
107
|
}
|
109
108
|
}
|
109
|
+
},
|
110
|
+
brainstem_key => object_reference(model_klass)
|
111
|
+
}.merge(associated_properties)
|
112
|
+
end
|
113
|
+
|
114
|
+
def associated_properties
|
115
|
+
presenter.valid_associations.each_with_object({}) do |(_key, association), obj|
|
116
|
+
if association.polymorphic?
|
117
|
+
associated_klasses = association.polymorphic_classes || []
|
118
|
+
associated_klasses.each do |assoc|
|
119
|
+
association_reference(assoc, obj)
|
120
|
+
end
|
121
|
+
else
|
122
|
+
association_reference(association.target_class, obj)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def association_reference(target_class, obj)
|
128
|
+
assoc_presenter = presenter.find_by_class(target_class)
|
129
|
+
brainstem_key = assoc_presenter.brainstem_keys.first
|
130
|
+
return if assoc_presenter.nodoc?
|
131
|
+
|
132
|
+
obj[brainstem_key] = object_reference(target_class)
|
133
|
+
end
|
134
|
+
|
135
|
+
def object_reference(klass)
|
136
|
+
{
|
137
|
+
type: 'object',
|
138
|
+
additionalProperties: {
|
139
|
+
'$ref' => "#/definitions/#{klass}"
|
110
140
|
}
|
111
141
|
}
|
112
142
|
end
|
@@ -124,66 +154,16 @@ module Brainstem
|
|
124
154
|
def format_custom_response!
|
125
155
|
output.merge! '200' => {
|
126
156
|
description: success_response_description,
|
127
|
-
schema: format_response
|
157
|
+
schema: format_response!
|
128
158
|
}
|
129
159
|
end
|
130
160
|
|
131
|
-
def format_response
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
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
|
161
|
+
def format_response!
|
162
|
+
Brainstem::ApiDocs::FORMATTERS[:response_field][:oas_v2].call(
|
163
|
+
endpoint,
|
164
|
+
'schema',
|
165
|
+
endpoint.custom_response_configuration_tree
|
166
|
+
)
|
187
167
|
end
|
188
168
|
end
|
189
169
|
end
|
data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint_formatter.rb
CHANGED
@@ -104,7 +104,7 @@ module Brainstem
|
|
104
104
|
#
|
105
105
|
def format_optional_info!
|
106
106
|
info = {
|
107
|
-
description:
|
107
|
+
description: format_sentence(description),
|
108
108
|
operation_id: endpoint.operation_id,
|
109
109
|
consumes: endpoint.consumes,
|
110
110
|
produces: endpoint.produces,
|