grape 1.5.2 → 1.7.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 (210) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +75 -0
  3. data/CONTRIBUTING.md +2 -1
  4. data/README.md +152 -21
  5. data/UPGRADING.md +86 -2
  6. data/grape.gemspec +5 -5
  7. data/lib/grape/api/instance.rb +14 -18
  8. data/lib/grape/api.rb +18 -13
  9. data/lib/grape/cookies.rb +2 -0
  10. data/lib/grape/dry_types.rb +12 -0
  11. data/lib/grape/dsl/api.rb +0 -2
  12. data/lib/grape/dsl/callbacks.rb +0 -2
  13. data/lib/grape/dsl/configuration.rb +0 -2
  14. data/lib/grape/dsl/desc.rb +2 -19
  15. data/lib/grape/dsl/headers.rb +5 -2
  16. data/lib/grape/dsl/helpers.rb +7 -7
  17. data/lib/grape/dsl/inside_route.rb +43 -30
  18. data/lib/grape/dsl/middleware.rb +4 -6
  19. data/lib/grape/dsl/parameters.rb +8 -10
  20. data/lib/grape/dsl/request_response.rb +9 -8
  21. data/lib/grape/dsl/routing.rb +6 -4
  22. data/lib/grape/dsl/settings.rb +5 -7
  23. data/lib/grape/dsl/validations.rb +0 -15
  24. data/lib/grape/endpoint.rb +21 -36
  25. data/lib/grape/error_formatter/json.rb +9 -7
  26. data/lib/grape/error_formatter/xml.rb +2 -6
  27. data/lib/grape/exceptions/base.rb +2 -2
  28. data/lib/grape/exceptions/empty_message_body.rb +11 -0
  29. data/lib/grape/exceptions/missing_group_type.rb +8 -1
  30. data/lib/grape/exceptions/too_many_multipart_files.rb +11 -0
  31. data/lib/grape/exceptions/unsupported_group_type.rb +8 -1
  32. data/lib/grape/exceptions/validation.rb +1 -6
  33. data/lib/grape/formatter/json.rb +1 -0
  34. data/lib/grape/formatter/serializable_hash.rb +2 -1
  35. data/lib/grape/formatter/xml.rb +1 -0
  36. data/lib/grape/locale/en.yml +9 -8
  37. data/lib/grape/middleware/auth/dsl.rb +7 -2
  38. data/lib/grape/middleware/base.rb +3 -1
  39. data/lib/grape/middleware/error.rb +2 -2
  40. data/lib/grape/middleware/formatter.rb +4 -4
  41. data/lib/grape/middleware/stack.rb +2 -2
  42. data/lib/grape/middleware/versioner/accept_version_header.rb +3 -5
  43. data/lib/grape/middleware/versioner/header.rb +6 -4
  44. data/lib/grape/middleware/versioner/param.rb +1 -0
  45. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
  46. data/lib/grape/middleware/versioner/path.rb +2 -0
  47. data/lib/grape/parser/json.rb +1 -1
  48. data/lib/grape/parser/xml.rb +1 -1
  49. data/lib/grape/path.rb +1 -0
  50. data/lib/grape/request.rb +5 -0
  51. data/lib/grape/router/pattern.rb +1 -1
  52. data/lib/grape/router/route.rb +2 -2
  53. data/lib/grape/router.rb +6 -0
  54. data/lib/grape/util/inheritable_setting.rb +1 -3
  55. data/lib/grape/util/json.rb +2 -0
  56. data/lib/grape/util/lazy_value.rb +3 -2
  57. data/lib/grape/util/strict_hash_configuration.rb +1 -1
  58. data/lib/grape/validations/attributes_doc.rb +58 -0
  59. data/lib/grape/validations/params_scope.rb +137 -78
  60. data/lib/grape/validations/types/array_coercer.rb +0 -2
  61. data/lib/grape/validations/types/custom_type_coercer.rb +1 -2
  62. data/lib/grape/validations/types/dry_type_coercer.rb +4 -8
  63. data/lib/grape/validations/types/json.rb +2 -1
  64. data/lib/grape/validations/types/primitive_coercer.rb +16 -8
  65. data/lib/grape/validations/types/set_coercer.rb +0 -2
  66. data/lib/grape/validations/types.rb +98 -30
  67. data/lib/grape/validations/validators/all_or_none_of_validator.rb +16 -0
  68. data/lib/grape/validations/validators/allow_blank_validator.rb +20 -0
  69. data/lib/grape/validations/validators/as_validator.rb +14 -0
  70. data/lib/grape/validations/validators/at_least_one_of_validator.rb +15 -0
  71. data/lib/grape/validations/validators/base.rb +82 -70
  72. data/lib/grape/validations/validators/coerce_validator.rb +75 -0
  73. data/lib/grape/validations/validators/default_validator.rb +51 -0
  74. data/lib/grape/validations/validators/exactly_one_of_validator.rb +17 -0
  75. data/lib/grape/validations/validators/except_values_validator.rb +24 -0
  76. data/lib/grape/validations/validators/multiple_params_base.rb +24 -20
  77. data/lib/grape/validations/validators/mutual_exclusion_validator.rb +16 -0
  78. data/lib/grape/validations/validators/presence_validator.rb +15 -0
  79. data/lib/grape/validations/validators/regexp_validator.rb +16 -0
  80. data/lib/grape/validations/validators/same_as_validator.rb +29 -0
  81. data/lib/grape/validations/validators/values_validator.rb +88 -0
  82. data/lib/grape/validations.rb +16 -6
  83. data/lib/grape/version.rb +1 -1
  84. data/lib/grape.rb +70 -29
  85. data/spec/grape/api/custom_validations_spec.rb +116 -45
  86. data/spec/grape/api/deeply_included_options_spec.rb +3 -5
  87. data/spec/grape/api/defines_boolean_in_params_spec.rb +2 -3
  88. data/spec/grape/api/documentation_spec.rb +59 -0
  89. data/spec/grape/api/inherited_helpers_spec.rb +0 -2
  90. data/spec/grape/api/instance_spec.rb +0 -1
  91. data/spec/grape/api/invalid_format_spec.rb +2 -2
  92. data/spec/grape/api/namespace_parameters_in_route_spec.rb +0 -2
  93. data/spec/grape/api/nested_helpers_spec.rb +0 -2
  94. data/spec/grape/api/optional_parameters_in_route_spec.rb +0 -2
  95. data/spec/grape/api/parameters_modification_spec.rb +0 -2
  96. data/spec/grape/api/patch_method_helpers_spec.rb +0 -2
  97. data/spec/grape/api/recognize_path_spec.rb +1 -3
  98. data/spec/grape/api/required_parameters_in_route_spec.rb +0 -2
  99. data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +0 -2
  100. data/spec/grape/api/routes_with_requirements_spec.rb +8 -10
  101. data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +9 -17
  102. data/spec/grape/api/shared_helpers_spec.rb +0 -2
  103. data/spec/grape/api_remount_spec.rb +16 -16
  104. data/spec/grape/api_spec.rb +527 -224
  105. data/spec/grape/config_spec.rb +0 -2
  106. data/spec/grape/dsl/callbacks_spec.rb +2 -3
  107. data/spec/grape/dsl/configuration_spec.rb +0 -2
  108. data/spec/grape/dsl/desc_spec.rb +0 -2
  109. data/spec/grape/dsl/headers_spec.rb +39 -11
  110. data/spec/grape/dsl/helpers_spec.rb +3 -4
  111. data/spec/grape/dsl/inside_route_spec.rb +16 -16
  112. data/spec/grape/dsl/logger_spec.rb +15 -19
  113. data/spec/grape/dsl/middleware_spec.rb +2 -3
  114. data/spec/grape/dsl/parameters_spec.rb +2 -2
  115. data/spec/grape/dsl/request_response_spec.rb +7 -8
  116. data/spec/grape/dsl/routing_spec.rb +11 -10
  117. data/spec/grape/dsl/settings_spec.rb +0 -2
  118. data/spec/grape/dsl/validations_spec.rb +0 -17
  119. data/spec/grape/endpoint/declared_spec.rb +261 -16
  120. data/spec/grape/endpoint_spec.rb +98 -57
  121. data/spec/grape/entity_spec.rb +22 -23
  122. data/spec/grape/exceptions/base_spec.rb +16 -2
  123. data/spec/grape/exceptions/body_parse_errors_spec.rb +3 -2
  124. data/spec/grape/exceptions/invalid_accept_header_spec.rb +61 -24
  125. data/spec/grape/exceptions/invalid_formatter_spec.rb +0 -2
  126. data/spec/grape/exceptions/invalid_response_spec.rb +0 -2
  127. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +1 -3
  128. data/spec/grape/exceptions/missing_group_type_spec.rb +21 -0
  129. data/spec/grape/exceptions/missing_mime_type_spec.rb +0 -2
  130. data/spec/grape/exceptions/missing_option_spec.rb +1 -3
  131. data/spec/grape/exceptions/unknown_options_spec.rb +0 -2
  132. data/spec/grape/exceptions/unknown_validator_spec.rb +0 -2
  133. data/spec/grape/exceptions/unsupported_group_type_spec.rb +23 -0
  134. data/spec/grape/exceptions/validation_errors_spec.rb +13 -11
  135. data/spec/grape/exceptions/validation_spec.rb +5 -5
  136. data/spec/grape/extensions/param_builders/hash_spec.rb +7 -9
  137. data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +8 -10
  138. data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +8 -10
  139. data/spec/grape/integration/global_namespace_function_spec.rb +0 -2
  140. data/spec/grape/integration/rack_sendfile_spec.rb +1 -3
  141. data/spec/grape/integration/rack_spec.rb +0 -2
  142. data/spec/grape/loading_spec.rb +8 -10
  143. data/spec/grape/middleware/auth/base_spec.rb +0 -1
  144. data/spec/grape/middleware/auth/dsl_spec.rb +15 -8
  145. data/spec/grape/middleware/auth/strategies_spec.rb +60 -22
  146. data/spec/grape/middleware/base_spec.rb +24 -17
  147. data/spec/grape/middleware/error_spec.rb +8 -3
  148. data/spec/grape/middleware/exception_spec.rb +111 -163
  149. data/spec/grape/middleware/formatter_spec.rb +27 -8
  150. data/spec/grape/middleware/globals_spec.rb +7 -6
  151. data/spec/grape/middleware/stack_spec.rb +14 -14
  152. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +2 -3
  153. data/spec/grape/middleware/versioner/header_spec.rb +30 -15
  154. data/spec/grape/middleware/versioner/param_spec.rb +7 -3
  155. data/spec/grape/middleware/versioner/path_spec.rb +5 -3
  156. data/spec/grape/middleware/versioner_spec.rb +1 -3
  157. data/spec/grape/named_api_spec.rb +0 -2
  158. data/spec/grape/parser_spec.rb +4 -2
  159. data/spec/grape/path_spec.rb +52 -54
  160. data/spec/grape/presenters/presenter_spec.rb +7 -8
  161. data/spec/grape/request_spec.rb +6 -6
  162. data/spec/grape/util/inheritable_setting_spec.rb +7 -8
  163. data/spec/grape/util/inheritable_values_spec.rb +3 -3
  164. data/spec/grape/util/reverse_stackable_values_spec.rb +3 -2
  165. data/spec/grape/util/stackable_values_spec.rb +7 -6
  166. data/spec/grape/util/strict_hash_configuration_spec.rb +0 -1
  167. data/spec/grape/validations/attributes_doc_spec.rb +153 -0
  168. data/spec/grape/validations/attributes_iterator_spec.rb +0 -2
  169. data/spec/grape/validations/instance_behaivour_spec.rb +9 -12
  170. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +1 -2
  171. data/spec/grape/validations/params_scope_spec.rb +361 -96
  172. data/spec/grape/validations/single_attribute_iterator_spec.rb +2 -3
  173. data/spec/grape/validations/types/array_coercer_spec.rb +0 -2
  174. data/spec/grape/validations/types/primitive_coercer_spec.rb +24 -9
  175. data/spec/grape/validations/types/set_coercer_spec.rb +0 -2
  176. data/spec/grape/validations/types_spec.rb +36 -10
  177. data/spec/grape/validations/validators/all_or_none_spec.rb +50 -58
  178. data/spec/grape/validations/validators/allow_blank_spec.rb +135 -141
  179. data/spec/grape/validations/validators/at_least_one_of_spec.rb +50 -58
  180. data/spec/grape/validations/validators/coerce_spec.rb +99 -24
  181. data/spec/grape/validations/validators/default_spec.rb +72 -80
  182. data/spec/grape/validations/validators/exactly_one_of_spec.rb +71 -79
  183. data/spec/grape/validations/validators/except_values_spec.rb +3 -5
  184. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +71 -79
  185. data/spec/grape/validations/validators/presence_spec.rb +16 -3
  186. data/spec/grape/validations/validators/regexp_spec.rb +25 -33
  187. data/spec/grape/validations/validators/same_as_spec.rb +14 -22
  188. data/spec/grape/validations/validators/values_spec.rb +182 -179
  189. data/spec/grape/validations_spec.rb +149 -80
  190. data/spec/integration/eager_load/eager_load_spec.rb +2 -2
  191. data/spec/integration/multi_json/json_spec.rb +1 -3
  192. data/spec/integration/multi_xml/xml_spec.rb +1 -3
  193. data/spec/shared/versioning_examples.rb +12 -9
  194. data/spec/spec_helper.rb +21 -6
  195. data/spec/support/basic_auth_encode_helpers.rb +1 -1
  196. metadata +125 -115
  197. data/lib/grape/validations/validators/all_or_none.rb +0 -15
  198. data/lib/grape/validations/validators/allow_blank.rb +0 -18
  199. data/lib/grape/validations/validators/as.rb +0 -16
  200. data/lib/grape/validations/validators/at_least_one_of.rb +0 -14
  201. data/lib/grape/validations/validators/coerce.rb +0 -91
  202. data/lib/grape/validations/validators/default.rb +0 -48
  203. data/lib/grape/validations/validators/exactly_one_of.rb +0 -16
  204. data/lib/grape/validations/validators/except_values.rb +0 -22
  205. data/lib/grape/validations/validators/mutual_exclusion.rb +0 -15
  206. data/lib/grape/validations/validators/presence.rb +0 -12
  207. data/lib/grape/validations/validators/regexp.rb +0 -13
  208. data/lib/grape/validations/validators/same_as.rb +0 -26
  209. data/lib/grape/validations/validators/values.rb +0 -83
  210. data/spec/support/eager_load.rb +0 -19
@@ -11,15 +11,21 @@ module Grape
11
11
  class PrimitiveCoercer < DryTypeCoercer
12
12
  MAPPING = {
13
13
  Grape::API::Boolean => DryTypes::Params::Bool,
14
- BigDecimal => DryTypes::Params::Decimal,
14
+ BigDecimal => DryTypes::Params::Decimal,
15
+ Numeric => DryTypes::Params::Integer | DryTypes::Params::Float | DryTypes::Params::Decimal,
16
+ TrueClass => DryTypes::Params::Bool.constrained(eql: true),
17
+ FalseClass => DryTypes::Params::Bool.constrained(eql: false),
15
18
 
16
19
  # unfortunately, a +Params+ scope doesn't contain String
17
- String => DryTypes::Coercible::String
20
+ String => DryTypes::Coercible::String
18
21
  }.freeze
19
22
 
20
23
  STRICT_MAPPING = {
21
24
  Grape::API::Boolean => DryTypes::Strict::Bool,
22
- BigDecimal => DryTypes::Strict::Decimal
25
+ BigDecimal => DryTypes::Strict::Decimal,
26
+ Numeric => DryTypes::Strict::Integer | DryTypes::Strict::Float | DryTypes::Strict::Decimal,
27
+ TrueClass => DryTypes::Strict::Bool.constrained(eql: true),
28
+ FalseClass => DryTypes::Strict::Bool.constrained(eql: false)
23
29
  }.freeze
24
30
 
25
31
  def initialize(type, strict = false)
@@ -27,11 +33,13 @@ module Grape
27
33
 
28
34
  @type = type
29
35
 
30
- @coercer = if strict
31
- STRICT_MAPPING.fetch(type) { scope.const_get(type.name) }
32
- else
33
- MAPPING.fetch(type) { scope.const_get(type.name) }
34
- end
36
+ @coercer = (strict ? STRICT_MAPPING : MAPPING).fetch(type) do
37
+ scope.const_get(type.name, false)
38
+ rescue NameError
39
+ raise ArgumentError, "type #{type} should support coercion via `[]`" unless type.respond_to?(:[])
40
+
41
+ type
42
+ end
35
43
  end
36
44
 
37
45
  def call(val)
@@ -9,8 +9,6 @@ module Grape
9
9
  # Takes the given array and converts it to a set. Every element of the set
10
10
  # is also coerced.
11
11
  class SetCoercer < ArrayCoercer
12
- register_collection Set
13
-
14
12
  def initialize(type, strict = false)
15
13
  super
16
14
 
@@ -1,13 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'types/build_coercer'
4
- require_relative 'types/custom_type_coercer'
5
- require_relative 'types/custom_type_collection_coercer'
6
- require_relative 'types/multiple_type_coercer'
7
- require_relative 'types/variant_collection_coercer'
8
- require_relative 'types/json'
9
- require_relative 'types/file'
10
- require_relative 'types/invalid_value'
3
+ require 'grape/validations/types/json'
4
+ require 'grape/validations/types/file'
11
5
 
12
6
  module Grape
13
7
  module Validations
@@ -22,7 +16,8 @@ module Grape
22
16
  # and {Grape::Dsl::Parameters#optional}. The main
23
17
  # entry point for this process is {Types.build_coercer}.
24
18
  module Types
25
- # Types representing a single value, which are coerced.
19
+ module_function
20
+
26
21
  PRIMITIVES = [
27
22
  # Numerical
28
23
  Integer,
@@ -44,33 +39,23 @@ module Grape
44
39
  ].freeze
45
40
 
46
41
  # Types representing data structures.
47
- STRUCTURES = [
48
- Hash,
49
- Array,
50
- Set
51
- ].freeze
42
+ STRUCTURES = [Hash, Array, Set].freeze
52
43
 
53
- # Special custom types provided by Grape.
54
44
  SPECIAL = {
55
- JSON => Json,
45
+ ::JSON => Json,
56
46
  Array[JSON] => JsonArray,
57
47
  ::File => File,
58
48
  Rack::Multipart::UploadedFile => File
59
49
  }.freeze
60
50
 
61
- GROUPS = [
62
- Array,
63
- Hash,
64
- JSON,
65
- Array[JSON]
66
- ].freeze
51
+ GROUPS = [Array, Hash, JSON, Array[JSON]].freeze
67
52
 
68
53
  # Is the given class a primitive type as recognized by Grape?
69
54
  #
70
55
  # @param type [Class] type to check
71
56
  # @return [Boolean] whether or not the type is known by Grape as a valid
72
57
  # type for a single value
73
- def self.primitive?(type)
58
+ def primitive?(type)
74
59
  PRIMITIVES.include?(type)
75
60
  end
76
61
 
@@ -80,7 +65,7 @@ module Grape
80
65
  # @param type [Class] type to check
81
66
  # @return [Boolean] whether or not the type is known by Grape as a valid
82
67
  # data structure type
83
- def self.structure?(type)
68
+ def structure?(type)
84
69
  STRUCTURES.include?(type)
85
70
  end
86
71
 
@@ -92,7 +77,7 @@ module Grape
92
77
  # @param type [Array<Class>,Set<Class>] type (or type list!) to check
93
78
  # @return [Boolean] +true+ if the given value will be treated as
94
79
  # a list of types.
95
- def self.multiple?(type)
80
+ def multiple?(type)
96
81
  (type.is_a?(Array) || type.is_a?(Set)) && type.size > 1
97
82
  end
98
83
 
@@ -103,7 +88,7 @@ module Grape
103
88
  #
104
89
  # @param type [Class] type to check
105
90
  # @return [Boolean] +true+ if special routines are available
106
- def self.special?(type)
91
+ def special?(type)
107
92
  SPECIAL.key? type
108
93
  end
109
94
 
@@ -112,7 +97,7 @@ module Grape
112
97
  #
113
98
  # @param type [Array<Class>,Class] type to check
114
99
  # @return [Boolean] +true+ if the type is a supported group type
115
- def self.group?(type)
100
+ def group?(type)
116
101
  GROUPS.include? type
117
102
  end
118
103
 
@@ -121,7 +106,7 @@ module Grape
121
106
  #
122
107
  # @param type [Class] type to check
123
108
  # @return [Boolean] whether or not the type can be used as a custom type
124
- def self.custom?(type)
109
+ def custom?(type)
125
110
  !primitive?(type) &&
126
111
  !structure?(type) &&
127
112
  !multiple?(type) &&
@@ -134,15 +119,98 @@ module Grape
134
119
  # @param type [Array<Class>,Class] type to check
135
120
  # @return [Boolean] true if +type+ is a collection of a type that implements
136
121
  # its own +#parse+ method.
137
- def self.collection_of_custom?(type)
122
+ def collection_of_custom?(type)
138
123
  (type.is_a?(Array) || type.is_a?(Set)) &&
139
124
  type.length == 1 &&
140
125
  (custom?(type.first) || special?(type.first))
141
126
  end
142
127
 
143
- def self.map_special(type)
128
+ def map_special(type)
144
129
  SPECIAL.fetch(type, type)
145
130
  end
131
+
132
+ # Chooses the best coercer for the given type. For example, if the type
133
+ # is Integer, it will return a coercer which will be able to coerce a value
134
+ # to the integer.
135
+ #
136
+ # There are a few very special coercers which might be returned.
137
+ #
138
+ # +Grape::Types::MultipleTypeCoercer+ is a coercer which is returned when
139
+ # the given type implies values in an array with different types.
140
+ # For example, +[Integer, String]+ allows integer and string values in
141
+ # an array.
142
+ #
143
+ # +Grape::Types::CustomTypeCoercer+ is a coercer which is returned when
144
+ # a method is specified by a user with +coerce_with+ option or the user
145
+ # specifies a custom type which implements requirments of
146
+ # +Grape::Types::CustomTypeCoercer+.
147
+ #
148
+ # +Grape::Types::CustomTypeCollectionCoercer+ is a very similar to the
149
+ # previous one, but it expects an array or set of values having a custom
150
+ # type implemented by the user.
151
+ #
152
+ # There is also a group of custom types implemented by Grape, check
153
+ # +Grape::Validations::Types::SPECIAL+ to get the full list.
154
+ #
155
+ # @param type [Class] the type to which input strings
156
+ # should be coerced
157
+ # @param method [Class,#call] the coercion method to use
158
+ # @return [Object] object to be used
159
+ # for coercion and type validation
160
+ def build_coercer(type, method: nil, strict: false)
161
+ cache_instance(type, method, strict) do
162
+ create_coercer_instance(type, method, strict)
163
+ end
164
+ end
165
+
166
+ def create_coercer_instance(type, method, strict)
167
+ # Maps a custom type provided by Grape, it doesn't map types wrapped by collections!!!
168
+ type = Types.map_special(type)
169
+
170
+ # Use a special coercer for multiply-typed parameters.
171
+ if Types.multiple?(type)
172
+ MultipleTypeCoercer.new(type, method)
173
+
174
+ # Use a special coercer for custom types and coercion methods.
175
+ elsif method || Types.custom?(type)
176
+ CustomTypeCoercer.new(type, method)
177
+
178
+ # Special coercer for collections of types that implement a parse method.
179
+ # CustomTypeCoercer (above) already handles such types when an explicit coercion
180
+ # method is supplied.
181
+ elsif Types.collection_of_custom?(type)
182
+ Types::CustomTypeCollectionCoercer.new(
183
+ Types.map_special(type.first), type.is_a?(Set)
184
+ )
185
+ else
186
+ DryTypeCoercer.coercer_instance_for(type, strict)
187
+ end
188
+ end
189
+
190
+ def cache_instance(type, method, strict, &_block)
191
+ key = cache_key(type, method, strict)
192
+
193
+ return @__cache[key] if @__cache.key?(key)
194
+
195
+ instance = yield
196
+
197
+ @__cache_write_lock.synchronize do
198
+ @__cache[key] = instance
199
+ end
200
+
201
+ instance
202
+ end
203
+
204
+ def cache_key(type, method, strict)
205
+ [type, method, strict].each_with_object(+'_') do |val, memo|
206
+ next if val.nil?
207
+
208
+ memo << '_' << val.to_s
209
+ end
210
+ end
211
+
212
+ instance_variable_set(:@__cache, {})
213
+ instance_variable_set(:@__cache_write_lock, Mutex.new)
146
214
  end
147
215
  end
148
216
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Validations
5
+ module Validators
6
+ class AllOrNoneOfValidator < MultipleParamsBase
7
+ def validate_params!(params)
8
+ keys = keys_in_common(params)
9
+ return if keys.empty? || keys.length == all_keys.length
10
+
11
+ raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:all_or_none))
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Validations
5
+ module Validators
6
+ class AllowBlankValidator < Base
7
+ def validate_param!(attr_name, params)
8
+ return if (options_key?(:value) ? @option[:value] : @option) || !params.is_a?(Hash)
9
+
10
+ value = params[attr_name]
11
+ value = value.strip if value.respond_to?(:strip)
12
+
13
+ return if value == false || value.present?
14
+
15
+ raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:blank))
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Validations
5
+ module Validators
6
+ class AsValidator < Base
7
+ # We use a validator for renaming parameters. This is just a marker for
8
+ # the parameter scope to handle the renaming. No actual validation
9
+ # happens here.
10
+ def validate_param!(*); end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Validations
5
+ module Validators
6
+ class AtLeastOneOfValidator < MultipleParamsBase
7
+ def validate_params!(params)
8
+ return unless keys_in_common(params).empty?
9
+
10
+ raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:at_least_one))
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -2,89 +2,101 @@
2
2
 
3
3
  module Grape
4
4
  module Validations
5
- class Base
6
- attr_reader :attrs
5
+ module Validators
6
+ class Base
7
+ attr_reader :attrs
7
8
 
8
- # Creates a new Validator from options specified
9
- # by a +requires+ or +optional+ directive during
10
- # parameter definition.
11
- # @param attrs [Array] names of attributes to which the Validator applies
12
- # @param options [Object] implementation-dependent Validator options
13
- # @param required [Boolean] attribute(s) are required or optional
14
- # @param scope [ParamsScope] parent scope for this Validator
15
- # @param opts [Array] additional validation options
16
- def initialize(attrs, options, required, scope, *opts)
17
- @attrs = Array(attrs)
18
- @option = options
19
- @required = required
20
- @scope = scope
21
- opts = opts.any? ? opts.shift : {}
22
- @fail_fast = opts.fetch(:fail_fast, false)
23
- @allow_blank = opts.fetch(:allow_blank, false)
24
- end
9
+ # Creates a new Validator from options specified
10
+ # by a +requires+ or +optional+ directive during
11
+ # parameter definition.
12
+ # @param attrs [Array] names of attributes to which the Validator applies
13
+ # @param options [Object] implementation-dependent Validator options
14
+ # @param required [Boolean] attribute(s) are required or optional
15
+ # @param scope [ParamsScope] parent scope for this Validator
16
+ # @param opts [Array] additional validation options
17
+ def initialize(attrs, options, required, scope, *opts)
18
+ @attrs = Array(attrs)
19
+ @option = options
20
+ @required = required
21
+ @scope = scope
22
+ opts = opts.any? ? opts.shift : {}
23
+ @fail_fast = opts.fetch(:fail_fast, false)
24
+ @allow_blank = opts.fetch(:allow_blank, false)
25
+ end
25
26
 
26
- # Validates a given request.
27
- # @note Override #validate! unless you need to access the entire request.
28
- # @param request [Grape::Request] the request currently being handled
29
- # @raise [Grape::Exceptions::Validation] if validation failed
30
- # @return [void]
31
- def validate(request)
32
- return unless @scope.should_validate?(request.params)
33
- validate!(request.params)
34
- end
27
+ # Validates a given request.
28
+ # @note Override #validate! unless you need to access the entire request.
29
+ # @param request [Grape::Request] the request currently being handled
30
+ # @raise [Grape::Exceptions::Validation] if validation failed
31
+ # @return [void]
32
+ def validate(request)
33
+ return unless @scope.should_validate?(request.params)
34
+
35
+ validate!(request.params)
36
+ end
35
37
 
36
- # Validates a given parameter hash.
37
- # @note Override #validate if you need to access the entire request.
38
- # @param params [Hash] parameters to validate
39
- # @raise [Grape::Exceptions::Validation] if validation failed
40
- # @return [void]
41
- def validate!(params)
42
- attributes = SingleAttributeIterator.new(self, @scope, params)
43
- # we collect errors inside array because
44
- # there may be more than one error per field
45
- array_errors = []
38
+ # Validates a given parameter hash.
39
+ # @note Override #validate if you need to access the entire request.
40
+ # @param params [Hash] parameters to validate
41
+ # @raise [Grape::Exceptions::Validation] if validation failed
42
+ # @return [void]
43
+ def validate!(params)
44
+ attributes = SingleAttributeIterator.new(self, @scope, params)
45
+ # we collect errors inside array because
46
+ # there may be more than one error per field
47
+ array_errors = []
46
48
 
47
- attributes.each do |val, attr_name, empty_val, skip_value|
48
- next if skip_value
49
- next if !@scope.required? && empty_val
50
- next unless @scope.meets_dependency?(val, params)
51
- begin
52
- validate_param!(attr_name, val) if @required || val.respond_to?(:key?) && val.key?(attr_name)
53
- rescue Grape::Exceptions::Validation => e
54
- array_errors << e
49
+ attributes.each do |val, attr_name, empty_val, skip_value|
50
+ next if skip_value
51
+ next if !@scope.required? && empty_val
52
+ next unless @scope.meets_dependency?(val, params)
53
+
54
+ begin
55
+ validate_param!(attr_name, val) if @required || (val.respond_to?(:key?) && val.key?(attr_name))
56
+ rescue Grape::Exceptions::Validation => e
57
+ array_errors << e
58
+ end
55
59
  end
60
+
61
+ raise Grape::Exceptions::ValidationArrayErrors.new(array_errors) if array_errors.any?
56
62
  end
57
63
 
58
- raise Grape::Exceptions::ValidationArrayErrors, array_errors if array_errors.any?
59
- end
64
+ def self.convert_to_short_name(klass)
65
+ ret = klass.name.gsub(/::/, '/')
66
+ ret.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
67
+ ret.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
68
+ ret.tr!('-', '_')
69
+ ret.downcase!
70
+ File.basename(ret, '_validator')
71
+ end
60
72
 
61
- def self.convert_to_short_name(klass)
62
- ret = klass.name.gsub(/::/, '/')
63
- ret.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
64
- ret.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
65
- ret.tr!('-', '_')
66
- ret.downcase!
67
- File.basename(ret, '_validator')
68
- end
73
+ def self.inherited(klass)
74
+ return unless klass.name.present?
69
75
 
70
- def self.inherited(klass)
71
- return unless klass.name.present?
72
- Validations.register_validator(convert_to_short_name(klass), klass)
73
- end
76
+ Validations.register_validator(convert_to_short_name(klass), klass)
77
+ end
74
78
 
75
- def message(default_key = nil)
76
- options = instance_variable_get(:@option)
77
- options_key?(:message) ? options[:message] : default_key
78
- end
79
+ def message(default_key = nil)
80
+ options = instance_variable_get(:@option)
81
+ options_key?(:message) ? options[:message] : default_key
82
+ end
79
83
 
80
- def options_key?(key, options = nil)
81
- options = instance_variable_get(:@option) if options.nil?
82
- options.respond_to?(:key?) && options.key?(key) && !options[key].nil?
83
- end
84
+ def options_key?(key, options = nil)
85
+ options = instance_variable_get(:@option) if options.nil?
86
+ options.respond_to?(:key?) && options.key?(key) && !options[key].nil?
87
+ end
84
88
 
85
- def fail_fast?
86
- @fail_fast
89
+ def fail_fast?
90
+ @fail_fast
91
+ end
87
92
  end
88
93
  end
89
94
  end
90
95
  end
96
+
97
+ Grape::Validations::Base = Class.new(Grape::Validations::Validators::Base) do
98
+ def initialize(*)
99
+ super
100
+ warn '[DEPRECATION] `Grape::Validations::Base` is deprecated. Use `Grape::Validations::Validators::Base` instead.'
101
+ end
102
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Validations
5
+ module Validators
6
+ class CoerceValidator < Base
7
+ def initialize(attrs, options, required, scope, **opts)
8
+ super
9
+
10
+ @converter = if type.is_a?(Grape::Validations::Types::VariantCollectionCoercer)
11
+ type
12
+ else
13
+ Types.build_coercer(type, method: @option[:method])
14
+ end
15
+ end
16
+
17
+ def validate_param!(attr_name, params)
18
+ raise validation_exception(attr_name) unless params.is_a? Hash
19
+
20
+ new_value = coerce_value(params[attr_name])
21
+
22
+ raise validation_exception(attr_name, new_value.message) unless valid_type?(new_value)
23
+
24
+ # Don't assign a value if it is identical. It fixes a problem with Hashie::Mash
25
+ # which looses wrappers for hashes and arrays after reassigning values
26
+ #
27
+ # h = Hashie::Mash.new(list: [1, 2, 3, 4])
28
+ # => #<Hashie::Mash list=#<Hashie::Array [1, 2, 3, 4]>>
29
+ # list = h.list
30
+ # h[:list] = list
31
+ # h
32
+ # => #<Hashie::Mash list=[1, 2, 3, 4]>
33
+ return if params[attr_name].instance_of?(new_value.class) && params[attr_name] == new_value
34
+
35
+ params[attr_name] = new_value
36
+ end
37
+
38
+ private
39
+
40
+ # @!attribute [r] converter
41
+ # Object that will be used for parameter coercion and type checking.
42
+ #
43
+ # See {Types.build_coercer}
44
+ #
45
+ # @return [Object]
46
+ attr_reader :converter
47
+
48
+ def valid_type?(val)
49
+ !val.is_a?(Types::InvalidValue)
50
+ end
51
+
52
+ def coerce_value(val)
53
+ converter.call(val)
54
+ # Some custom types might fail, so it should be treated as an invalid value
55
+ rescue StandardError
56
+ Types::InvalidValue.new
57
+ end
58
+
59
+ # Type to which the parameter will be coerced.
60
+ #
61
+ # @return [Class]
62
+ def type
63
+ @option[:type].is_a?(Hash) ? @option[:type][:value] : @option[:type]
64
+ end
65
+
66
+ def validation_exception(attr_name, custom_msg = nil)
67
+ Grape::Exceptions::Validation.new(
68
+ params: [@scope.full_name(attr_name)],
69
+ message: custom_msg || message(:coerce)
70
+ )
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Validations
5
+ module Validators
6
+ class DefaultValidator < Base
7
+ def initialize(attrs, options, required, scope, **opts)
8
+ @default = options
9
+ super
10
+ end
11
+
12
+ def validate_param!(attr_name, params)
13
+ params[attr_name] = if @default.is_a? Proc
14
+ @default.call
15
+ elsif @default.frozen? || !duplicatable?(@default)
16
+ @default
17
+ else
18
+ duplicate(@default)
19
+ end
20
+ end
21
+
22
+ def validate!(params)
23
+ attrs = SingleAttributeIterator.new(self, @scope, params)
24
+ attrs.each do |resource_params, attr_name|
25
+ next unless @scope.meets_dependency?(resource_params, params)
26
+
27
+ validate_param!(attr_name, resource_params) if resource_params.is_a?(Hash) && resource_params[attr_name].nil?
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ # return true if we might be able to dup this object
34
+ def duplicatable?(obj)
35
+ !obj.nil? &&
36
+ obj != true &&
37
+ obj != false &&
38
+ !obj.is_a?(Symbol) &&
39
+ !obj.is_a?(Numeric)
40
+ end
41
+
42
+ # make a best effort to dup the object
43
+ def duplicate(obj)
44
+ obj.dup
45
+ rescue TypeError
46
+ obj
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Validations
5
+ module Validators
6
+ class ExactlyOneOfValidator < MultipleParamsBase
7
+ def validate_params!(params)
8
+ keys = keys_in_common(params)
9
+ return if keys.length == 1
10
+ raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:exactly_one)) if keys.length.zero?
11
+
12
+ raise Grape::Exceptions::Validation.new(params: keys, message: message(:mutual_exclusion))
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Validations
5
+ module Validators
6
+ class ExceptValuesValidator < Base
7
+ def initialize(attrs, options, required, scope, **opts)
8
+ @except = options.is_a?(Hash) ? options[:value] : options
9
+ super
10
+ end
11
+
12
+ def validate_param!(attr_name, params)
13
+ return unless params.respond_to?(:key?) && params.key?(attr_name)
14
+
15
+ excepts = @except.is_a?(Proc) ? @except.call : @except
16
+ return if excepts.nil?
17
+
18
+ param_array = params[attr_name].nil? ? [nil] : Array.wrap(params[attr_name])
19
+ raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:except_values)) if param_array.any? { |param| excepts.include?(param) }
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end