Guard array fields against required: + default: - #71
Merged
Merged
Conversation
The same required+default contradiction validate_scalar_opts! and validate_json_opts! already reject at class load was missing from array. required: true was silently a no-op on an array field with a default:, and the exported JSON Schema said the field was required while the real contract accepted a request that omitted it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
VSN2015
pushed a commit
that referenced
this pull request
Sep 27, 2026
Bumps version.rb, retitles the Unreleased CHANGELOG section, and includes the regenerated Gemfile.lock — CI runs bundler in frozen mode, so a version bump without the lockfile fails the tag build. Twenty PRs since 0.8.0 (#16, #35, #51-#57, #59-#66, #71-#73): Rails 8 params.expect support and model-aware drafting in permittable:generate, the permittable:audit coverage command, reusable field groups (Permittable.fields/use), accept_params/reject_params RSpec matchers, RFC 9457 problem+json, named format: presets, and a type-checking mode for the schema-drift guard — plus a from-scratch Ruby-to-ECMA-262 pattern translator, a correctness overhaul of in: list casting and authored default:/example: storage, several encoding-crash and log-forging fixes, and three freshly-found gaps: an array field's required: + default: silently behaved as optional, accept_params/reject_params disagreed with a standalone Contract's own unknown: strictness, and the generator could draft a field from a permit call that only existed inside a log string. Minor rather than patch: mostly new surface, but a :string field's in: list of Symbols now matches correctly where it used to reject every request, and an array field combining required: and default: now fails at class load instead of silently treating the field as optional. See CHANGELOG.md for the complete list. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
arrayfields had no guard against declaring bothrequired: trueanddefault:— the two are semantically contradictory, since a default only ever applies when a field is absent, and a required field can never be absent by definition without erroring first.validate_scalar_opts!andvalidate_json_opts!already raise on this combination at class load, butarrayhad no equivalent check, so this loaded silently:At request time,
permittable_check_hashchecksfield.key?(:default)beforefield[:required], so an absenttagsis quietly treated as optional-with-default —required: truebecomes a no-op. Worse,lib/permittable/json_schema.rbbuilds the exported"required"array purely fromfield[:required], with no awareness ofdefault:, so the published JSON Schema saystagsis required while the real contract happily accepts a request that omits it — a genuine behavior/schema divergence, not just cosmetic.Nested
required(:name) { ... }fields were never affected:NESTED_OPTSdoesn't even list:defaultas an allowed option, soassert_opts!already rejectsdefault:there at class load via a different mechanism. This bug — and this fix — is scoped toarrayfields only.The fix
Add the same required+default guard
validate_scalar_opts!/validate_json_opts!already carry, toarray:Now the same declaration fails loudly at class load, the same way the scalar and JSON cases already do:
Verification
bundle exec rspec— 849 examples, 0 failuresbundle exec rubocop lib/permittable.rb spec/permittable_spec.rb— no offensesspec/permittable_spec.rb), confirmed to fail before the implementation change and pass after🤖 Generated with Claude Code