grape 1.8.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 (210) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +65 -1
  3. data/README.md +377 -334
  4. data/UPGRADING.md +231 -6
  5. data/grape.gemspec +6 -10
  6. data/lib/grape/api/instance.rb +13 -10
  7. data/lib/grape/api.rb +17 -8
  8. data/lib/grape/content_types.rb +0 -2
  9. data/lib/grape/cookies.rb +2 -1
  10. data/lib/grape/dry_types.rb +0 -2
  11. data/lib/grape/dsl/desc.rb +23 -21
  12. data/lib/grape/dsl/headers.rb +1 -1
  13. data/lib/grape/dsl/inside_route.rb +47 -22
  14. data/lib/grape/dsl/parameters.rb +4 -3
  15. data/lib/grape/dsl/routing.rb +20 -4
  16. data/lib/grape/dsl/validations.rb +13 -0
  17. data/lib/grape/endpoint.rb +15 -10
  18. data/lib/grape/{util/env.rb → env.rb} +0 -5
  19. data/lib/grape/error_formatter/txt.rb +11 -10
  20. data/lib/grape/exceptions/base.rb +3 -3
  21. data/lib/grape/exceptions/missing_group_type.rb +1 -1
  22. data/lib/grape/exceptions/unsupported_group_type.rb +1 -1
  23. data/lib/grape/exceptions/validation.rb +0 -2
  24. data/lib/grape/exceptions/validation_array_errors.rb +1 -0
  25. data/lib/grape/exceptions/validation_errors.rb +1 -3
  26. data/lib/grape/extensions/hash.rb +5 -1
  27. data/lib/grape/http/headers.rb +18 -24
  28. data/lib/grape/{util/json.rb → json.rb} +1 -3
  29. data/lib/grape/locale/en.yml +3 -0
  30. data/lib/grape/middleware/auth/base.rb +0 -2
  31. data/lib/grape/middleware/auth/dsl.rb +0 -2
  32. data/lib/grape/middleware/auth/strategies.rb +1 -2
  33. data/lib/grape/middleware/base.rb +0 -2
  34. data/lib/grape/middleware/error.rb +55 -50
  35. data/lib/grape/middleware/formatter.rb +21 -18
  36. data/lib/grape/middleware/globals.rb +1 -3
  37. data/lib/grape/middleware/stack.rb +2 -3
  38. data/lib/grape/middleware/versioner/accept_version_header.rb +0 -2
  39. data/lib/grape/middleware/versioner/header.rb +17 -163
  40. data/lib/grape/middleware/versioner/param.rb +2 -4
  41. data/lib/grape/middleware/versioner/path.rb +1 -3
  42. data/lib/grape/namespace.rb +3 -4
  43. data/lib/grape/path.rb +24 -29
  44. data/lib/grape/railtie.rb +9 -0
  45. data/lib/grape/request.rb +3 -5
  46. data/lib/grape/router/base_route.rb +39 -0
  47. data/lib/grape/router/greedy_route.rb +20 -0
  48. data/lib/grape/router/pattern.rb +39 -30
  49. data/lib/grape/router/route.rb +22 -59
  50. data/lib/grape/router.rb +30 -36
  51. data/lib/grape/util/accept/header.rb +19 -0
  52. data/lib/grape/util/accept_header_handler.rb +105 -0
  53. data/lib/grape/util/base_inheritable.rb +4 -4
  54. data/lib/grape/util/cache.rb +0 -3
  55. data/lib/grape/util/endpoint_configuration.rb +1 -1
  56. data/lib/grape/util/header.rb +13 -0
  57. data/lib/grape/util/inheritable_values.rb +0 -2
  58. data/lib/grape/util/lazy/block.rb +29 -0
  59. data/lib/grape/util/lazy/object.rb +45 -0
  60. data/lib/grape/util/lazy/value.rb +38 -0
  61. data/lib/grape/util/lazy/value_array.rb +21 -0
  62. data/lib/grape/util/lazy/value_enumerable.rb +34 -0
  63. data/lib/grape/util/lazy/value_hash.rb +21 -0
  64. data/lib/grape/util/media_type.rb +70 -0
  65. data/lib/grape/util/reverse_stackable_values.rb +1 -6
  66. data/lib/grape/util/stackable_values.rb +1 -6
  67. data/lib/grape/util/strict_hash_configuration.rb +3 -3
  68. data/lib/grape/validations/attributes_doc.rb +38 -36
  69. data/lib/grape/validations/contract_scope.rb +71 -0
  70. data/lib/grape/validations/params_scope.rb +10 -9
  71. data/lib/grape/validations/types/array_coercer.rb +0 -2
  72. data/lib/grape/validations/types/build_coercer.rb +69 -71
  73. data/lib/grape/validations/types/dry_type_coercer.rb +1 -11
  74. data/lib/grape/validations/types/json.rb +0 -2
  75. data/lib/grape/validations/types/primitive_coercer.rb +0 -2
  76. data/lib/grape/validations/types/set_coercer.rb +0 -3
  77. data/lib/grape/validations/types.rb +0 -3
  78. data/lib/grape/validations/validators/base.rb +2 -1
  79. data/lib/grape/validations/validators/default_validator.rb +5 -1
  80. data/lib/grape/validations/validators/length_validator.rb +42 -0
  81. data/lib/grape/validations/validators/values_validator.rb +8 -3
  82. data/lib/grape/validations.rb +3 -7
  83. data/lib/grape/version.rb +1 -1
  84. data/lib/grape/{util/xml.rb → xml.rb} +1 -1
  85. data/lib/grape.rb +38 -269
  86. metadata +33 -274
  87. data/lib/grape/eager_load.rb +0 -20
  88. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +0 -24
  89. data/lib/grape/router/attribute_translator.rb +0 -63
  90. data/lib/grape/util/lazy_block.rb +0 -27
  91. data/lib/grape/util/lazy_object.rb +0 -43
  92. data/lib/grape/util/lazy_value.rb +0 -91
  93. data/spec/grape/api/custom_validations_spec.rb +0 -213
  94. data/spec/grape/api/deeply_included_options_spec.rb +0 -56
  95. data/spec/grape/api/defines_boolean_in_params_spec.rb +0 -38
  96. data/spec/grape/api/documentation_spec.rb +0 -59
  97. data/spec/grape/api/inherited_helpers_spec.rb +0 -114
  98. data/spec/grape/api/instance_spec.rb +0 -103
  99. data/spec/grape/api/invalid_format_spec.rb +0 -45
  100. data/spec/grape/api/namespace_parameters_in_route_spec.rb +0 -38
  101. data/spec/grape/api/nested_helpers_spec.rb +0 -50
  102. data/spec/grape/api/optional_parameters_in_route_spec.rb +0 -43
  103. data/spec/grape/api/parameters_modification_spec.rb +0 -41
  104. data/spec/grape/api/patch_method_helpers_spec.rb +0 -79
  105. data/spec/grape/api/recognize_path_spec.rb +0 -21
  106. data/spec/grape/api/required_parameters_in_route_spec.rb +0 -37
  107. data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +0 -26
  108. data/spec/grape/api/routes_with_requirements_spec.rb +0 -59
  109. data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +0 -41
  110. data/spec/grape/api/shared_helpers_spec.rb +0 -36
  111. data/spec/grape/api_remount_spec.rb +0 -509
  112. data/spec/grape/api_spec.rb +0 -4356
  113. data/spec/grape/dsl/callbacks_spec.rb +0 -45
  114. data/spec/grape/dsl/desc_spec.rb +0 -98
  115. data/spec/grape/dsl/headers_spec.rb +0 -62
  116. data/spec/grape/dsl/helpers_spec.rb +0 -100
  117. data/spec/grape/dsl/inside_route_spec.rb +0 -531
  118. data/spec/grape/dsl/logger_spec.rb +0 -24
  119. data/spec/grape/dsl/middleware_spec.rb +0 -60
  120. data/spec/grape/dsl/parameters_spec.rb +0 -180
  121. data/spec/grape/dsl/request_response_spec.rb +0 -225
  122. data/spec/grape/dsl/routing_spec.rb +0 -275
  123. data/spec/grape/dsl/settings_spec.rb +0 -261
  124. data/spec/grape/dsl/validations_spec.rb +0 -55
  125. data/spec/grape/endpoint/declared_spec.rb +0 -846
  126. data/spec/grape/endpoint_spec.rb +0 -1085
  127. data/spec/grape/entity_spec.rb +0 -336
  128. data/spec/grape/exceptions/base_spec.rb +0 -81
  129. data/spec/grape/exceptions/body_parse_errors_spec.rb +0 -185
  130. data/spec/grape/exceptions/invalid_accept_header_spec.rb +0 -358
  131. data/spec/grape/exceptions/invalid_formatter_spec.rb +0 -15
  132. data/spec/grape/exceptions/invalid_response_spec.rb +0 -11
  133. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +0 -15
  134. data/spec/grape/exceptions/missing_group_type_spec.rb +0 -17
  135. data/spec/grape/exceptions/missing_mime_type_spec.rb +0 -17
  136. data/spec/grape/exceptions/missing_option_spec.rb +0 -15
  137. data/spec/grape/exceptions/unknown_options_spec.rb +0 -15
  138. data/spec/grape/exceptions/unknown_validator_spec.rb +0 -15
  139. data/spec/grape/exceptions/unsupported_group_type_spec.rb +0 -19
  140. data/spec/grape/exceptions/validation_errors_spec.rb +0 -92
  141. data/spec/grape/exceptions/validation_spec.rb +0 -19
  142. data/spec/grape/extensions/param_builders/hash_spec.rb +0 -83
  143. data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +0 -105
  144. data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +0 -79
  145. data/spec/grape/grape_spec.rb +0 -9
  146. data/spec/grape/integration/global_namespace_function_spec.rb +0 -29
  147. data/spec/grape/integration/rack_sendfile_spec.rb +0 -48
  148. data/spec/grape/integration/rack_spec.rb +0 -51
  149. data/spec/grape/loading_spec.rb +0 -44
  150. data/spec/grape/middleware/auth/base_spec.rb +0 -31
  151. data/spec/grape/middleware/auth/dsl_spec.rb +0 -60
  152. data/spec/grape/middleware/auth/strategies_spec.rb +0 -120
  153. data/spec/grape/middleware/base_spec.rb +0 -221
  154. data/spec/grape/middleware/error_spec.rb +0 -85
  155. data/spec/grape/middleware/exception_spec.rb +0 -294
  156. data/spec/grape/middleware/formatter_spec.rb +0 -461
  157. data/spec/grape/middleware/globals_spec.rb +0 -30
  158. data/spec/grape/middleware/stack_spec.rb +0 -155
  159. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +0 -122
  160. data/spec/grape/middleware/versioner/header_spec.rb +0 -345
  161. data/spec/grape/middleware/versioner/param_spec.rb +0 -171
  162. data/spec/grape/middleware/versioner/path_spec.rb +0 -62
  163. data/spec/grape/middleware/versioner_spec.rb +0 -21
  164. data/spec/grape/named_api_spec.rb +0 -19
  165. data/spec/grape/parser_spec.rb +0 -86
  166. data/spec/grape/path_spec.rb +0 -252
  167. data/spec/grape/presenters/presenter_spec.rb +0 -71
  168. data/spec/grape/request_spec.rb +0 -126
  169. data/spec/grape/util/inheritable_setting_spec.rb +0 -242
  170. data/spec/grape/util/inheritable_values_spec.rb +0 -79
  171. data/spec/grape/util/reverse_stackable_values_spec.rb +0 -134
  172. data/spec/grape/util/stackable_values_spec.rb +0 -128
  173. data/spec/grape/util/strict_hash_configuration_spec.rb +0 -38
  174. data/spec/grape/validations/attributes_doc_spec.rb +0 -153
  175. data/spec/grape/validations/instance_behaivour_spec.rb +0 -43
  176. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +0 -38
  177. data/spec/grape/validations/params_scope_spec.rb +0 -1420
  178. data/spec/grape/validations/single_attribute_iterator_spec.rb +0 -56
  179. data/spec/grape/validations/types/array_coercer_spec.rb +0 -33
  180. data/spec/grape/validations/types/primitive_coercer_spec.rb +0 -150
  181. data/spec/grape/validations/types/set_coercer_spec.rb +0 -32
  182. data/spec/grape/validations/types_spec.rb +0 -111
  183. data/spec/grape/validations/validators/all_or_none_spec.rb +0 -162
  184. data/spec/grape/validations/validators/allow_blank_spec.rb +0 -575
  185. data/spec/grape/validations/validators/at_least_one_of_spec.rb +0 -205
  186. data/spec/grape/validations/validators/base_spec.rb +0 -38
  187. data/spec/grape/validations/validators/coerce_spec.rb +0 -1261
  188. data/spec/grape/validations/validators/default_spec.rb +0 -463
  189. data/spec/grape/validations/validators/exactly_one_of_spec.rb +0 -233
  190. data/spec/grape/validations/validators/except_values_spec.rb +0 -192
  191. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +0 -214
  192. data/spec/grape/validations/validators/presence_spec.rb +0 -315
  193. data/spec/grape/validations/validators/regexp_spec.rb +0 -161
  194. data/spec/grape/validations/validators/same_as_spec.rb +0 -57
  195. data/spec/grape/validations/validators/values_spec.rb +0 -733
  196. data/spec/grape/validations/validators/zh-CN.yml +0 -10
  197. data/spec/grape/validations_spec.rb +0 -2030
  198. data/spec/integration/eager_load/eager_load_spec.rb +0 -15
  199. data/spec/integration/multi_json/json_spec.rb +0 -7
  200. data/spec/integration/multi_xml/xml_spec.rb +0 -7
  201. data/spec/shared/deprecated_class_examples.rb +0 -16
  202. data/spec/shared/versioning_examples.rb +0 -215
  203. data/spec/spec_helper.rb +0 -52
  204. data/spec/support/basic_auth_encode_helpers.rb +0 -11
  205. data/spec/support/chunks.rb +0 -14
  206. data/spec/support/content_type_helpers.rb +0 -15
  207. data/spec/support/endpoint_faker.rb +0 -25
  208. data/spec/support/file_streamer.rb +0 -13
  209. data/spec/support/integer_helpers.rb +0 -13
  210. data/spec/support/versioned_helpers.rb +0 -55
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Util
5
+ module Lazy
6
+ class ValueEnumerable < Value
7
+ def [](key)
8
+ if @value_hash[key].nil?
9
+ Value.new(nil).reached_by(access_keys, key)
10
+ else
11
+ @value_hash[key].reached_by(access_keys, key)
12
+ end
13
+ end
14
+
15
+ def fetch(access_keys)
16
+ fetched_keys = access_keys.dup
17
+ value = self[fetched_keys.shift]
18
+ fetched_keys.any? ? value.fetch(fetched_keys) : value
19
+ end
20
+
21
+ def []=(key, value)
22
+ @value_hash[key] = case value
23
+ when Hash
24
+ ValueHash.new(value)
25
+ when Array
26
+ ValueArray.new(value)
27
+ else
28
+ Value.new(value)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Util
5
+ module Lazy
6
+ class ValueHash < ValueEnumerable
7
+ def initialize(hash)
8
+ super
9
+ @value_hash = ActiveSupport::HashWithIndifferentAccess.new
10
+ hash.each do |key, value|
11
+ self[key] = value
12
+ end
13
+ end
14
+
15
+ def evaluate
16
+ @value_hash.transform_values(&:evaluate)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Util
5
+ class MediaType
6
+ attr_reader :type, :subtype, :vendor, :version, :format
7
+
8
+ # based on the HTTP Accept header with the pattern:
9
+ # application/vnd.:vendor-:version+:format
10
+ VENDOR_VERSION_HEADER_REGEX = /\Avnd\.(?<vendor>[a-z0-9.\-_!^]+?)(?:-(?<version>[a-z0-9*.]+))?(?:\+(?<format>[a-z0-9*\-.]+))?\z/.freeze
11
+
12
+ def initialize(type:, subtype:)
13
+ @type = type
14
+ @subtype = subtype
15
+ VENDOR_VERSION_HEADER_REGEX.match(subtype) do |m|
16
+ @vendor = m[:vendor]
17
+ @version = m[:version]
18
+ @format = m[:format]
19
+ end
20
+ end
21
+
22
+ def ==(other)
23
+ eql?(other)
24
+ end
25
+
26
+ def eql?(other)
27
+ self.class == other.class &&
28
+ other.type == type &&
29
+ other.subtype == subtype &&
30
+ other.vendor == vendor &&
31
+ other.version == version &&
32
+ other.format == format
33
+ end
34
+
35
+ def hash
36
+ [self.class, type, subtype, vendor, version, format].hash
37
+ end
38
+
39
+ class << self
40
+ def best_quality(header, available_media_types)
41
+ parse(best_quality_media_type(header, available_media_types))
42
+ end
43
+
44
+ def parse(media_type)
45
+ return if media_type.blank?
46
+
47
+ type, subtype = media_type.split('/', 2)
48
+ return if type.blank? || subtype.blank?
49
+
50
+ new(type: type, subtype: subtype)
51
+ end
52
+
53
+ def match?(media_type)
54
+ return false if media_type.blank?
55
+
56
+ subtype = media_type.split('/', 2).last
57
+ return false if subtype.blank?
58
+
59
+ VENDOR_VERSION_HEADER_REGEX.match?(subtype)
60
+ end
61
+
62
+ def best_quality_media_type(header, available_media_types)
63
+ header.blank? ? available_media_types.first : Rack::Utils.best_q_match(header, available_media_types)
64
+ end
65
+ end
66
+
67
+ private_class_method :best_quality_media_type
68
+ end
69
+ end
70
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'stackable_values'
4
-
5
3
  module Grape
6
4
  module Util
7
5
  class ReverseStackableValues < StackableValues
@@ -10,10 +8,7 @@ module Grape
10
8
  def concat_values(inherited_value, new_value)
11
9
  return inherited_value unless new_value
12
10
 
13
- [].tap do |value|
14
- value.concat(new_value)
15
- value.concat(inherited_value)
16
- end
11
+ new_value + inherited_value
17
12
  end
18
13
  end
19
14
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base_inheritable'
4
-
5
3
  module Grape
6
4
  module Util
7
5
  class StackableValues < BaseInheritable
@@ -31,10 +29,7 @@ module Grape
31
29
  def concat_values(inherited_value, new_value)
32
30
  return inherited_value unless new_value
33
31
 
34
- [].tap do |value|
35
- value.concat(inherited_value)
36
- value.concat(new_value)
37
- end
32
+ inherited_value + new_value
38
33
  end
39
34
  end
40
35
  end
@@ -56,19 +56,19 @@ module Grape
56
56
  def self.nested_settings_methods(setting_name, new_config_class)
57
57
  new_config_class.class_eval do
58
58
  setting_name.each_pair do |key, value|
59
- define_method "#{key}_context" do
59
+ define_method :"#{key}_context" do
60
60
  @contexts[key] ||= Grape::Util::StrictHashConfiguration.config_class(*value).new
61
61
  end
62
62
 
63
63
  define_method key do |&block|
64
- send("#{key}_context").instance_exec(&block)
64
+ send(:"#{key}_context").instance_exec(&block)
65
65
  end
66
66
  end
67
67
 
68
68
  define_method :to_hash do
69
69
  @settings.to_hash.merge(
70
70
  setting_name.each_key.with_object({}) do |k, merge_hash|
71
- merge_hash[k] = send("#{k}_context").to_hash
71
+ merge_hash[k] = send(:"#{k}_context").to_hash
72
72
  end
73
73
  )
74
74
  end
@@ -2,56 +2,58 @@
2
2
 
3
3
  module Grape
4
4
  module Validations
5
- class ParamsScope
6
- # Documents parameters of an endpoint. If documentation isn't needed (for instance, it is an
7
- # internal API), the class only cleans up attributes to avoid junk in RAM.
8
- class AttributesDoc
9
- attr_accessor :type, :values
10
-
11
- # @param api [Grape::API::Instance]
12
- # @param scope [Validations::ParamsScope]
13
- def initialize(api, scope)
14
- @api = api
15
- @scope = scope
16
- @type = type
17
- end
5
+ # Documents parameters of an endpoint. If documentation isn't needed (for instance, it is an
6
+ # internal API), the class only cleans up attributes to avoid junk in RAM.
7
+
8
+ class AttributesDoc
9
+ attr_accessor :type, :values
10
+
11
+ # @param api [Grape::API::Instance]
12
+ # @param scope [Validations::ParamsScope]
13
+ def initialize(api, scope)
14
+ @api = api
15
+ @scope = scope
16
+ @type = type
17
+ end
18
18
 
19
- def extract_details(validations)
20
- details[:required] = validations.key?(:presence)
19
+ def extract_details(validations)
20
+ details[:required] = validations.key?(:presence)
21
21
 
22
- desc = validations.delete(:desc) || validations.delete(:description)
22
+ desc = validations.delete(:desc) || validations.delete(:description)
23
23
 
24
- details[:desc] = desc if desc
24
+ details[:desc] = desc if desc
25
25
 
26
- documentation = validations.delete(:documentation)
26
+ documentation = validations.delete(:documentation)
27
27
 
28
- details[:documentation] = documentation if documentation
28
+ details[:documentation] = documentation if documentation
29
29
 
30
- details[:default] = validations[:default] if validations.key?(:default)
31
- end
30
+ details[:default] = validations[:default] if validations.key?(:default)
32
31
 
33
- def document(attrs)
34
- return if @api.namespace_inheritable(:do_not_document)
32
+ details[:min_length] = validations[:length][:min] if validations.key?(:length) && validations[:length].key?(:min)
33
+ details[:max_length] = validations[:length][:max] if validations.key?(:length) && validations[:length].key?(:max)
34
+ end
35
35
 
36
- details[:type] = type.to_s if type
37
- details[:values] = values if values
36
+ def document(attrs)
37
+ return if @api.namespace_inheritable(:do_not_document)
38
38
 
39
- documented_attrs = attrs.each_with_object({}) do |name, memo|
40
- memo[@scope.full_name(name)] = details
41
- end
39
+ details[:type] = type.to_s if type
40
+ details[:values] = values if values
42
41
 
43
- @api.namespace_stackable(:params, documented_attrs)
42
+ documented_attrs = attrs.each_with_object({}) do |name, memo|
43
+ memo[@scope.full_name(name)] = details
44
44
  end
45
45
 
46
- def required
47
- details[:required]
48
- end
46
+ @api.namespace_stackable(:params, documented_attrs)
47
+ end
48
+
49
+ def required
50
+ details[:required]
51
+ end
49
52
 
50
- protected
53
+ protected
51
54
 
52
- def details
53
- @details ||= {}
54
- end
55
+ def details
56
+ @details ||= {}
55
57
  end
56
58
  end
57
59
  end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grape
4
+ module Validations
5
+ class ContractScope
6
+ # Declare the contract to be used for the endpoint's parameters.
7
+ # @param api [API] the API endpoint to modify.
8
+ # @param contract the contract or schema to be used for validation. Optional.
9
+ # @yield a block yielding a new schema class. Optional.
10
+ def initialize(api, contract = nil, &block)
11
+ # When block is passed, the first arg is either schema or nil.
12
+ contract = Dry::Schema.Params(parent: contract, &block) if block
13
+
14
+ if contract.respond_to?(:schema)
15
+ # It's a Dry::Validation::Contract, then.
16
+ contract = contract.new
17
+ key_map = contract.schema.key_map
18
+ else
19
+ # Dry::Schema::Processor, hopefully.
20
+ key_map = contract.key_map
21
+ end
22
+
23
+ api.namespace_stackable(:contract_key_map, key_map)
24
+
25
+ validator_options = {
26
+ validator_class: Validator,
27
+ opts: { schema: contract }
28
+ }
29
+
30
+ api.namespace_stackable(:validations, validator_options)
31
+ end
32
+
33
+ class Validator
34
+ attr_reader :schema
35
+
36
+ def initialize(*_args, schema:)
37
+ @schema = schema
38
+ end
39
+
40
+ # Validates a given request.
41
+ # @param request [Grape::Request] the request currently being handled
42
+ # @raise [Grape::Exceptions::ValidationArrayErrors] if validation failed
43
+ # @return [void]
44
+ def validate(request)
45
+ res = schema.call(request.params)
46
+
47
+ if res.success?
48
+ request.params.deep_merge!(res.to_h)
49
+ return
50
+ end
51
+
52
+ errors = []
53
+
54
+ res.errors.messages.each do |message|
55
+ full_name = message.path.first.to_s
56
+
57
+ full_name += "[#{message.path[1..].join('][')}]" if message.path.size > 1
58
+
59
+ errors << Grape::Exceptions::Validation.new(params: [full_name], message: message.text)
60
+ end
61
+
62
+ raise Grape::Exceptions::ValidationArrayErrors.new(errors)
63
+ end
64
+
65
+ def fail_fast?
66
+ false
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'attributes_doc'
4
-
5
3
  module Grape
6
4
  module Validations
7
5
  class ParamsScope
@@ -211,11 +209,11 @@ module Grape
211
209
 
212
210
  def require_required_and_optional_fields(context, opts)
213
211
  if context == :all
214
- optional_fields = Array(opts[:except])
215
- required_fields = opts[:using].keys - optional_fields
212
+ optional_fields = Array.wrap(opts[:except])
213
+ required_fields = opts[:using].keys.delete_if { |f| optional_fields.include?(f) }
216
214
  else # context == :none
217
- required_fields = Array(opts[:except])
218
- optional_fields = opts[:using].keys - required_fields
215
+ required_fields = Array.wrap(opts[:except])
216
+ optional_fields = opts[:using].keys.delete_if { |f| required_fields.include?(f) }
219
217
  end
220
218
  required_fields.each do |field|
221
219
  field_opts = opts[:using][field]
@@ -231,7 +229,10 @@ module Grape
231
229
 
232
230
  def require_optional_fields(context, opts)
233
231
  optional_fields = opts[:using].keys
234
- optional_fields -= Array(opts[:except]) unless context == :all
232
+ unless context == :all
233
+ except_fields = Array.wrap(opts[:except])
234
+ optional_fields.delete_if { |f| except_fields.include?(f) }
235
+ end
235
236
  optional_fields.each do |field|
236
237
  field_opts = opts[:using][field]
237
238
  optional(field, field_opts) if field_opts
@@ -266,6 +267,7 @@ module Grape
266
267
  parent: self,
267
268
  optional: optional,
268
269
  type: type || Array,
270
+ group: @group,
269
271
  &block
270
272
  )
271
273
  end
@@ -463,8 +465,7 @@ module Grape
463
465
  raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :values, values) if values && !values.is_a?(Proc) && !Array(default).all? { |def_val| values.include?(def_val) }
464
466
 
465
467
  if except_values && !except_values.is_a?(Proc) && Array(default).any? { |def_val| except_values.include?(def_val) }
466
- raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :except, except_values) \
467
-
468
+ raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :except, except_values)
468
469
  end
469
470
 
470
471
  return unless excepts && !excepts.is_a?(Proc)
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'dry_type_coercer'
4
-
5
3
  module Grape
6
4
  module Validations
7
5
  module Types
@@ -1,94 +1,92 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'array_coercer'
4
- require_relative 'set_coercer'
5
- require_relative 'primitive_coercer'
6
-
7
3
  module Grape
8
4
  module Validations
9
5
  module Types
10
- # Chooses the best coercer for the given type. For example, if the type
11
- # is Integer, it will return a coercer which will be able to coerce a value
12
- # to the integer.
13
- #
14
- # There are a few very special coercers which might be returned.
15
- #
16
- # +Grape::Types::MultipleTypeCoercer+ is a coercer which is returned when
17
- # the given type implies values in an array with different types.
18
- # For example, +[Integer, String]+ allows integer and string values in
19
- # an array.
20
- #
21
- # +Grape::Types::CustomTypeCoercer+ is a coercer which is returned when
22
- # a method is specified by a user with +coerce_with+ option or the user
23
- # specifies a custom type which implements requirments of
24
- # +Grape::Types::CustomTypeCoercer+.
25
- #
26
- # +Grape::Types::CustomTypeCollectionCoercer+ is a very similar to the
27
- # previous one, but it expects an array or set of values having a custom
28
- # type implemented by the user.
29
- #
30
- # There is also a group of custom types implemented by Grape, check
31
- # +Grape::Validations::Types::SPECIAL+ to get the full list.
32
- #
33
- # @param type [Class] the type to which input strings
34
- # should be coerced
35
- # @param method [Class,#call] the coercion method to use
36
- # @return [Object] object to be used
37
- # for coercion and type validation
38
- def self.build_coercer(type, method: nil, strict: false)
39
- cache_instance(type, method, strict) do
40
- create_coercer_instance(type, method, strict)
6
+ module BuildCoercer
7
+ # Chooses the best coercer for the given type. For example, if the type
8
+ # is Integer, it will return a coercer which will be able to coerce a value
9
+ # to the integer.
10
+ #
11
+ # There are a few very special coercers which might be returned.
12
+ #
13
+ # +Grape::Types::MultipleTypeCoercer+ is a coercer which is returned when
14
+ # the given type implies values in an array with different types.
15
+ # For example, +[Integer, String]+ allows integer and string values in
16
+ # an array.
17
+ #
18
+ # +Grape::Types::CustomTypeCoercer+ is a coercer which is returned when
19
+ # a method is specified by a user with +coerce_with+ option or the user
20
+ # specifies a custom type which implements requirments of
21
+ # +Grape::Types::CustomTypeCoercer+.
22
+ #
23
+ # +Grape::Types::CustomTypeCollectionCoercer+ is a very similar to the
24
+ # previous one, but it expects an array or set of values having a custom
25
+ # type implemented by the user.
26
+ #
27
+ # There is also a group of custom types implemented by Grape, check
28
+ # +Grape::Validations::Types::SPECIAL+ to get the full list.
29
+ #
30
+ # @param type [Class] the type to which input strings
31
+ # should be coerced
32
+ # @param method [Class,#call] the coercion method to use
33
+ # @return [Object] object to be used
34
+ # for coercion and type validation
35
+ def self.build_coercer(type, method: nil, strict: false)
36
+ cache_instance(type, method, strict) do
37
+ create_coercer_instance(type, method, strict)
38
+ end
41
39
  end
42
- end
43
40
 
44
- def self.create_coercer_instance(type, method, strict)
45
- # Maps a custom type provided by Grape, it doesn't map types wrapped by collections!!!
46
- type = Types.map_special(type)
41
+ def self.create_coercer_instance(type, method, strict)
42
+ # Maps a custom type provided by Grape, it doesn't map types wrapped by collections!!!
43
+ type = Types.map_special(type)
47
44
 
48
- # Use a special coercer for multiply-typed parameters.
49
- if Types.multiple?(type)
50
- MultipleTypeCoercer.new(type, method)
45
+ # Use a special coercer for multiply-typed parameters.
46
+ if Types.multiple?(type)
47
+ MultipleTypeCoercer.new(type, method)
51
48
 
52
- # Use a special coercer for custom types and coercion methods.
53
- elsif method || Types.custom?(type)
54
- CustomTypeCoercer.new(type, method)
49
+ # Use a special coercer for custom types and coercion methods.
50
+ elsif method || Types.custom?(type)
51
+ CustomTypeCoercer.new(type, method)
55
52
 
56
- # Special coercer for collections of types that implement a parse method.
57
- # CustomTypeCoercer (above) already handles such types when an explicit coercion
58
- # method is supplied.
59
- elsif Types.collection_of_custom?(type)
60
- Types::CustomTypeCollectionCoercer.new(
61
- Types.map_special(type.first), type.is_a?(Set)
62
- )
63
- else
64
- DryTypeCoercer.coercer_instance_for(type, strict)
53
+ # Special coercer for collections of types that implement a parse method.
54
+ # CustomTypeCoercer (above) already handles such types when an explicit coercion
55
+ # method is supplied.
56
+ elsif Types.collection_of_custom?(type)
57
+ Types::CustomTypeCollectionCoercer.new(
58
+ Types.map_special(type.first), type.is_a?(Set)
59
+ )
60
+ else
61
+ DryTypeCoercer.coercer_instance_for(type, strict)
62
+ end
65
63
  end
66
- end
67
64
 
68
- def self.cache_instance(type, method, strict, &_block)
69
- key = cache_key(type, method, strict)
65
+ def self.cache_instance(type, method, strict, &_block)
66
+ key = cache_key(type, method, strict)
70
67
 
71
- return @__cache[key] if @__cache.key?(key)
68
+ return @__cache[key] if @__cache.key?(key)
72
69
 
73
- instance = yield
70
+ instance = yield
74
71
 
75
- @__cache_write_lock.synchronize do
76
- @__cache[key] = instance
77
- end
72
+ @__cache_write_lock.synchronize do
73
+ @__cache[key] = instance
74
+ end
78
75
 
79
- instance
80
- end
76
+ instance
77
+ end
81
78
 
82
- def self.cache_key(type, method, strict)
83
- [type, method, strict].each_with_object(+'_') do |val, memo|
84
- next if val.nil?
79
+ def self.cache_key(type, method, strict)
80
+ [type, method, strict].each_with_object(+'_') do |val, memo|
81
+ next if val.nil?
85
82
 
86
- memo << '_' << val.to_s
83
+ memo << '_' << val.to_s
84
+ end
87
85
  end
88
- end
89
86
 
90
- instance_variable_set(:@__cache, {})
91
- instance_variable_set(:@__cache_write_lock, Mutex.new)
87
+ instance_variable_set(:@__cache, {})
88
+ instance_variable_set(:@__cache_write_lock, Mutex.new)
89
+ end
92
90
  end
93
91
  end
94
92
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry-types'
4
-
5
3
  module DryTypes
6
4
  # Call +Dry.Types()+ to add all registered types to +DryTypes+ which is
7
5
  # a container in this case. Check documentation for more information
@@ -24,9 +22,7 @@ module Grape
24
22
  # collection_coercer_for(Array)
25
23
  # #=> Grape::Validations::Types::ArrayCoercer
26
24
  def collection_coercer_for(type)
27
- collection_coercers.fetch(type) do
28
- DryTypeCoercer.collection_coercers[type] = Grape::Validations::Types.const_get("#{type.name.camelize}Coercer")
29
- end
25
+ Grape::Validations::Types.const_get(:"#{type.name.camelize}Coercer")
30
26
  end
31
27
 
32
28
  # Returns an instance of a coercer for a given type
@@ -37,12 +33,6 @@ module Grape
37
33
  # so we need to figure out the actual type
38
34
  collection_coercer_for(type.class).new(type, strict)
39
35
  end
40
-
41
- protected
42
-
43
- def collection_coercers
44
- @collection_coercers ||= {}
45
- end
46
36
  end
47
37
 
48
38
  def initialize(type, strict = false)
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
4
-
5
3
  module Grape
6
4
  module Validations
7
5
  module Types
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'dry_type_coercer'
4
-
5
3
  module Grape
6
4
  module Validations
7
5
  module Types
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'set'
4
- require_relative 'array_coercer'
5
-
6
3
  module Grape
7
4
  module Validations
8
5
  module Types
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grape/validations/types/json'
4
- require 'grape/validations/types/file'
5
-
6
3
  module Grape
7
4
  module Validations
8
5
  # Module for code related to grape's system for