diff --git a/CHANGELOG.md b/CHANGELOG.md index 1448c755c4..5bea546a85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * [#2193](https://github.com/ruby-grape/grape/pull/2193): Fixed the broken ruby-head NoMethodError spec - [@Jack12816](https://github.com/Jack12816). * [#2192](https://github.com/ruby-grape/grape/pull/2192): Memoize the result of Grape::Middleware::Base#response - [@Jack12816](https://github.com/Jack12816). +* [#2200](https://github.com/ruby-grape/grape/pull/2200): Add validators module to all validators - [@ericproulx](https://github.com/ericproulx). * Your contribution here. ### 1.6.0 (2021/10/04) diff --git a/Gemfile b/Gemfile index 383f71ba0d..fded526d12 100644 --- a/Gemfile +++ b/Gemfile @@ -34,6 +34,7 @@ group :test do gem 'rack-test', '~> 1.1.0' gem 'rspec', '~> 3.0' gem 'ruby-grape-danger', '~> 0.2.0', require: false + gem 'test-prof', require: false end platforms :jruby do diff --git a/gemfiles/multi_json.gemfile b/gemfiles/multi_json.gemfile index 726c8be196..28ee629727 100644 --- a/gemfiles/multi_json.gemfile +++ b/gemfiles/multi_json.gemfile @@ -34,6 +34,7 @@ group :test do gem 'rack-test', '~> 1.1.0' gem 'rspec', '~> 3.0' gem 'ruby-grape-danger', '~> 0.2.0', require: false + gem 'test-prof', require: false end gemspec path: '../' diff --git a/gemfiles/multi_xml.gemfile b/gemfiles/multi_xml.gemfile index 72d4e3f302..9f2631e2f6 100644 --- a/gemfiles/multi_xml.gemfile +++ b/gemfiles/multi_xml.gemfile @@ -34,6 +34,7 @@ group :test do gem 'rack-test', '~> 1.1.0' gem 'rspec', '~> 3.0' gem 'ruby-grape-danger', '~> 0.2.0', require: false + gem 'test-prof', require: false end gemspec path: '../' diff --git a/gemfiles/rack1.gemfile b/gemfiles/rack1.gemfile index bb72144a01..87d0558d66 100644 --- a/gemfiles/rack1.gemfile +++ b/gemfiles/rack1.gemfile @@ -34,6 +34,7 @@ group :test do gem 'rack-test', '~> 1.1.0' gem 'rspec', '~> 3.0' gem 'ruby-grape-danger', '~> 0.2.0', require: false + gem 'test-prof', require: false end gemspec path: '../' diff --git a/gemfiles/rack2.gemfile b/gemfiles/rack2.gemfile index 6522140d99..a40bc53d6a 100644 --- a/gemfiles/rack2.gemfile +++ b/gemfiles/rack2.gemfile @@ -34,6 +34,7 @@ group :test do gem 'rack-test', '~> 1.1.0' gem 'rspec', '~> 3.0' gem 'ruby-grape-danger', '~> 0.2.0', require: false + gem 'test-prof', require: false end gemspec path: '../' diff --git a/gemfiles/rack2_2.gemfile b/gemfiles/rack2_2.gemfile index 88cfa240d0..1c82143308 100644 --- a/gemfiles/rack2_2.gemfile +++ b/gemfiles/rack2_2.gemfile @@ -34,6 +34,7 @@ group :test do gem 'rack-test', '~> 1.1.0' gem 'rspec', '~> 3.0' gem 'ruby-grape-danger', '~> 0.2.0', require: false + gem 'test-prof', require: false end gemspec path: '../' diff --git a/gemfiles/rack_edge.gemfile b/gemfiles/rack_edge.gemfile index 25b275b6c0..06955ea3c5 100644 --- a/gemfiles/rack_edge.gemfile +++ b/gemfiles/rack_edge.gemfile @@ -34,6 +34,7 @@ group :test do gem 'rack-test', '~> 1.1.0' gem 'rspec', '~> 3.0' gem 'ruby-grape-danger', '~> 0.2.0', require: false + gem 'test-prof', require: false end gemspec path: '../' diff --git a/gemfiles/rails_5.gemfile b/gemfiles/rails_5.gemfile index 7dc94e6950..5d7c30e672 100644 --- a/gemfiles/rails_5.gemfile +++ b/gemfiles/rails_5.gemfile @@ -34,6 +34,7 @@ group :test do gem 'rack-test', '~> 1.1.0' gem 'rspec', '~> 3.0' gem 'ruby-grape-danger', '~> 0.2.0', require: false + gem 'test-prof', require: false end gemspec path: '../' diff --git a/gemfiles/rails_6.gemfile b/gemfiles/rails_6.gemfile index 5db9b049df..9ffed6ac9d 100644 --- a/gemfiles/rails_6.gemfile +++ b/gemfiles/rails_6.gemfile @@ -34,6 +34,7 @@ group :test do gem 'rack-test', '~> 1.1.0' gem 'rspec', '~> 3.0' gem 'ruby-grape-danger', '~> 0.2.0', require: false + gem 'test-prof', require: false end gemspec path: '../' diff --git a/gemfiles/rails_6_1.gemfile b/gemfiles/rails_6_1.gemfile index d27f9ed024..d441b184dd 100644 --- a/gemfiles/rails_6_1.gemfile +++ b/gemfiles/rails_6_1.gemfile @@ -34,6 +34,7 @@ group :test do gem 'rack-test', '~> 1.1.0' gem 'rspec', '~> 3.0' gem 'ruby-grape-danger', '~> 0.2.0', require: false + gem 'test-prof', require: false end gemspec path: '../' diff --git a/gemfiles/rails_edge.gemfile b/gemfiles/rails_edge.gemfile index 5d179f078e..e4c5e69192 100644 --- a/gemfiles/rails_edge.gemfile +++ b/gemfiles/rails_edge.gemfile @@ -34,6 +34,7 @@ group :test do gem 'rack-test', '~> 1.1.0' gem 'rspec', '~> 3.0' gem 'ruby-grape-danger', '~> 0.2.0', require: false + gem 'test-prof', require: false end gemspec path: '../' diff --git a/lib/grape/api.rb b/lib/grape/api.rb index 8e4fbbe1d6..e597f257b4 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -10,6 +10,18 @@ class API # Class methods that we want to call on the API rather than on the API object NON_OVERRIDABLE = (Class.new.methods + %i[call call! configuration compile! inherited]).freeze + class Boolean + def self.build(val) + return nil if val != true && val != false + + new + end + end + + class Instance + Boolean = Grape::API::Boolean + end + class << self attr_accessor :base_instance, :instances diff --git a/lib/grape/validations.rb b/lib/grape/validations.rb index bd55c0611f..c0736ef22c 100644 --- a/lib/grape/validations.rb +++ b/lib/grape/validations.rb @@ -1,5 +1,11 @@ # frozen_string_literal: true +require 'grape/validations/attributes_iterator' +require 'grape/validations/single_attribute_iterator' +require 'grape/validations/multiple_attributes_iterator' +require 'grape/validations/params_scope' +require 'grape/validations/types' + module Grape # Registry to store and locate known Validators. module Validations diff --git a/lib/grape/validations/validators/all_or_none.rb b/lib/grape/validations/validators/all_or_none.rb index 385e8ea823..24dc4f8b63 100644 --- a/lib/grape/validations/validators/all_or_none.rb +++ b/lib/grape/validations/validators/all_or_none.rb @@ -4,12 +4,14 @@ module Grape module Validations - class AllOrNoneOfValidator < MultipleParamsBase - def validate_params!(params) - keys = keys_in_common(params) - return if keys.empty? || keys.length == all_keys.length + module Validators + class AllOrNoneOfValidator < MultipleParamsBase + def validate_params!(params) + keys = keys_in_common(params) + return if keys.empty? || keys.length == all_keys.length - raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:all_or_none)) + raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:all_or_none)) + end end end end diff --git a/lib/grape/validations/validators/allow_blank.rb b/lib/grape/validations/validators/allow_blank.rb index e212c273ca..c35753ed3f 100644 --- a/lib/grape/validations/validators/allow_blank.rb +++ b/lib/grape/validations/validators/allow_blank.rb @@ -2,16 +2,18 @@ module Grape module Validations - class AllowBlankValidator < Base - def validate_param!(attr_name, params) - return if (options_key?(:value) ? @option[:value] : @option) || !params.is_a?(Hash) + module Validators + class AllowBlankValidator < Base + def validate_param!(attr_name, params) + return if (options_key?(:value) ? @option[:value] : @option) || !params.is_a?(Hash) - value = params[attr_name] - value = value.strip if value.respond_to?(:strip) + value = params[attr_name] + value = value.strip if value.respond_to?(:strip) - return if value == false || value.present? + return if value == false || value.present? - raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:blank)) + raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:blank)) + end end end end diff --git a/lib/grape/validations/validators/as.rb b/lib/grape/validations/validators/as.rb index 78f8e592e5..8a3d8db166 100644 --- a/lib/grape/validations/validators/as.rb +++ b/lib/grape/validations/validators/as.rb @@ -2,11 +2,13 @@ module Grape module Validations - class AsValidator < Base - # We use a validator for renaming parameters. This is just a marker for - # the parameter scope to handle the renaming. No actual validation - # happens here. - def validate_param!(*); end + module Validators + class AsValidator < Base + # We use a validator for renaming parameters. This is just a marker for + # the parameter scope to handle the renaming. No actual validation + # happens here. + def validate_param!(*); end + end end end end diff --git a/lib/grape/validations/validators/at_least_one_of.rb b/lib/grape/validations/validators/at_least_one_of.rb index 61e1d30c7f..6fedbef464 100644 --- a/lib/grape/validations/validators/at_least_one_of.rb +++ b/lib/grape/validations/validators/at_least_one_of.rb @@ -4,11 +4,13 @@ module Grape module Validations - class AtLeastOneOfValidator < MultipleParamsBase - def validate_params!(params) - return unless keys_in_common(params).empty? + module Validators + class AtLeastOneOfValidator < MultipleParamsBase + def validate_params!(params) + return unless keys_in_common(params).empty? - raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:at_least_one)) + raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:at_least_one)) + end end end end diff --git a/lib/grape/validations/validators/base.rb b/lib/grape/validations/validators/base.rb index 40fce8d078..aaa06dc379 100644 --- a/lib/grape/validations/validators/base.rb +++ b/lib/grape/validations/validators/base.rb @@ -2,91 +2,93 @@ module Grape module Validations - class Base - attr_reader :attrs + module Validators + class Base + attr_reader :attrs - # Creates a new Validator from options specified - # by a +requires+ or +optional+ directive during - # parameter definition. - # @param attrs [Array] names of attributes to which the Validator applies - # @param options [Object] implementation-dependent Validator options - # @param required [Boolean] attribute(s) are required or optional - # @param scope [ParamsScope] parent scope for this Validator - # @param opts [Array] additional validation options - def initialize(attrs, options, required, scope, *opts) - @attrs = Array(attrs) - @option = options - @required = required - @scope = scope - opts = opts.any? ? opts.shift : {} - @fail_fast = opts.fetch(:fail_fast, false) - @allow_blank = opts.fetch(:allow_blank, false) - end + # Creates a new Validator from options specified + # by a +requires+ or +optional+ directive during + # parameter definition. + # @param attrs [Array] names of attributes to which the Validator applies + # @param options [Object] implementation-dependent Validator options + # @param required [Boolean] attribute(s) are required or optional + # @param scope [ParamsScope] parent scope for this Validator + # @param opts [Array] additional validation options + def initialize(attrs, options, required, scope, *opts) + @attrs = Array(attrs) + @option = options + @required = required + @scope = scope + opts = opts.any? ? opts.shift : {} + @fail_fast = opts.fetch(:fail_fast, false) + @allow_blank = opts.fetch(:allow_blank, false) + end - # Validates a given request. - # @note Override #validate! unless you need to access the entire request. - # @param request [Grape::Request] the request currently being handled - # @raise [Grape::Exceptions::Validation] if validation failed - # @return [void] - def validate(request) - return unless @scope.should_validate?(request.params) + # Validates a given request. + # @note Override #validate! unless you need to access the entire request. + # @param request [Grape::Request] the request currently being handled + # @raise [Grape::Exceptions::Validation] if validation failed + # @return [void] + def validate(request) + return unless @scope.should_validate?(request.params) - validate!(request.params) - end + validate!(request.params) + end - # Validates a given parameter hash. - # @note Override #validate if you need to access the entire request. - # @param params [Hash] parameters to validate - # @raise [Grape::Exceptions::Validation] if validation failed - # @return [void] - def validate!(params) - attributes = SingleAttributeIterator.new(self, @scope, params) - # we collect errors inside array because - # there may be more than one error per field - array_errors = [] + # Validates a given parameter hash. + # @note Override #validate if you need to access the entire request. + # @param params [Hash] parameters to validate + # @raise [Grape::Exceptions::Validation] if validation failed + # @return [void] + def validate!(params) + attributes = SingleAttributeIterator.new(self, @scope, params) + # we collect errors inside array because + # there may be more than one error per field + array_errors = [] - attributes.each do |val, attr_name, empty_val, skip_value| - next if skip_value - next if !@scope.required? && empty_val - next unless @scope.meets_dependency?(val, params) + attributes.each do |val, attr_name, empty_val, skip_value| + next if skip_value + next if !@scope.required? && empty_val + next unless @scope.meets_dependency?(val, params) - begin - validate_param!(attr_name, val) if @required || (val.respond_to?(:key?) && val.key?(attr_name)) - rescue Grape::Exceptions::Validation => e - array_errors << e + begin + validate_param!(attr_name, val) if @required || (val.respond_to?(:key?) && val.key?(attr_name)) + rescue Grape::Exceptions::Validation => e + array_errors << e + end end - end - raise Grape::Exceptions::ValidationArrayErrors.new(array_errors) if array_errors.any? - end + raise Grape::Exceptions::ValidationArrayErrors.new(array_errors) if array_errors.any? + end - def self.convert_to_short_name(klass) - ret = klass.name.gsub(/::/, '/') - ret.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2') - ret.gsub!(/([a-z\d])([A-Z])/, '\1_\2') - ret.tr!('-', '_') - ret.downcase! - File.basename(ret, '_validator') - end + def self.convert_to_short_name(klass) + ret = klass.name.gsub(/::/, '/') + ret.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2') + ret.gsub!(/([a-z\d])([A-Z])/, '\1_\2') + ret.tr!('-', '_') + ret.downcase! + File.basename(ret, '_validator') + end - def self.inherited(klass) - return unless klass.name.present? + def self.inherited(klass) + return unless klass.name.present? - Validations.register_validator(convert_to_short_name(klass), klass) - end + Validations.register_validator(convert_to_short_name(klass), klass) + end - def message(default_key = nil) - options = instance_variable_get(:@option) - options_key?(:message) ? options[:message] : default_key - end + def message(default_key = nil) + options = instance_variable_get(:@option) + options_key?(:message) ? options[:message] : default_key + end - def options_key?(key, options = nil) - options = instance_variable_get(:@option) if options.nil? - options.respond_to?(:key?) && options.key?(key) && !options[key].nil? - end + def options_key?(key, options = nil) + options = instance_variable_get(:@option) if options.nil? + options.respond_to?(:key?) && options.key?(key) && !options[key].nil? + end - def fail_fast? - @fail_fast + def fail_fast? + @fail_fast + end end end end diff --git a/lib/grape/validations/validators/coerce.rb b/lib/grape/validations/validators/coerce.rb index edbc6a06d1..979ad47c67 100644 --- a/lib/grape/validations/validators/coerce.rb +++ b/lib/grape/validations/validators/coerce.rb @@ -1,86 +1,74 @@ # frozen_string_literal: true module Grape - class API - class Boolean - def self.build(val) - return nil if val != true && val != false - - new - end - end - - class Instance - Boolean = Grape::API::Boolean - end - end - module Validations - class CoerceValidator < Base - def initialize(attrs, options, required, scope, **opts) - super - - @converter = if type.is_a?(Grape::Validations::Types::VariantCollectionCoercer) - type - else - Types.build_coercer(type, method: @option[:method]) - end - end - - def validate_param!(attr_name, params) - raise validation_exception(attr_name) unless params.is_a? Hash - - new_value = coerce_value(params[attr_name]) - - raise validation_exception(attr_name, new_value.message) unless valid_type?(new_value) - - # Don't assign a value if it is identical. It fixes a problem with Hashie::Mash - # which looses wrappers for hashes and arrays after reassigning values + module Validators + class CoerceValidator < Base + def initialize(attrs, options, required, scope, **opts) + super + + @converter = if type.is_a?(Grape::Validations::Types::VariantCollectionCoercer) + type + else + Types.build_coercer(type, method: @option[:method]) + end + end + + def validate_param!(attr_name, params) + raise validation_exception(attr_name) unless params.is_a? Hash + + new_value = coerce_value(params[attr_name]) + + raise validation_exception(attr_name, new_value.message) unless valid_type?(new_value) + + # Don't assign a value if it is identical. It fixes a problem with Hashie::Mash + # which looses wrappers for hashes and arrays after reassigning values + # + # h = Hashie::Mash.new(list: [1, 2, 3, 4]) + # => #> + # list = h.list + # h[:list] = list + # h + # => # + return if params[attr_name].instance_of?(new_value.class) && params[attr_name] == new_value + + params[attr_name] = new_value + end + + private + + # @!attribute [r] converter + # Object that will be used for parameter coercion and type checking. # - # h = Hashie::Mash.new(list: [1, 2, 3, 4]) - # => #> - # list = h.list - # h[:list] = list - # h - # => # - return if params[attr_name].instance_of?(new_value.class) && params[attr_name] == new_value - - params[attr_name] = new_value - end - - private - - # @!attribute [r] converter - # Object that will be used for parameter coercion and type checking. - # - # See {Types.build_coercer} - # - # @return [Object] - attr_reader :converter - - def valid_type?(val) - !val.is_a?(Types::InvalidValue) - end + # See {Types.build_coercer} + # + # @return [Object] + attr_reader :converter - def coerce_value(val) - converter.call(val) - # Some custom types might fail, so it should be treated as an invalid value - rescue StandardError - Types::InvalidValue.new - end + def valid_type?(val) + !val.is_a?(Types::InvalidValue) + end - # Type to which the parameter will be coerced. - # - # @return [Class] - def type - @option[:type].is_a?(Hash) ? @option[:type][:value] : @option[:type] - end + def coerce_value(val) + converter.call(val) + # Some custom types might fail, so it should be treated as an invalid value + rescue StandardError + Types::InvalidValue.new + end - def validation_exception(attr_name, custom_msg = nil) - Grape::Exceptions::Validation.new( - params: [@scope.full_name(attr_name)], - message: custom_msg || message(:coerce) - ) + # Type to which the parameter will be coerced. + # + # @return [Class] + def type + @option[:type].is_a?(Hash) ? @option[:type][:value] : @option[:type] + end + + def validation_exception(attr_name, custom_msg = nil) + Grape::Exceptions::Validation.new( + params: [@scope.full_name(attr_name)], + message: custom_msg || message(:coerce) + ) + end end end end diff --git a/lib/grape/validations/validators/default.rb b/lib/grape/validations/validators/default.rb index 5f004d685d..8ed5936756 100644 --- a/lib/grape/validations/validators/default.rb +++ b/lib/grape/validations/validators/default.rb @@ -2,47 +2,49 @@ module Grape module Validations - class DefaultValidator < Base - def initialize(attrs, options, required, scope, **opts) - @default = options - super - end + module Validators + class DefaultValidator < Base + def initialize(attrs, options, required, scope, **opts) + @default = options + super + end - def validate_param!(attr_name, params) - params[attr_name] = if @default.is_a? Proc - @default.call - elsif @default.frozen? || !duplicatable?(@default) - @default - else - duplicate(@default) - end - end + def validate_param!(attr_name, params) + params[attr_name] = if @default.is_a? Proc + @default.call + elsif @default.frozen? || !duplicatable?(@default) + @default + else + duplicate(@default) + end + end - def validate!(params) - attrs = SingleAttributeIterator.new(self, @scope, params) - attrs.each do |resource_params, attr_name| - next unless @scope.meets_dependency?(resource_params, params) + def validate!(params) + attrs = SingleAttributeIterator.new(self, @scope, params) + attrs.each do |resource_params, attr_name| + next unless @scope.meets_dependency?(resource_params, params) - validate_param!(attr_name, resource_params) if resource_params.is_a?(Hash) && resource_params[attr_name].nil? + validate_param!(attr_name, resource_params) if resource_params.is_a?(Hash) && resource_params[attr_name].nil? + end end - end - private + private - # return true if we might be able to dup this object - def duplicatable?(obj) - !obj.nil? && - obj != true && - obj != false && - !obj.is_a?(Symbol) && - !obj.is_a?(Numeric) - end + # return true if we might be able to dup this object + def duplicatable?(obj) + !obj.nil? && + obj != true && + obj != false && + !obj.is_a?(Symbol) && + !obj.is_a?(Numeric) + end - # make a best effort to dup the object - def duplicate(obj) - obj.dup - rescue TypeError - obj + # make a best effort to dup the object + def duplicate(obj) + obj.dup + rescue TypeError + obj + end end end end diff --git a/lib/grape/validations/validators/exactly_one_of.rb b/lib/grape/validations/validators/exactly_one_of.rb index 2a6f956141..84d6142fbf 100644 --- a/lib/grape/validations/validators/exactly_one_of.rb +++ b/lib/grape/validations/validators/exactly_one_of.rb @@ -4,13 +4,15 @@ module Grape module Validations - class ExactlyOneOfValidator < MultipleParamsBase - def validate_params!(params) - keys = keys_in_common(params) - return if keys.length == 1 - raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:exactly_one)) if keys.length.zero? + module Validators + class ExactlyOneOfValidator < MultipleParamsBase + def validate_params!(params) + keys = keys_in_common(params) + return if keys.length == 1 + raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:exactly_one)) if keys.length.zero? - raise Grape::Exceptions::Validation.new(params: keys, message: message(:mutual_exclusion)) + raise Grape::Exceptions::Validation.new(params: keys, message: message(:mutual_exclusion)) + end end end end diff --git a/lib/grape/validations/validators/except_values.rb b/lib/grape/validations/validators/except_values.rb index 5ba1e306b3..b125c12cfb 100644 --- a/lib/grape/validations/validators/except_values.rb +++ b/lib/grape/validations/validators/except_values.rb @@ -2,20 +2,22 @@ module Grape module Validations - class ExceptValuesValidator < Base - def initialize(attrs, options, required, scope, **opts) - @except = options.is_a?(Hash) ? options[:value] : options - super - end + module Validators + class ExceptValuesValidator < Base + def initialize(attrs, options, required, scope, **opts) + @except = options.is_a?(Hash) ? options[:value] : options + super + end - def validate_param!(attr_name, params) - return unless params.respond_to?(:key?) && params.key?(attr_name) + def validate_param!(attr_name, params) + return unless params.respond_to?(:key?) && params.key?(attr_name) - excepts = @except.is_a?(Proc) ? @except.call : @except - return if excepts.nil? + excepts = @except.is_a?(Proc) ? @except.call : @except + return if excepts.nil? - param_array = params[attr_name].nil? ? [nil] : Array.wrap(params[attr_name]) - raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:except_values)) if param_array.any? { |param| excepts.include?(param) } + param_array = params[attr_name].nil? ? [nil] : Array.wrap(params[attr_name]) + raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:except_values)) if param_array.any? { |param| excepts.include?(param) } + end end end end diff --git a/lib/grape/validations/validators/multiple_params_base.rb b/lib/grape/validations/validators/multiple_params_base.rb index 1a3f44bd2d..c0b02ac50f 100644 --- a/lib/grape/validations/validators/multiple_params_base.rb +++ b/lib/grape/validations/validators/multiple_params_base.rb @@ -2,34 +2,36 @@ module Grape module Validations - class MultipleParamsBase < Base - def validate!(params) - attributes = MultipleAttributesIterator.new(self, @scope, params) - array_errors = [] - - attributes.each do |resource_params, skip_value| - next if skip_value - - begin - validate_params!(resource_params) - rescue Grape::Exceptions::Validation => e - array_errors << e + module Validators + class MultipleParamsBase < Base + def validate!(params) + attributes = MultipleAttributesIterator.new(self, @scope, params) + array_errors = [] + + attributes.each do |resource_params, skip_value| + next if skip_value + + begin + validate_params!(resource_params) + rescue Grape::Exceptions::Validation => e + array_errors << e + end end - end - raise Grape::Exceptions::ValidationArrayErrors.new(array_errors) if array_errors.any? - end + raise Grape::Exceptions::ValidationArrayErrors.new(array_errors) if array_errors.any? + end - private + private - def keys_in_common(resource_params) - return [] unless resource_params.is_a?(Hash) + def keys_in_common(resource_params) + return [] unless resource_params.is_a?(Hash) - all_keys & resource_params.keys.map! { |attr| @scope.full_name(attr) } - end + all_keys & resource_params.keys.map! { |attr| @scope.full_name(attr) } + end - def all_keys - attrs.map { |attr| @scope.full_name(attr) } + def all_keys + attrs.map { |attr| @scope.full_name(attr) } + end end end end diff --git a/lib/grape/validations/validators/mutual_exclusion.rb b/lib/grape/validations/validators/mutual_exclusion.rb index e2817bb1d2..e0f49278b4 100644 --- a/lib/grape/validations/validators/mutual_exclusion.rb +++ b/lib/grape/validations/validators/mutual_exclusion.rb @@ -4,12 +4,14 @@ module Grape module Validations - class MutualExclusionValidator < MultipleParamsBase - def validate_params!(params) - keys = keys_in_common(params) - return if keys.length <= 1 + module Validators + class MutualExclusionValidator < MultipleParamsBase + def validate_params!(params) + keys = keys_in_common(params) + return if keys.length <= 1 - raise Grape::Exceptions::Validation.new(params: keys, message: message(:mutual_exclusion)) + raise Grape::Exceptions::Validation.new(params: keys, message: message(:mutual_exclusion)) + end end end end diff --git a/lib/grape/validations/validators/presence.rb b/lib/grape/validations/validators/presence.rb index a75d3d0d6c..ae31dc3fbe 100644 --- a/lib/grape/validations/validators/presence.rb +++ b/lib/grape/validations/validators/presence.rb @@ -2,11 +2,13 @@ module Grape module Validations - class PresenceValidator < Base - def validate_param!(attr_name, params) - return if params.respond_to?(:key?) && params.key?(attr_name) + module Validators + class PresenceValidator < Base + def validate_param!(attr_name, params) + return if params.respond_to?(:key?) && params.key?(attr_name) - raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:presence)) + raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:presence)) + end end end end diff --git a/lib/grape/validations/validators/regexp.rb b/lib/grape/validations/validators/regexp.rb index e796d3d579..ce7af87b60 100644 --- a/lib/grape/validations/validators/regexp.rb +++ b/lib/grape/validations/validators/regexp.rb @@ -2,12 +2,14 @@ module Grape module Validations - class RegexpValidator < Base - def validate_param!(attr_name, params) - return unless params.respond_to?(:key?) && params.key?(attr_name) - return if Array.wrap(params[attr_name]).all? { |param| param.nil? || param.to_s.match?((options_key?(:value) ? @option[:value] : @option)) } + module Validators + class RegexpValidator < Base + def validate_param!(attr_name, params) + return unless params.respond_to?(:key?) && params.key?(attr_name) + return if Array.wrap(params[attr_name]).all? { |param| param.nil? || param.to_s.match?((options_key?(:value) ? @option[:value] : @option)) } - raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:regexp)) + raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:regexp)) + end end end end diff --git a/lib/grape/validations/validators/same_as.rb b/lib/grape/validations/validators/same_as.rb index ae98617c6d..5a65afa608 100644 --- a/lib/grape/validations/validators/same_as.rb +++ b/lib/grape/validations/validators/same_as.rb @@ -2,24 +2,26 @@ module Grape module Validations - class SameAsValidator < Base - def validate_param!(attr_name, params) - confirmation = options_key?(:value) ? @option[:value] : @option - return if params[attr_name] == params[confirmation] + module Validators + class SameAsValidator < Base + def validate_param!(attr_name, params) + confirmation = options_key?(:value) ? @option[:value] : @option + return if params[attr_name] == params[confirmation] - raise Grape::Exceptions::Validation.new( - params: [@scope.full_name(attr_name)], - message: build_message - ) - end + raise Grape::Exceptions::Validation.new( + params: [@scope.full_name(attr_name)], + message: build_message + ) + end - private + private - def build_message - if options_key?(:message) - @option[:message] - else - format I18n.t(:same_as, scope: 'grape.errors.messages'), parameter: @option + def build_message + if options_key?(:message) + @option[:message] + else + format I18n.t(:same_as, scope: 'grape.errors.messages'), parameter: @option + end end end end diff --git a/lib/grape/validations/validators/values.rb b/lib/grape/validations/validators/values.rb index b72ffd4c35..b8b05fb6b3 100644 --- a/lib/grape/validations/validators/values.rb +++ b/lib/grape/validations/validators/values.rb @@ -2,84 +2,86 @@ module Grape module Validations - class ValuesValidator < Base - def initialize(attrs, options, required, scope, **opts) - if options.is_a?(Hash) - @excepts = options[:except] - @values = options[:value] - @proc = options[:proc] - - warn '[DEPRECATION] The values validator except option is deprecated. ' \ - 'Use the except validator instead.' if @excepts - - raise ArgumentError, 'proc must be a Proc' if @proc && !@proc.is_a?(Proc) - - warn '[DEPRECATION] The values validator proc option is deprecated. ' \ - 'The lambda expression can now be assigned directly to values.' if @proc - else - @excepts = nil - @values = nil - @proc = nil - @values = options + module Validators + class ValuesValidator < Base + def initialize(attrs, options, required, scope, **opts) + if options.is_a?(Hash) + @excepts = options[:except] + @values = options[:value] + @proc = options[:proc] + + warn '[DEPRECATION] The values validator except option is deprecated. ' \ + 'Use the except validator instead.' if @excepts + + raise ArgumentError, 'proc must be a Proc' if @proc && !@proc.is_a?(Proc) + + warn '[DEPRECATION] The values validator proc option is deprecated. ' \ + 'The lambda expression can now be assigned directly to values.' if @proc + else + @excepts = nil + @values = nil + @proc = nil + @values = options + end + super end - super - end - def validate_param!(attr_name, params) - return unless params.is_a?(Hash) + def validate_param!(attr_name, params) + return unless params.is_a?(Hash) - val = params[attr_name] + val = params[attr_name] - return if val.nil? && !required_for_root_scope? + return if val.nil? && !required_for_root_scope? - # don't forget that +false.blank?+ is true - return if val != false && val.blank? && @allow_blank + # don't forget that +false.blank?+ is true + return if val != false && val.blank? && @allow_blank - param_array = val.nil? ? [nil] : Array.wrap(val) + param_array = val.nil? ? [nil] : Array.wrap(val) - raise validation_exception(attr_name, except_message) \ + raise validation_exception(attr_name, except_message) \ unless check_excepts(param_array) - raise validation_exception(attr_name, message(:values)) \ + raise validation_exception(attr_name, message(:values)) \ unless check_values(param_array, attr_name) - raise validation_exception(attr_name, message(:values)) \ + raise validation_exception(attr_name, message(:values)) \ if @proc && !param_array.all? { |param| @proc.call(param) } - end + end - private + private - def check_values(param_array, attr_name) - values = @values.is_a?(Proc) && @values.arity.zero? ? @values.call : @values - return true if values.nil? + def check_values(param_array, attr_name) + values = @values.is_a?(Proc) && @values.arity.zero? ? @values.call : @values + return true if values.nil? - begin - return param_array.all? { |param| values.call(param) } if values.is_a? Proc - rescue StandardError => e - warn "Error '#{e}' raised while validating attribute '#{attr_name}'" - return false + begin + return param_array.all? { |param| values.call(param) } if values.is_a? Proc + rescue StandardError => e + warn "Error '#{e}' raised while validating attribute '#{attr_name}'" + return false + end + param_array.all? { |param| values.include?(param) } end - param_array.all? { |param| values.include?(param) } - end - def check_excepts(param_array) - excepts = @excepts.is_a?(Proc) ? @excepts.call : @excepts - return true if excepts.nil? + def check_excepts(param_array) + excepts = @excepts.is_a?(Proc) ? @excepts.call : @excepts + return true if excepts.nil? - param_array.none? { |param| excepts.include?(param) } - end + param_array.none? { |param| excepts.include?(param) } + end - def except_message - options = instance_variable_get(:@option) - options_key?(:except_message) ? options[:except_message] : message(:except_values) - end + def except_message + options = instance_variable_get(:@option) + options_key?(:except_message) ? options[:except_message] : message(:except_values) + end - def required_for_root_scope? - @required && @scope.root? - end + def required_for_root_scope? + @required && @scope.root? + end - def validation_exception(attr_name, message) - Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message) + def validation_exception(attr_name, message) + Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message) + end end end end diff --git a/spec/grape/api/custom_validations_spec.rb b/spec/grape/api/custom_validations_spec.rb index 8a5b1ec5cf..f9000ec652 100644 --- a/spec/grape/api/custom_validations_spec.rb +++ b/spec/grape/api/custom_validations_spec.rb @@ -4,18 +4,25 @@ describe Grape::Validations do context 'using a custom length validator' do - before do - module CustomValidationsSpec - class DefaultLength < Grape::Validations::Base - def validate_param!(attr_name, params) - @option = params[:max].to_i if params.key?(:max) - return if params[attr_name].length <= @option - - raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: "must be at the most #{@option} characters long") - end + let(:default_length_validator) do + Class.new(Grape::Validations::Validators::Base) do + def validate_param!(attr_name, params) + @option = params[:max].to_i if params.key?(:max) + return if params[attr_name].length <= @option + + raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: "must be at the most #{@option} characters long") end end end + + before do + Grape::Validations.register_validator('default_length', default_length_validator) + end + + after do + Grape::Validations.deregister_validator('default_length') + end + subject do Class.new(Grape::API) do params do @@ -49,15 +56,22 @@ def app end context 'using a custom body-only validator' do - before do - module CustomValidationsSpec - class InBody < Grape::Validations::PresenceValidator - def validate(request) - validate!(request.env['api.request.body']) - end + let(:in_body_validator) do + Class.new(Grape::Validations::Validators::PresenceValidator) do + def validate(request) + validate!(request.env['api.request.body']) end end end + + before do + Grape::Validations.register_validator('in_body', in_body_validator) + end + + after do + Grape::Validations.deregister_validator('in_body') + end + subject do Class.new(Grape::API) do params do @@ -86,15 +100,22 @@ def app end context 'using a custom validator with message_key' do - before do - module CustomValidationsSpec - class WithMessageKey < Grape::Validations::PresenceValidator - def validate_param!(attr_name, _params) - raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: :presence) - end + let(:message_key_validator) do + Class.new(Grape::Validations::Validators::PresenceValidator) do + def validate_param!(attr_name, _params) + raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: :presence) end end end + + before do + Grape::Validations.register_validator('with_message_key', message_key_validator) + end + + after do + Grape::Validations.deregister_validator('with_message_key') + end + subject do Class.new(Grape::API) do params do @@ -118,22 +139,29 @@ def app end context 'using a custom request/param validator' do - before do - module CustomValidationsSpec - class Admin < Grape::Validations::Base - def validate(request) - # return if the param we are checking was not in request - # @attrs is a list containing the attribute we are currently validating - return unless request.params.key? @attrs.first - # check if admin flag is set to true - return unless @option - # check if user is admin or not - # as an example get a token from request and check if it's admin or not - raise Grape::Exceptions::Validation.new(params: @attrs, message: 'Can not set Admin only field.') unless request.headers['X-Access-Token'] == 'admin' - end + let(:admin_validator) do + Class.new(Grape::Validations::Validators::Base) do + def validate(request) + # return if the param we are checking was not in request + # @attrs is a list containing the attribute we are currently validating + return unless request.params.key? @attrs.first + # check if admin flag is set to true + return unless @option + # check if user is admin or not + # as an example get a token from request and check if it's admin or not + raise Grape::Exceptions::Validation.new(params: @attrs, message: 'Can not set Admin only field.') unless request.headers['X-Access-Token'] == 'admin' end end end + + before do + Grape::Validations.register_validator('admin', admin_validator) + end + + after do + Grape::Validations.deregister_validator('admin') + end + subject do Class.new(Grape::API) do params do diff --git a/spec/grape/validations/instance_behaivour_spec.rb b/spec/grape/validations/instance_behaivour_spec.rb index 9f2038dce3..f6ba28c94c 100644 --- a/spec/grape/validations/instance_behaivour_spec.rb +++ b/spec/grape/validations/instance_behaivour_spec.rb @@ -4,7 +4,7 @@ describe 'Validator with instance variables' do let(:validator_type) do - Class.new(Grape::Validations::Base) do + Class.new(Grape::Validations::Validators::Base) do def validate_param!(_attr_name, _params) if instance_variable_defined?(:@instance_variable) && @instance_variable raise Grape::Exceptions::Validation.new(params: ['params'], diff --git a/spec/grape/validations/validators/all_or_none_spec.rb b/spec/grape/validations/validators/all_or_none_spec.rb index ce4124946a..4623d88c67 100644 --- a/spec/grape/validations/validators/all_or_none_spec.rb +++ b/spec/grape/validations/validators/all_or_none_spec.rb @@ -2,73 +2,67 @@ require 'spec_helper' -describe Grape::Validations::AllOrNoneOfValidator do - describe '#validate!' do - subject(:validate) { post path, params } - - module ValidationsSpec - module AllOrNoneOfValidatorSpec - class API < Grape::API - rescue_from Grape::Exceptions::ValidationErrors do |e| - error!(e.errors.transform_keys! { |key| key.join(',') }, 400) - end +describe Grape::Validations::Validators::AllOrNoneOfValidator do + let_it_be(:app) do + Class.new(Grape::API) do + rescue_from Grape::Exceptions::ValidationErrors do |e| + error!(e.errors.transform_keys! { |key| key.join(',') }, 400) + end - params do - optional :beer, :wine, type: Boolean - all_or_none_of :beer, :wine - end - post do - end + params do + optional :beer, :wine, type: Grape::API::Boolean + all_or_none_of :beer, :wine + end + post do + end - params do - optional :beer, :wine, :other, type: Boolean - all_or_none_of :beer, :wine - end - post 'mixed-params' do - end + params do + optional :beer, :wine, :other, type: Grape::API::Boolean + all_or_none_of :beer, :wine + end + post 'mixed-params' do + end - params do - optional :beer, :wine, type: Boolean - all_or_none_of :beer, :wine, message: 'choose all or none' - end - post '/custom-message' do - end + params do + optional :beer, :wine, type: Grape::API::Boolean + all_or_none_of :beer, :wine, message: 'choose all or none' + end + post '/custom-message' do + end - params do - requires :item, type: Hash do - optional :beer, :wine, type: Boolean - all_or_none_of :beer, :wine - end - end - post '/nested-hash' do - end + params do + requires :item, type: Hash do + optional :beer, :wine, type: Grape::API::Boolean + all_or_none_of :beer, :wine + end + end + post '/nested-hash' do + end - params do - requires :items, type: Array do - optional :beer, :wine, type: Boolean - all_or_none_of :beer, :wine - end - end - post '/nested-array' do - end + params do + requires :items, type: Array do + optional :beer, :wine, type: Grape::API::Boolean + all_or_none_of :beer, :wine + end + end + post '/nested-array' do + end - params do - requires :items, type: Array do - requires :nested_items, type: Array do - optional :beer, :wine, type: Boolean - all_or_none_of :beer, :wine - end - end - end - post '/deeply-nested-array' do + params do + requires :items, type: Array do + requires :nested_items, type: Array do + optional :beer, :wine, type: Grape::API::Boolean + all_or_none_of :beer, :wine end end end + post '/deeply-nested-array' do + end end + end - def app - ValidationsSpec::AllOrNoneOfValidatorSpec::API - end + describe '#validate!' do + subject(:validate) { post path, params } context 'when all restricted params are present' do let(:path) { '/' } diff --git a/spec/grape/validations/validators/allow_blank_spec.rb b/spec/grape/validations/validators/allow_blank_spec.rb index cb74e332c6..c23a755a6d 100644 --- a/spec/grape/validations/validators/allow_blank_spec.rb +++ b/spec/grape/validations/validators/allow_blank_spec.rb @@ -2,24 +2,139 @@ require 'spec_helper' -describe Grape::Validations::AllowBlankValidator do - module ValidationsSpec - module AllowBlankValidatorSpec - class API < Grape::API - default_format :json +describe Grape::Validations::Validators::AllowBlankValidator do + let_it_be(:app) do + Class.new(Grape::API) do + default_format :json - params do + params do + requires :name, allow_blank: false + end + get '/disallow_blank' + + params do + optional :name, type: String, allow_blank: false + end + get '/opt_disallow_string_blank' + + params do + optional :name, allow_blank: false + end + get '/disallow_blank_optional_param' + + params do + requires :name, allow_blank: true + end + get '/allow_blank' + + params do + requires :val, type: DateTime, allow_blank: true + end + get '/allow_datetime_blank' + + params do + requires :val, type: DateTime, allow_blank: false + end + get '/disallow_datetime_blank' + + params do + requires :val, type: DateTime + end + get '/default_allow_datetime_blank' + + params do + requires :val, type: Date, allow_blank: true + end + get '/allow_date_blank' + + params do + requires :val, type: Integer, allow_blank: true + end + get '/allow_integer_blank' + + params do + requires :val, type: Float, allow_blank: true + end + get '/allow_float_blank' + + params do + requires :val, type: Integer, allow_blank: true + end + get '/allow_integer_blank' + + params do + requires :val, type: Symbol, allow_blank: true + end + get '/allow_symbol_blank' + + params do + requires :val, type: Grape::API::Boolean, allow_blank: true + end + get '/allow_boolean_blank' + + params do + requires :val, type: Grape::API::Boolean, allow_blank: false + end + get '/disallow_boolean_blank' + + params do + optional :user, type: Hash do + requires :name, allow_blank: false + end + end + get '/disallow_blank_required_param_in_an_optional_group' + + params do + optional :user, type: Hash do + requires :name, type: Date, allow_blank: true + end + end + get '/allow_blank_date_param_in_an_optional_group' + + params do + optional :user, type: Hash do + optional :name, allow_blank: false + requires :age + end + end + get '/disallow_blank_optional_param_in_an_optional_group' + + params do + requires :user, type: Hash do requires :name, allow_blank: false end - get '/disallow_blank' + end + get '/disallow_blank_required_param_in_a_required_group' + + params do + requires :user, type: Hash do + requires :name, allow_blank: false + end + end + get '/disallow_string_value_in_a_required_hash_group' + + params do + requires :user, type: Hash do + optional :name, allow_blank: false + end + end + get '/disallow_blank_optional_param_in_a_required_group' + + params do + optional :user, type: Hash do + optional :name, allow_blank: false + end + end + get '/disallow_string_value_in_an_optional_hash_group' + resources :custom_message do params do - optional :name, type: String, allow_blank: false + requires :name, allow_blank: { value: false, message: 'has no value' } end - get '/opt_disallow_string_blank' + get params do - optional :name, allow_blank: false + optional :name, allow_blank: { value: false, message: 'has no value' } end get '/disallow_blank_optional_param' @@ -34,7 +149,7 @@ class API < Grape::API get '/allow_datetime_blank' params do - requires :val, type: DateTime, allow_blank: false + requires :val, type: DateTime, allow_blank: { value: false, message: 'has no value' } end get '/disallow_datetime_blank' @@ -69,18 +184,18 @@ class API < Grape::API get '/allow_symbol_blank' params do - requires :val, type: Boolean, allow_blank: true + requires :val, type: Grape::API::Boolean, allow_blank: true end get '/allow_boolean_blank' params do - requires :val, type: Boolean, allow_blank: false + requires :val, type: Grape::API::Boolean, allow_blank: { value: false, message: 'has no value' } end get '/disallow_boolean_blank' params do optional :user, type: Hash do - requires :name, allow_blank: false + requires :name, allow_blank: { value: false, message: 'has no value' } end end get '/disallow_blank_required_param_in_an_optional_group' @@ -94,7 +209,7 @@ class API < Grape::API params do optional :user, type: Hash do - optional :name, allow_blank: false + optional :name, allow_blank: { value: false, message: 'has no value' } requires :age end end @@ -102,156 +217,35 @@ class API < Grape::API params do requires :user, type: Hash do - requires :name, allow_blank: false + requires :name, allow_blank: { value: false, message: 'has no value' } end end get '/disallow_blank_required_param_in_a_required_group' params do requires :user, type: Hash do - requires :name, allow_blank: false + requires :name, allow_blank: { value: false, message: 'has no value' } end end get '/disallow_string_value_in_a_required_hash_group' params do requires :user, type: Hash do - optional :name, allow_blank: false + optional :name, allow_blank: { value: false, message: 'has no value' } end end get '/disallow_blank_optional_param_in_a_required_group' params do optional :user, type: Hash do - optional :name, allow_blank: false - end - end - get '/disallow_string_value_in_an_optional_hash_group' - - resources :custom_message do - params do - requires :name, allow_blank: { value: false, message: 'has no value' } - end - get - - params do optional :name, allow_blank: { value: false, message: 'has no value' } end - get '/disallow_blank_optional_param' - - params do - requires :name, allow_blank: true - end - get '/allow_blank' - - params do - requires :val, type: DateTime, allow_blank: true - end - get '/allow_datetime_blank' - - params do - requires :val, type: DateTime, allow_blank: { value: false, message: 'has no value' } - end - get '/disallow_datetime_blank' - - params do - requires :val, type: DateTime - end - get '/default_allow_datetime_blank' - - params do - requires :val, type: Date, allow_blank: true - end - get '/allow_date_blank' - - params do - requires :val, type: Integer, allow_blank: true - end - get '/allow_integer_blank' - - params do - requires :val, type: Float, allow_blank: true - end - get '/allow_float_blank' - - params do - requires :val, type: Integer, allow_blank: true - end - get '/allow_integer_blank' - - params do - requires :val, type: Symbol, allow_blank: true - end - get '/allow_symbol_blank' - - params do - requires :val, type: Boolean, allow_blank: true - end - get '/allow_boolean_blank' - - params do - requires :val, type: Boolean, allow_blank: { value: false, message: 'has no value' } - end - get '/disallow_boolean_blank' - - params do - optional :user, type: Hash do - requires :name, allow_blank: { value: false, message: 'has no value' } - end - end - get '/disallow_blank_required_param_in_an_optional_group' - - params do - optional :user, type: Hash do - requires :name, type: Date, allow_blank: true - end - end - get '/allow_blank_date_param_in_an_optional_group' - - params do - optional :user, type: Hash do - optional :name, allow_blank: { value: false, message: 'has no value' } - requires :age - end - end - get '/disallow_blank_optional_param_in_an_optional_group' - - params do - requires :user, type: Hash do - requires :name, allow_blank: { value: false, message: 'has no value' } - end - end - get '/disallow_blank_required_param_in_a_required_group' - - params do - requires :user, type: Hash do - requires :name, allow_blank: { value: false, message: 'has no value' } - end - end - get '/disallow_string_value_in_a_required_hash_group' - - params do - requires :user, type: Hash do - optional :name, allow_blank: { value: false, message: 'has no value' } - end - end - get '/disallow_blank_optional_param_in_a_required_group' - - params do - optional :user, type: Hash do - optional :name, allow_blank: { value: false, message: 'has no value' } - end - end - get '/disallow_string_value_in_an_optional_hash_group' end + get '/disallow_string_value_in_an_optional_hash_group' end end end - def app - ValidationsSpec::AllowBlankValidatorSpec::API - end - context 'invalid input' do it 'refuses empty string' do get '/disallow_blank', name: '' diff --git a/spec/grape/validations/validators/at_least_one_of_spec.rb b/spec/grape/validations/validators/at_least_one_of_spec.rb index b6468e3e59..4189ec0695 100644 --- a/spec/grape/validations/validators/at_least_one_of_spec.rb +++ b/spec/grape/validations/validators/at_least_one_of_spec.rb @@ -2,73 +2,67 @@ require 'spec_helper' -describe Grape::Validations::AtLeastOneOfValidator do - describe '#validate!' do - subject(:validate) { post path, params } - - module ValidationsSpec - module AtLeastOneOfValidatorSpec - class API < Grape::API - rescue_from Grape::Exceptions::ValidationErrors do |e| - error!(e.errors.transform_keys! { |key| key.join(',') }, 400) - end +describe Grape::Validations::Validators::AtLeastOneOfValidator do + let_it_be(:app) do + Class.new(Grape::API) do + rescue_from Grape::Exceptions::ValidationErrors do |e| + error!(e.errors.transform_keys! { |key| key.join(',') }, 400) + end - params do - optional :beer, :wine, :grapefruit - at_least_one_of :beer, :wine, :grapefruit - end - post do - end + params do + optional :beer, :wine, :grapefruit + at_least_one_of :beer, :wine, :grapefruit + end + post do + end - params do - optional :beer, :wine, :grapefruit, :other - at_least_one_of :beer, :wine, :grapefruit - end - post 'mixed-params' do - end + params do + optional :beer, :wine, :grapefruit, :other + at_least_one_of :beer, :wine, :grapefruit + end + post 'mixed-params' do + end - params do - optional :beer, :wine, :grapefruit - at_least_one_of :beer, :wine, :grapefruit, message: 'you should choose something' - end - post '/custom-message' do - end + params do + optional :beer, :wine, :grapefruit + at_least_one_of :beer, :wine, :grapefruit, message: 'you should choose something' + end + post '/custom-message' do + end - params do - requires :item, type: Hash do - optional :beer, :wine, :grapefruit - at_least_one_of :beer, :wine, :grapefruit, message: 'fail' - end - end - post '/nested-hash' do - end + params do + requires :item, type: Hash do + optional :beer, :wine, :grapefruit + at_least_one_of :beer, :wine, :grapefruit, message: 'fail' + end + end + post '/nested-hash' do + end - params do - requires :items, type: Array do - optional :beer, :wine, :grapefruit - at_least_one_of :beer, :wine, :grapefruit, message: 'fail' - end - end - post '/nested-array' do - end + params do + requires :items, type: Array do + optional :beer, :wine, :grapefruit + at_least_one_of :beer, :wine, :grapefruit, message: 'fail' + end + end + post '/nested-array' do + end - params do - requires :items, type: Array do - requires :nested_items, type: Array do - optional :beer, :wine, :grapefruit - at_least_one_of :beer, :wine, :grapefruit, message: 'fail' - end - end - end - post '/deeply-nested-array' do + params do + requires :items, type: Array do + requires :nested_items, type: Array do + optional :beer, :wine, :grapefruit + at_least_one_of :beer, :wine, :grapefruit, message: 'fail' end end end + post '/deeply-nested-array' do + end end + end - def app - ValidationsSpec::AtLeastOneOfValidatorSpec::API - end + describe '#validate!' do + subject(:validate) { post path, params } context 'when all restricted params are present' do let(:path) { '/' } diff --git a/spec/grape/validations/validators/coerce_spec.rb b/spec/grape/validations/validators/coerce_spec.rb index a24ca94cf6..626859420f 100644 --- a/spec/grape/validations/validators/coerce_spec.rb +++ b/spec/grape/validations/validators/coerce_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe Grape::Validations::CoerceValidator do +describe Grape::Validations::Validators::CoerceValidator do subject do Class.new(Grape::API) end @@ -83,7 +83,7 @@ def self.parsed?(value) context 'on custom coercion rules' do before do subject.params do - requires :a, types: { value: [Boolean, String], message: 'type cast is invalid' }, coerce_with: (lambda do |val| + requires :a, types: { value: [Grape::API::Boolean, String], message: 'type cast is invalid' }, coerce_with: (lambda do |val| case val when 'yup' true @@ -171,9 +171,9 @@ def self.parsed?(value) expect(last_response.body).to eq('BigDecimal 45.1') end - it 'Boolean' do + it 'Grape::API::Boolean' do subject.params do - requires :boolean, type: Boolean + requires :boolean, type: Grape::API::Boolean end subject.post '/boolean' do params[:boolean] @@ -370,9 +370,9 @@ def self.parse(_val) end end - it 'Boolean' do + it 'Grape::API::Boolean' do subject.params do - requires :boolean, type: Boolean + requires :boolean, type: Grape::API::Boolean end subject.get '/boolean' do params[:boolean].class @@ -1018,11 +1018,9 @@ def self.parse(_val) end context 'multiple types' do - Boolean = Grape::API::Boolean - it 'coerces to first possible type' do subject.params do - requires :a, types: [Boolean, Integer, String] + requires :a, types: [Grape::API::Boolean, Integer, String] end subject.get '/' do params[:a].class.to_s @@ -1043,7 +1041,7 @@ def self.parse(_val) it 'fails when no coercion is possible' do subject.params do - requires :a, types: [Boolean, Integer] + requires :a, types: [Grape::API::Boolean, Integer] end subject.get '/' do params[:a].class.to_s @@ -1202,7 +1200,7 @@ def self.parse(_val) context 'custom coercion rules' do before do subject.params do - requires :a, types: [Boolean, String], coerce_with: (lambda do |val| + requires :a, types: [Grape::API::Boolean, String], coerce_with: (lambda do |val| case val when 'yup' true diff --git a/spec/grape/validations/validators/default_spec.rb b/spec/grape/validations/validators/default_spec.rb index ae16445eb6..c6e399c5c3 100644 --- a/spec/grape/validations/validators/default_spec.rb +++ b/spec/grape/validations/validators/default_spec.rb @@ -2,104 +2,98 @@ require 'spec_helper' -describe Grape::Validations::DefaultValidator do - module ValidationsSpec - module DefaultValidatorSpec - class API < Grape::API - default_format :json - - params do - optional :id - optional :type, default: 'default-type' - end - get '/' do - { id: params[:id], type: params[:type] } - end +describe Grape::Validations::Validators::DefaultValidator do + let_it_be(:app) do + Class.new(Grape::API) do + default_format :json + + params do + optional :id + optional :type, default: 'default-type' + end + get '/' do + { id: params[:id], type: params[:type] } + end - params do - optional :type1, default: 'default-type1' - optional :type2, default: 'default-type2' - end - get '/user' do - { type1: params[:type1], type2: params[:type2] } - end + params do + optional :type1, default: 'default-type1' + optional :type2, default: 'default-type2' + end + get '/user' do + { type1: params[:type1], type2: params[:type2] } + end - params do - requires :id - optional :type1, default: 'default-type1' - optional :type2, default: 'default-type2' - end + params do + requires :id + optional :type1, default: 'default-type1' + optional :type2, default: 'default-type2' + end - get '/message' do - { id: params[:id], type1: params[:type1], type2: params[:type2] } - end + get '/message' do + { id: params[:id], type1: params[:type1], type2: params[:type2] } + end - params do - optional :random, default: -> { Random.rand } - optional :not_random, default: Random.rand - end - get '/numbers' do - { random_number: params[:random], non_random_number: params[:non_random_number] } - end + params do + optional :random, default: -> { Random.rand } + optional :not_random, default: Random.rand + end + get '/numbers' do + { random_number: params[:random], non_random_number: params[:non_random_number] } + end - params do - optional :array, type: Array do - requires :name - optional :with_default, default: 'default' - end - end - get '/array' do - { array: params[:array] } + params do + optional :array, type: Array do + requires :name + optional :with_default, default: 'default' end + end + get '/array' do + { array: params[:array] } + end - params do - requires :thing1 - optional :more_things, type: Array do - requires :nested_thing - requires :other_thing, default: 1 - end - end - get '/optional_array' do - { thing1: params[:thing1] } + params do + requires :thing1 + optional :more_things, type: Array do + requires :nested_thing + requires :other_thing, default: 1 end + end + get '/optional_array' do + { thing1: params[:thing1] } + end - params do - requires :root, type: Hash do - optional :some_things, type: Array do - requires :foo - optional :options, type: Array do - requires :name, type: String - requires :value, type: String - end + params do + requires :root, type: Hash do + optional :some_things, type: Array do + requires :foo + optional :options, type: Array do + requires :name, type: String + requires :value, type: String end end end - get '/nested_optional_array' do - { root: params[:root] } - end + end + get '/nested_optional_array' do + { root: params[:root] } + end - params do - requires :root, type: Hash do - optional :some_things, type: Array do - requires :foo - optional :options, type: Array do - optional :name, type: String - optional :value, type: String - end + params do + requires :root, type: Hash do + optional :some_things, type: Array do + requires :foo + optional :options, type: Array do + optional :name, type: String + optional :value, type: String end end end - get '/another_nested_optional_array' do - { root: params[:root] } - end + end + get '/another_nested_optional_array' do + { root: params[:root] } end end end - def app - ValidationsSpec::DefaultValidatorSpec::API - end - it 'lets you leave required values nested inside an optional blank' do get '/optional_array', thing1: 'stuff' expect(last_response.status).to eq(200) diff --git a/spec/grape/validations/validators/exactly_one_of_spec.rb b/spec/grape/validations/validators/exactly_one_of_spec.rb index 87eba59d3a..8076c703ba 100644 --- a/spec/grape/validations/validators/exactly_one_of_spec.rb +++ b/spec/grape/validations/validators/exactly_one_of_spec.rb @@ -2,95 +2,89 @@ require 'spec_helper' -describe Grape::Validations::ExactlyOneOfValidator do - describe '#validate!' do - subject(:validate) { post path, params } - - module ValidationsSpec - module ExactlyOneOfValidatorSpec - class API < Grape::API - rescue_from Grape::Exceptions::ValidationErrors do |e| - error!(e.errors.transform_keys! { |key| key.join(',') }, 400) - end +describe Grape::Validations::Validators::ExactlyOneOfValidator do + let_it_be(:app) do + Class.new(Grape::API) do + rescue_from Grape::Exceptions::ValidationErrors do |e| + error!(e.errors.transform_keys! { |key| key.join(',') }, 400) + end - params do - optional :beer - optional :wine - optional :grapefruit - exactly_one_of :beer, :wine, :grapefruit - end - post do - end + params do + optional :beer + optional :wine + optional :grapefruit + exactly_one_of :beer, :wine, :grapefruit + end + post do + end - params do - optional :beer - optional :wine - optional :grapefruit - optional :other - exactly_one_of :beer, :wine, :grapefruit - end - post 'mixed-params' do - end + params do + optional :beer + optional :wine + optional :grapefruit + optional :other + exactly_one_of :beer, :wine, :grapefruit + end + post 'mixed-params' do + end - params do - optional :beer - optional :wine - optional :grapefruit - exactly_one_of :beer, :wine, :grapefruit, message: 'you should choose one' - end - post '/custom-message' do - end + params do + optional :beer + optional :wine + optional :grapefruit + exactly_one_of :beer, :wine, :grapefruit, message: 'you should choose one' + end + post '/custom-message' do + end - params do - requires :item, type: Hash do - optional :beer - optional :wine - optional :grapefruit - exactly_one_of :beer, :wine, :grapefruit - end - end - post '/nested-hash' do - end + params do + requires :item, type: Hash do + optional :beer + optional :wine + optional :grapefruit + exactly_one_of :beer, :wine, :grapefruit + end + end + post '/nested-hash' do + end - params do - optional :item, type: Hash do - optional :beer - optional :wine - optional :grapefruit - exactly_one_of :beer, :wine, :grapefruit - end - end - post '/nested-optional-hash' do - end + params do + optional :item, type: Hash do + optional :beer + optional :wine + optional :grapefruit + exactly_one_of :beer, :wine, :grapefruit + end + end + post '/nested-optional-hash' do + end - params do - requires :items, type: Array do - optional :beer - optional :wine - optional :grapefruit - exactly_one_of :beer, :wine, :grapefruit - end - end - post '/nested-array' do - end + params do + requires :items, type: Array do + optional :beer + optional :wine + optional :grapefruit + exactly_one_of :beer, :wine, :grapefruit + end + end + post '/nested-array' do + end - params do - requires :items, type: Array do - requires :nested_items, type: Array do - optional :beer, :wine, :grapefruit, type: Boolean - exactly_one_of :beer, :wine, :grapefruit - end - end - end - post '/deeply-nested-array' do + params do + requires :items, type: Array do + requires :nested_items, type: Array do + optional :beer, :wine, :grapefruit, type: Grape::API::Boolean + exactly_one_of :beer, :wine, :grapefruit end end end + post '/deeply-nested-array' do + end end + end - def app - ValidationsSpec::ExactlyOneOfValidatorSpec::API - end + describe '#validate!' do + subject(:validate) { post path, params } context 'when all params are present' do let(:path) { '/' } diff --git a/spec/grape/validations/validators/except_values_spec.rb b/spec/grape/validations/validators/except_values_spec.rb index bcc756dd53..6bed034c98 100644 --- a/spec/grape/validations/validators/except_values_spec.rb +++ b/spec/grape/validations/validators/except_values_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe Grape::Validations::ExceptValuesValidator do +describe Grape::Validations::Validators::ExceptValuesValidator do module ValidationsSpec class ExceptValuesModel DEFAULT_EXCEPTS = %w[invalid-type1 invalid-type2 invalid-type3].freeze diff --git a/spec/grape/validations/validators/mutual_exclusion_spec.rb b/spec/grape/validations/validators/mutual_exclusion_spec.rb index ac1b46989d..e01dee282d 100644 --- a/spec/grape/validations/validators/mutual_exclusion_spec.rb +++ b/spec/grape/validations/validators/mutual_exclusion_spec.rb @@ -2,95 +2,89 @@ require 'spec_helper' -describe Grape::Validations::MutualExclusionValidator do - describe '#validate!' do - subject(:validate) { post path, params } - - module ValidationsSpec - module MutualExclusionValidatorSpec - class API < Grape::API - rescue_from Grape::Exceptions::ValidationErrors do |e| - error!(e.errors.transform_keys! { |key| key.join(',') }, 400) - end +describe Grape::Validations::Validators::MutualExclusionValidator do + let_it_be(:app) do + Class.new(Grape::API) do + rescue_from Grape::Exceptions::ValidationErrors do |e| + error!(e.errors.transform_keys! { |key| key.join(',') }, 400) + end - params do - optional :beer - optional :wine - optional :grapefruit - mutually_exclusive :beer, :wine, :grapefruit - end - post do - end + params do + optional :beer + optional :wine + optional :grapefruit + mutually_exclusive :beer, :wine, :grapefruit + end + post do + end - params do - optional :beer - optional :wine - optional :grapefruit - optional :other - mutually_exclusive :beer, :wine, :grapefruit - end - post 'mixed-params' do - end + params do + optional :beer + optional :wine + optional :grapefruit + optional :other + mutually_exclusive :beer, :wine, :grapefruit + end + post 'mixed-params' do + end - params do - optional :beer - optional :wine - optional :grapefruit - mutually_exclusive :beer, :wine, :grapefruit, message: 'you should not mix beer and wine' - end - post '/custom-message' do - end + params do + optional :beer + optional :wine + optional :grapefruit + mutually_exclusive :beer, :wine, :grapefruit, message: 'you should not mix beer and wine' + end + post '/custom-message' do + end - params do - requires :item, type: Hash do - optional :beer - optional :wine - optional :grapefruit - mutually_exclusive :beer, :wine, :grapefruit - end - end - post '/nested-hash' do - end + params do + requires :item, type: Hash do + optional :beer + optional :wine + optional :grapefruit + mutually_exclusive :beer, :wine, :grapefruit + end + end + post '/nested-hash' do + end - params do - optional :item, type: Hash do - optional :beer - optional :wine - optional :grapefruit - mutually_exclusive :beer, :wine, :grapefruit - end - end - post '/nested-optional-hash' do - end + params do + optional :item, type: Hash do + optional :beer + optional :wine + optional :grapefruit + mutually_exclusive :beer, :wine, :grapefruit + end + end + post '/nested-optional-hash' do + end - params do - requires :items, type: Array do - optional :beer - optional :wine - optional :grapefruit - mutually_exclusive :beer, :wine, :grapefruit - end - end - post '/nested-array' do - end + params do + requires :items, type: Array do + optional :beer + optional :wine + optional :grapefruit + mutually_exclusive :beer, :wine, :grapefruit + end + end + post '/nested-array' do + end - params do - requires :items, type: Array do - requires :nested_items, type: Array do - optional :beer, :wine, :grapefruit, type: Boolean - mutually_exclusive :beer, :wine, :grapefruit - end - end - end - post '/deeply-nested-array' do + params do + requires :items, type: Array do + requires :nested_items, type: Array do + optional :beer, :wine, :grapefruit, type: Grape::API::Boolean + mutually_exclusive :beer, :wine, :grapefruit end end end + post '/deeply-nested-array' do + end end + end - def app - ValidationsSpec::MutualExclusionValidatorSpec::API - end + describe '#validate!' do + subject(:validate) { post path, params } context 'when all mutually exclusive params are present' do let(:path) { '/' } diff --git a/spec/grape/validations/validators/presence_spec.rb b/spec/grape/validations/validators/presence_spec.rb index 5eaabe72a1..a83399588f 100644 --- a/spec/grape/validations/validators/presence_spec.rb +++ b/spec/grape/validations/validators/presence_spec.rb @@ -2,12 +2,13 @@ require 'spec_helper' -describe Grape::Validations::PresenceValidator do +describe Grape::Validations::Validators::PresenceValidator do subject do Class.new(Grape::API) do format :json end end + def app subject end diff --git a/spec/grape/validations/validators/regexp_spec.rb b/spec/grape/validations/validators/regexp_spec.rb index b4ffc99df5..5a6aec73a2 100644 --- a/spec/grape/validations/validators/regexp_spec.rb +++ b/spec/grape/validations/validators/regexp_spec.rb @@ -2,53 +2,47 @@ require 'spec_helper' -describe Grape::Validations::RegexpValidator do - module ValidationsSpec - module RegexpValidatorSpec - class API < Grape::API - default_format :json - - resources :custom_message do - params do - requires :name, regexp: { value: /^[a-z]+$/, message: 'format is invalid' } - end - get do - end - - params do - requires :names, type: { value: Array[String], message: 'can\'t be nil' }, regexp: { value: /^[a-z]+$/, message: 'format is invalid' } - end - get 'regexp_with_array' do - end - end +describe Grape::Validations::Validators::RegexpValidator do + let_it_be(:app) do + Class.new(Grape::API) do + default_format :json + resources :custom_message do params do - requires :name, regexp: /^[a-z]+$/ + requires :name, regexp: { value: /^[a-z]+$/, message: 'format is invalid' } end get do end params do - requires :names, type: Array[String], regexp: /^[a-z]+$/ + requires :names, type: { value: Array[String], message: 'can\'t be nil' }, regexp: { value: /^[a-z]+$/, message: 'format is invalid' } end get 'regexp_with_array' do end + end - params do - requires :people, type: Hash do - requires :names, type: Array[String], regexp: /^[a-z]+$/ - end - end - get 'nested_regexp_with_array' do + params do + requires :name, regexp: /^[a-z]+$/ + end + get do + end + + params do + requires :names, type: Array[String], regexp: /^[a-z]+$/ + end + get 'regexp_with_array' do + end + + params do + requires :people, type: Hash do + requires :names, type: Array[String], regexp: /^[a-z]+$/ end end + get 'nested_regexp_with_array' do + end end end - def app - ValidationsSpec::RegexpValidatorSpec::API - end - context 'custom validation message' do context 'with invalid input' do it 'refuses inapppopriate' do diff --git a/spec/grape/validations/validators/same_as_spec.rb b/spec/grape/validations/validators/same_as_spec.rb index da4c945c01..f21fd94b53 100644 --- a/spec/grape/validations/validators/same_as_spec.rb +++ b/spec/grape/validations/validators/same_as_spec.rb @@ -2,31 +2,25 @@ require 'spec_helper' -describe Grape::Validations::SameAsValidator do - module ValidationsSpec - module SameAsValidatorSpec - class API < Grape::API - params do - requires :password - requires :password_confirmation, same_as: :password - end - post do - end +describe Grape::Validations::Validators::SameAsValidator do + let_it_be(:app) do + Class.new(Grape::API) do + params do + requires :password + requires :password_confirmation, same_as: :password + end + post do + end - params do - requires :password - requires :password_confirmation, same_as: { value: :password, message: 'not match' } - end - post '/custom-message' do - end + params do + requires :password + requires :password_confirmation, same_as: { value: :password, message: 'not match' } + end + post '/custom-message' do end end end - def app - ValidationsSpec::SameAsValidatorSpec::API - end - describe '/' do context 'is the same' do it do diff --git a/spec/grape/validations/validators/values_spec.rb b/spec/grape/validations/validators/values_spec.rb index 73c35b390d..ca59c6f098 100644 --- a/spec/grape/validations/validators/values_spec.rb +++ b/spec/grape/validations/validators/values_spec.rb @@ -2,11 +2,12 @@ require 'spec_helper' -describe Grape::Validations::ValuesValidator do - module ValidationsSpec - class ValuesModel +describe Grape::Validations::Validators::ValuesValidator do + let_it_be(:values_model) do + Class.new do DEFAULT_VALUES = %w[valid-type1 valid-type2 valid-type3].freeze DEFAULT_EXCEPTS = %w[invalid-type1 invalid-type2 invalid-type3].freeze + class << self def values @values ||= [] @@ -33,212 +34,213 @@ def include?(value) end end end + end - module ValuesValidatorSpec - class API < Grape::API - default_format :json - - resources :custom_message do - params do - requires :type, values: { value: ValuesModel.values, message: 'value does not include in values' } - end - get '/' do - { type: params[:type] } - end - - params do - optional :type, values: { value: -> { ValuesModel.values }, message: 'value does not include in values' }, default: 'valid-type2' - end - get '/lambda' do - { type: params[:type] } - end - - params do - requires :type, values: { except: ValuesModel.excepts, except_message: 'value is on exclusions list', message: 'default exclude message' } - end - get '/exclude/exclude_message' - - params do - requires :type, values: { except: -> { ValuesModel.excepts }, except_message: 'value is on exclusions list' } - end - get '/exclude/lambda/exclude_message' - - params do - requires :type, values: { except: ValuesModel.excepts, message: 'default exclude message' } - end - get '/exclude/fallback_message' - end + before do + stub_const('ValuesModel', values_model) + end + + let_it_be(:app) do + ValuesModel = values_model + Class.new(Grape::API) do + default_format :json + resources :custom_message do params do - requires :type, values: ValuesModel.values + requires :type, values: { value: ValuesModel.values, message: 'value does not include in values' } end get '/' do { type: params[:type] } end params do - requires :type, values: [] + optional :type, values: { value: -> { ValuesModel.values }, message: 'value does not include in values' }, default: 'valid-type2' end - get '/empty' - - params do - optional :type, values: { value: ValuesModel.values }, default: 'valid-type2' - end - get '/default/hash/valid' do + get '/lambda' do { type: params[:type] } end params do - optional :type, values: ValuesModel.values, default: 'valid-type2' - end - get '/default/valid' do - { type: params[:type] } + requires :type, values: { except: ValuesModel.excepts, except_message: 'value is on exclusions list', message: 'default exclude message' } end + get '/exclude/exclude_message' params do - optional :type, values: { except: ValuesModel.excepts }, default: 'valid-type2' - end - get '/default/except' do - { type: params[:type] } + requires :type, values: { except: -> { ValuesModel.excepts }, except_message: 'value is on exclusions list' } end + get '/exclude/lambda/exclude_message' params do - optional :type, values: -> { ValuesModel.values }, default: 'valid-type2' - end - get '/lambda' do - { type: params[:type] } + requires :type, values: { except: ValuesModel.excepts, message: 'default exclude message' } end + get '/exclude/fallback_message' + end - params do - requires :type, values: ->(v) { ValuesModel.include? v } - end - get '/lambda_val' do - { type: params[:type] } - end + params do + requires :type, values: ValuesModel.values + end + get '/' do + { type: params[:type] } + end - params do - requires :number, type: Integer, values: ->(v) { v > 0 } - end - get '/lambda_int_val' do - { number: params[:number] } - end + params do + requires :type, values: [] + end + get '/empty' - params do - requires :type, values: -> { [] } - end - get '/empty_lambda' + params do + optional :type, values: { value: ValuesModel.values }, default: 'valid-type2' + end + get '/default/hash/valid' do + { type: params[:type] } + end - params do - optional :type, values: ValuesModel.values, default: -> { ValuesModel.values.sample } - end - get '/default_lambda' do - { type: params[:type] } - end + params do + optional :type, values: ValuesModel.values, default: 'valid-type2' + end + get '/default/valid' do + { type: params[:type] } + end - params do - optional :type, values: -> { ValuesModel.values }, default: -> { ValuesModel.values.sample } - end - get '/default_and_values_lambda' do - { type: params[:type] } - end + params do + optional :type, values: { except: ValuesModel.excepts }, default: 'valid-type2' + end + get '/default/except' do + { type: params[:type] } + end - params do - optional :type, type: Boolean, desc: 'A boolean', values: [true] - end - get '/values/optional_boolean' do - { type: params[:type] } - end + params do + optional :type, values: -> { ValuesModel.values }, default: 'valid-type2' + end + get '/lambda' do + { type: params[:type] } + end - params do - requires :type, type: Integer, desc: 'An integer', values: [10, 11], default: 10 - end - get '/values/coercion' do - { type: params[:type] } - end + params do + requires :type, values: ->(v) { ValuesModel.include? v } + end + get '/lambda_val' do + { type: params[:type] } + end - params do - requires :type, type: Array[Integer], desc: 'An integer', values: [10, 11], default: 10 - end - get '/values/array_coercion' do - { type: params[:type] } - end + params do + requires :number, type: Integer, values: ->(v) { v > 0 } + end + get '/lambda_int_val' do + { number: params[:number] } + end - params do - optional :optional, type: Array do - requires :type, values: %w[a b] - end - end - get '/optional_with_required_values' + params do + requires :type, values: -> { [] } + end + get '/empty_lambda' - params do - requires :type, values: { except: ValuesModel.excepts } - end - get '/except/exclusive' do - { type: params[:type] } - end + params do + optional :type, values: ValuesModel.values, default: -> { ValuesModel.values.sample } + end + get '/default_lambda' do + { type: params[:type] } + end - params do - requires :type, type: String, values: { except: ValuesModel.excepts } - end - get '/except/exclusive/type' do - { type: params[:type] } - end + params do + optional :type, values: -> { ValuesModel.values }, default: -> { ValuesModel.values.sample } + end + get '/default_and_values_lambda' do + { type: params[:type] } + end - params do - requires :type, values: { except: -> { ValuesModel.excepts } } - end - get '/except/exclusive/lambda' do - { type: params[:type] } - end + params do + optional :type, type: Grape::API::Boolean, desc: 'A boolean', values: [true] + end + get '/values/optional_boolean' do + { type: params[:type] } + end - params do - requires :type, type: String, values: { except: -> { ValuesModel.excepts } } - end - get '/except/exclusive/lambda/type' do - { type: params[:type] } - end + params do + requires :type, type: Integer, desc: 'An integer', values: [10, 11], default: 10 + end + get '/values/coercion' do + { type: params[:type] } + end - params do - requires :type, type: Integer, values: { except: -> { [3, 4, 5] } } - end - get '/except/exclusive/lambda/coercion' do - { type: params[:type] } - end + params do + requires :type, type: Array[Integer], desc: 'An integer', values: [10, 11], default: 10 + end + get '/values/array_coercion' do + { type: params[:type] } + end - params do - requires :type, type: Integer, values: { value: 1..5, except: [3] } - end - get '/mixed/value/except' do - { type: params[:type] } + params do + optional :optional, type: Array do + requires :type, values: %w[a b] end + end + get '/optional_with_required_values' - params do - optional :optional, type: Array[String], values: %w[a b c] - end - put '/optional_with_array_of_string_values' + params do + requires :type, values: { except: ValuesModel.excepts } + end + get '/except/exclusive' do + { type: params[:type] } + end - params do - requires :type, values: { proc: ->(v) { ValuesModel.include? v } } - end - get '/proc' do - { type: params[:type] } - end + params do + requires :type, type: String, values: { except: ValuesModel.excepts } + end + get '/except/exclusive/type' do + { type: params[:type] } + end - params do - requires :type, values: { proc: ->(v) { ValuesModel.include? v }, message: 'failed check' } - end - get '/proc/message' + params do + requires :type, values: { except: -> { ValuesModel.excepts } } + end + get '/except/exclusive/lambda' do + { type: params[:type] } + end - params do - optional :name, type: String, values: %w[a b], allow_blank: true - end - get '/allow_blank' + params do + requires :type, type: String, values: { except: -> { ValuesModel.excepts } } + end + get '/except/exclusive/lambda/type' do + { type: params[:type] } end - end - end - def app - ValidationsSpec::ValuesValidatorSpec::API + params do + requires :type, type: Integer, values: { except: -> { [3, 4, 5] } } + end + get '/except/exclusive/lambda/coercion' do + { type: params[:type] } + end + + params do + requires :type, type: Integer, values: { value: 1..5, except: [3] } + end + get '/mixed/value/except' do + { type: params[:type] } + end + + params do + optional :optional, type: Array[String], values: %w[a b c] + end + put '/optional_with_array_of_string_values' + + params do + requires :type, values: { proc: ->(v) { ValuesModel.include? v } } + end + get '/proc' do + { type: params[:type] } + end + + params do + requires :type, values: { proc: ->(v) { ValuesModel.include? v }, message: 'failed check' } + end + get '/proc/message' + + params do + optional :name, type: String, values: %w[a b], allow_blank: true + end + get '/allow_blank' + end end context 'with a custom validation message' do @@ -255,7 +257,7 @@ def app end it 'validates against values in a proc' do - ValidationsSpec::ValuesModel.add_value('valid-type4') + ValuesModel.add_value('valid-type4') get('/custom_message/lambda', type: 'valid-type4') expect(last_response.status).to eq 200 @@ -354,15 +356,14 @@ def app end it 'does not validate updated values without proc' do - ValidationsSpec::ValuesModel.add_value('valid-type4') - + ValuesModel.add_value('valid-type4') get('/', type: 'valid-type4') expect(last_response.status).to eq 400 expect(last_response.body).to eq({ error: 'type does not have a valid value' }.to_json) end it 'validates against values in a proc' do - ValidationsSpec::ValuesModel.add_value('valid-type4') + ValuesModel.add_value('valid-type4') get('/lambda', type: 'valid-type4') expect(last_response.status).to eq 200 @@ -424,7 +425,7 @@ def app it 'raises IncompatibleOptionValues on an invalid default value from proc' do subject = Class.new(Grape::API) expect do - subject.params { optional :type, values: %w[valid-type1 valid-type2 valid-type3], default: "#{ValidationsSpec::ValuesModel.values.sample}_invalid" } + subject.params { optional :type, values: %w[valid-type1 valid-type2 valid-type3], default: "#{ValuesModel.values.sample}_invalid" } end.to raise_error Grape::Exceptions::IncompatibleOptionValues end diff --git a/spec/grape/validations_spec.rb b/spec/grape/validations_spec.rb index 5d6ee7f131..9c2486f88c 100644 --- a/spec/grape/validations_spec.rb +++ b/spec/grape/validations_spec.rb @@ -489,18 +489,24 @@ def define_requires_none end context 'custom validator for a Hash' do - module ValuesSpec - module DateRangeValidations - class DateRangeValidator < Grape::Validations::Base - def validate_param!(attr_name, params) - return if params[attr_name][:from] <= params[attr_name][:to] + let(:date_range_validator) do + Class.new(Grape::Validations::Validators::Base) do + def validate_param!(attr_name, params) + return if params[attr_name][:from] <= params[attr_name][:to] - raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: "'from' must be lower or equal to 'to'") - end + raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: "'from' must be lower or equal to 'to'") end end end + before do + Grape::Validations.register_validator('date_range', date_range_validator) + end + + after do + Grape::Validations.deregister_validator('date_range') + end + before do subject.params do optional :date_range, date_range: true, type: Hash do @@ -1183,8 +1189,8 @@ def validate_param!(attr_name, params) end context 'custom validation' do - module CustomValidations - class Customvalidator < Grape::Validations::Base + let(:custom_validator) do + Class.new(Grape::Validations::Validators::Base) do def validate_param!(attr_name, params) return if params[attr_name] == 'im custom' @@ -1193,6 +1199,14 @@ def validate_param!(attr_name, params) end end + before do + Grape::Validations.register_validator('customvalidator', custom_validator) + end + + after do + Grape::Validations.deregister_validator('customvalidator') + end + context 'when using optional with a custom validator' do before do subject.params do @@ -1332,8 +1346,8 @@ def validate_param!(attr_name, params) end context 'when using options on param' do - module CustomValidations - class CustomvalidatorWithOptions < Grape::Validations::Base + let(:custom_validator_with_options) do + Class.new(Grape::Validations::Validators::Base) do def validate_param!(attr_name, params) return if params[attr_name] == @option[:text] @@ -1342,6 +1356,14 @@ def validate_param!(attr_name, params) end end + before do + Grape::Validations.register_validator('customvalidator_with_options', custom_validator_with_options) + end + + after do + Grape::Validations.deregister_validator('customvalidator_with_options') + end + before do subject.params do optional :custom, customvalidator_with_options: { text: 'im custom with options', message: 'is not custom with options!' } diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ce4e84d313..d8e2566917 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -5,6 +5,15 @@ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'support')) require 'grape' +require 'test_prof/recipes/rspec/let_it_be' + +class NullAdapter + def begin_transaction; end + + def rollback_transaction; end +end + +TestProf::BeforeAll.adapter = NullAdapter.new require 'rubygems' require 'bundler'