ruby-grape/grape · error · ArgumentError

oneof: requires type: Hash

Error message

oneof: requires type: Hash

What it means

`oneof:` declares alternative shapes for a parameter: each variant is a block evaluated in its own ParamsScope backed by a OneofCollector, so the full params DSL (requires, optional, nesting) is available inside. Because every variant describes nested keys, the parent declaration must be exactly `type: Hash`; `process_oneof!` raises ArgumentError at definition time otherwise.

Source

Thrown at lib/grape/validations/params_scope.rb:404

        check_coerce_with(coerce_options)
        # Falsy check is intentional: when a remountable API is first evaluated
        # on its base instance (no configuration supplied yet),
        # configuration[:some_type] evaluates to nil. Skipping instantiation
        # here is correct — the real mounted instance will replay this step
        # with the actual type value.
        return unless coerce_options.type

        validate('coerce', coerce_options, attrs, spec.required?, spec.shared_opts)
      end

      # Translate a `oneof: [proc, proc, ...]` declaration into a list of
      # captured validator arrays — one array per variant. Each variant's
      # block is evaluated in its own +ParamsScope+ backed by an
      # {OneofCollector} so the full params DSL is available inside variants
      # and the resulting validators are kept out of the real API's
      # registration list.
      def process_oneof!(validations)
        raise ArgumentError, 'oneof: requires type: Hash' unless validations[:type] == Hash

        variants = validations[:oneof]
        raise ArgumentError, 'oneof: must be a non-empty Array of blocks' unless variants.is_a?(Array) && variants.any?
        raise ArgumentError, 'oneof: each variant must be a Proc' unless variants.all?(Proc)

        validations[:oneof] = variants.map { |block| OneofCollector.collect(block) }
      end

      def validate(type, options, attrs, required, opts)
        validator_class = Validations.require_validator(type)
        validator_instance = validator_class.new(
          attrs,
          options,
          required,
          self,
          opts
        )
        @api.inheritable_setting.add_validation(validator_instance)

View on GitHub (pinned to 22d7975629)

Solutions

  1. Add `type: Hash` to the same declaration: `requires :filter, type: Hash, oneof: [-> { ... }, -> { ... }]`
  2. Use exactly the `Hash` constant — custom hash-like classes fail the equality check
  3. If the alternatives are scalar types rather than nested keys, use `types: [A, B]` instead of `oneof:`

Example fix

# before
requires :filter, oneof: [-> { requires :name }, -> { requires :id }]

# after
requires :filter, type: Hash, oneof: [
  -> { requires :name, type: String },
  -> { requires :id, type: Integer }
]
Defensive patterns

Strategy: validation

Validate before calling

def assert_oneof_shape!(opts)
  return unless opts.key?(:oneof) && opts[:type] != Hash

  raise ArgumentError, 'oneof: declarations must pair with type: Hash'
end

Try / catch

begin
  requires :filter, oneof: [-> { requires :name }]
rescue ArgumentError => e
  raise "oneof declaration error: #{e.message}"
end

Prevention

When it happens

Trigger: `requires :filter, oneof: [-> { requires :name }, -> { requires :id }]` with no `type:`; `optional :q, type: Array, oneof: [...]` (any type other than exactly the `Hash` constant, including `Array[Hash]`).

Common situations: Adopting the oneof syntax from changelogs or examples without noticing the mandatory type pairing; converting a scalar `types: [Integer, String]` union into a structural oneof and leaving the old type in place.

Related errors


AI-assisted analysis of ruby-grape/grape@22d7975629 (2026-08-21). Data as JSON: /api/errors/a21e6f0b48704003. Report an issue: GitHub.