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
- Add `type: Hash` to the same declaration: `requires :filter, type: Hash, oneof: [-> { ... }, -> { ... }]`
- Use exactly the `Hash` constant — custom hash-like classes fail the equality check
- 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
- Memorize the oneof shape: `type: Hash, oneof: [proc, proc]` — always four parts
- Use `types:` for scalar unions; reserve `oneof:` for structural alternatives
- Cover every oneof declaration with a mounting spec that exercises each variant
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
- oneof: must be a non-empty Array of blocks
- oneof: each variant must be a Proc
- :type may not be supplied with :types
- must supply type for coerce_with
- coerce_with disallowed for type: JSON
AI-assisted analysis of ruby-grape/grape@22d7975629 (2026-08-21).
Data as JSON: /api/errors/a21e6f0b48704003.
Report an issue: GitHub.