brainstem 2.0.0 → 2.1.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 +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,
|