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