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.
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
@@ -54,12 +54,12 @@ module Brainstem
54
54
  end
55
55
  end
56
56
 
57
- describe "format_description" do
57
+ describe "format_sentence" do
58
58
  context "when description is given" do
59
59
  let(:description) { " lorem ipsum dolor sit amet " }
60
60
 
61
61
  it "returns the formatted description" do
62
- expect(subject.format_description(description)).to eq("Lorem ipsum dolor sit amet.")
62
+ expect(subject.format_sentence(description)).to eq("Lorem ipsum dolor sit amet.")
63
63
  end
64
64
  end
65
65
 
@@ -67,7 +67,7 @@ module Brainstem
67
67
  let(:description) { "" }
68
68
 
69
69
  it "returns nil" do
70
- expect(subject.format_description(description)).to eq('')
70
+ expect(subject.format_sentence(description)).to eq('')
71
71
  end
72
72
  end
73
73
  end
@@ -191,12 +191,23 @@ module Brainstem
191
191
  it "returns type as `array` and given item type" do
192
192
  expect(subject.send(:type_and_format, 'array', 'integer')).to eq({
193
193
  'type' => 'array',
194
- 'items' => { 'type' => 'integer' }
194
+ 'items' => { 'type' => 'integer', 'format' => 'int32' }
195
195
  })
196
196
  end
197
197
  end
198
198
  end
199
199
 
200
+ context "when dealing with delimited strings" do
201
+ it 'returns string as type and the collection format' do
202
+ %w(csv ssv tsv pipes).each do |delimited_string_type|
203
+ expect(subject.type_and_format(delimited_string_type)).to eq(
204
+ 'type' => 'string',
205
+ 'collectionFormat' => delimited_string_type,
206
+ )
207
+ end
208
+ end
209
+ end
210
+
200
211
  context "when type is unknown" do
201
212
  it "returns nil" do
202
213
  expect(subject.send(:type_and_format, 'invalid')).to be_nil
@@ -34,38 +34,86 @@ module Brainstem
34
34
  end
35
35
 
36
36
  describe '#call' do
37
- context 'when request type is get' do
38
- let(:http_methods) { %w(GET) }
37
+ context 'when an endpoint presents a brainstem object' do
38
+ context 'when request type is get' do
39
+ let(:http_methods) { %w(GET) }
40
+
41
+ context 'when action is index' do
42
+ let(:action) { 'index' }
43
+
44
+ it 'formats path, shared, query and body params for the endpoint' do
45
+ any_instance_of(described_class) do |instance|
46
+ mock(instance).format_path_params!
47
+ mock(instance).format_optional_params!
48
+ mock(instance).format_include_params!
49
+ mock(instance).format_query_params!
50
+ mock(instance).format_body_params!
51
+ mock(instance).format_pagination_params!
52
+ mock(instance).format_search_param!
53
+ mock(instance).format_only_param!
54
+ mock(instance).format_sort_order_params!
55
+ mock(instance).format_filter_params!
56
+ end
57
+
58
+ subject.call
59
+ end
60
+ end
39
61
 
40
- context 'when action is index' do
41
- let(:action) { 'index' }
62
+ context 'when action is show' do
63
+ let(:action) { 'show' }
64
+
65
+ it 'formats path, optional, query and body params for the endpoint' do
66
+ any_instance_of(described_class) do |instance|
67
+ mock(instance).format_path_params!
68
+ mock(instance).format_optional_params!
69
+ mock(instance).format_include_params!
70
+ mock(instance).format_query_params!
71
+ mock(instance).format_body_params!
72
+
73
+ dont_allow(instance).format_pagination_params!
74
+ dont_allow(instance).format_search_param!
75
+ dont_allow(instance).format_only_param!
76
+ dont_allow(instance).format_sort_order_params!
77
+ dont_allow(instance).format_filter_params!
78
+ end
79
+
80
+ subject.call
81
+ end
82
+ end
83
+ end
84
+
85
+ context 'when request type is `delete`' do
86
+ let(:http_methods) { %w(DELETE) }
87
+ let(:action) { 'destroy' }
42
88
 
43
- it 'formats path, shared, query and body params for the endpoint' do
89
+ it 'formats path, query and body param for the endpoint' do
44
90
  any_instance_of(described_class) do |instance|
45
91
  mock(instance).format_path_params!
46
- mock(instance).format_optional_params!
47
- mock(instance).format_include_params!
48
92
  mock(instance).format_query_params!
49
93
  mock(instance).format_body_params!
50
- mock(instance).format_pagination_params!
51
- mock(instance).format_search_param!
52
- mock(instance).format_only_param!
53
- mock(instance).format_sort_order_params!
54
- mock(instance).format_filter_params!
94
+
95
+ dont_allow(instance).format_pagination_params!
96
+ dont_allow(instance).format_search_param!
97
+ dont_allow(instance).format_only_param!
98
+ dont_allow(instance).format_sort_order_params!
99
+ dont_allow(instance).format_optional_params!
100
+ dont_allow(instance).format_include_params!
101
+ dont_allow(instance).format_filter_params!
55
102
  end
56
103
 
57
104
  subject.call
58
105
  end
59
106
  end
60
107
 
61
- context 'when action is show' do
62
- let(:action) { 'show' }
108
+ context 'when request type is not delete' do
109
+ let(:http_methods) { %w(PATCH) }
110
+ let(:action) { 'update' }
63
111
 
64
- it 'formats path, optional, query and body params for the endpoint' do
112
+ it 'formats path, query and body param for the endpoint' do
65
113
  any_instance_of(described_class) do |instance|
66
- mock(instance).format_path_params!
67
114
  mock(instance).format_optional_params!
68
115
  mock(instance).format_include_params!
116
+ mock(instance).format_path_params!
69
117
  mock(instance).format_query_params!
70
118
  mock(instance).format_body_params!
71
119
 
@@ -81,9 +129,8 @@ module Brainstem
81
129
  end
82
130
  end
83
131
 
84
- context 'when request type is `delete`' do
85
- let(:http_methods) { %w(DELETE) }
86
- let(:action) { 'destroy' }
132
+ context 'when an endpoint does not present a brainstem object' do
133
+ let(:presenter) { nil }
87
134
 
88
135
  it 'formats path, query and body param for the endpoint' do
89
136
  any_instance_of(described_class) do |instance|
@@ -103,29 +150,6 @@ module Brainstem
103
150
  subject.call
104
151
  end
105
152
  end
106
-
107
- context 'when request type is not delete' do
108
- let(:http_methods) { %w(PATCH) }
109
- let(:action) { 'update' }
110
-
111
- it 'formats path, query and body param for the endpoint' do
112
- any_instance_of(described_class) do |instance|
113
- mock(instance).format_optional_params!
114
- mock(instance).format_include_params!
115
- mock(instance).format_path_params!
116
- mock(instance).format_query_params!
117
- mock(instance).format_body_params!
118
-
119
- dont_allow(instance).format_pagination_params!
120
- dont_allow(instance).format_search_param!
121
- dont_allow(instance).format_only_param!
122
- dont_allow(instance).format_sort_order_params!
123
- dont_allow(instance).format_filter_params!
124
- end
125
-
126
- subject.call
127
- end
128
- end
129
153
  end
130
154
 
131
155
  describe '#formatting' do
@@ -239,7 +263,7 @@ module Brainstem
239
263
  'in' => 'query',
240
264
  'name' => 'sprocket_ids',
241
265
  'type' => 'array',
242
- 'items' => { 'type' => 'integer' }
266
+ 'items' => { 'type' => 'integer', 'format' => 'int32' }
243
267
  },
244
268
  {
245
269
  'in' => 'query',
@@ -274,7 +298,7 @@ module Brainstem
274
298
  type: 'hash',
275
299
  info: 'attributes for the task '
276
300
  },
277
- name: {
301
+ title: {
278
302
  _config: {
279
303
  type: 'string',
280
304
  required: true,
@@ -286,7 +310,7 @@ module Brainstem
286
310
  type: 'hash',
287
311
  info: 'sub tasks of the task'
288
312
  },
289
- name: {
313
+ title: {
290
314
  _config: {
291
315
  type: 'string',
292
316
  required: true
@@ -300,7 +324,15 @@ module Brainstem
300
324
  },
301
325
  name: {
302
326
  _config: {
303
- type: 'string'
327
+ type: 'string',
328
+ required: true,
329
+ }
330
+ },
331
+ _dynamic_key: {
332
+ _config: {
333
+ type: 'boolean',
334
+ info: 'something',
335
+ dynamic_key: true,
304
336
  }
305
337
  },
306
338
  },
@@ -324,6 +356,7 @@ module Brainstem
324
356
  },
325
357
  id: {
326
358
  _config: {
359
+ required: true,
327
360
  type: 'integer',
328
361
  info: 'ID of the assignee'
329
362
  }
@@ -334,6 +367,15 @@ module Brainstem
334
367
  info: 'activates the assignment'
335
368
  }
336
369
  }
370
+ },
371
+ _dynamic_key: {
372
+ _config: {
373
+ required: true,
374
+ type: 'array',
375
+ item_type: 'integer',
376
+ info: 'IDs of assignees',
377
+ dynamic_key: true,
378
+ }
337
379
  }
338
380
  }.with_indifferent_access
339
381
  end
@@ -353,38 +395,38 @@ module Brainstem
353
395
  'schema' => {
354
396
  'type' => 'object',
355
397
  'properties' => {
356
-
357
398
  'task' => {
358
- 'title' => 'task',
359
- 'type' => 'object',
360
399
  'description' => 'Attributes for the task.',
400
+ 'required' => ['title'],
401
+ 'type' => 'object',
361
402
  'properties' => {
362
- 'name' => {
363
- 'title' => 'name',
403
+ 'title' => {
364
404
  'description' => 'Name of the task.',
365
405
  'type' => 'string'
366
406
  },
367
407
  'subs' => {
368
- 'title' => 'subs',
369
408
  'description' => 'Sub tasks of the task.',
409
+ 'required' => ['title'],
370
410
  'type' => 'object',
371
411
  'properties' => {
372
- 'name' => {
373
- 'title' => 'name',
374
- 'type' => 'string'
412
+ 'title' => {
413
+ 'type' => 'string'
375
414
  }
376
415
  }
377
416
  },
378
417
  'checklist' => {
379
- 'title' => 'checklist',
380
418
  'type' => 'array',
381
419
  'items' => {
382
420
  'type' => 'object',
421
+ 'required' => ['name'],
383
422
  'properties' => {
384
423
  'name' => {
385
- 'title' => 'name',
386
- 'type' => 'string'
424
+ 'type' => 'string',
387
425
  }
426
+ },
427
+ 'additionalProperties' => {
428
+ 'type' => 'boolean',
429
+ 'description' => 'Something.',
388
430
  }
389
431
  }
390
432
  }
@@ -392,12 +434,10 @@ module Brainstem
392
434
  },
393
435
 
394
436
  'creator' => {
395
- 'title' => 'creator',
396
437
  'type' => 'object',
397
438
  'description' => 'Attributes for the creator.',
398
439
  'properties' => {
399
440
  'id' => {
400
- 'title' => 'id',
401
441
  'description' => 'ID of the creator.',
402
442
  'type' => 'integer',
403
443
  'format' => 'int32'
@@ -406,25 +446,31 @@ module Brainstem
406
446
  },
407
447
 
408
448
  'assignees' => {
409
- 'title' => 'assignees',
410
449
  'type' => 'array',
411
450
  'description' => 'Attributes for the assignees.',
412
451
  'items' => {
413
452
  'type' => 'object',
453
+ 'required' => ['id'],
414
454
  'properties' => {
415
455
  'id' => {
416
- 'title' => 'id',
417
456
  'description' => 'ID of the assignee.',
418
457
  'type' => 'integer',
419
458
  'format' => 'int32'
420
459
  },
421
460
  'active' => {
422
- 'title' => 'active',
423
461
  'description' => 'Activates the assignment.',
424
462
  'type' => 'boolean'
425
463
  },
426
464
  }
427
465
  }
466
+ },
467
+ },
468
+ 'additionalProperties' => {
469
+ 'type' => 'array',
470
+ 'description' => 'IDs of assignees.',
471
+ 'items' => {
472
+ 'type' => 'integer',
473
+ 'format' => 'int32',
428
474
  }
429
475
  }
430
476
  },
@@ -595,8 +641,8 @@ module Brainstem
595
641
  mock(presenter).default_sort_order { 'title:asc' }
596
642
  mock(presenter).valid_sort_orders {
597
643
  {
598
- 'title' => { info: 'Order by title aphabetically' },
599
- 'sprocket_name' => { info: 'Order by sprocket name aphabetically' },
644
+ 'title' => { info: 'Order by title alphabetically', direction: true },
645
+ 'sprocket_name' => { info: 'Order by sprocket name alphabetically', direction: false },
600
646
  }
601
647
  }
602
648
  end
@@ -611,7 +657,7 @@ module Brainstem
611
657
  'type' => 'string',
612
658
  'default' => 'title:asc',
613
659
  'description' => "Supply `order` with the name of a valid sort field for the endpoint and a direction.\n\n" +
614
- "Valid values: `sprocket_name:asc`, `sprocket_name:desc`, `title:asc`, and `title:desc`."
660
+ "Valid values: `sprocket_name`, `title:asc`, and `title:desc`."
615
661
  }
616
662
  ])
617
663
  end
@@ -114,6 +114,7 @@ module Brainstem
114
114
  before do
115
115
  stub(presenter).brainstem_keys { ['widgets'] }
116
116
  stub(presenter).target_class { 'Widget' }
117
+ stub(presenter).valid_associations { {} }
117
118
  end
118
119
 
119
120
  it 'returns the structured response for an endpoint' do
@@ -154,6 +155,172 @@ module Brainstem
154
155
  }
155
156
  })
156
157
  end
158
+
159
+ context 'when there are valid associations' do
160
+ let(:task_stubbed_presenter) { Object.new }
161
+ let(:user_stubbed_presenter) { Object.new }
162
+ let(:nodoc) { false }
163
+
164
+ before do
165
+ stub(task_stubbed_presenter).nodoc? { nodoc }
166
+ stub(task_stubbed_presenter).brainstem_keys { ['tasks'] }
167
+ stub(user_stubbed_presenter).nodoc? { nodoc }
168
+ stub(user_stubbed_presenter).brainstem_keys { ['users'] }
169
+ stub(presenter).find_by_class(Task) { task_stubbed_presenter }
170
+ stub(presenter).find_by_class(User) { user_stubbed_presenter }
171
+ end
172
+
173
+ context 'when association is pointing to a nodoc class' do
174
+ let(:nodoc) { true }
175
+
176
+ it 'does not generate references for the association' do
177
+ subject.send(:format_schema_response!)
178
+
179
+ expect(subject.output).to eq('200' => {
180
+ 'description' => 'A list of Widgets have been retrieved.',
181
+ 'schema' => {
182
+ 'type' => 'object',
183
+ 'properties' => {
184
+ 'count' => { 'type' => 'integer', 'format' => 'int32' },
185
+ 'meta' => {
186
+ 'type' => 'object',
187
+ 'properties' => {
188
+ 'count' => { 'type' => 'integer', 'format' => 'int32' },
189
+ 'page_count' => { 'type' => 'integer', 'format' => 'int32' },
190
+ 'page_number' => { 'type' => 'integer', 'format' => 'int32' },
191
+ 'page_size' => { 'type' => 'integer', 'format' => 'int32' },
192
+ }
193
+ },
194
+ 'results' => {
195
+ 'type' => 'array',
196
+ 'items' => {
197
+ 'type' => 'object',
198
+ 'properties' => {
199
+ 'key' => { 'type' => 'string' },
200
+ 'id' => { 'type' => 'string' }
201
+ }
202
+ }
203
+ },
204
+ 'widgets' => {
205
+ 'type' => 'object',
206
+ 'additionalProperties' => {
207
+ '$ref' => '#/definitions/Widget'
208
+ }
209
+ }
210
+ }
211
+ }
212
+ })
213
+ end
214
+ end
215
+
216
+ context 'when the association is polymorphic' do
217
+ before do
218
+ stub(presenter).valid_associations { { 'tasks' => ::Brainstem::DSL::Association.new('polymorphic', :polymorphic, polymorphic_classes: [Task, User]) } }
219
+ end
220
+
221
+ it 'returns the appropriate associations with their additional properties' do
222
+ subject.send(:format_schema_response!)
223
+
224
+ expect(subject.output).to eq('200' => {
225
+ 'description' => 'A list of Widgets have been retrieved.',
226
+ 'schema' => {
227
+ 'type' => 'object',
228
+ 'properties' => {
229
+ 'count' => { 'type' => 'integer', 'format' => 'int32' },
230
+ 'meta' => {
231
+ 'type' => 'object',
232
+ 'properties' => {
233
+ 'count' => { 'type' => 'integer', 'format' => 'int32' },
234
+ 'page_count' => { 'type' => 'integer', 'format' => 'int32' },
235
+ 'page_number' => { 'type' => 'integer', 'format' => 'int32' },
236
+ 'page_size' => { 'type' => 'integer', 'format' => 'int32' },
237
+ }
238
+ },
239
+ 'results' => {
240
+ 'type' => 'array',
241
+ 'items' => {
242
+ 'type' => 'object',
243
+ 'properties' => {
244
+ 'key' => { 'type' => 'string' },
245
+ 'id' => { 'type' => 'string' }
246
+ }
247
+ }
248
+ },
249
+ 'widgets' => {
250
+ 'type' => 'object',
251
+ 'additionalProperties' => {
252
+ '$ref' => '#/definitions/Widget'
253
+ }
254
+ },
255
+ 'tasks' => {
256
+ 'type' => 'object',
257
+ 'additionalProperties' => {
258
+ '$ref' => '#/definitions/Task'
259
+ }
260
+ },
261
+ 'users' => {
262
+ 'type' => 'object',
263
+ 'additionalProperties' => {
264
+ '$ref' => '#/definitions/User'
265
+ }
266
+ }
267
+ }
268
+ }
269
+ })
270
+ end
271
+ end
272
+
273
+ context 'when the association is not polymorphic' do
274
+ before do
275
+ stub(presenter).valid_associations { { 'tasks' => ::Brainstem::DSL::Association.new('Task', Task, {}) } }
276
+ end
277
+
278
+ it 'returns the appropriate association with its additional properties' do
279
+ subject.send(:format_schema_response!)
280
+
281
+ expect(subject.output).to eq('200' => {
282
+ 'description' => 'A list of Widgets have been retrieved.',
283
+ 'schema' => {
284
+ 'type' => 'object',
285
+ 'properties' => {
286
+ 'count' => { 'type' => 'integer', 'format' => 'int32' },
287
+ 'meta' => {
288
+ 'type' => 'object',
289
+ 'properties' => {
290
+ 'count' => { 'type' => 'integer', 'format' => 'int32' },
291
+ 'page_count' => { 'type' => 'integer', 'format' => 'int32' },
292
+ 'page_number' => { 'type' => 'integer', 'format' => 'int32' },
293
+ 'page_size' => { 'type' => 'integer', 'format' => 'int32' },
294
+ }
295
+ },
296
+ 'results' => {
297
+ 'type' => 'array',
298
+ 'items' => {
299
+ 'type' => 'object',
300
+ 'properties' => {
301
+ 'key' => { 'type' => 'string' },
302
+ 'id' => { 'type' => 'string' }
303
+ }
304
+ }
305
+ },
306
+ 'widgets' => {
307
+ 'type' => 'object',
308
+ 'additionalProperties' => {
309
+ '$ref' => '#/definitions/Widget'
310
+ }
311
+ },
312
+ 'tasks' => {
313
+ 'type' => 'object',
314
+ 'additionalProperties' => {
315
+ '$ref' => '#/definitions/Task'
316
+ }
317
+ }
318
+ }
319
+ }
320
+ })
321
+ end
322
+ end
323
+ end
157
324
  end
158
325
 
159
326
  describe '#format_error_responses!' do
@@ -197,7 +364,25 @@ module Brainstem
197
364
  'info' => 'Can edit the widget.',
198
365
  'nodoc' => false
199
366
  },
200
- }
367
+ },
368
+ '_dynamic_key' => {
369
+ '_config' => {
370
+ 'type' => 'array',
371
+ 'item_type' => 'integer',
372
+ 'info' => 'Viewable Widget Ids.',
373
+ 'nodoc' => false,
374
+ 'dynamic_key' => true
375
+ },
376
+ },
377
+ },
378
+ '_dynamic_key' => {
379
+ '_config' => {
380
+ 'type' => 'array',
381
+ 'item_type' => 'integer',
382
+ 'info' => 'Association Ids.',
383
+ 'nodoc' => false,
384
+ 'dynamic_key' => true
385
+ },
201
386
  },
202
387
  }.with_indifferent_access
203
388
  }
@@ -223,49 +408,93 @@ module Brainstem
223
408
  'type' => 'boolean',
224
409
  'description' => 'Can edit the widget.'
225
410
  }
226
- }
411
+ },
412
+ 'additionalProperties' => {
413
+ 'type' => 'array',
414
+ 'description' => 'Viewable Widget Ids.',
415
+ 'items' => {
416
+ 'type' => 'integer',
417
+ 'format' => 'int32',
418
+ }
419
+ },
227
420
  }
228
- }
421
+ },
422
+ 'additionalProperties' => {
423
+ 'type' => 'array',
424
+ 'description' => 'Association Ids.',
425
+ 'items' => {
426
+ 'type' => 'integer',
427
+ 'format' => 'int32',
428
+ }
429
+ },
229
430
  }
230
431
  })
231
432
  end
232
433
  end
233
434
 
234
435
  context 'when the response is an array' do
235
- before do
236
- stub(endpoint).custom_response_configuration_tree {
237
- {
238
- '_config' => {
239
- 'type' => 'array',
240
- 'item_type' => 'hash',
241
- },
242
- 'widget_name' => {
436
+ context 'when array of string / number' do
437
+ before do
438
+ stub(endpoint).custom_response_configuration_tree {
439
+ {
243
440
  '_config' => {
244
- 'type' => 'string',
245
- 'info' => 'The name of the widget.',
246
- 'nodoc' => false
441
+ 'type' => 'array',
442
+ 'item_type' => 'string',
247
443
  },
248
- },
249
- 'widget_permissions' => {
444
+ }.with_indifferent_access
445
+ }
446
+ end
447
+
448
+ it 'returns the response structure' do
449
+ subject.send(:format_custom_response!)
450
+
451
+ expect(subject.output).to eq('200' => {
452
+ 'description' => 'A list of Widgets have been retrieved.',
453
+ 'schema' => {
454
+ 'type' => 'array',
455
+ 'items' => {
456
+ 'type' => 'string',
457
+ }
458
+ }
459
+ })
460
+ end
461
+ end
462
+
463
+ context 'when array of hashes' do
464
+ before do
465
+ stub(endpoint).custom_response_configuration_tree {
466
+ {
250
467
  '_config' => {
251
468
  'type' => 'array',
252
469
  'item_type' => 'hash',
253
- 'info' => 'The permissions of the widget.',
254
- 'nodoc' => false
255
470
  },
256
- 'can_edit' => {
471
+ 'widget_name' => {
257
472
  '_config' => {
258
- 'type' => 'boolean',
259
- 'info' => 'Can edit the widget.',
473
+ 'type' => 'string',
474
+ 'info' => 'The name of the widget.',
260
475
  'nodoc' => false
261
476
  },
262
- }
263
- },
264
- }.with_indifferent_access
265
- }
266
- end
477
+ },
478
+ 'widget_permissions' => {
479
+ '_config' => {
480
+ 'type' => 'array',
481
+ 'item_type' => 'hash',
482
+ 'info' => 'The permissions of the widget.',
483
+ 'nodoc' => false
484
+ },
485
+ 'can_edit' => {
486
+ '_config' => {
487
+ 'type' => 'boolean',
488
+ 'info' => 'Can edit the widget.',
489
+ 'nodoc' => false
490
+ },
491
+ }
492
+ },
493
+ }.with_indifferent_access
494
+ }
495
+ end
267
496
 
268
- it 'returns the response structure' do
497
+ it 'returns the response structure' do
269
498
  subject.send(:format_custom_response!)
270
499
 
271
500
  expect(subject.output).to eq('200' => {
@@ -296,16 +525,158 @@ module Brainstem
296
525
  }
297
526
  }
298
527
  })
528
+ end
529
+ end
530
+ end
531
+
532
+ context 'when the response is multi nested array' do
533
+ before do
534
+ stub(endpoint).custom_response_configuration_tree { response_config_tree }
535
+ end
536
+
537
+ # response :array, nested_level: 2, item_type: [:hash, :string, :integer] do
538
+ # nested.field :a, :string
539
+ # nested.field :b, :integer
540
+ # end
541
+
542
+ # response :array, nested_level: 2, item_type: :hash do
543
+ # nested.field :a, :string
544
+ # nested.field :a, :integer
545
+ # end
546
+ #
547
+ # [
548
+ # [
549
+ # { widget_name: 'Widget A', can_edit: false },
550
+ # { widget_name: 'Widget B', can_edit: true },
551
+ # ]
552
+ # ]
553
+ context 'when the leaf array is an array of objects' do
554
+ let(:response_config_tree) do
555
+ {
556
+ '_config' => {
557
+ 'type' => 'array',
558
+ 'nested_levels' => 2,
559
+ 'item_type' => 'hash',
560
+ },
561
+ 'widget_name' => {
562
+ '_config' => {
563
+ 'type' => 'string',
564
+ 'info' => 'The name of the widget.',
565
+ 'nodoc' => false
566
+ },
567
+ },
568
+ 'widget_permissions' => {
569
+ '_config' => {
570
+ 'type' => 'array',
571
+ 'item_type' => 'hash',
572
+ 'info' => 'The permissions of the widget.',
573
+ 'nodoc' => false
574
+ },
575
+ 'can_edit' => {
576
+ '_config' => {
577
+ 'type' => 'array',
578
+ 'nested_levels' => 3,
579
+ 'item_type' => 'boolean',
580
+ 'nodoc' => false
581
+ },
582
+ }
583
+ },
584
+ }.with_indifferent_access
585
+ end
586
+
587
+ it 'returns the response structure' do
588
+ subject.send(:format_custom_response!)
589
+
590
+ expect(subject.output).to eq('200' => {
591
+ 'description' => 'A list of Widgets have been retrieved.',
592
+ 'schema' => {
593
+ 'type' => 'array',
594
+ 'items' => {
595
+ 'type' => 'array',
596
+ 'items' => {
597
+ 'type' => 'object',
598
+ 'properties' => {
599
+ 'widget_name' => {
600
+ 'type' => 'string',
601
+ 'description' => 'The name of the widget.'
602
+ },
603
+ 'widget_permissions' => {
604
+ 'type' => 'array',
605
+ 'description' => 'The permissions of the widget.',
606
+ 'items' => {
607
+ 'type' => 'object',
608
+ 'properties' => {
609
+ 'can_edit' => {
610
+ 'type' => 'array',
611
+ 'items' => {
612
+ 'type' => 'array',
613
+ 'items' => {
614
+ 'type' => 'array',
615
+ 'items' => {
616
+ 'type' => 'boolean'
617
+ }
618
+ }
619
+ }
620
+ }
621
+ }
622
+ }
623
+ }
624
+ }
625
+ }
626
+ }
627
+ }
628
+ })
629
+ end
630
+ end
631
+
632
+ # response :array, nested_level: 3, item_type: :string
633
+ #
634
+ # [
635
+ # [
636
+ # [1,3,4,5],
637
+ # [6,7,8,9],
638
+ # ]
639
+ # ]
640
+ context 'when the leaf array is an array of strings' do
641
+ let(:response_config_tree) do
642
+ {
643
+ '_config' => {
644
+ 'type' => 'array',
645
+ 'nested_levels' => 3,
646
+ 'item_type' => 'string',
647
+ },
648
+ }.with_indifferent_access
649
+ end
650
+
651
+ it 'returns the response structure' do
652
+ subject.send(:format_custom_response!)
653
+
654
+ expect(subject.output).to eq('200' => {
655
+ 'description' => 'A list of Widgets have been retrieved.',
656
+ 'schema' => {
657
+ 'type' => 'array',
658
+ 'items' => {
659
+ 'type' => 'array',
660
+ 'items' => {
661
+ 'type' => 'array',
662
+ 'items' => {
663
+ 'type' => 'string',
664
+ }
665
+ }
666
+ }
667
+ }
668
+ })
669
+ end
299
670
  end
300
671
  end
301
672
 
302
- context 'when the response is not a hash or array' do
673
+ context 'when the response is not a hash or an array' do
303
674
  before do
304
675
  stub(endpoint).custom_response_configuration_tree {
305
676
  {
306
677
  '_config' => {
307
- 'type' => 'boolean',
308
- 'info' => 'whether the widget exists',
678
+ 'type' => 'integer',
679
+ 'info' => 'Indicates the number of widgets',
309
680
  'nodoc' => false
310
681
  },
311
682
  }.with_indifferent_access
@@ -318,8 +689,9 @@ module Brainstem
318
689
  expect(subject.output).to eq('200' => {
319
690
  'description' => 'A list of Widgets have been retrieved.',
320
691
  'schema' => {
321
- 'type' => 'boolean',
322
- 'description' => 'Whether the widget exists.'
692
+ 'type' => 'integer',
693
+ 'format' => 'int32',
694
+ 'description' => 'Indicates the number of widgets.'
323
695
  }
324
696
  })
325
697
  end