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
@@ -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