Skip to content

Move declaration-coherence checks into ValidationsSpec#2720

Open
ericproulx wants to merge 1 commit into
masterfrom
refactor/validations-spec-self-validate
Open

Move declaration-coherence checks into ValidationsSpec#2720
ericproulx wants to merge 1 commit into
masterfrom
refactor/validations-spec-self-validate

Conversation

@ericproulx
Copy link
Copy Markdown
Contributor

Summary

check_incompatible_option_values and validate_value_coercion were private methods on ParamsScope, invoked from #validates immediately after building the ValidationsSpec. Both are pure cross-field invariants computed entirely from fields the spec already owns (default, values, except_values, coerce_type).

ValidationsSpec already enforced one construction-time invariant — the :type + :types mutual-exclusion ArgumentError in #initialize. Concentrating the rest there makes "you cannot construct an incoherent spec" true by definition: the invariant no longer depends on a caller remembering to run the checks after .from(...). ParamsScope#validates collapses to build-spec → document → dispatch.

Changes

  • Moved check_incompatible_option_values and validate_value_coercion into ValidationsSpec as private methods.
  • New private ValidationsSpec#validate! runs them from #initialize, before freeze.
  • Deleted both methods from ParamsScope and removed the two call sites in #validates.

Deliberately left behind: check_coerce_with

It looks similar but isn't a pure construction-time invariant. It's invoked from ParamsScope#validate_coerce, gated on the remountable-API skip (return unless coerce_options[:type] — a base instance has no resolved type until the mounted instance replays). Moving it into ValidationsSpec#initialize would make a remountable API raise on the base instance before its type resolves. It stays in validate_coerce.

Behaviour change

Constructing a ValidationsSpec with an incoherent combination (e.g. default: 42, values: [1, 2, 3]) now raises Grape::Exceptions::IncompatibleOptionValues at construction instead of only when ParamsScope#validates reached the check. For real APIs this is identical (the spec is always built inside #validates). One documentation spec built such a deliberately-incompatible spec just to exercise document_params; its fixture now uses a coherent default: 1.

ValidationsSpec gains a dependency on Grape::Exceptions::IncompatibleOptionValues — acceptable since it already raises ArgumentError and is Grape-internal.

Test plan

  • bundle exec rspec — 2307 examples, 0 failures
  • RuboCop clean
  • CI green across Gemfile variants

🤖 Generated with Claude Code

`check_incompatible_option_values` and `validate_value_coercion` were
private methods on `ParamsScope`, invoked from `#validates` right after
building the `ValidationsSpec`. Both are pure cross-field invariants
computed entirely from fields the spec already owns (`default`,
`values`, `except_values`, `coerce_type`).

`ValidationsSpec` already enforced one construction-time invariant (the
`:type` + `:types` mutual-exclusion `ArgumentError`), so concentrating
the rest there makes "you cannot construct an incoherent spec" true by
definition: the invariant no longer depends on a caller remembering to
run the checks after `.from(...)`. `ParamsScope#validates` shrinks to
build-spec → document → dispatch.

- Moved both methods into `ValidationsSpec` as private; a new private
  `validate!` runs them from `initialize`, before `freeze`.
- Removed both from `ParamsScope` and the two call sites in `#validates`.
- `check_coerce_with` deliberately stays in `ParamsScope#validate_coerce`:
  it is gated on the remountable-API skip (`return unless
  coerce_options[:type]`, because a base instance has no resolved type
  until the mounted instance replays) and is therefore not a pure
  construction-time invariant.

Spec fixture update: `params_documentation_spec` built a spec with
`default: 42, values: [1, 2, 3]` purely to exercise `document_params`;
that combination can no longer be constructed, so the fixture now uses a
coherent `default: 1`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ericproulx ericproulx force-pushed the refactor/validations-spec-self-validate branch from 55b22ab to 0a099e9 Compare May 15, 2026 08:51
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 15, 2026

Danger Report

No issues found.

View run

@ericproulx ericproulx requested a review from dblock May 15, 2026 12:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant