grape 0.14.0 → 0.15.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.
Potentially problematic release.
This version of grape might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +32 -4
- data/Gemfile.lock +13 -13
- data/README.md +290 -12
- data/UPGRADING.md +68 -1
- data/gemfiles/rails_3.gemfile +1 -1
- data/lib/grape.rb +8 -2
- data/lib/grape/api.rb +40 -34
- data/lib/grape/dsl/configuration.rb +2 -115
- data/lib/grape/dsl/desc.rb +101 -0
- data/lib/grape/dsl/headers.rb +16 -0
- data/lib/grape/dsl/helpers.rb +5 -9
- data/lib/grape/dsl/inside_route.rb +3 -11
- data/lib/grape/dsl/logger.rb +20 -0
- data/lib/grape/dsl/parameters.rb +12 -10
- data/lib/grape/dsl/request_response.rb +17 -4
- data/lib/grape/dsl/routing.rb +24 -7
- data/lib/grape/dsl/settings.rb +8 -2
- data/lib/grape/endpoint.rb +30 -26
- data/lib/grape/error_formatter.rb +31 -0
- data/lib/grape/error_formatter/base.rb +0 -28
- data/lib/grape/error_formatter/json.rb +13 -2
- data/lib/grape/error_formatter/txt.rb +3 -1
- data/lib/grape/error_formatter/xml.rb +3 -1
- data/lib/grape/exceptions/base.rb +11 -4
- data/lib/grape/exceptions/incompatible_option_values.rb +1 -1
- data/lib/grape/exceptions/invalid_accept_header.rb +1 -1
- data/lib/grape/exceptions/invalid_formatter.rb +1 -1
- data/lib/grape/exceptions/invalid_message_body.rb +1 -1
- data/lib/grape/exceptions/invalid_version_header.rb +1 -1
- data/lib/grape/exceptions/invalid_versioner_option.rb +1 -1
- data/lib/grape/exceptions/invalid_with_option_for_represent.rb +1 -1
- data/lib/grape/exceptions/method_not_allowed.rb +10 -0
- data/lib/grape/exceptions/missing_group_type.rb +1 -1
- data/lib/grape/exceptions/missing_mime_type.rb +1 -1
- data/lib/grape/exceptions/missing_option.rb +1 -1
- data/lib/grape/exceptions/missing_vendor_option.rb +1 -1
- data/lib/grape/exceptions/unknown_options.rb +1 -1
- data/lib/grape/exceptions/unknown_parameter.rb +1 -1
- data/lib/grape/exceptions/unknown_validator.rb +1 -1
- data/lib/grape/exceptions/unsupported_group_type.rb +1 -1
- data/lib/grape/exceptions/validation.rb +2 -1
- data/lib/grape/formatter.rb +31 -0
- data/lib/grape/middleware/base.rb +28 -2
- data/lib/grape/middleware/error.rb +24 -1
- data/lib/grape/middleware/formatter.rb +4 -3
- data/lib/grape/middleware/versioner/param.rb +13 -2
- data/lib/grape/parser.rb +29 -0
- data/lib/grape/util/sendfile_response.rb +19 -0
- data/lib/grape/util/strict_hash_configuration.rb +1 -1
- data/lib/grape/validations/params_scope.rb +39 -9
- data/lib/grape/validations/types.rb +16 -0
- data/lib/grape/validations/validators/all_or_none.rb +1 -1
- data/lib/grape/validations/validators/allow_blank.rb +2 -2
- data/lib/grape/validations/validators/at_least_one_of.rb +1 -1
- data/lib/grape/validations/validators/base.rb +26 -0
- data/lib/grape/validations/validators/coerce.rb +16 -14
- data/lib/grape/validations/validators/default.rb +1 -1
- data/lib/grape/validations/validators/exactly_one_of.rb +10 -1
- data/lib/grape/validations/validators/mutual_exclusion.rb +1 -1
- data/lib/grape/validations/validators/presence.rb +1 -1
- data/lib/grape/validations/validators/regexp.rb +2 -2
- data/lib/grape/validations/validators/values.rb +2 -2
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api/custom_validations_spec.rb +156 -21
- data/spec/grape/api/namespace_parameters_in_route_spec.rb +38 -0
- data/spec/grape/api/optional_parameters_in_route_spec.rb +43 -0
- data/spec/grape/api/required_parameters_in_route_spec.rb +37 -0
- data/spec/grape/api_spec.rb +118 -60
- data/spec/grape/dsl/configuration_spec.rb +0 -75
- data/spec/grape/dsl/desc_spec.rb +77 -0
- data/spec/grape/dsl/headers_spec.rb +32 -0
- data/spec/grape/dsl/inside_route_spec.rb +0 -18
- data/spec/grape/dsl/logger_spec.rb +26 -0
- data/spec/grape/dsl/parameters_spec.rb +13 -7
- data/spec/grape/dsl/request_response_spec.rb +17 -3
- data/spec/grape/dsl/routing_spec.rb +8 -1
- data/spec/grape/dsl/settings_spec.rb +42 -0
- data/spec/grape/endpoint_spec.rb +60 -9
- data/spec/grape/exceptions/validation_errors_spec.rb +2 -2
- data/spec/grape/exceptions/validation_spec.rb +7 -0
- data/spec/grape/integration/rack_sendfile_spec.rb +44 -0
- data/spec/grape/middleware/base_spec.rb +100 -0
- data/spec/grape/middleware/exception_spec.rb +1 -2
- data/spec/grape/middleware/formatter_spec.rb +12 -2
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +1 -1
- data/spec/grape/middleware/versioner/header_spec.rb +11 -1
- data/spec/grape/middleware/versioner/param_spec.rb +105 -1
- data/spec/grape/validations/params_scope_spec.rb +77 -0
- data/spec/grape/validations/validators/allow_blank_spec.rb +277 -0
- data/spec/grape/validations/validators/coerce_spec.rb +91 -0
- data/spec/grape/validations/validators/default_spec.rb +6 -0
- data/spec/grape/validations/validators/presence_spec.rb +27 -0
- data/spec/grape/validations/validators/regexp_spec.rb +36 -0
- data/spec/grape/validations/validators/values_spec.rb +44 -0
- data/spec/grape/validations_spec.rb +149 -4
- data/spec/spec_helper.rb +1 -0
- metadata +26 -5
- data/lib/grape/formatter/base.rb +0 -31
- data/lib/grape/parser/base.rb +0 -29
- data/pkg/grape-0.13.0.gem +0 -0
| @@ -19,6 +19,7 @@ module Grape | |
| 19 19 | 
             
                  end
         | 
| 20 20 |  | 
| 21 21 | 
             
                  def after
         | 
| 22 | 
            +
                    return unless @app_response
         | 
| 22 23 | 
             
                    status, headers, bodies = *@app_response
         | 
| 23 24 |  | 
| 24 25 | 
             
                    if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status)
         | 
| @@ -34,7 +35,7 @@ module Grape | |
| 34 35 | 
             
                    headers = ensure_content_type(headers)
         | 
| 35 36 |  | 
| 36 37 | 
             
                    if bodies.is_a?(Grape::Util::FileResponse)
         | 
| 37 | 
            -
                       | 
| 38 | 
            +
                      Grape::Util::SendfileResponse.new([], status, headers) do |resp|
         | 
| 38 39 | 
             
                        resp.body = bodies.file
         | 
| 39 40 | 
             
                      end
         | 
| 40 41 | 
             
                    else
         | 
| @@ -49,7 +50,7 @@ module Grape | |
| 49 50 |  | 
| 50 51 | 
             
                  def fetch_formatter(headers, options)
         | 
| 51 52 | 
             
                    api_format = mime_types[headers[Grape::Http::Headers::CONTENT_TYPE]] || env[Grape::Env::API_FORMAT]
         | 
| 52 | 
            -
                    Grape::Formatter | 
| 53 | 
            +
                    Grape::Formatter.formatter_for(api_format, options)
         | 
| 53 54 | 
             
                  end
         | 
| 54 55 |  | 
| 55 56 | 
             
                  # Set the content type header for the API format if it is not already present.
         | 
| @@ -92,7 +93,7 @@ module Grape | |
| 92 93 | 
             
                    fmt = mime_types[request.media_type] if request.media_type
         | 
| 93 94 | 
             
                    fmt ||= options[:default_format]
         | 
| 94 95 | 
             
                    if content_type_for(fmt)
         | 
| 95 | 
            -
                      parser = Grape::Parser | 
| 96 | 
            +
                      parser = Grape::Parser.parser_for fmt, options
         | 
| 96 97 | 
             
                      if parser
         | 
| 97 98 | 
             
                        begin
         | 
| 98 99 | 
             
                          body = (env[Grape::Env::API_REQUEST_BODY] = parser.call(body, env))
         | 
| @@ -21,18 +21,29 @@ module Grape | |
| 21 21 | 
             
                  class Param < Base
         | 
| 22 22 | 
             
                    def default_options
         | 
| 23 23 | 
             
                      {
         | 
| 24 | 
            -
                         | 
| 24 | 
            +
                        version_options: {
         | 
| 25 | 
            +
                          parameter: 'apiver'.freeze
         | 
| 26 | 
            +
                        }
         | 
| 25 27 | 
             
                      }
         | 
| 26 28 | 
             
                    end
         | 
| 27 29 |  | 
| 28 30 | 
             
                    def before
         | 
| 29 | 
            -
                      paramkey = options[:parameter]
         | 
| 30 31 | 
             
                      potential_version = Rack::Utils.parse_nested_query(env[Grape::Http::Headers::QUERY_STRING])[paramkey]
         | 
| 31 32 | 
             
                      return if potential_version.nil?
         | 
| 32 33 | 
             
                      throw :error, status: 404, message: '404 API Version Not Found', headers: { Grape::Http::Headers::X_CASCADE => 'pass' } if options[:versions] && !options[:versions].find { |v| v.to_s == potential_version }
         | 
| 33 34 | 
             
                      env[Grape::Env::API_VERSION] = potential_version
         | 
| 34 35 | 
             
                      env[Grape::Env::RACK_REQUEST_QUERY_HASH].delete(paramkey) if env.key? Grape::Env::RACK_REQUEST_QUERY_HASH
         | 
| 35 36 | 
             
                    end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                    private
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                    def paramkey
         | 
| 41 | 
            +
                      version_options[:parameter] || default_options[:version_options][:parameter]
         | 
| 42 | 
            +
                    end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                    def version_options
         | 
| 45 | 
            +
                      options[:version_options]
         | 
| 46 | 
            +
                    end
         | 
| 36 47 | 
             
                  end
         | 
| 37 48 | 
             
                end
         | 
| 38 49 | 
             
              end
         | 
    
        data/lib/grape/parser.rb
    ADDED
    
    | @@ -0,0 +1,29 @@ | |
| 1 | 
            +
            module Grape
         | 
| 2 | 
            +
              module Parser
         | 
| 3 | 
            +
                class << self
         | 
| 4 | 
            +
                  def builtin_parsers
         | 
| 5 | 
            +
                    {
         | 
| 6 | 
            +
                      json: Grape::Parser::Json,
         | 
| 7 | 
            +
                      jsonapi: Grape::Parser::Json,
         | 
| 8 | 
            +
                      xml: Grape::Parser::Xml
         | 
| 9 | 
            +
                    }
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def parsers(options)
         | 
| 13 | 
            +
                    builtin_parsers.merge(options[:parsers] || {})
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def parser_for(api_format, options = {})
         | 
| 17 | 
            +
                    spec = parsers(options)[api_format]
         | 
| 18 | 
            +
                    case spec
         | 
| 19 | 
            +
                    when nil
         | 
| 20 | 
            +
                      nil
         | 
| 21 | 
            +
                    when Symbol
         | 
| 22 | 
            +
                      method(spec)
         | 
| 23 | 
            +
                    else
         | 
| 24 | 
            +
                      spec
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
            end
         | 
| @@ -0,0 +1,19 @@ | |
| 1 | 
            +
            module Grape
         | 
| 2 | 
            +
              module Util
         | 
| 3 | 
            +
                # Response should respond to to_path method
         | 
| 4 | 
            +
                # for using Rack::SendFile middleware
         | 
| 5 | 
            +
                class SendfileResponse < Rack::Response
         | 
| 6 | 
            +
                  def respond_to?(method_name, include_all = false)
         | 
| 7 | 
            +
                    if method_name == :to_path
         | 
| 8 | 
            +
                      @body.respond_to?(:to_path, include_all)
         | 
| 9 | 
            +
                    else
         | 
| 10 | 
            +
                      super
         | 
| 11 | 
            +
                    end
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  def to_path
         | 
| 15 | 
            +
                    @body.to_path
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
            end
         | 
| @@ -36,7 +36,9 @@ module Grape | |
| 36 36 | 
             
                  #   validated
         | 
| 37 37 | 
             
                  def should_validate?(parameters)
         | 
| 38 38 | 
             
                    return false if @optional && params(parameters).respond_to?(:all?) && params(parameters).all?(&:blank?)
         | 
| 39 | 
            -
                     | 
| 39 | 
            +
                    @dependent_on.each do |dependency|
         | 
| 40 | 
            +
                      return false if params(parameters).try(:[], dependency).blank?
         | 
| 41 | 
            +
                    end if @dependent_on
         | 
| 40 42 | 
             
                    return true if parent.nil?
         | 
| 41 43 | 
             
                    parent.should_validate?(parameters)
         | 
| 42 44 | 
             
                  end
         | 
| @@ -90,7 +92,11 @@ module Grape | |
| 90 92 | 
             
                  # Adds a parameter declaration to our list of validations.
         | 
| 91 93 | 
             
                  # @param attrs [Array] (see Grape::DSL::Parameters#requires)
         | 
| 92 94 | 
             
                  def push_declared_params(attrs)
         | 
| 93 | 
            -
                     | 
| 95 | 
            +
                    if lateral?
         | 
| 96 | 
            +
                      @parent.push_declared_params(attrs)
         | 
| 97 | 
            +
                    else
         | 
| 98 | 
            +
                      @declared_params.concat attrs
         | 
| 99 | 
            +
                    end
         | 
| 94 100 | 
             
                  end
         | 
| 95 101 |  | 
| 96 102 | 
             
                  private
         | 
| @@ -141,11 +147,18 @@ module Grape | |
| 141 147 | 
             
                    type = attrs[1] ? attrs[1][:type] : nil
         | 
| 142 148 | 
             
                    if attrs.first && !optional
         | 
| 143 149 | 
             
                      fail Grape::Exceptions::MissingGroupTypeError.new if type.nil?
         | 
| 144 | 
            -
                      fail Grape::Exceptions::UnsupportedGroupTypeError.new unless  | 
| 150 | 
            +
                      fail Grape::Exceptions::UnsupportedGroupTypeError.new unless Grape::Validations::Types.group?(type)
         | 
| 145 151 | 
             
                    end
         | 
| 146 152 |  | 
| 147 153 | 
             
                    opts = attrs[1] || { type: Array }
         | 
| 148 | 
            -
             | 
| 154 | 
            +
             | 
| 155 | 
            +
                    self.class.new(
         | 
| 156 | 
            +
                      api:      @api,
         | 
| 157 | 
            +
                      element:  attrs.first,
         | 
| 158 | 
            +
                      parent:   self,
         | 
| 159 | 
            +
                      optional: optional,
         | 
| 160 | 
            +
                      type:     opts[:type],
         | 
| 161 | 
            +
                      &block)
         | 
| 149 162 | 
             
                  end
         | 
| 150 163 |  | 
| 151 164 | 
             
                  # Returns a new parameter scope, not nested under any current-level param
         | 
| @@ -191,7 +204,7 @@ module Grape | |
| 191 204 | 
             
                    default = validations[:default]
         | 
| 192 205 | 
             
                    doc_attrs[:default] = default if validations.key?(:default)
         | 
| 193 206 |  | 
| 194 | 
            -
                    values = validations[:values]
         | 
| 207 | 
            +
                    values = (options_key?(:values, :value, validations)) ? validations[:values][:value] : validations[:values]
         | 
| 195 208 | 
             
                    doc_attrs[:values] = values if values
         | 
| 196 209 |  | 
| 197 210 | 
             
                    coerce_type = guess_coerce_type(coerce_type, values)
         | 
| @@ -211,6 +224,7 @@ module Grape | |
| 211 224 | 
             
                    if validations.key?(:presence) && validations[:presence]
         | 
| 212 225 | 
             
                      validate('presence', validations[:presence], attrs, doc_attrs)
         | 
| 213 226 | 
             
                      validations.delete(:presence)
         | 
| 227 | 
            +
                      validations.delete(:message) if validations.key?(:message)
         | 
| 214 228 | 
             
                    end
         | 
| 215 229 |  | 
| 216 230 | 
             
                    # Before we run the rest of the validators, let's handle
         | 
| @@ -241,8 +255,12 @@ module Grape | |
| 241 255 | 
             
                      fail ArgumentError, ':type may not be supplied with :types'
         | 
| 242 256 | 
             
                    end
         | 
| 243 257 |  | 
| 244 | 
            -
                    validations[:coerce] = validations[:type] if validations.key?(:type)
         | 
| 245 | 
            -
                    validations[: | 
| 258 | 
            +
                    validations[:coerce] = (options_key?(:type, :value, validations) ? validations[:type][:value] : validations[:type]) if validations.key?(:type)
         | 
| 259 | 
            +
                    validations[:coerce_message] = (options_key?(:type, :message, validations) ? validations[:type][:message] : nil) if validations.key?(:type)
         | 
| 260 | 
            +
                    validations[:coerce] = (options_key?(:types, :value, validations) ? validations[:types][:value] : validations[:types]) if validations.key?(:types)
         | 
| 261 | 
            +
                    validations[:coerce_message] = (options_key?(:types, :message, validations) ? validations[:types][:message] : nil) if validations.key?(:types)
         | 
| 262 | 
            +
             | 
| 263 | 
            +
                    validations.delete(:types) if validations.key?(:types)
         | 
| 246 264 |  | 
| 247 265 | 
             
                    coerce_type = validations[:coerce]
         | 
| 248 266 |  | 
| @@ -287,11 +305,13 @@ module Grape | |
| 287 305 |  | 
| 288 306 | 
             
                    coerce_options = {
         | 
| 289 307 | 
             
                      type: validations[:coerce],
         | 
| 290 | 
            -
                      method: validations[:coerce_with]
         | 
| 308 | 
            +
                      method: validations[:coerce_with],
         | 
| 309 | 
            +
                      message: validations[:coerce_message]
         | 
| 291 310 | 
             
                    }
         | 
| 292 311 | 
             
                    validate('coerce', coerce_options, attrs, doc_attrs)
         | 
| 293 312 | 
             
                    validations.delete(:coerce_with)
         | 
| 294 313 | 
             
                    validations.delete(:coerce)
         | 
| 314 | 
            +
                    validations.delete(:coerce_message)
         | 
| 295 315 | 
             
                  end
         | 
| 296 316 |  | 
| 297 317 | 
             
                  def guess_coerce_type(coerce_type, values)
         | 
| @@ -303,7 +323,7 @@ module Grape | |
| 303 323 | 
             
                  def check_incompatible_option_values(values, default)
         | 
| 304 324 | 
             
                    return unless values && default
         | 
| 305 325 | 
             
                    return if values.is_a?(Proc) || default.is_a?(Proc)
         | 
| 306 | 
            -
                    return if values.include?(default)
         | 
| 326 | 
            +
                    return if values.include?(default) || (Array(default) - Array(values)).empty?
         | 
| 307 327 | 
             
                    fail Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :values, values)
         | 
| 308 328 | 
             
                  end
         | 
| 309 329 |  | 
| @@ -329,6 +349,16 @@ module Grape | |
| 329 349 | 
             
                    return unless value_types.any? { |v| !v.is_a?(coerce_type) }
         | 
| 330 350 | 
             
                    fail Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values)
         | 
| 331 351 | 
             
                  end
         | 
| 352 | 
            +
             | 
| 353 | 
            +
                  def extract_message_option(attrs)
         | 
| 354 | 
            +
                    return nil unless attrs.is_a?(Array)
         | 
| 355 | 
            +
                    opts = attrs.last.is_a?(Hash) ? attrs.pop : {}
         | 
| 356 | 
            +
                    (opts.key?(:message) && !opts[:message].nil?) ? opts.delete(:message) : nil
         | 
| 357 | 
            +
                  end
         | 
| 358 | 
            +
             | 
| 359 | 
            +
                  def options_key?(type, key, validations)
         | 
| 360 | 
            +
                    validations[type].respond_to?(:key?) && validations[type].key?(key) && !validations[type][key].nil?
         | 
| 361 | 
            +
                  end
         | 
| 332 362 | 
             
                end
         | 
| 333 363 | 
             
              end
         | 
| 334 364 | 
             
            end
         | 
| @@ -63,6 +63,13 @@ module Grape | |
| 63 63 | 
             
                    Rack::Multipart::UploadedFile => File
         | 
| 64 64 | 
             
                  }
         | 
| 65 65 |  | 
| 66 | 
            +
                  GROUPS = [
         | 
| 67 | 
            +
                    Array,
         | 
| 68 | 
            +
                    Hash,
         | 
| 69 | 
            +
                    JSON,
         | 
| 70 | 
            +
                    Array[JSON]
         | 
| 71 | 
            +
                  ]
         | 
| 72 | 
            +
             | 
| 66 73 | 
             
                  # Is the given class a primitive type as recognized by Grape?
         | 
| 67 74 | 
             
                  #
         | 
| 68 75 | 
             
                  # @param type [Class] type to check
         | 
| @@ -126,6 +133,15 @@ module Grape | |
| 126 133 | 
             
                    SPECIAL.key? type
         | 
| 127 134 | 
             
                  end
         | 
| 128 135 |  | 
| 136 | 
            +
                  # Is the declared type a supported group type?
         | 
| 137 | 
            +
                  # Currently supported group types are Array, Hash, JSON, and Array[JSON]
         | 
| 138 | 
            +
                  #
         | 
| 139 | 
            +
                  # @param type [Array<Class>,Class] type to check
         | 
| 140 | 
            +
                  # @return [Boolean] +true+ if the type is a supported group type
         | 
| 141 | 
            +
                  def self.group?(type)
         | 
| 142 | 
            +
                    GROUPS.include? type
         | 
| 143 | 
            +
                  end
         | 
| 144 | 
            +
             | 
| 129 145 | 
             
                  # A valid custom type must implement a class-level `parse` method, taking
         | 
| 130 146 | 
             
                  #   one String argument and returning the parsed value in its correct type.
         | 
| 131 147 | 
             
                  # @param type [Class] type to check
         | 
| @@ -5,7 +5,7 @@ module Grape | |
| 5 5 | 
             
                  def validate!(params)
         | 
| 6 6 | 
             
                    super
         | 
| 7 7 | 
             
                    if scope_requires_params && only_subset_present
         | 
| 8 | 
            -
                      fail Grape::Exceptions::Validation, params: all_keys,  | 
| 8 | 
            +
                      fail Grape::Exceptions::Validation, params: all_keys, message: message(:all_or_none)
         | 
| 9 9 | 
             
                    end
         | 
| 10 10 | 
             
                    params
         | 
| 11 11 | 
             
                  end
         | 
| @@ -2,7 +2,7 @@ module Grape | |
| 2 2 | 
             
              module Validations
         | 
| 3 3 | 
             
                class AllowBlankValidator < Base
         | 
| 4 4 | 
             
                  def validate_param!(attr_name, params)
         | 
| 5 | 
            -
                    return if @option || !params.is_a?(Hash)
         | 
| 5 | 
            +
                    return if (options_key?(:value) ? @option[:value] : @option) || !params.is_a?(Hash)
         | 
| 6 6 |  | 
| 7 7 | 
             
                    value = params[attr_name]
         | 
| 8 8 | 
             
                    value = value.strip if value.respond_to?(:strip)
         | 
| @@ -23,7 +23,7 @@ module Grape | |
| 23 23 |  | 
| 24 24 | 
             
                    return if value == false || value.present?
         | 
| 25 25 |  | 
| 26 | 
            -
                    fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)],  | 
| 26 | 
            +
                    fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:blank)
         | 
| 27 27 | 
             
                  end
         | 
| 28 28 | 
             
                end
         | 
| 29 29 | 
             
              end
         | 
| @@ -5,7 +5,7 @@ module Grape | |
| 5 5 | 
             
                  def validate!(params)
         | 
| 6 6 | 
             
                    super
         | 
| 7 7 | 
             
                    if scope_requires_params && no_exclusive_params_are_present
         | 
| 8 | 
            -
                      fail Grape::Exceptions::Validation, params: all_keys,  | 
| 8 | 
            +
                      fail Grape::Exceptions::Validation, params: all_keys, message: message(:at_least_one)
         | 
| 9 9 | 
             
                    end
         | 
| 10 10 | 
             
                    params
         | 
| 11 11 | 
             
                  end
         | 
| @@ -17,6 +17,22 @@ module Grape | |
| 17 17 | 
             
                    @scope = scope
         | 
| 18 18 | 
             
                  end
         | 
| 19 19 |  | 
| 20 | 
            +
                  # Validates a given request.
         | 
| 21 | 
            +
                  # @note This method must be thread-safe.
         | 
| 22 | 
            +
                  # @note Override #validate! unless you need to access the entire request.
         | 
| 23 | 
            +
                  # @param request [Grape::Request] the request currently being handled
         | 
| 24 | 
            +
                  # @raise [Grape::Exceptions::Validation] if validation failed
         | 
| 25 | 
            +
                  # @return [void]
         | 
| 26 | 
            +
                  def validate(request)
         | 
| 27 | 
            +
                    validate!(request.params)
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  # Validates a given parameter hash.
         | 
| 31 | 
            +
                  # @note This method must be thread-safe.
         | 
| 32 | 
            +
                  # @note Override #validate iff you need to access the entire request.
         | 
| 33 | 
            +
                  # @param params [Hash] parameters to validate
         | 
| 34 | 
            +
                  # @raise [Grape::Exceptions::Validation] if validation failed
         | 
| 35 | 
            +
                  # @return [void]
         | 
| 20 36 | 
             
                  def validate!(params)
         | 
| 21 37 | 
             
                    attributes = AttributesIterator.new(self, @scope, params)
         | 
| 22 38 | 
             
                    attributes.each do |resource_params, attr_name|
         | 
| @@ -39,6 +55,16 @@ module Grape | |
| 39 55 | 
             
                    short_name = convert_to_short_name(klass)
         | 
| 40 56 | 
             
                    Validations.register_validator(short_name, klass)
         | 
| 41 57 | 
             
                  end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                  def message(default_key = nil)
         | 
| 60 | 
            +
                    options = instance_variable_get(:@option)
         | 
| 61 | 
            +
                    options_key?(:message) ? options[:message] : default_key
         | 
| 62 | 
            +
                  end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  def options_key?(key, options = nil)
         | 
| 65 | 
            +
                    options = instance_variable_get(:@option) if options.nil?
         | 
| 66 | 
            +
                    options.respond_to?(:key?) && options.key?(key) && !options[key].nil?
         | 
| 67 | 
            +
                  end
         | 
| 42 68 | 
             
                end
         | 
| 43 69 | 
             
              end
         | 
| 44 70 | 
             
            end
         | 
| @@ -5,18 +5,31 @@ module Grape | |
| 5 5 |  | 
| 6 6 | 
             
              module Validations
         | 
| 7 7 | 
             
                class CoerceValidator < Base
         | 
| 8 | 
            +
                  def initialize(*_args)
         | 
| 9 | 
            +
                    super
         | 
| 10 | 
            +
                    @converter = Types.build_coercer(type, @option[:method])
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
             | 
| 8 13 | 
             
                  def validate_param!(attr_name, params)
         | 
| 9 | 
            -
                    fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)],  | 
| 14 | 
            +
                    fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:coerce) unless params.is_a? Hash
         | 
| 10 15 | 
             
                    new_value = coerce_value(params[attr_name])
         | 
| 11 16 | 
             
                    if valid_type?(new_value)
         | 
| 12 17 | 
             
                      params[attr_name] = new_value
         | 
| 13 18 | 
             
                    else
         | 
| 14 | 
            -
                      fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)],  | 
| 19 | 
            +
                      fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:coerce)
         | 
| 15 20 | 
             
                    end
         | 
| 16 21 | 
             
                  end
         | 
| 17 22 |  | 
| 18 23 | 
             
                  private
         | 
| 19 24 |  | 
| 25 | 
            +
                  # @!attribute [r] converter
         | 
| 26 | 
            +
                  # Object that will be used for parameter coercion and type checking.
         | 
| 27 | 
            +
                  #
         | 
| 28 | 
            +
                  # See {Types.build_coercer}
         | 
| 29 | 
            +
                  #
         | 
| 30 | 
            +
                  # @return [Virtus::Attribute]
         | 
| 31 | 
            +
                  attr_reader :converter
         | 
| 32 | 
            +
             | 
| 20 33 | 
             
                  def valid_type?(val)
         | 
| 21 34 | 
             
                    # Special value to denote coercion failure
         | 
| 22 35 | 
             
                    return false if val.instance_of?(Types::InvalidValue)
         | 
| @@ -47,18 +60,7 @@ module Grape | |
| 47 60 | 
             
                  #
         | 
| 48 61 | 
             
                  # @return [Class]
         | 
| 49 62 | 
             
                  def type
         | 
| 50 | 
            -
                    @option[:type]
         | 
| 51 | 
            -
                  end
         | 
| 52 | 
            -
             | 
| 53 | 
            -
                  # Create and cache the attribute object
         | 
| 54 | 
            -
                  # that will be used for parameter coercion
         | 
| 55 | 
            -
                  # and type checking.
         | 
| 56 | 
            -
                  #
         | 
| 57 | 
            -
                  # See {Types.build_coercer}
         | 
| 58 | 
            -
                  #
         | 
| 59 | 
            -
                  # @return [Virtus::Attribute]
         | 
| 60 | 
            -
                  def converter
         | 
| 61 | 
            -
                    @converter ||= Types.build_coercer(type, @option[:method])
         | 
| 63 | 
            +
                    @option[:type].is_a?(Hash) ? @option[:type][:value] : @option[:type]
         | 
| 62 64 | 
             
                  end
         | 
| 63 65 | 
             
                end
         | 
| 64 66 | 
             
              end
         | 
| @@ -15,7 +15,7 @@ module Grape | |
| 15 15 |  | 
| 16 16 | 
             
                    attrs = AttributesIterator.new(self, @scope, params)
         | 
| 17 17 | 
             
                    attrs.each do |resource_params, attr_name|
         | 
| 18 | 
            -
                      if resource_params[attr_name].nil?
         | 
| 18 | 
            +
                      if resource_params.is_a?(Hash) && resource_params[attr_name].nil?
         | 
| 19 19 | 
             
                        validate_param!(attr_name, resource_params)
         | 
| 20 20 | 
             
                      end
         | 
| 21 21 | 
             
                    end
         | 
| @@ -5,11 +5,20 @@ module Grape | |
| 5 5 | 
             
                  def validate!(params)
         | 
| 6 6 | 
             
                    super
         | 
| 7 7 | 
             
                    if scope_requires_params && none_of_restricted_params_is_present
         | 
| 8 | 
            -
                      fail Grape::Exceptions::Validation, params: all_keys,  | 
| 8 | 
            +
                      fail Grape::Exceptions::Validation, params: all_keys, message: message(:exactly_one)
         | 
| 9 9 | 
             
                    end
         | 
| 10 10 | 
             
                    params
         | 
| 11 11 | 
             
                  end
         | 
| 12 12 |  | 
| 13 | 
            +
                  def message(default_key = nil)
         | 
| 14 | 
            +
                    options = instance_variable_get(:@option)
         | 
| 15 | 
            +
                    if options_key?(:message)
         | 
| 16 | 
            +
                      (options_key?(default_key, options[:message]) ? options[:message][default_key] : options[:message])
         | 
| 17 | 
            +
                    else
         | 
| 18 | 
            +
                      default_key
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 13 22 | 
             
                  private
         | 
| 14 23 |  | 
| 15 24 | 
             
                  def none_of_restricted_params_is_present
         | 
| @@ -7,7 +7,7 @@ module Grape | |
| 7 7 | 
             
                  def validate!(params)
         | 
| 8 8 | 
             
                    super
         | 
| 9 9 | 
             
                    if two_or_more_exclusive_params_are_present
         | 
| 10 | 
            -
                      fail Grape::Exceptions::Validation, params: processing_keys_in_common,  | 
| 10 | 
            +
                      fail Grape::Exceptions::Validation, params: processing_keys_in_common, message: message(:mutual_exclusion)
         | 
| 11 11 | 
             
                    end
         | 
| 12 12 | 
             
                    params
         | 
| 13 13 | 
             
                  end
         | 
| @@ -8,7 +8,7 @@ module Grape | |
| 8 8 |  | 
| 9 9 | 
             
                  def validate_param!(attr_name, params)
         | 
| 10 10 | 
             
                    return if params.respond_to?(:key?) && params.key?(attr_name)
         | 
| 11 | 
            -
                    fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)],  | 
| 11 | 
            +
                    fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:presence)
         | 
| 12 12 | 
             
                  end
         | 
| 13 13 | 
             
                end
         | 
| 14 14 | 
             
              end
         |