{"record":{"id":"da6381b7e49dcec1","repo":"ruby-grape/grape","slug":"must-supply-type-for-coerce-with","errorCode":null,"errorMessage":"must supply type for coerce_with","messagePattern":"must supply type for coerce_with","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/grape/validations/params_scope.rb","lineNumber":372,"sourceCode":"\n        # Presence runs first — `required` is forwarded to every subsequent\n        # validator (some short-circuit on it).\n        validate_presence(spec, attrs)\n\n        # Coerce runs second — later validators see the typed value.\n        validate_coerce(spec, attrs)\n\n        spec.validator_entries.each do |type, options|\n          validate(type, options, attrs, spec.required?, spec.shared_opts)\n        end\n      end\n\n      # Enforce correct usage of :coerce_with on a CoerceOptions.\n      # We do not allow coercion without a type, nor with +JSON+ as a type\n      # since that defines its own coercion method.\n      def check_coerce_with(coerce_options)\n        return unless coerce_options.coerce_method\n        raise ArgumentError, 'must supply type for coerce_with' unless coerce_options.type\n        return unless SPECIAL_JSON.include?(coerce_options.type)\n\n        raise ArgumentError, 'coerce_with disallowed for type: JSON'\n      end\n\n      def validate_presence(spec, attrs)\n        return unless spec.required?\n\n        validate('presence', spec.presence_options, attrs, true, spec.shared_opts)\n      end\n\n      def validate_coerce(spec, attrs)\n        coerce_options = spec.coerce_options\n        check_coerce_with(coerce_options)\n        # Falsy check is intentional: when a remountable API is first evaluated\n        # on its base instance (no configuration supplied yet),\n        # configuration[:some_type] evaluates to nil. Skipping instantiation\n        # here is correct — the real mounted instance will replay this step","sourceCodeStart":354,"sourceCodeEnd":390,"githubUrl":"https://github.com/ruby-grape/grape/blob/22d7975629846a3c0c7bd2b34e140a7a1b4af8f6/lib/grape/validations/params_scope.rb#L354-L390","documentation":"Grape requires every `coerce_with:` option on `requires`/`optional` to be paired with an explicit `type:`. `coerce_with` only says HOW the incoming param is converted; `type:` says WHAT the param becomes and drives later validation, defaults, and documentation. When `ParamsScope#check_coerce_with` finds a coercion method but no type, it raises ArgumentError while the API class body is being evaluated, before any request is served.","triggerScenarios":"Declaring `requires :age, coerce_with: ->(v) { v.to_i }` (proc with no type); `optional :token, coerce_with: CustomParser` (object responding to call/parse with no type); any requires/optional declaration whose options hash reaches ParamsScope with a `:coerce_with` key and no `:type`/`:types` key.","commonSituations":"Porting Sinatra-style endpoints and assuming the coercion proc's return value implies the type; copying example snippets that omit `type:`; building declarations from dynamically merged option hashes where the `:type` key is dropped or renamed.","solutions":["Add `type:` to the same declaration, e.g. `requires :age, type: Integer, coerce_with: ->(v) { v.to_i }`","If the conversion belongs to a custom class, give the class a class-level `parse` method and pass it as `type:` alone (Grape custom type), dropping `coerce_with`","If declarations are generated dynamically, assert that any hash containing `:coerce_with` also contains `:type` or `:types` before it reaches the DSL"],"exampleFix":"# before\nrequires :age, coerce_with: ->(v) { v.to_i }\n\n# after\nrequires :age, type: Integer, coerce_with: ->(v) { v.to_i }","handlingStrategy":"validation","validationCode":"def assert_coerce_declaration!(opts)\n  return unless opts.key?(:coerce_with) && !opts.key?(:type) && !opts.key?(:types)\n\n  raise ArgumentError, ':coerce_with must be paired with :type (keys given: ' + opts.keys.inspect + ')'\nend\n\nassert_coerce_declaration!(type: Integer, coerce_with: ->(v) { v.to_i })","typeGuard":null,"tryCatchPattern":"begin\n  MyAPI = Class.new(Grape::API) do\n    requires :age, coerce_with: ->(v) { v.to_i } # raises during class definition\n  end\nrescue ArgumentError => e\n  raise \"Invalid param declaration while loading MyAPI: #{e.message}\"\nend","preventionTips":["Treat `type:` and `coerce_with:` as an inseparable pair in code review","Write one mounting spec per endpoint so declaration errors fail in CI, not at boot","Prefer custom types with a class-level `parse` over raw `coerce_with` procs — then only `type:` is needed"],"tags":["grape","params","coercion","coerce-with","argumenterror","dsl"],"backgroundTag":"missing-required-option","analyzedSha":"22d7975629846a3c0c7bd2b34e140a7a1b4af8f6","analyzedAt":"2026-08-21T17:03:54.627Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}