grape 1.3.1 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +82 -1
  3. data/LICENSE +1 -1
  4. data/README.md +104 -21
  5. data/UPGRADING.md +265 -39
  6. data/lib/grape.rb +2 -2
  7. data/lib/grape/api.rb +2 -2
  8. data/lib/grape/api/instance.rb +29 -28
  9. data/lib/grape/dsl/helpers.rb +1 -0
  10. data/lib/grape/dsl/inside_route.rb +69 -36
  11. data/lib/grape/dsl/parameters.rb +7 -3
  12. data/lib/grape/dsl/routing.rb +2 -4
  13. data/lib/grape/dsl/validations.rb +18 -1
  14. data/lib/grape/eager_load.rb +1 -1
  15. data/lib/grape/endpoint.rb +8 -6
  16. data/lib/grape/http/headers.rb +1 -0
  17. data/lib/grape/middleware/base.rb +2 -1
  18. data/lib/grape/middleware/error.rb +10 -12
  19. data/lib/grape/middleware/formatter.rb +3 -3
  20. data/lib/grape/middleware/stack.rb +21 -8
  21. data/lib/grape/middleware/versioner/header.rb +1 -1
  22. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
  23. data/lib/grape/path.rb +2 -2
  24. data/lib/grape/request.rb +1 -1
  25. data/lib/grape/router.rb +30 -43
  26. data/lib/grape/router/attribute_translator.rb +26 -5
  27. data/lib/grape/router/route.rb +3 -22
  28. data/lib/grape/{serve_file → serve_stream}/file_body.rb +1 -1
  29. data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +1 -1
  30. data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +8 -8
  31. data/lib/grape/util/base_inheritable.rb +11 -8
  32. data/lib/grape/util/lazy_value.rb +1 -0
  33. data/lib/grape/util/reverse_stackable_values.rb +3 -1
  34. data/lib/grape/util/stackable_values.rb +3 -1
  35. data/lib/grape/validations/attributes_iterator.rb +8 -0
  36. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  37. data/lib/grape/validations/params_scope.rb +8 -6
  38. data/lib/grape/validations/single_attribute_iterator.rb +1 -1
  39. data/lib/grape/validations/types.rb +6 -5
  40. data/lib/grape/validations/types/array_coercer.rb +14 -5
  41. data/lib/grape/validations/types/build_coercer.rb +5 -8
  42. data/lib/grape/validations/types/custom_type_coercer.rb +14 -2
  43. data/lib/grape/validations/types/dry_type_coercer.rb +36 -1
  44. data/lib/grape/validations/types/file.rb +15 -13
  45. data/lib/grape/validations/types/json.rb +40 -36
  46. data/lib/grape/validations/types/primitive_coercer.rb +11 -5
  47. data/lib/grape/validations/types/set_coercer.rb +6 -4
  48. data/lib/grape/validations/types/variant_collection_coercer.rb +1 -1
  49. data/lib/grape/validations/validator_factory.rb +1 -1
  50. data/lib/grape/validations/validators/as.rb +1 -1
  51. data/lib/grape/validations/validators/base.rb +4 -5
  52. data/lib/grape/validations/validators/coerce.rb +3 -10
  53. data/lib/grape/validations/validators/default.rb +3 -5
  54. data/lib/grape/validations/validators/except_values.rb +1 -1
  55. data/lib/grape/validations/validators/multiple_params_base.rb +2 -1
  56. data/lib/grape/validations/validators/regexp.rb +1 -1
  57. data/lib/grape/validations/validators/values.rb +1 -1
  58. data/lib/grape/version.rb +1 -1
  59. data/spec/grape/api/instance_spec.rb +50 -0
  60. data/spec/grape/api_spec.rb +75 -0
  61. data/spec/grape/dsl/inside_route_spec.rb +182 -33
  62. data/spec/grape/endpoint/declared_spec.rb +601 -0
  63. data/spec/grape/endpoint_spec.rb +0 -521
  64. data/spec/grape/entity_spec.rb +6 -0
  65. data/spec/grape/integration/rack_sendfile_spec.rb +12 -8
  66. data/spec/grape/middleware/auth/strategies_spec.rb +1 -1
  67. data/spec/grape/middleware/error_spec.rb +1 -1
  68. data/spec/grape/middleware/formatter_spec.rb +1 -1
  69. data/spec/grape/middleware/stack_spec.rb +3 -1
  70. data/spec/grape/path_spec.rb +4 -4
  71. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +13 -3
  72. data/spec/grape/validations/params_scope_spec.rb +26 -0
  73. data/spec/grape/validations/single_attribute_iterator_spec.rb +17 -6
  74. data/spec/grape/validations/types/array_coercer_spec.rb +35 -0
  75. data/spec/grape/validations/types/primitive_coercer_spec.rb +65 -5
  76. data/spec/grape/validations/types/set_coercer_spec.rb +34 -0
  77. data/spec/grape/validations/types_spec.rb +1 -1
  78. data/spec/grape/validations/validators/coerce_spec.rb +317 -29
  79. data/spec/grape/validations/validators/default_spec.rb +170 -0
  80. data/spec/grape/validations/validators/except_values_spec.rb +1 -0
  81. data/spec/grape/validations/validators/values_spec.rb +1 -1
  82. data/spec/grape/validations_spec.rb +290 -18
  83. data/spec/integration/eager_load/eager_load_spec.rb +15 -0
  84. data/spec/spec_helper.rb +0 -10
  85. data/spec/support/chunks.rb +14 -0
  86. data/spec/support/versioned_helpers.rb +3 -5
  87. metadata +18 -8
@@ -8,6 +8,7 @@ describe Grape::Validations::ExceptValuesValidator do
8
8
  DEFAULT_EXCEPTS = ['invalid-type1', 'invalid-type2', 'invalid-type3'].freeze
9
9
  class << self
10
10
  attr_accessor :excepts
11
+
11
12
  def excepts
12
13
  @excepts ||= []
13
14
  [DEFAULT_EXCEPTS + @excepts].flatten.uniq
@@ -319,7 +319,7 @@ describe Grape::Validations::ValuesValidator do
319
319
  expect(last_response.status).to eq 200
320
320
  end
321
321
 
322
- it 'allows for an optional param with a list of values' do
322
+ it 'accepts for an optional param with a list of values' do
323
323
  put('/optional_with_array_of_string_values', optional: nil)
324
324
  expect(last_response.status).to eq 200
325
325
  end
@@ -9,6 +9,10 @@ describe Grape::Validations do
9
9
  subject
10
10
  end
11
11
 
12
+ def declared_params
13
+ subject.namespace_stackable(:declared_params).flatten
14
+ end
15
+
12
16
  describe 'params' do
13
17
  context 'optional' do
14
18
  before do
@@ -41,7 +45,7 @@ describe Grape::Validations do
41
45
  subject.params do
42
46
  optional :some_param
43
47
  end
44
- expect(subject.route_setting(:declared_params)).to eq([:some_param])
48
+ expect(declared_params).to eq([:some_param])
45
49
  end
46
50
  end
47
51
 
@@ -61,7 +65,7 @@ describe Grape::Validations do
61
65
 
62
66
  it 'adds entity documentation to declared params' do
63
67
  define_optional_using
64
- expect(subject.route_setting(:declared_params)).to eq(%i[field_a field_b])
68
+ expect(declared_params).to eq(%i[field_a field_b])
65
69
  end
66
70
 
67
71
  it 'works when field_a and field_b are not present' do
@@ -108,7 +112,7 @@ describe Grape::Validations do
108
112
  subject.params do
109
113
  requires :some_param
110
114
  end
111
- expect(subject.route_setting(:declared_params)).to eq([:some_param])
115
+ expect(declared_params).to eq([:some_param])
112
116
  end
113
117
 
114
118
  it 'works when required field is present but nil' do
@@ -193,7 +197,7 @@ describe Grape::Validations do
193
197
 
194
198
  it 'adds entity documentation to declared params' do
195
199
  define_requires_all
196
- expect(subject.route_setting(:declared_params)).to eq(%i[required_field optional_field])
200
+ expect(declared_params).to eq(%i[required_field optional_field])
197
201
  end
198
202
 
199
203
  it 'errors when required_field is not present' do
@@ -228,7 +232,7 @@ describe Grape::Validations do
228
232
 
229
233
  it 'adds entity documentation to declared params' do
230
234
  define_requires_none
231
- expect(subject.route_setting(:declared_params)).to eq(%i[required_field optional_field])
235
+ expect(declared_params).to eq(%i[required_field optional_field])
232
236
  end
233
237
 
234
238
  it 'errors when required_field is not present' do
@@ -258,7 +262,7 @@ describe Grape::Validations do
258
262
 
259
263
  it 'adds only the entity documentation to declared params, nothing more' do
260
264
  define_requires_all
261
- expect(subject.route_setting(:declared_params)).to eq(%i[required_field optional_field])
265
+ expect(declared_params).to eq(%i[required_field optional_field])
262
266
  end
263
267
  end
264
268
 
@@ -324,7 +328,7 @@ describe Grape::Validations do
324
328
  requires :key
325
329
  end
326
330
  end
327
- expect(subject.route_setting(:declared_params)).to eq([items: [:key]])
331
+ expect(declared_params).to eq([items: [:key]])
328
332
  end
329
333
  end
330
334
 
@@ -396,7 +400,7 @@ describe Grape::Validations do
396
400
  requires :key
397
401
  end
398
402
  end
399
- expect(subject.route_setting(:declared_params)).to eq([items: [:key]])
403
+ expect(declared_params).to eq([items: [:key]])
400
404
  end
401
405
  end
402
406
 
@@ -459,7 +463,7 @@ describe Grape::Validations do
459
463
  requires :key
460
464
  end
461
465
  end
462
- expect(subject.route_setting(:declared_params)).to eq([items: [:key]])
466
+ expect(declared_params).to eq([items: [:key]])
463
467
  end
464
468
  end
465
469
 
@@ -574,7 +578,7 @@ describe Grape::Validations do
574
578
  # NOTE: with body parameters in json or XML or similar this
575
579
  # should actually fail with: children[parents][name] is missing.
576
580
  expect(last_response.status).to eq(400)
577
- expect(last_response.body).to eq('children[1][parents] is missing')
581
+ expect(last_response.body).to eq('children[1][parents] is missing, children[0][parents][1][name] is missing, children[0][parents][1][name] is empty')
578
582
  end
579
583
 
580
584
  it 'errors when a parameter is not present in array within array' do
@@ -615,7 +619,7 @@ describe Grape::Validations do
615
619
 
616
620
  get '/within_array', children: [name: 'Jay']
617
621
  expect(last_response.status).to eq(400)
618
- expect(last_response.body).to eq('children[0][parents] is missing')
622
+ expect(last_response.body).to eq('children[0][parents] is missing, children[0][parents][0][name] is missing, children[0][parents][0][name] is empty')
619
623
  end
620
624
 
621
625
  it 'errors when param is not an Array' do
@@ -763,7 +767,7 @@ describe Grape::Validations do
763
767
  expect(last_response.status).to eq(200)
764
768
  put_with_json '/within_array', children: [name: 'Jay']
765
769
  expect(last_response.status).to eq(400)
766
- expect(last_response.body).to eq('children[0][parents] is missing')
770
+ expect(last_response.body).to eq('children[0][parents] is missing, children[0][parents][0][name] is missing')
767
771
  end
768
772
  end
769
773
 
@@ -813,7 +817,7 @@ describe Grape::Validations do
813
817
  requires :key
814
818
  end
815
819
  end
816
- expect(subject.route_setting(:declared_params)).to eq([items: [:key]])
820
+ expect(declared_params).to eq([items: [:key]])
817
821
  end
818
822
  end
819
823
 
@@ -838,7 +842,7 @@ describe Grape::Validations do
838
842
  it 'does internal validations if the outer group is present' do
839
843
  get '/nested_optional_group', items: [{ key: 'foo' }]
840
844
  expect(last_response.status).to eq(400)
841
- expect(last_response.body).to eq('items[0][required_subitems] is missing')
845
+ expect(last_response.body).to eq('items[0][required_subitems] is missing, items[0][required_subitems][0][value] is missing')
842
846
 
843
847
  get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }] }]
844
848
  expect(last_response.status).to eq(200)
@@ -858,7 +862,7 @@ describe Grape::Validations do
858
862
  it 'handles validation within arrays' do
859
863
  get '/nested_optional_group', items: [{ key: 'foo' }]
860
864
  expect(last_response.status).to eq(400)
861
- expect(last_response.body).to eq('items[0][required_subitems] is missing')
865
+ expect(last_response.body).to eq('items[0][required_subitems] is missing, items[0][required_subitems][0][value] is missing')
862
866
 
863
867
  get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }] }]
864
868
  expect(last_response.status).to eq(200)
@@ -877,7 +881,275 @@ describe Grape::Validations do
877
881
  requires(:required_subitems, type: Array) { requires :value }
878
882
  end
879
883
  end
880
- expect(subject.route_setting(:declared_params)).to eq([items: [:key, { optional_subitems: [:value] }, { required_subitems: [:value] }]])
884
+ expect(declared_params).to eq([items: [:key, { optional_subitems: [:value] }, { required_subitems: [:value] }]])
885
+ end
886
+
887
+ context <<~DESC do
888
+ Issue occurs whenever:
889
+ * param structure with at least three levels
890
+ * 1st level item is a required Array that has >1 entry with an optional item present and >1 entry with an optional item missing
891
+ * 2nd level is an optional Array or Hash
892
+ * 3rd level is a required item (can be any type)
893
+ * additional levels do not effect the issue from occuring
894
+ DESC
895
+
896
+ it "example based off actual real world use case" do
897
+ subject.params do
898
+ requires :orders, type: Array do
899
+ requires :id, type: Integer
900
+ optional :drugs, type: Array do
901
+ requires :batches, type: Array do
902
+ requires :batch_no, type: String
903
+ end
904
+ end
905
+ end
906
+ end
907
+
908
+ subject.get '/validate_required_arrays_under_optional_arrays' do
909
+ 'validate_required_arrays_under_optional_arrays works!'
910
+ end
911
+
912
+ data = {
913
+ orders: [
914
+ { id: 77, drugs: [{batches: [{batch_no: "A1234567"}]}]},
915
+ { id: 70 }
916
+ ]
917
+ }
918
+
919
+ get '/validate_required_arrays_under_optional_arrays', data
920
+ expect(last_response.body).to eq("validate_required_arrays_under_optional_arrays works!")
921
+ expect(last_response.status).to eq(200)
922
+ end
923
+
924
+ it "simplest example using Arry -> Array -> Hash -> String" do
925
+ subject.params do
926
+ requires :orders, type: Array do
927
+ requires :id, type: Integer
928
+ optional :drugs, type: Array do
929
+ requires :batch_no, type: String
930
+ end
931
+ end
932
+ end
933
+
934
+ subject.get '/validate_required_arrays_under_optional_arrays' do
935
+ 'validate_required_arrays_under_optional_arrays works!'
936
+ end
937
+
938
+ data = {
939
+ orders: [
940
+ { id: 77, drugs: [{batch_no: "A1234567"}]},
941
+ { id: 70 }
942
+ ]
943
+ }
944
+
945
+ get '/validate_required_arrays_under_optional_arrays', data
946
+ expect(last_response.body).to eq("validate_required_arrays_under_optional_arrays works!")
947
+ expect(last_response.status).to eq(200)
948
+ end
949
+
950
+ it "simplest example using Arry -> Hash -> String" do
951
+ subject.params do
952
+ requires :orders, type: Array do
953
+ requires :id, type: Integer
954
+ optional :drugs, type: Hash do
955
+ requires :batch_no, type: String
956
+ end
957
+ end
958
+ end
959
+
960
+ subject.get '/validate_required_arrays_under_optional_arrays' do
961
+ 'validate_required_arrays_under_optional_arrays works!'
962
+ end
963
+
964
+ data = {
965
+ orders: [
966
+ { id: 77, drugs: {batch_no: "A1234567"}},
967
+ { id: 70 }
968
+ ]
969
+ }
970
+
971
+ get '/validate_required_arrays_under_optional_arrays', data
972
+ expect(last_response.body).to eq("validate_required_arrays_under_optional_arrays works!")
973
+ expect(last_response.status).to eq(200)
974
+ end
975
+
976
+ it "correctly indexes invalida data" do
977
+ subject.params do
978
+ requires :orders, type: Array do
979
+ requires :id, type: Integer
980
+ optional :drugs, type: Array do
981
+ requires :batch_no, type: String
982
+ requires :quantity, type: Integer
983
+ end
984
+ end
985
+ end
986
+
987
+ subject.get '/correctly_indexes' do
988
+ 'correctly_indexes works!'
989
+ end
990
+
991
+ data = {
992
+ orders: [
993
+ { id: 70 },
994
+ { id: 77, drugs: [{batch_no: "A1234567", quantity: 12}, {batch_no: "B222222"}]}
995
+ ]
996
+ }
997
+
998
+ get '/correctly_indexes', data
999
+ expect(last_response.body).to eq("orders[1][drugs][1][quantity] is missing")
1000
+ expect(last_response.status).to eq(400)
1001
+ end
1002
+
1003
+ context "multiple levels of optional and requires settings" do
1004
+ before do
1005
+ subject.params do
1006
+ requires :top, type: Array do
1007
+ requires :top_id, type: Integer, allow_blank: false
1008
+ optional :middle_1, type: Array do
1009
+ requires :middle_1_id, type: Integer, allow_blank: false
1010
+ optional :middle_2, type: Array do
1011
+ requires :middle_2_id, type: String, allow_blank: false
1012
+ optional :bottom, type: Array do
1013
+ requires :bottom_id, type: Integer, allow_blank: false
1014
+ end
1015
+ end
1016
+ end
1017
+ end
1018
+ end
1019
+
1020
+ subject.get '/multi_level' do
1021
+ 'multi_level works!'
1022
+ end
1023
+ end
1024
+
1025
+ it "with valid data" do
1026
+ data = {
1027
+ top: [
1028
+ { top_id: 1, middle_1: [
1029
+ {middle_1_id: 11}, {middle_1_id: 12, middle_2: [
1030
+ {middle_2_id: 121}, {middle_2_id: 122, bottom: [{bottom_id: 1221}]}]}]},
1031
+ { top_id: 2, middle_1: [
1032
+ {middle_1_id: 21}, {middle_1_id: 22, middle_2: [
1033
+ {middle_2_id: 221}]}]},
1034
+ { top_id: 3, middle_1: [
1035
+ {middle_1_id: 31}, {middle_1_id: 32}]},
1036
+ { top_id: 4 }
1037
+ ]
1038
+ }
1039
+
1040
+ get '/multi_level', data
1041
+ expect(last_response.body).to eq("multi_level works!")
1042
+ expect(last_response.status).to eq(200)
1043
+ end
1044
+
1045
+ it "with invalid data" do
1046
+ data = {
1047
+ top: [
1048
+ { top_id: 1, middle_1: [
1049
+ {middle_1_id: 11}, {middle_1_id: 12, middle_2: [
1050
+ {middle_2_id: 121}, {middle_2_id: 122, bottom: [{bottom_id: nil}]}]}]},
1051
+ { top_id: 2, middle_1: [
1052
+ {middle_1_id: 21}, {middle_1_id: 22, middle_2: [{middle_2_id: nil}]}]},
1053
+ { top_id: 3, middle_1: [
1054
+ {middle_1_id: nil}, {middle_1_id: 32}]},
1055
+ { top_id: nil, missing_top_id: 4 }
1056
+ ]
1057
+ }
1058
+ # debugger
1059
+ get '/multi_level', data
1060
+ expect(last_response.body.split(", ")).to match_array([
1061
+ "top[3][top_id] is empty",
1062
+ "top[2][middle_1][0][middle_1_id] is empty",
1063
+ "top[1][middle_1][1][middle_2][0][middle_2_id] is empty",
1064
+ "top[0][middle_1][1][middle_2][1][bottom][0][bottom_id] is empty"
1065
+ ])
1066
+ expect(last_response.status).to eq(400)
1067
+ end
1068
+ end
1069
+ end
1070
+
1071
+ it "exactly_one_of" do
1072
+ subject.params do
1073
+ requires :orders, type: Array do
1074
+ requires :id, type: Integer
1075
+ optional :drugs, type: Hash do
1076
+ optional :batch_no, type: String
1077
+ optional :batch_id, type: String
1078
+ exactly_one_of :batch_no, :batch_id
1079
+ end
1080
+ end
1081
+ end
1082
+
1083
+ subject.get '/exactly_one_of' do
1084
+ 'exactly_one_of works!'
1085
+ end
1086
+
1087
+ data = {
1088
+ orders: [
1089
+ { id: 77, drugs: {batch_no: "A1234567"}},
1090
+ { id: 70 }
1091
+ ]
1092
+ }
1093
+
1094
+ get '/exactly_one_of', data
1095
+ expect(last_response.body).to eq("exactly_one_of works!")
1096
+ expect(last_response.status).to eq(200)
1097
+ end
1098
+
1099
+ it "at_least_one_of" do
1100
+ subject.params do
1101
+ requires :orders, type: Array do
1102
+ requires :id, type: Integer
1103
+ optional :drugs, type: Hash do
1104
+ optional :batch_no, type: String
1105
+ optional :batch_id, type: String
1106
+ at_least_one_of :batch_no, :batch_id
1107
+ end
1108
+ end
1109
+ end
1110
+
1111
+ subject.get '/at_least_one_of' do
1112
+ 'at_least_one_of works!'
1113
+ end
1114
+
1115
+ data = {
1116
+ orders: [
1117
+ { id: 77, drugs: {batch_no: "A1234567"}},
1118
+ { id: 70 }
1119
+ ]
1120
+ }
1121
+
1122
+ get '/at_least_one_of', data
1123
+ expect(last_response.body).to eq("at_least_one_of works!")
1124
+ expect(last_response.status).to eq(200)
1125
+ end
1126
+
1127
+ it "all_or_none_of" do
1128
+ subject.params do
1129
+ requires :orders, type: Array do
1130
+ requires :id, type: Integer
1131
+ optional :drugs, type: Hash do
1132
+ optional :batch_no, type: String
1133
+ optional :batch_id, type: String
1134
+ all_or_none_of :batch_no, :batch_id
1135
+ end
1136
+ end
1137
+ end
1138
+
1139
+ subject.get '/all_or_none_of' do
1140
+ 'all_or_none_of works!'
1141
+ end
1142
+
1143
+ data = {
1144
+ orders: [
1145
+ { id: 77, drugs: {batch_no: "A1234567", batch_id: "12"}},
1146
+ { id: 70 }
1147
+ ]
1148
+ }
1149
+
1150
+ get '/all_or_none_of', data
1151
+ expect(last_response.body).to eq("all_or_none_of works!")
1152
+ expect(last_response.status).to eq(200)
881
1153
  end
882
1154
  end
883
1155
 
@@ -1122,14 +1394,14 @@ describe Grape::Validations do
1122
1394
  subject.params do
1123
1395
  use :pagination
1124
1396
  end
1125
- expect(subject.route_setting(:declared_params)).to eq %i[page per_page]
1397
+ expect(declared_params).to eq %i[page per_page]
1126
1398
  end
1127
1399
 
1128
1400
  it 'by #use with multiple params' do
1129
1401
  subject.params do
1130
1402
  use :pagination, :period
1131
1403
  end
1132
- expect(subject.route_setting(:declared_params)).to eq %i[page per_page start_date end_date]
1404
+ expect(declared_params).to eq %i[page per_page start_date end_date]
1133
1405
  end
1134
1406
  end
1135
1407
 
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', '..', 'lib'))
4
+ require 'grape'
5
+
6
+ describe Grape do
7
+ it 'eager_load!' do
8
+ require 'grape/eager_load'
9
+ expect { Grape.eager_load! }.to_not raise_error
10
+ end
11
+
12
+ it 'compile!' do
13
+ expect { Class.new(Grape::API).compile! }.to_not raise_error
14
+ end
15
+ end