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
@@ -119,6 +119,14 @@ module Brainstem
119
119
  end
120
120
  end
121
121
 
122
+ context "when --include-internal" do
123
+ let(:args) { %w(--include-internal) }
124
+
125
+ it "sets the internal option in the args for atlas" do
126
+ expect(subject.options[:builder][:args_for_atlas][:include_internal]).to eq true
127
+ end
128
+ end
129
+
122
130
  context "when --oas-filename-pattern" do
123
131
  let(:args) { %w(--oas-filename-pattern=blah/{{version}}.{{extension}}) }
124
132
 
@@ -13,7 +13,15 @@ module Brainstem
13
13
  end
14
14
 
15
15
  describe ".nodoc!" do
16
- it "sets the config nodoc to true" do
16
+ it "sets the config nodoc to passed in description" do
17
+ subject.brainstem_params do
18
+ nodoc! "Description for why these are nodoc"
19
+ end
20
+
21
+ expect(subject.configuration[:_default][:nodoc]).to eq "Description for why these are nodoc"
22
+ end
23
+
24
+ it "sets the config nodoc to default value (true)" do
17
25
  subject.brainstem_params do
18
26
  nodoc!
19
27
  end
@@ -22,6 +30,24 @@ module Brainstem
22
30
  end
23
31
  end
24
32
 
33
+ describe ".internal!" do
34
+ it "sets the config internal to passed in description" do
35
+ subject.brainstem_params do
36
+ internal! "Description for why these are internal docs"
37
+ end
38
+
39
+ expect(subject.configuration[:_default][:internal]).to eq "Description for why these are internal docs"
40
+ end
41
+
42
+ it "sets the config internal to default value (true)" do
43
+ subject.brainstem_params do
44
+ internal!
45
+ end
46
+
47
+ expect(subject.configuration[:_default][:internal]).to eq true
48
+ end
49
+ end
50
+
25
51
  describe ".brainstem_params" do
26
52
  it "evaluates the given block in the class context" do
27
53
  mock(subject).configuration
@@ -388,43 +414,6 @@ module Brainstem
388
414
  expect(template_key.call).to eq('template')
389
415
  expect(valid_params[template_key][:required]).to be_falsey
390
416
  end
391
-
392
- context "when one of the nested fields is required" do
393
- it "sets the required attribute for the parent configuration to true" do
394
- subject.brainstem_params do
395
- valid :template, :hash do |param|
396
- param.valid :id, :integer, required: true
397
- param.valid :title, :string
398
- end
399
-
400
- model_params :sprocket do |param|
401
- param.valid :details, :hash do |nested_param|
402
- nested_param.valid :data, :hash do |double_nested_param|
403
- double_nested_param.valid :raw_text, :string, required: true
404
- end
405
- end
406
- end
407
- end
408
-
409
- valid_params = subject.configuration[:_default][:valid_params]
410
-
411
- template_key = valid_params.keys[0]
412
- expect(template_key.call).to eq('template')
413
- expect(valid_params[template_key][:required]).to be_truthy
414
-
415
- sprocket_details_key = valid_params.keys[3]
416
- expect(sprocket_details_key.call).to eq('details')
417
- expect(valid_params[sprocket_details_key][:required]).to be_truthy
418
-
419
- sprocket_details_data_key = valid_params.keys[4]
420
- expect(sprocket_details_data_key.call).to eq('data')
421
- expect(valid_params[sprocket_details_data_key][:required]).to be_truthy
422
-
423
- sprocket_details_data_raw_text_key = valid_params.keys[5]
424
- expect(sprocket_details_data_raw_text_key.call).to eq('raw_text')
425
- expect(valid_params[sprocket_details_data_raw_text_key][:required]).to be_truthy
426
- end
427
- end
428
417
  end
429
418
 
430
419
  context "when root is nodoc" do
@@ -524,6 +513,171 @@ module Brainstem
524
513
  end
525
514
  end
526
515
  end
516
+
517
+ context "when the param has a dynamic key" do
518
+ let(:dynamic_keyword) { described_class::DYNAMIC_KEY.to_s }
519
+
520
+ it "sets the dynamic key property" do
521
+ subject.brainstem_params do
522
+ valid :id, :integer
523
+ valid :_dynamic_key, :string, required: true
524
+ valid :user_id, :integer, dynamic_key: true
525
+ end
526
+
527
+ valid_params = subject.configuration[:_default][:valid_params]
528
+ expect(valid_params.keys.length).to eq(3)
529
+
530
+ id_config_key = valid_params.keys[0]
531
+ expect(id_config_key.call).to eq('id')
532
+ id_config = valid_params[id_config_key]
533
+ expect(id_config[:required]).to be_falsey
534
+ expect(id_config[:type]).to eq("integer")
535
+
536
+ dynamic_string_key = valid_params.keys[1]
537
+ expect(dynamic_string_key.call).to eq(dynamic_keyword)
538
+ dynamic_string_config = valid_params[dynamic_string_key]
539
+ expect(dynamic_string_config[:type]).to eq("string")
540
+ expect(dynamic_string_config[:required]).to be_truthy
541
+ expect(dynamic_string_config[:dynamic_key]).to be_truthy
542
+
543
+ dynamic_user_id_key = valid_params.keys[2]
544
+ expect(dynamic_user_id_key.call).to eq('user_id')
545
+ dynamic_user_id_config = valid_params[dynamic_user_id_key]
546
+ expect(dynamic_user_id_config[:type]).to eq("integer")
547
+ expect(dynamic_user_id_config[:dynamic_key]).to be_truthy
548
+ end
549
+ end
550
+ end
551
+
552
+ describe ".valid_dynamic_param" do
553
+ let(:dynamic_keyword) { described_class::DYNAMIC_KEY.to_s }
554
+
555
+ it "sets the correct configuration" do
556
+ subject.brainstem_params do
557
+ valid_dynamic_param :integer,
558
+ info: "User ID is required",
559
+ required: true
560
+ end
561
+
562
+ valid_params = subject.configuration[:_default][:valid_params]
563
+ expect(valid_params.keys.length).to eq(1)
564
+ expect(valid_params.keys[0]).to be_a(Proc)
565
+ expect(valid_params.keys[0].call).to eq(dynamic_keyword)
566
+
567
+ sprocket_ids_config = valid_params[valid_params.keys[0]]
568
+ expect(sprocket_ids_config[:info]).to eq "User ID is required"
569
+ expect(sprocket_ids_config[:required]).to be_truthy
570
+ expect(sprocket_ids_config[:type]).to eq("integer")
571
+ expect(sprocket_ids_config[:dynamic_key]).to be_truthy
572
+ end
573
+
574
+ context "when type is hash" do
575
+ it "adds the nested fields to valid params" do
576
+ subject.brainstem_params do
577
+ valid :id, :integer
578
+
579
+ valid :info, :hash, required: true do |param|
580
+ param.valid_dynamic_param :string, required: true
581
+
582
+ param.valid :data, :hash do |double_nested_param|
583
+ double_nested_param.valid_dynamic_param :string
584
+ end
585
+ end
586
+ end
587
+
588
+ valid_params = subject.configuration[:_default][:valid_params]
589
+ param_keys = valid_params.keys
590
+ expect(param_keys.length).to eq(5)
591
+
592
+ expect(param_keys[0].call).to eq('id')
593
+ id_config = valid_params[param_keys[0]]
594
+ expect(id_config[:root]).to be_nil
595
+ expect(id_config[:ancestors]).to be_nil
596
+
597
+ expect(param_keys[1].call).to eq('info')
598
+ info_key = param_keys[1]
599
+ info_config = valid_params[info_key]
600
+ expect(info_config[:root]).to be_nil
601
+ expect(info_config[:ancestors]).to be_nil
602
+
603
+ expect(param_keys[2].call).to eq(dynamic_keyword)
604
+ dynamic_info_nested_config = valid_params[param_keys[2]]
605
+ expect(dynamic_info_nested_config[:root]).to be_nil
606
+ expect(dynamic_info_nested_config[:ancestors]).to eq([info_key])
607
+ expect(dynamic_info_nested_config[:dynamic_key]).to be_truthy
608
+ expect(dynamic_info_nested_config[:required]).to be_truthy
609
+
610
+ expect(param_keys[3].call).to eq('data')
611
+ details_data_key = param_keys[3]
612
+ details_data_config = valid_params[details_data_key]
613
+ expect(details_data_config[:root]).to be_nil
614
+ expect(details_data_config[:ancestors]).to eq([info_key])
615
+
616
+ expect(param_keys[4].call).to eq(dynamic_keyword)
617
+ dynamic_data_nested_config = valid_params[param_keys[4]]
618
+ expect(dynamic_data_nested_config[:root]).to be_nil
619
+ expect(dynamic_data_nested_config[:ancestors]).to eq([info_key, details_data_key])
620
+ end
621
+ end
622
+
623
+ context "when type is array" do
624
+ it "sets the type and sub type appropriately" do
625
+ subject.brainstem_params do
626
+ valid :sprocket_ids, :array,
627
+ required: true,
628
+ item_type: :string
629
+ end
630
+
631
+ valid_params = subject.configuration[:_default][:valid_params]
632
+
633
+ sprocket_ids_key = valid_params.keys[0]
634
+ expect(sprocket_ids_key.call).to eq('sprocket_ids')
635
+
636
+ sprocket_ids_config = valid_params[sprocket_ids_key]
637
+ expect(sprocket_ids_config[:required]).to be_truthy
638
+ expect(sprocket_ids_config[:type]).to eq('array')
639
+ expect(sprocket_ids_config[:item_type]).to eq('string')
640
+ end
641
+
642
+ context "when a block is given" do
643
+ it "sets the type and sub type appropriately" do
644
+ subject.brainstem_params do
645
+ valid :sprocket_tasks, :array, required: true, item_type: 'hash' do |param|
646
+ param.valid :task_id, :integer, required: true
647
+ param.valid :task_title, :string
648
+ end
649
+ end
650
+
651
+ valid_params = subject.configuration[:_default][:valid_params]
652
+
653
+ sprocket_tasks_key = valid_params.keys[0]
654
+ expect(sprocket_tasks_key.call).to eq('sprocket_tasks')
655
+
656
+ sprocket_tasks_config = valid_params[sprocket_tasks_key]
657
+ expect(sprocket_tasks_config[:required]).to be_truthy
658
+ expect(sprocket_tasks_config[:type]).to eq('array')
659
+ expect(sprocket_tasks_config[:item_type]).to eq('hash')
660
+
661
+ task_id_key = valid_params.keys[1]
662
+ expect(task_id_key.call).to eq('task_id')
663
+
664
+ task_id_config = valid_params[task_id_key]
665
+ expect(task_id_config[:required]).to be_truthy
666
+ expect(task_id_config[:type]).to eq('integer')
667
+ expect(task_id_config[:root]).to be_nil
668
+ expect(task_id_config[:ancestors]).to eq([sprocket_tasks_key])
669
+
670
+ task_title_key = valid_params.keys[2]
671
+ expect(task_title_key.call).to eq('task_title')
672
+
673
+ task_title_config = valid_params[task_title_key]
674
+ expect(task_title_config[:required]).to be_falsey
675
+ expect(task_title_config[:type]).to eq('string')
676
+ expect(task_title_config[:root]).to be_nil
677
+ expect(task_title_config[:ancestors]).to eq([sprocket_tasks_key])
678
+ end
679
+ end
680
+ end
527
681
  end
528
682
 
529
683
  describe ".transform" do
@@ -721,6 +875,114 @@ module Brainstem
721
875
  required: false,
722
876
  }.with_indifferent_access)
723
877
  end
878
+
879
+ context "when key is dynamic" do
880
+ it "sets the custom_response configuration" do
881
+ subject.brainstem_params do
882
+ actions :show do
883
+ response :hash do |response_param|
884
+ response_param.fields :mk, :hash do |dk|
885
+ dk.dynamic_key_field :string,
886
+ info: "I am a dynamic key field I"
887
+ dk.field :mk_blah, :string,
888
+ required: true
889
+ end
890
+
891
+ response_param.fields :mk2, :hash do |dk|
892
+ dk.field :_dynamic_key, :string,
893
+ required: true,
894
+ info: "dynamic key field II"
895
+ end
896
+
897
+ response_param.dynamic_key_fields :hash do |dk|
898
+ dk.field :user_id, :string,
899
+ dynamic_key: true,
900
+ info: "dynamic key field with dynamic_key attribute"
901
+ end
902
+ end
903
+ end
904
+ end
905
+
906
+ configuration = subject.configuration[:show][:custom_response]
907
+ expect(configuration).to be_present
908
+ expect(configuration[:_config]).to eq({
909
+ type: 'hash',
910
+ nodoc: false,
911
+ required: false,
912
+ }.with_indifferent_access)
913
+
914
+ param_keys = configuration.keys
915
+ expect(param_keys.length).to eq(8)
916
+
917
+ mk_key = param_keys[1]
918
+ expect(mk_key.call).to eq('mk')
919
+ my_key_config = configuration.to_h[mk_key]
920
+ expect(my_key_config).to eq({
921
+ nodoc: false,
922
+ type: 'hash',
923
+ required: false,
924
+ }.with_indifferent_access)
925
+
926
+ mk_dynamic_key = param_keys[2]
927
+ mk_dynamic_config = configuration.to_h[mk_dynamic_key]
928
+ expect(mk_dynamic_config).to eq({
929
+ nodoc: false,
930
+ dynamic_key: true,
931
+ type: 'string',
932
+ ancestors: [mk_key],
933
+ required: false,
934
+ info: "I am a dynamic key field I"
935
+ }.with_indifferent_access)
936
+
937
+ mk_blah_key = param_keys[3]
938
+ mk_blah_config = configuration.to_h[mk_blah_key]
939
+ expect(mk_blah_config).to eq({
940
+ nodoc: false,
941
+ type: 'string',
942
+ ancestors: [mk_key],
943
+ required: true,
944
+ }.with_indifferent_access)
945
+
946
+ mk2_key = param_keys[4]
947
+ mk2_config = configuration.to_h[mk2_key]
948
+ expect(mk2_config).to eq({
949
+ nodoc: false,
950
+ type: 'hash',
951
+ required: false,
952
+ }.with_indifferent_access)
953
+
954
+ mk2_dynamic_key = param_keys[5]
955
+ mk2_dynamic_config = configuration.to_h[mk2_dynamic_key]
956
+ expect(mk2_dynamic_config).to eq({
957
+ nodoc: false,
958
+ dynamic_key: true,
959
+ type: 'string',
960
+ ancestors: [mk2_key],
961
+ required: true,
962
+ info: "dynamic key field II",
963
+ }.with_indifferent_access)
964
+
965
+ dynamic_key = param_keys[6]
966
+ dynamic_config = configuration.to_h[dynamic_key]
967
+ expect(dynamic_config).to eq({
968
+ nodoc: false,
969
+ dynamic_key: true,
970
+ type: 'hash',
971
+ required: false,
972
+ }.with_indifferent_access)
973
+
974
+ dynamic_child_key = param_keys[7]
975
+ dynamic_child_config = configuration.to_h[dynamic_child_key]
976
+ expect(dynamic_child_config).to eq({
977
+ nodoc: false,
978
+ dynamic_key: true,
979
+ ancestors: [dynamic_key],
980
+ type: 'string',
981
+ required: false,
982
+ info: "dynamic key field with dynamic_key attribute",
983
+ }.with_indifferent_access)
984
+ end
985
+ end
724
986
  end
725
987
 
726
988
  context "when block not given" do
@@ -740,6 +1002,45 @@ module Brainstem
740
1002
  required: false,
741
1003
  }.with_indifferent_access)
742
1004
  end
1005
+
1006
+ context "when response is a nested array of strings" do
1007
+ it "sets the custom_response configuration" do
1008
+ subject.brainstem_params do
1009
+ actions :show do
1010
+ response :array, nested_levels: 2, item_type: :string
1011
+ end
1012
+ end
1013
+
1014
+ configuration = subject.configuration[:show][:custom_response]
1015
+ expect(configuration).to be_present
1016
+ expect(configuration[:_config]).to eq({
1017
+ type: 'array',
1018
+ item_type: 'string',
1019
+ nested_levels: 2,
1020
+ nodoc: false,
1021
+ required: false,
1022
+ }.with_indifferent_access)
1023
+ end
1024
+
1025
+ context "when the nested level is less than 2" do
1026
+ it "does not return a config with a `nested_levels` key" do
1027
+ subject.brainstem_params do
1028
+ actions :show do
1029
+ response :array, nested_levels: 1, item_type: :string
1030
+ end
1031
+ end
1032
+
1033
+ configuration = subject.configuration[:show][:custom_response]
1034
+ expect(configuration).to be_present
1035
+ expect(configuration[:_config]).to eq({
1036
+ type: 'array',
1037
+ item_type: 'string',
1038
+ nodoc: false,
1039
+ required: false,
1040
+ }.with_indifferent_access)
1041
+ end
1042
+ end
1043
+ end
743
1044
  end
744
1045
  end
745
1046
 
@@ -796,8 +1097,13 @@ module Brainstem
796
1097
  subject.brainstem_params do
797
1098
  actions :show do
798
1099
  response :hash do
799
- fields :contacts, :array do
800
- field :full_name, :string
1100
+ fields :contacts, :array do |contact|
1101
+ contact.field :full_name, :string
1102
+ contact.field :friends, :array, nested_levels: 3, item_type: :string
1103
+ contact.field :enemies, :array, nested_levels: 1, item_type: :string
1104
+ contact.fields :frenemies, :array, nested_levels: 2 do |frenemy|
1105
+ frenemy.field :jim, :string
1106
+ end
801
1107
  end
802
1108
  end
803
1109
  end
@@ -806,20 +1112,63 @@ module Brainstem
806
1112
  configuration = subject.configuration[:show][:custom_response]
807
1113
  param_keys = configuration.keys
808
1114
 
809
- expect(param_keys[1].call).to eq('contacts')
810
- expect(configuration[param_keys[1]]).to eq({
1115
+ contacts_proc = param_keys[1]
1116
+ expect(contacts_proc.call).to eq('contacts')
1117
+ expect(configuration[contacts_proc]).to eq({
811
1118
  type: 'array',
812
1119
  item_type: 'hash',
813
1120
  nodoc: false,
814
1121
  required: false,
815
1122
  }.with_indifferent_access)
816
1123
 
817
- expect(param_keys[2].call).to eq('full_name')
818
- expect(configuration[param_keys[2]]).to eq({
1124
+ full_name_proc = param_keys[2]
1125
+ expect(full_name_proc.call).to eq('full_name')
1126
+ expect(configuration[full_name_proc]).to eq({
819
1127
  type: 'string',
820
1128
  nodoc: false,
821
1129
  required: false,
822
- ancestors: [param_keys[1]]
1130
+ ancestors: [contacts_proc]
1131
+ }.with_indifferent_access)
1132
+
1133
+ friends_proc = param_keys[3]
1134
+ expect(friends_proc.call).to eq('friends')
1135
+ expect(configuration[friends_proc]).to eq({
1136
+ type: 'array',
1137
+ item_type: 'string',
1138
+ nested_levels: 3,
1139
+ nodoc: false,
1140
+ required: false,
1141
+ ancestors: [contacts_proc]
1142
+ }.with_indifferent_access)
1143
+
1144
+ enemies_proc = param_keys[4]
1145
+ expect(enemies_proc.call).to eq('enemies')
1146
+ expect(configuration[enemies_proc]).to eq({
1147
+ type: 'array',
1148
+ item_type: 'string',
1149
+ nodoc: false,
1150
+ required: false,
1151
+ ancestors: [contacts_proc]
1152
+ }.with_indifferent_access)
1153
+
1154
+ frenemies_proc = param_keys[5]
1155
+ expect(frenemies_proc.call).to eq('frenemies')
1156
+ expect(configuration[frenemies_proc]).to eq({
1157
+ type: 'array',
1158
+ nested_levels: 2,
1159
+ item_type: 'hash',
1160
+ nodoc: false,
1161
+ required: false,
1162
+ ancestors: [contacts_proc]
1163
+ }.with_indifferent_access)
1164
+
1165
+ jim_proc = param_keys[6]
1166
+ expect(jim_proc.call).to eq('jim')
1167
+ expect(configuration[jim_proc]).to eq({
1168
+ type: 'string',
1169
+ nodoc: false,
1170
+ required: false,
1171
+ ancestors: [contacts_proc, frenemies_proc]
823
1172
  }.with_indifferent_access)
824
1173
  end
825
1174
  end
@@ -868,6 +1217,119 @@ module Brainstem
868
1217
  end
869
1218
  end
870
1219
 
1220
+ describe ".dynamic_key_fields" do
1221
+ context "when used outside of the response block" do
1222
+ it "raises an error" do
1223
+ expect {
1224
+ subject.brainstem_params do
1225
+ actions :show do
1226
+ dynamic_key_fields :array do
1227
+ field :full_name, :string
1228
+ end
1229
+ end
1230
+ end
1231
+ }.to raise_error(StandardError)
1232
+ end
1233
+ end
1234
+
1235
+ context "when used within the response block" do
1236
+ let(:dynamic_keyword) { described_class::DYNAMIC_KEY.to_s }
1237
+
1238
+ context "when type is hash" do
1239
+ it "adds the field block to custom_response configuration" do
1240
+ subject.brainstem_params do
1241
+ actions :show do
1242
+ response :hash do
1243
+ dynamic_key_fields :hash do
1244
+ field :full_name, :string
1245
+ end
1246
+ end
1247
+ end
1248
+ end
1249
+
1250
+ configuration = subject.configuration[:show][:custom_response]
1251
+ param_keys = configuration.keys
1252
+
1253
+ expect(param_keys[1].call).to eq(dynamic_keyword)
1254
+ expect(configuration[param_keys[1]]).to eq({
1255
+ type: 'hash',
1256
+ nodoc: false,
1257
+ required: false,
1258
+ dynamic_key: true,
1259
+ }.with_indifferent_access)
1260
+
1261
+ expect(param_keys[2].call).to eq('full_name')
1262
+ expect(configuration[param_keys[2]]).to eq({
1263
+ type: 'string',
1264
+ nodoc: false,
1265
+ ancestors: [param_keys[1]],
1266
+ required: false,
1267
+ }.with_indifferent_access)
1268
+ end
1269
+ end
1270
+
1271
+ context "when type is array" do
1272
+ it "adds the field block to custom_response configuration" do
1273
+ subject.brainstem_params do
1274
+ actions :show do
1275
+ response :hash do
1276
+ dynamic_key_fields :array do |contact|
1277
+ contact.field :full_name, :string
1278
+ contact.dynamic_key_fields :array, nested_levels: 2 do |frenemy|
1279
+ frenemy.field :jim, :string
1280
+ end
1281
+ end
1282
+ end
1283
+ end
1284
+ end
1285
+
1286
+ configuration = subject.configuration[:show][:custom_response]
1287
+ param_keys = configuration.keys
1288
+
1289
+ dynamic_parent_proc = param_keys[1]
1290
+ expect(dynamic_parent_proc.call).to eq(dynamic_keyword)
1291
+ expect(configuration[dynamic_parent_proc]).to eq({
1292
+ type: 'array',
1293
+ item_type: 'hash',
1294
+ nodoc: false,
1295
+ required: false,
1296
+ dynamic_key: true,
1297
+ }.with_indifferent_access)
1298
+
1299
+ full_name_proc = param_keys[2]
1300
+ expect(full_name_proc.call).to eq('full_name')
1301
+ expect(configuration[full_name_proc]).to eq({
1302
+ type: 'string',
1303
+ nodoc: false,
1304
+ required: false,
1305
+ ancestors: [dynamic_parent_proc]
1306
+ }.with_indifferent_access)
1307
+
1308
+ dynamic_nested_proc = param_keys[3]
1309
+ expect(dynamic_nested_proc.call).to eq(dynamic_keyword)
1310
+ expect(configuration[dynamic_nested_proc]).to eq({
1311
+ type: 'array',
1312
+ nested_levels: 2,
1313
+ item_type: 'hash',
1314
+ nodoc: false,
1315
+ required: false,
1316
+ ancestors: [dynamic_parent_proc],
1317
+ dynamic_key: true,
1318
+ }.with_indifferent_access)
1319
+
1320
+ jim_proc = param_keys[4]
1321
+ expect(jim_proc.call).to eq('jim')
1322
+ expect(configuration[jim_proc]).to eq({
1323
+ type: 'string',
1324
+ nodoc: false,
1325
+ required: false,
1326
+ ancestors: [dynamic_parent_proc, dynamic_nested_proc]
1327
+ }.with_indifferent_access)
1328
+ end
1329
+ end
1330
+ end
1331
+ end
1332
+
871
1333
  describe ".field" do
872
1334
  context "when used outside of the response block" do
873
1335
  it "raises an error" do
@@ -961,6 +1423,105 @@ module Brainstem
961
1423
  end
962
1424
  end
963
1425
 
1426
+ describe ".dynamic_key_field" do
1427
+ let(:dynamic_keyword) { described_class::DYNAMIC_KEY.to_s }
1428
+
1429
+ context "when used outside of the response block" do
1430
+ it "raises an error" do
1431
+ expect {
1432
+ subject.brainstem_params do
1433
+ actions :show do
1434
+ dynamic_key_field :string
1435
+ end
1436
+ end
1437
+ }.to raise_error(StandardError)
1438
+ end
1439
+ end
1440
+
1441
+ context "when used within the response block" do
1442
+ context "when type is array" do
1443
+ it "adds the field block to custom_response configuration" do
1444
+ subject.brainstem_params do
1445
+ actions :show do
1446
+ response :hash do
1447
+ dynamic_key_field :array
1448
+ end
1449
+ end
1450
+ end
1451
+
1452
+ configuration = subject.configuration[:show][:custom_response]
1453
+ param_keys = configuration.keys
1454
+
1455
+ expect(param_keys[1].call).to eq(dynamic_keyword)
1456
+ expect(configuration[param_keys[1]]).to eq({
1457
+ type: 'array',
1458
+ item_type: 'string',
1459
+ nodoc: false,
1460
+ required: false,
1461
+ dynamic_key: true
1462
+ }.with_indifferent_access)
1463
+ end
1464
+ end
1465
+
1466
+ context "when type is not array" do
1467
+ it "adds the field block to custom_response configuration" do
1468
+ subject.brainstem_params do
1469
+ actions :show do
1470
+ response :hash do
1471
+ dynamic_key_field :string
1472
+ end
1473
+ end
1474
+ end
1475
+
1476
+ configuration = subject.configuration[:show][:custom_response]
1477
+ param_keys = configuration.keys
1478
+
1479
+ expect(param_keys[1].call).to eq(dynamic_keyword)
1480
+ expect(configuration[param_keys[1]]).to eq({
1481
+ type: 'string',
1482
+ nodoc: false,
1483
+ required: false,
1484
+ dynamic_key: true,
1485
+ }.with_indifferent_access)
1486
+ end
1487
+ end
1488
+
1489
+ context "when nested under parent field" do
1490
+ it "inherits the nodoc attribute" do
1491
+ subject.brainstem_params do
1492
+ actions :show do
1493
+ response :hash do
1494
+ dynamic_key_fields :hash, nodoc: true do
1495
+ dynamic_key_field :string
1496
+ end
1497
+ end
1498
+ end
1499
+ end
1500
+
1501
+ configuration = subject.configuration[:show][:custom_response]
1502
+ param_keys = configuration.keys
1503
+
1504
+ expect(param_keys[1].call).to eq(dynamic_keyword)
1505
+ expect(configuration[param_keys[1]]).to eq({
1506
+ type: 'hash',
1507
+ nodoc: true,
1508
+ required: false,
1509
+ dynamic_key: true,
1510
+ }.with_indifferent_access)
1511
+
1512
+ expect(param_keys[2].call).to eq(dynamic_keyword)
1513
+ expect(configuration[param_keys[2]]).to eq({
1514
+ type: 'string',
1515
+ nodoc: true,
1516
+ required: false,
1517
+ dynamic_key: true,
1518
+ ancestors: [param_keys[1]]
1519
+ }.with_indifferent_access)
1520
+ end
1521
+ end
1522
+ end
1523
+ end
1524
+
964
1525
  describe ".operation_id" do
965
1526
  it "sets the operation_id for the context" do
966
1527
  subject.brainstem_params do