brainstem 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +147 -0
  3. data/Gemfile.lock +68 -39
  4. data/lib/brainstem/api_docs.rb +9 -4
  5. data/lib/brainstem/api_docs/atlas.rb +3 -3
  6. data/lib/brainstem/api_docs/controller.rb +12 -4
  7. data/lib/brainstem/api_docs/controller_collection.rb +11 -2
  8. data/lib/brainstem/api_docs/endpoint.rb +17 -7
  9. data/lib/brainstem/api_docs/endpoint_collection.rb +9 -1
  10. data/lib/brainstem/api_docs/formatters/open_api_specification/helper.rb +19 -16
  11. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/param_definitions_formatter.rb +52 -80
  12. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/response_definitions_formatter.rb +64 -84
  13. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint_formatter.rb +1 -1
  14. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/field_definitions/endpoint_param_formatter.rb +39 -0
  15. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/field_definitions/presenter_field_formatter.rb +147 -0
  16. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/field_definitions/response_field_formatter.rb +146 -0
  17. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/presenter_formatter.rb +53 -55
  18. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/tags_formatter.rb +1 -1
  19. data/lib/brainstem/api_docs/presenter.rb +16 -8
  20. data/lib/brainstem/api_docs/presenter_collection.rb +8 -5
  21. data/lib/brainstem/api_docs/sinks/open_api_specification_sink.rb +3 -1
  22. data/lib/brainstem/cli/generate_api_docs_command.rb +4 -0
  23. data/lib/brainstem/concerns/controller_dsl.rb +90 -20
  24. data/lib/brainstem/concerns/presenter_dsl.rb +16 -8
  25. data/lib/brainstem/dsl/association.rb +12 -0
  26. data/lib/brainstem/dsl/fields_block.rb +1 -1
  27. data/lib/brainstem/version.rb +1 -1
  28. data/spec/brainstem/api_docs/controller_spec.rb +127 -5
  29. data/spec/brainstem/api_docs/endpoint_spec.rb +489 -57
  30. data/spec/brainstem/api_docs/formatters/open_api_specification/helper_spec.rb +15 -4
  31. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/param_definitions_formatter_spec.rb +112 -66
  32. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/response_definitions_formatter_spec.rb +404 -32
  33. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/field_definitions/endpoint_param_formatter_spec.rb +335 -0
  34. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/field_definitions/presenter_field_formatter_spec.rb +237 -0
  35. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/field_definitions/response_field_formatter_spec.rb +413 -0
  36. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/presenter_formatter_spec.rb +116 -4
  37. data/spec/brainstem/api_docs/presenter_spec.rb +406 -24
  38. data/spec/brainstem/cli/generate_api_docs_command_spec.rb +8 -0
  39. data/spec/brainstem/concerns/controller_dsl_spec.rb +606 -45
  40. data/spec/brainstem/concerns/presenter_dsl_spec.rb +34 -2
  41. data/spec/brainstem/dsl/association_spec.rb +54 -3
  42. metadata +11 -2
@@ -0,0 +1,335 @@
1
+ require 'spec_helper'
2
+ require 'brainstem/api_docs/formatters/open_api_specification/version_2/field_definitions/endpoint_param_formatter'
3
+
4
+ module Brainstem
5
+ module ApiDocs
6
+ module Formatters
7
+ module OpenApiSpecification
8
+ module Version2
9
+ module FieldDefinitions
10
+ describe EndpointParamFormatter do
11
+ describe '#format' do
12
+ let(:endpoint) { OpenStruct.new(controller_name: 'Test', action: 'create') }
13
+ let(:field_name) { 'sprocket' }
14
+ let(:configuration_tree) { field_configuration_tree.with_indifferent_access }
15
+
16
+ subject { described_class.new(endpoint, field_name, configuration_tree).format }
17
+
18
+ context 'when formatting nested field' do
19
+ context 'when formatting a nested array field with objects' do
20
+ let(:field_configuration_tree) do
21
+ {
22
+ _config: {
23
+ type: 'array',
24
+ nested_levels: 2,
25
+ item_type: 'hash',
26
+ },
27
+ widget_name: {
28
+ _config: {
29
+ required: true,
30
+ type: 'string',
31
+ info: 'the name of the widget',
32
+ nodoc: false
33
+ },
34
+ },
35
+ support_email: {
36
+ _config: {
37
+ required: true,
38
+ type: 'string',
39
+ info: 'contact support',
40
+ nodoc: false
41
+ },
42
+ },
43
+ version: {
44
+ _config: {
45
+ type: 'string',
46
+ info: 'the version of the widget',
47
+ nodoc: false
48
+ },
49
+ },
50
+ widget_permissions: {
51
+ _config: {
52
+ type: 'array',
53
+ item_type: 'hash',
54
+ info: 'the permissions of the widget',
55
+ nodoc: false
56
+ },
57
+ can_edit: {
58
+ _config: {
59
+ required: true,
60
+ type: 'array',
61
+ item_type: 'boolean',
62
+ nodoc: false
63
+ },
64
+ },
65
+ can_delete: {
66
+ _config: {
67
+ type: 'boolean',
68
+ nodoc: false
69
+ },
70
+ },
71
+ can_rename: {
72
+ _config: {
73
+ type: 'boolean',
74
+ info: 'can rename the widget',
75
+ nodoc: false
76
+ },
77
+ },
78
+ },
79
+ }
80
+ end
81
+
82
+ it 'formats a complicated tree with arrays and hashes as children' do
83
+ expect(subject).to eq(
84
+ 'type' => 'array',
85
+ 'items' => {
86
+ 'type' => 'array',
87
+ 'items' => {
88
+ 'type' => 'object',
89
+ 'required' => ['widget_name', 'support_email'],
90
+ 'properties' => {
91
+ 'widget_name' => {
92
+ 'type' => 'string',
93
+ 'description' => 'The name of the widget.',
94
+ },
95
+ 'support_email' => {
96
+ 'type' => 'string',
97
+ 'description' => 'Contact support.',
98
+ },
99
+ 'version' => {
100
+ 'type' => 'string',
101
+ 'description' => 'The version of the widget.',
102
+ },
103
+ 'widget_permissions' => {
104
+ 'type' => 'array',
105
+ 'description' => 'The permissions of the widget.',
106
+ 'items' => {
107
+ 'type' => 'object',
108
+ 'required' => ['can_edit'],
109
+ 'properties' => {
110
+ 'can_edit' => {
111
+ 'type' => 'array',
112
+ 'items' => {
113
+ 'type' => 'boolean',
114
+ }
115
+ },
116
+ 'can_delete' => {
117
+ 'type' => 'boolean',
118
+ },
119
+ 'can_rename' => {
120
+ 'type' => 'boolean',
121
+ 'description' => 'Can rename the widget.',
122
+ },
123
+ },
124
+ },
125
+ },
126
+ },
127
+ }
128
+ }
129
+ )
130
+ end
131
+ end
132
+
133
+ context 'when formatting a hash field' do
134
+ let(:field_configuration_tree) do
135
+ {
136
+ _config: {
137
+ type: 'hash',
138
+ info: 'Details about the widget',
139
+ },
140
+ widget_name: {
141
+ _config: {
142
+ required: true,
143
+ type: 'string',
144
+ info: 'the name of the widget',
145
+ nodoc: false
146
+ },
147
+ },
148
+ widget_permissions: {
149
+ _config: {
150
+ type: 'array',
151
+ item_type: 'string',
152
+ info: 'the permissions of the widget',
153
+ nodoc: false
154
+ },
155
+ },
156
+ }
157
+ end
158
+
159
+ it 'returns the formatted field schema' do
160
+ expect(subject).to eq(
161
+ 'type' => 'object',
162
+ 'description' => 'Details about the widget.',
163
+ 'required' => ['widget_name'],
164
+ 'properties' => {
165
+ 'widget_name' => {
166
+ 'type' => 'string',
167
+ 'description' => 'The name of the widget.',
168
+ },
169
+ 'widget_permissions' => {
170
+ 'type' => 'array',
171
+ 'description' => 'The permissions of the widget.',
172
+ 'items' => {
173
+ 'type' => 'string',
174
+ }
175
+ }
176
+ }
177
+ )
178
+ end
179
+ end
180
+ end
181
+
182
+ context 'when formatting params with dynamic keys' do
183
+ context 'when formatting a hash param' do
184
+ let(:field_configuration_tree) do
185
+ {
186
+ _config: {
187
+ type: 'hash',
188
+ info: 'Dynamic keys hash.',
189
+ },
190
+ _dynamic_key: {
191
+ _config: {
192
+ nodoc: false,
193
+ type: 'hash',
194
+ dynamic_key: true,
195
+ info: 'a dynamic description.'
196
+ },
197
+ blah: {
198
+ _config: {
199
+ nodoc: false,
200
+ type: 'string',
201
+ },
202
+ },
203
+ },
204
+ }
205
+ end
206
+
207
+ it 'returns the formatted field schema' do
208
+ expect(subject).to eq({
209
+ 'type' => 'object',
210
+ 'description' => 'Dynamic keys hash.',
211
+ 'additionalProperties' => {
212
+ 'type' => 'object',
213
+ 'description' => 'A dynamic description.',
214
+ 'properties' => {
215
+ 'blah' => {
216
+ 'type' => 'string',
217
+ },
218
+ },
219
+ },
220
+ })
221
+ end
222
+ end
223
+
224
+ context 'when formatting a nested hash field' do
225
+ let(:field_configuration_tree) do
226
+ {
227
+ _config: {
228
+ type: 'hash',
229
+ info: 'Dynamic keys hash.',
230
+ },
231
+ non_dynamic_key: {
232
+ _config: {
233
+ nodoc: false,
234
+ type: 'hash',
235
+ info: 'A non-dynamic description.'
236
+ },
237
+ non_dynamic_property: {
238
+ _config: {
239
+ nodoc: false,
240
+ type: 'string',
241
+ },
242
+ },
243
+ },
244
+ _dynamic_key: {
245
+ _config: {
246
+ nodoc: false,
247
+ type: 'hash',
248
+ dynamic_key: true,
249
+ required: true,
250
+ info: 'A dynamic description.'
251
+ },
252
+ dynamic_property1: {
253
+ _config: {
254
+ nodoc: false,
255
+ type: 'string',
256
+ required: true,
257
+ },
258
+ },
259
+ _dynamic_key: {
260
+ _config: {
261
+ nodoc: false,
262
+ type: 'hash',
263
+ dynamic_key: true,
264
+ info: 'A 2nd dynamic description.'
265
+ },
266
+ _dynamic_key: {
267
+ _config: {
268
+ nodoc: false,
269
+ type: 'string',
270
+ dynamic_key: true,
271
+ info: 'A dynamic string.',
272
+ required: true,
273
+ },
274
+ },
275
+ dynamic_property2: {
276
+ _config: {
277
+ nodoc: false,
278
+ type: 'string',
279
+ },
280
+ },
281
+ },
282
+ },
283
+ }
284
+ end
285
+
286
+ it 'returns the formatted field schema' do
287
+ expect(subject).to eq({
288
+ 'type' => 'object',
289
+ 'description' => 'Dynamic keys hash.',
290
+ 'properties' => {
291
+ 'non_dynamic_key' => {
292
+ 'type' => 'object',
293
+ 'description' => 'A non-dynamic description.',
294
+ 'properties' => {
295
+ 'non_dynamic_property' => {
296
+ 'type' => 'string',
297
+ }
298
+ }
299
+ },
300
+ },
301
+ 'additionalProperties' => {
302
+ 'type' => 'object',
303
+ 'description' => 'A dynamic description.',
304
+ 'required' => ['dynamic_property1'],
305
+ 'properties' => {
306
+ 'dynamic_property1' => {
307
+ 'type' => 'string',
308
+ },
309
+ },
310
+ 'additionalProperties' => {
311
+ 'type' => 'object',
312
+ 'description' => 'A 2nd dynamic description.',
313
+ 'properties' => {
314
+ 'dynamic_property2' => {
315
+ 'type' => 'string',
316
+ },
317
+ },
318
+ 'additionalProperties' => {
319
+ 'type' => 'string',
320
+ 'description' => 'A dynamic string.',
321
+ },
322
+ },
323
+ },
324
+ })
325
+ end
326
+ end
327
+ end
328
+ end
329
+ end
330
+ end
331
+ end
332
+ end
333
+ end
334
+ end
335
+ end
@@ -0,0 +1,237 @@
1
+ require 'spec_helper'
2
+ require 'brainstem/api_docs/presenter'
3
+ require 'brainstem/api_docs/formatters/open_api_specification/version_2/field_definitions/presenter_field_formatter'
4
+
5
+ module Brainstem
6
+ module ApiDocs
7
+ module Formatters
8
+ module OpenApiSpecification
9
+ module Version2
10
+ module FieldDefinitions
11
+ describe PresenterFieldFormatter do
12
+ describe '#format' do
13
+ let(:presenter_class) do
14
+ Class.new(Brainstem::Presenter) do
15
+ presents Workspace
16
+ end
17
+ end
18
+ let(:presenter) { Presenter.new(Object.new, const: presenter_class, target_class: 'Workspace') }
19
+ let(:conditionals) { {} }
20
+ let(:field) { presenter.valid_fields.values.first }
21
+
22
+
23
+ subject { described_class.new(presenter, field).format }
24
+
25
+ before do
26
+ stub(presenter).conditionals { conditionals }
27
+ end
28
+
29
+ context 'when formatting non-nested field' do
30
+ before do
31
+ presenter_class.fields do
32
+ field :sprocket_name, :string, via: :name, info: 'The name of the sprocket'
33
+ end
34
+ end
35
+
36
+ it 'returns the formatted field schema' do
37
+ expect(subject).to eq(
38
+ 'type' => 'string',
39
+ 'description' => 'The name of the sprocket.',
40
+ )
41
+ end
42
+ end
43
+
44
+ context 'when formatting nested field' do
45
+ context 'when formatting an array field' do
46
+ before do
47
+ presenter_class.fields do
48
+ field :sprocket_names, :array, info: 'All the names for the sprocket'
49
+ end
50
+ end
51
+
52
+ it 'returns the formatted field schema' do
53
+ expect(subject).to eq(
54
+ 'type' => 'array',
55
+ 'description' => 'All the names for the sprocket.',
56
+ 'items' => {
57
+ 'type' => 'string',
58
+ }
59
+ )
60
+ end
61
+ end
62
+
63
+ context 'when formatting a nested array field with non nested data type' do
64
+ before do
65
+ presenter_class.fields do
66
+ field :sprocket_usages, :array,
67
+ item_type: :decimal,
68
+ nested_levels: 3,
69
+ info: 'All the names for the sprocket'
70
+ end
71
+ end
72
+
73
+ it 'returns the formatted field schema' do
74
+ expect(subject).to eq(
75
+ 'type' => 'array',
76
+ 'description' => 'All the names for the sprocket.',
77
+ 'items' => {
78
+ 'type' => 'array',
79
+ 'items' => {
80
+ 'type' => 'array',
81
+ 'items' => {
82
+ 'type' => 'number',
83
+ 'format' => 'float'
84
+ }
85
+ }
86
+ }
87
+ )
88
+ end
89
+ end
90
+
91
+ context 'when formatting a nested array field with objects' do
92
+ before do
93
+ presenter_class.fields do
94
+ fields :sprockets, :array, nested_levels: 2, info: 'I am a sprocket' do
95
+ field :widget_name, :string,
96
+ info: 'the name of the widget'
97
+
98
+ fields :widget_permissions, :array, info: 'the permissions of the widget' do
99
+ field :can_edit, :array,
100
+ nested_levels: 3,
101
+ item_type: 'boolean',
102
+ info: 'the ethos of the widget'
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ it 'formats a complicated tree with arrays and hashes as children' do
109
+ expect(subject).to eq(
110
+ 'type' => 'array',
111
+ 'description' => 'I am a sprocket.',
112
+ 'items' => {
113
+ 'type' => 'array',
114
+ 'items' => {
115
+ 'type' => 'object',
116
+ 'properties' => {
117
+ 'widget_name' => {
118
+ 'type' => 'string',
119
+ 'description' => 'The name of the widget.',
120
+ },
121
+ 'widget_permissions' => {
122
+ 'type' => 'array',
123
+ 'description' => 'The permissions of the widget.',
124
+ 'items' => {
125
+ 'type' => 'object',
126
+ 'properties' => {
127
+ 'can_edit' => {
128
+ 'type' => 'array',
129
+ 'description' => 'The ethos of the widget.',
130
+ 'items' => {
131
+ 'type' => 'array',
132
+ 'items' => {
133
+ 'type' => 'array',
134
+ 'items' => {
135
+ 'type' => 'boolean'
136
+ }
137
+ }
138
+ }
139
+ }
140
+ }
141
+ }
142
+ }
143
+ }
144
+ }
145
+ }
146
+ )
147
+ end
148
+ end
149
+
150
+ context 'when formatting a hash field' do
151
+ before do
152
+ presenter_class.fields do
153
+ fields :sprockets, :hash, info: 'Details about the widget' do
154
+ field :widget_name, :string,
155
+ info: 'the name of the widget'
156
+
157
+ fields :widget_permissions, :array, info: 'the permissions of the widget' do
158
+ field :can_edit, :boolean,
159
+ info: 'can edit the widget'
160
+ end
161
+ end
162
+ end
163
+ end
164
+
165
+ it 'returns the formatted field schema' do
166
+ expect(subject).to eq(
167
+ 'type' => 'object',
168
+ 'description' => 'Details about the widget.',
169
+ 'properties' => {
170
+ 'widget_name' => {
171
+ 'type' => 'string',
172
+ 'description' => 'The name of the widget.',
173
+ },
174
+ 'widget_permissions' => {
175
+ 'type' => 'array',
176
+ 'description' => 'The permissions of the widget.',
177
+ 'items' => {
178
+ 'type' => 'object',
179
+ 'properties' => {
180
+ 'can_edit' => {
181
+ 'type' => 'boolean',
182
+ 'description' => 'Can edit the widget.'
183
+ }
184
+ }
185
+ }
186
+ }
187
+ }
188
+ )
189
+ end
190
+ end
191
+
192
+ context 'when formatting a multi nested hash field' do
193
+ before do
194
+ presenter_class.fields do
195
+ fields :sprockets do
196
+ field :widget_name, :string,
197
+ info: 'the name of the widget'
198
+
199
+ fields :widget_permissions, :hash, info: 'the permissions of the widget' do
200
+ field :can_edit, :boolean,
201
+ info: 'can edit the widget'
202
+ end
203
+ end
204
+ end
205
+ end
206
+
207
+ it 'returns the formatted field schema' do
208
+ expect(subject).to eq(
209
+ 'type' => 'object',
210
+ 'properties' => {
211
+ 'widget_name' => {
212
+ 'type' => 'string',
213
+ 'description' => 'The name of the widget.',
214
+ },
215
+ 'widget_permissions' => {
216
+ 'type' => 'object',
217
+ 'description' => 'The permissions of the widget.',
218
+ 'properties' => {
219
+ 'can_edit' => {
220
+ 'type' => 'boolean',
221
+ 'description' => 'Can edit the widget.',
222
+ }
223
+ }
224
+ }
225
+ }
226
+ )
227
+ end
228
+ end
229
+ end
230
+ end
231
+ end
232
+ end
233
+ end
234
+ end
235
+ end
236
+ end
237
+ end