ruby-grape/grape · error · ArgumentError

oneof: must be a non-empty Array of blocks

Error message

oneof: must be a non-empty Array of blocks

What it means

`process_oneof!` requires the `oneof:` value to be a non-empty Array so there is at least one alternative to validate against. `nil`, an empty Array, or a bare (unwrapped) Proc all fail `variants.is_a?(Array) && variants.any?` and raise ArgumentError at API-definition time.

Source

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

        # 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)
      end

      def all_element_blank?(scoped_params)

View on GitHub (pinned to 22d7975629)

Solutions

  1. Wrap variants in an Array with at least one element: `oneof: [-> { requires :id }, -> { requires :name }]`
  2. Wrap a single alternative: `oneof: [-> { requires :id }]`
  3. If there are no real alternatives, delete the `oneof:` key entirely

Example fix

# before
requires :filter, type: Hash, oneof: variants # variants == [] at load time

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

Strategy: validation

Validate before calling

def oneof_variants_present?(opts)
  variants = opts[:oneof]
  variants.is_a?(Array) && !variants.empty?
end

raise ArgumentError, 'oneof needs at least one variant' unless oneof_variants_present?(filter_opts)

Try / catch

begin
  requires :filter, type: Hash, oneof: build_variants # may return []
rescue ArgumentError => e
  raise "oneof built no variants for :filter: #{e.message}"
end

Prevention

When it happens

Trigger: `oneof: []`; `oneof: nil`; `requires :filter, type: Hash, oneof: -> { requires :id }` (a single proc not wrapped in an Array).

Common situations: Dynamically building the variant list and passing through an empty result when no variants apply; forgetting the extra brackets around a single alternative.

Related errors


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