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.
- 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)
|