Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
version: 2

updates:
# The lockfile is a development artifact (consumers resolve their own), but
# keeping it current is what stops the advisory backlog rebuilding quietly.
- package-ecosystem: bundler
directory: "/"
schedule:
interval: weekly
open-pull-requests-limit: 5

- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
27 changes: 23 additions & 4 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ name: Ruby

on: [push, pull_request]

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
name: "rspec Ruby ${{ matrix.ruby }} / ${{ matrix.gemfile }}"
name: "rspec - Ruby ${{ matrix.ruby }} / ${{ matrix.gemfile }}"
strategy:
fail-fast: false
matrix:
Expand All @@ -22,7 +25,7 @@ jobs:
env:
BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
Expand All @@ -35,11 +38,27 @@ jobs:
runs-on: ubuntu-latest
name: rubocop
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: "3.2"
# The root lockfile pins dev tooling that needs Ruby >= 3.3; the
# gem's own 3.2 floor is covered by the test matrix.
ruby-version: "3.4"
bundler-cache: true
- name: Run rubocop
run: bundle exec rubocop

audit:
runs-on: ubuntu-latest
name: bundler-audit
steps:
- uses: actions/checkout@v5
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
# As in the lint job: the root lockfile needs Ruby >= 3.3.
ruby-version: "3.4"
bundler-cache: true
- name: Check the lockfile against the advisory database
run: bundle exec bundle-audit check --update
3 changes: 3 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@ Metrics/BlockLength:
Style/Documentation:
Enabled: false

# RSpec fixes both names: `have_no_content` is the matcher DSL entry point and
# `does_not_match?` is part of the matcher protocol.
Naming/PredicatePrefix:
Exclude:
- "lib/rspec/json_api/matchers.rb"
- "lib/rspec/json_api/matchers/match_json_schema.rb"

Naming/PredicateMethod:
Exclude:
Expand Down
24 changes: 23 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,31 @@
## [Unreleased]

## [1.6.0] - 2026-09-03

### Fixed
- A list schema (`[String]`, `[INTERFACE]`) fails the match instead of raising `NoMethodError` when the response holds `null` or a scalar where an array was expected. This affected nested lists too, so an interface element with a scalar in place of a list crashed the example.
- `Types::URI` is anchored with `\A...\z`. It previously accepted any value that merely contained a URI, so `"see https://example.com for details"` matched. `EMAIL` and `UUID` were already anchored. Suites that relied on the substring behaviour will start failing.
- `match_json_schema` fails with `expected a JSON String to match against the schema, got NilClass` instead of raising `TypeError` when handed `nil`, an already-parsed Hash, or any other non-String.
- A schema `Proc` that returns something other than an options Hash, or that expects the value as an argument, raises a descriptive `ArgumentError` naming the mistake. Both previously surfaced as a bare `NoMethodError` or `wrong number of arguments` from inside the matcher.
- An object schema compared against array elements of another shape (`[{ id: Integer }]` against `["a", "b"]`) fails instead of raising `NoMethodError`. This is the same class of bug as the list-schema crash above, on the object comparison path.
- `expect(body).not_to match_json_schema(schema)` fails when the body is not a JSON String, rather than passing by default. A type error in the spec is now a failure whichever way the expectation is written.
- A non-lambda `proc { |value| ... }` used as a schema Proc is rejected alongside the lambda form. Ruby reports a non-lambda block parameter as optional, so the check reads the parameter list rather than the arity; a Proc declaring an optional parameter is rejected for the same reason.
- The `have_no_content` specs gave every body its own context. A repeated `let(:actual)` in one context meant the `"{}"` case never ran.

### Changed
- The released gem contains the tracked files under `lib/` plus the licence, the README and the CHANGELOG, and nothing else. It previously packaged the repository's own tooling: the CI workflow, the RuboCop config, the Gemfile and lockfile, the Rakefile, `bin/` and `gemfiles/`. Scoping the file list to tracked paths also keeps an untracked local file in `lib/` out of a release.
- README regex examples anchor with `\A` and `\z` instead of `^` and `$`, with a note explaining that the line-boundary anchors let a multi-line value satisfy a schema.
- Updated the locked development dependencies past every advisory `bundler-audit` reported: rack, nokogiri, railties, activesupport, concurrent-ruby, loofah, crass, erb, json, rails-html-sanitizer and rack-session.

### Added
- Exact-array schemas dispatch each element the same way any other schema value is dispatched, so `[Integer, Integer]` is a fixed-length tuple and `[RSpec::JsonApi::Types::URI]` type-checks its single element. Elements were previously compared with `==`, so a Class, Regexp or Proc in that position could never match.
- A `bundler-audit` job in CI, a Dependabot config for bundler and github-actions, and `permissions: contents: read` on the workflow.
- `ROADMAP.md`, the prioritised findings from a full review of the codebase.

## [1.5.0] - 2026-06-05

### Added
- CI compatibility matrix across Ruby 3.23.4 and Rails 6.1, 7.1, 7.2, 8.0 and 8.1 (`gemfiles/` + GitHub Actions matrix), so the advertised version support is actually tested.
- CI compatibility matrix across Ruby 3.2-3.4 and Rails 6.1, 7.1, 7.2, 8.0 and 8.1 (`gemfiles/` + GitHub Actions matrix), so the advertised version support is actually tested.
- `RSpec::JsonApi::Constraints` module encapsulating the schema `Proc` options DSL.
- `RSpec::JsonApi::SchemaMatch` as the single comparison entry point, and `RSpec::JsonApi::Traversal` for the internal structural helpers.

Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gemspec

gem "activesupport", ">= 6.1.4.1"
gem "bundler-audit", "~> 0.9"
gem "diffy", "~> 3.4"
gem "rake", "~> 13.2"
gem "rspec-rails", ">= 5.0.2"
Expand Down
116 changes: 61 additions & 55 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
rspec-json_api (1.5.0)
rspec-json_api (1.6.0)
activesupport (>= 6.1.4.1)
diffy (>= 3.4.2)
railties (>= 6.1.4.1)
Expand All @@ -10,23 +10,23 @@ PATH
GEM
remote: https://rubygems.org/
specs:
actionpack (8.1.2)
actionview (= 8.1.2)
activesupport (= 8.1.2)
actionpack (8.1.3.1)
actionview (= 8.1.3.1)
activesupport (= 8.1.3.1)
nokogiri (>= 1.8.5)
rack (>= 2.2.4)
rack-session (>= 1.0.1)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
useragent (~> 0.16)
actionview (8.1.2)
activesupport (= 8.1.2)
actionview (8.1.3.1)
activesupport (= 8.1.3.1)
builder (~> 3.1)
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
activesupport (8.1.2)
activesupport (8.1.3.1)
base64
bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1)
Expand All @@ -41,53 +41,54 @@ GEM
uri (>= 0.13.1)
ast (2.4.3)
base64 (0.3.0)
bigdecimal (4.0.1)
bigdecimal (4.1.2)
builder (3.3.0)
concurrent-ruby (1.3.6)
bundler-audit (0.9.3)
bundler (>= 1.2.0)
thor (~> 1.0)
concurrent-ruby (1.3.8)
connection_pool (3.0.2)
crass (1.0.6)
date (3.5.1)
crass (1.0.7)
diff-lcs (1.6.2)
diffy (3.4.4)
drb (2.2.3)
erb (6.0.1)
erb (6.0.7)
erubi (1.13.1)
i18n (1.14.8)
i18n (1.15.2)
concurrent-ruby (~> 1.0)
io-console (0.8.2)
irb (1.16.0)
io-console (0.9.2)
irb (1.18.0)
pp (>= 0.6.0)
prism (>= 1.3.0)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
json (2.18.0)
language_server-protocol (3.17.0.5)
json (2.21.2)
language_server-protocol (3.17.0.6)
lint_roller (1.1.0)
logger (1.7.0)
loofah (2.25.0)
loofah (2.25.2)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
minitest (6.0.1)
minitest (6.0.6)
drb (~> 2.0)
prism (~> 1.5)
nokogiri (1.19.0-arm64-darwin)
nokogiri (1.19.4-arm64-darwin)
racc (~> 1.4)
nokogiri (1.19.0-x86_64-darwin)
nokogiri (1.19.4-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.19.0-x86_64-linux-gnu)
nokogiri (1.19.4-x86_64-linux-gnu)
racc (~> 1.4)
parallel (1.27.0)
parser (3.3.10.1)
parallel (2.1.0)
parser (3.3.12.0)
ast (~> 2.4.1)
racc
pp (0.6.3)
pp (0.6.4)
prettyprint
prettyprint (0.2.0)
prism (1.8.0)
psych (5.3.1)
date
stringio
prism (1.9.0)
racc (1.8.1)
rack (3.2.4)
rack-session (2.1.1)
rack (3.2.7)
rack-session (2.1.2)
base64 (>= 0.1.0)
rack (>= 3.0.0)
rack-test (2.2.0)
Expand All @@ -98,61 +99,65 @@ GEM
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
rails-html-sanitizer (1.6.2)
loofah (~> 2.21)
rails-html-sanitizer (1.7.1)
loofah (~> 2.25, >= 2.25.2)
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
railties (8.1.2)
actionpack (= 8.1.2)
activesupport (= 8.1.2)
railties (8.1.3.1)
actionpack (= 8.1.3.1)
activesupport (= 8.1.3.1)
irb (~> 1.13)
rackup (>= 1.0.0)
rake (>= 12.2)
thor (~> 1.0, >= 1.2.2)
tsort (>= 0.2)
zeitwerk (~> 2.6)
rainbow (3.1.1)
rake (13.3.1)
rdoc (7.1.0)
rake (13.4.2)
rbs (4.2.0)
logger
prism (>= 1.6.0)
tsort
rdoc (8.0.0)
erb
psych (>= 4.0.0)
prism (>= 1.6.0)
rbs (>= 4.0.0)
tsort
regexp_parser (2.11.3)
reline (0.6.3)
regexp_parser (2.12.0)
reline (0.7.0)
io-console (~> 0.5)
rspec-core (3.13.6)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-mocks (3.13.7)
rspec-mocks (3.13.8)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-rails (8.0.2)
rspec-rails (8.0.4)
actionpack (>= 7.2)
activesupport (>= 7.2)
railties (>= 7.2)
rspec-core (~> 3.13)
rspec-expectations (~> 3.13)
rspec-mocks (~> 3.13)
rspec-support (~> 3.13)
rspec-support (3.13.6)
rubocop (1.82.1)
json (~> 2.3)
rspec-core (>= 3.13.0, < 5.0.0)
rspec-expectations (>= 3.13.0, < 5.0.0)
rspec-mocks (>= 3.13.0, < 5.0.0)
rspec-support (>= 3.13.0, < 5.0.0)
rspec-support (3.13.7)
rubocop (1.90.0)
json (>= 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
parallel (~> 1.10)
parallel (>= 1.10)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.9.3, < 3.0)
rubocop-ast (>= 1.48.0, < 2.0)
rubocop-ast (>= 1.49.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0)
rubocop-ast (1.49.0)
rubocop-ast (1.50.0)
parser (>= 3.3.7.2)
prism (~> 1.7)
ruby-progressbar (1.13.0)
securerandom (0.4.1)
stringio (3.2.0)
thor (1.5.0)
tsort (0.2.0)
tzinfo (2.0.6)
Expand All @@ -162,7 +167,7 @@ GEM
unicode-emoji (4.2.0)
uri (1.1.1)
useragent (0.16.11)
zeitwerk (2.7.4)
zeitwerk (2.8.3)

PLATFORMS
arm64-darwin-22
Expand All @@ -172,6 +177,7 @@ PLATFORMS

DEPENDENCIES
activesupport (>= 6.1.4.1)
bundler-audit (~> 0.9)
diffy (~> 3.4)
rake (~> 13.2)
rspec-json_api!
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ RSpec.describe UsersController, type: :controller do
id: RSpec::JsonApi::Types::UUID,
name: String,
age: Integer,
favoriteColorHex: /^\#([a-fA-F]|[0-9]){3,6}$/,
favoriteColorHex: /\A\#([a-fA-F]|[0-9]){3,6}\z/,
number: -> { { type: Integer, min: 10, max: 20, lambda: lambda(&:even?) } }
}]
end
Expand Down Expand Up @@ -133,7 +133,7 @@ Custom type example:
module RSpec
module JsonApi
module Types
COLOR_HEX = /^#(?:[0-9a-fA-F]{3}){1,2}$/
COLOR_HEX = /\A#(?:[0-9a-fA-F]{3}){1,2}\z/
end
end
end
Expand Down Expand Up @@ -223,10 +223,11 @@ end
```ruby
let(:expected_schema) do
{
color: /^\#([a-fA-F]|[0-9]){3,6}$/
color: /\A\#([a-fA-F]|[0-9]){3,6}\z/
}
end
```
_Note: anchor with `\A` and `\z`, not `^` and `$`. `^` and `$` match at line boundaries, so `/^\#[0-9a-fA-F]{3}$/` also accepts `"not a color\n#FFF"` and the value only has to contain a matching line for the schema to pass. The built-in `EMAIL`, `URI` and `UUID` types are anchored this way._

### Interface match
```ruby
Expand Down
Loading