rspec-json_api 1.5.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +49 -2
- data/CONTRIBUTING.md +24 -0
- data/README.md +143 -236
- data/SECURITY.md +9 -0
- data/lib/rspec/json_api/blank.rb +22 -0
- data/lib/rspec/json_api/constraints.rb +8 -2
- data/lib/rspec/json_api/matchers/match_json_schema.rb +26 -1
- data/lib/rspec/json_api/schema_match.rb +36 -12
- data/lib/rspec/json_api/traversal.rb +16 -0
- data/lib/rspec/json_api/types/uri.rb +5 -1
- data/lib/rspec/json_api/version.rb +1 -1
- data/lib/rspec/json_api.rb +2 -1
- metadata +11 -55
- data/.gitattributes +0 -28
- data/.github/workflows/main.yml +0 -45
- data/.gitignore +0 -17
- data/.rspec +0 -3
- data/.rubocop.yml +0 -41
- data/.ruby-version +0 -1
- data/CODE_OF_CONDUCT.md +0 -84
- data/Gemfile +0 -14
- data/Gemfile.lock +0 -182
- data/Rakefile +0 -12
- data/bin/console +0 -15
- data/bin/setup +0 -8
- data/gemfiles/rails_6_1.gemfile +0 -7
- data/gemfiles/rails_7_1.gemfile +0 -7
- data/gemfiles/rails_7_2.gemfile +0 -7
- data/gemfiles/rails_8_0.gemfile +0 -7
- data/gemfiles/rails_8_1.gemfile +0 -7
- data/lib/rspec/json_api/interfaces/example_interface.rb +0 -14
- data/rspec-json_api.gemspec +0 -42
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1af83b52a7a2a283b8f88e3d35920a6a5222fe10ec786a7a7f09f509ee923b75
|
|
4
|
+
data.tar.gz: 7772ea842f6e73370494969b5f5ad867ef0cb67f77449a95607b71190fd37545
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7bd1537b2ea54021789bc2bf2d9ace9df346c2a75ee1d3183eafd6ca0aaebfa332b534f233047605bb252cc0afb76e35d9e7e26d97c921cbc44f321760ef1fe9
|
|
7
|
+
data.tar.gz: 6a2161022a3bff6d5af28398653289eaee7be1e3cc27cddeedd74cffaeb644a7e2548c6e5e04891e211c826a68b6c0d0de2cf7d98253459b0bc90d35e7ca126c
|
data/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,56 @@
|
|
|
1
|
-
## [
|
|
1
|
+
## [2.0.0] - 2026-09-14
|
|
2
|
+
|
|
3
|
+
### Fixed
|
|
4
|
+
- Exact arrays enforce full key structure for Hash elements. Extra null-valued keys and missing keys that happen to accept blank values no longer pass inside tuples.
|
|
5
|
+
- Nested key checks preserve each key's parent association. Objects with the same nested key names attached to different parents no longer match when the affected values are `null`.
|
|
6
|
+
- Regexp schemas require a String value instead of matching `to_s`. Numeric values and `null` no longer satisfy permissive regular expressions.
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
- Removed ActiveSupport blank/present core extensions in favor of a JSON-focused internal helper with the same relevant semantics.
|
|
10
|
+
- Runtime dependencies are now only `diffy` and `rspec-expectations`. Rails, Railties, ActiveSupport, and `rspec-rails` are no longer installed for matcher-only consumers. Projects using the optional generators must provide Rails themselves.
|
|
11
|
+
- The compatibility matrix tests matcher behavior without Rails on Ruby 3.2, 3.3, 3.4, and 4.0. Separate generator jobs exercise Rails 6.1 and Rails 8.1 at the supported boundaries.
|
|
12
|
+
- The README now documents strict key behavior, schema dispatch, errors, array shorthand, supported runtimes, and the fact that this is a general JSON shape matcher rather than a JSON:API implementation.
|
|
13
|
+
- CI runs `actions/checkout@v7`, up from v5, alongside `ruby/setup-ruby@v1`. Both track their major tag, so upstream patch releases arrive without a commit here and Dependabot opens a pull request for each new major. Checkout also runs with `persist-credentials: false`: no step needs git credentials, and bundler evaluates gemspec and native-extension code from the branch under test.
|
|
14
|
+
- The workflow runs once per change rather than twice. `push` is scoped to `master`, so a branch with an open pull request no longer builds under both events, and a `concurrency` group cancels superseded pull request runs. Runs on `master` are left alone, so every commit there keeps a result.
|
|
15
|
+
- `rubocop` and `bundler-audit` share one job definition instead of two byte-identical ones, and every job carries `timeout-minutes: 30` in place of the six-hour default.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- Top-level Class and Regexp schemas now work for scalar JSON documents, using the same dispatch rules as nested values.
|
|
19
|
+
- `match_json_schema` exposes an RSpec description for one-line and documentation formatter output.
|
|
20
|
+
- Direct unit coverage for schema matching, traversal, constraints, and the Rails generators; randomized spec ordering; Ruby warnings; and a 90% SimpleCov floor.
|
|
21
|
+
- Contributor and security policy documents, included in the packaged gem.
|
|
22
|
+
- A weekly `schedule` trigger, so `bundler-audit` reports an advisory published against a lockfile nobody has touched. Only the audit runs on that trigger. `workflow_dispatch` runs it on demand and is also how the cron is re-armed, since GitHub disables scheduled workflows after 60 days without repository activity.
|
|
23
|
+
|
|
24
|
+
### Removed
|
|
25
|
+
- The internal example interface fixture from the packaged library; it now lives under test support.
|
|
26
|
+
- Redundant Rails 7.1, 7.2, and 8.0 appraisal files after reducing generator compatibility checks to the supported boundaries.
|
|
27
|
+
|
|
28
|
+
## [1.6.0] - 2026-09-03
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
- 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.
|
|
32
|
+
- `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.
|
|
33
|
+
- `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.
|
|
34
|
+
- 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.
|
|
35
|
+
- 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.
|
|
36
|
+
- `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.
|
|
37
|
+
- 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.
|
|
38
|
+
- The `have_no_content` specs gave every body its own context. A repeated `let(:actual)` in one context meant the `"{}"` case never ran.
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
- 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.
|
|
42
|
+
- 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.
|
|
43
|
+
- 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.
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
- 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.
|
|
47
|
+
- A `bundler-audit` job in CI, a Dependabot config for bundler and github-actions, and `permissions: contents: read` on the workflow.
|
|
48
|
+
- `ROADMAP.md`, the prioritised findings from a full review of the codebase.
|
|
2
49
|
|
|
3
50
|
## [1.5.0] - 2026-06-05
|
|
4
51
|
|
|
5
52
|
### Added
|
|
6
|
-
- CI compatibility matrix across Ruby 3.2
|
|
53
|
+
- 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.
|
|
7
54
|
- `RSpec::JsonApi::Constraints` module encapsulating the schema `Proc` options DSL.
|
|
8
55
|
- `RSpec::JsonApi::SchemaMatch` as the single comparison entry point, and `RSpec::JsonApi::Traversal` for the internal structural helpers.
|
|
9
56
|
|
data/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Bug reports and pull requests are welcome.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
Use the Ruby version declared in `.ruby-version` and Bundler 4.0.4:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
gem install bundler -v 4.0.4
|
|
11
|
+
bundle install
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Run the local checks before opening a pull request:
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
bundle exec rspec
|
|
18
|
+
bundle exec rubocop
|
|
19
|
+
bundle exec bundle-audit check --update
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Matcher changes should include focused examples under `spec/rspec/json_api`. Generator changes should include examples under `spec/generators` and be checked against both appraisal gemfiles.
|
|
23
|
+
|
|
24
|
+
Keep pull requests focused and explain user-visible behavior changes in `CHANGELOG.md`. By participating, you agree to follow the [code of conduct](CODE_OF_CONDUCT.md).
|
data/README.md
CHANGED
|
@@ -1,319 +1,226 @@
|
|
|
1
1
|
# RSpec::JsonApi
|
|
2
2
|
|
|
3
|
-
[
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
`rspec-json_api` adds RSpec matchers for checking JSON values against compact Ruby schemas. Despite the name, it validates general JSON response shapes; it does not implement the [JSON:API specification](https://jsonapi.org/).
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Ruby 3.2 or newer
|
|
8
|
+
- RSpec 3
|
|
9
|
+
- Rails 6.1 or newer only when using the optional generators
|
|
10
|
+
|
|
11
|
+
The CI suite covers matcher behavior on Ruby 3.2, 3.3, 3.4, and 4.0. Generator integration is exercised at the supported Rails boundaries: Rails 6.1 on Ruby 3.2 and Rails 8.1 on Ruby 3.4.
|
|
6
12
|
|
|
7
13
|
## Installation
|
|
8
14
|
|
|
9
|
-
Add
|
|
15
|
+
Add the gem to your test group:
|
|
10
16
|
|
|
11
17
|
```ruby
|
|
12
|
-
|
|
18
|
+
group :test do
|
|
19
|
+
gem "rspec-json_api"
|
|
20
|
+
end
|
|
13
21
|
```
|
|
14
22
|
|
|
15
|
-
|
|
23
|
+
Then run:
|
|
16
24
|
|
|
17
|
-
|
|
25
|
+
```sh
|
|
26
|
+
bundle install
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Load the matchers from `spec/spec_helper.rb` (or your equivalent RSpec setup file):
|
|
18
30
|
|
|
19
|
-
|
|
31
|
+
```ruby
|
|
32
|
+
require "rspec/json_api"
|
|
33
|
+
```
|
|
20
34
|
|
|
21
|
-
|
|
35
|
+
Rails projects can generate the definition directories:
|
|
22
36
|
|
|
23
|
-
|
|
37
|
+
```sh
|
|
38
|
+
rails generate rspec:json_api:install
|
|
39
|
+
```
|
|
24
40
|
|
|
25
|
-
|
|
41
|
+
Load custom types before interfaces in `rails_helper.rb`, because interfaces may reference types:
|
|
26
42
|
|
|
27
|
-
Require gem assets in your `rails_helper.rb`
|
|
28
43
|
```ruby
|
|
29
|
-
Dir[File.join(__dir__,
|
|
30
|
-
Dir[File.join(__dir__,
|
|
44
|
+
Dir[File.join(__dir__, "rspec", "json_api", "types", "*.rb")].each { |file| require file }
|
|
45
|
+
Dir[File.join(__dir__, "rspec", "json_api", "interfaces", "*.rb")].each { |file| require file }
|
|
31
46
|
```
|
|
32
47
|
|
|
33
|
-
|
|
48
|
+
The matchers themselves do not depend on Rails, ActiveSupport, or `rspec-rails`.
|
|
34
49
|
|
|
35
|
-
|
|
50
|
+
## Matchers
|
|
36
51
|
|
|
37
|
-
|
|
52
|
+
### `match_json_schema`
|
|
38
53
|
|
|
39
|
-
|
|
54
|
+
Pass the matcher a JSON String and describe the parsed value with Ruby values, classes, regular expressions, arrays, hashes, or constraint Procs:
|
|
40
55
|
|
|
41
|
-
|
|
56
|
+
```ruby
|
|
57
|
+
schema = {
|
|
58
|
+
id: RSpec::JsonApi::Types::UUID,
|
|
59
|
+
name: String,
|
|
60
|
+
age: -> { { type: Integer, min: 18 } },
|
|
61
|
+
tags: [String]
|
|
62
|
+
}
|
|
42
63
|
|
|
43
|
-
|
|
64
|
+
expect(response.body).to match_json_schema(schema)
|
|
65
|
+
```
|
|
44
66
|
|
|
67
|
+
The actual value must be a JSON String. Invalid JSON and non-String inputs fail the match. Schema keys must be symbols because JSON object keys are symbolized while parsing.
|
|
45
68
|
|
|
46
|
-
|
|
69
|
+
Object schemas are strict at every nesting level: every expected key must be present and unexpected keys fail the match. `allow_blank` permits a blank value; it does not make a key optional.
|
|
70
|
+
|
|
71
|
+
Root schemas may describe objects, arrays, or scalar JSON values:
|
|
47
72
|
|
|
48
73
|
```ruby
|
|
49
|
-
|
|
74
|
+
expect('"ready"').to match_json_schema(String)
|
|
75
|
+
expect('"ready"').to match_json_schema(/\Aready\z/)
|
|
76
|
+
expect("42").to match_json_schema(42)
|
|
77
|
+
```
|
|
50
78
|
|
|
51
|
-
|
|
52
|
-
describe '#index' do
|
|
53
|
-
let(:expected_schema) do
|
|
54
|
-
[{
|
|
55
|
-
id: RSpec::JsonApi::Types::UUID,
|
|
56
|
-
name: String,
|
|
57
|
-
age: Integer,
|
|
58
|
-
favoriteColorHex: /^\#([a-fA-F]|[0-9]){3,6}$/,
|
|
59
|
-
number: -> { { type: Integer, min: 10, max: 20, lambda: lambda(&:even?) } }
|
|
60
|
-
}]
|
|
61
|
-
end
|
|
79
|
+
### `have_no_content`
|
|
62
80
|
|
|
63
|
-
|
|
64
|
-
get :index
|
|
81
|
+
`have_no_content` matches only an empty String:
|
|
65
82
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
describe '#update' do
|
|
71
|
-
it 'matches API response' do
|
|
72
|
-
put :update, params: { name: 'John', age: 35 }
|
|
73
|
-
|
|
74
|
-
expect(response.body).to have_no_content
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
end
|
|
83
|
+
```ruby
|
|
84
|
+
expect(response.body).to have_no_content
|
|
78
85
|
```
|
|
79
86
|
|
|
80
|
-
|
|
81
|
-
- ### match_json_schema
|
|
82
|
-
```
|
|
83
|
-
expect(response.body).to match_json_schema(expected_schema)
|
|
84
|
-
```
|
|
87
|
+
JSON objects, JSON arrays, and whitespace-only bodies are considered content.
|
|
85
88
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
## Schema Values
|
|
90
|
+
|
|
91
|
+
### Exact values
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
schema = { status: "ready", count: 2 }
|
|
89
95
|
```
|
|
90
96
|
|
|
91
|
-
|
|
92
|
-
|
|
97
|
+
### Classes
|
|
98
|
+
|
|
99
|
+
Classes use `instance_of?`, so subclasses do not match:
|
|
93
100
|
|
|
94
101
|
```ruby
|
|
95
|
-
|
|
102
|
+
schema = { id: Integer, name: String }
|
|
103
|
+
```
|
|
96
104
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
number: Integer,
|
|
104
|
-
color: -> { { inclusion: %w[black red white], allow_blank: true } }
|
|
105
|
-
}.freeze
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
end
|
|
105
|
+
### Regular expressions
|
|
106
|
+
|
|
107
|
+
A regular expression matches only a JSON String. Numbers, booleans, and `null` do not match after conversion:
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
schema = { color: /\A#[0-9a-fA-F]{6}\z/ }
|
|
109
111
|
```
|
|
110
|
-
_Note: You can either generate file on your own or use generator._
|
|
111
|
-
## Types
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
Use `\A` and `\z` for whole-string validation. Ruby's `^` and `$` are line anchors and may accept a matching line inside a multiline value.
|
|
114
|
+
|
|
115
|
+
### Arrays
|
|
116
|
+
|
|
117
|
+
Array schemas have three forms:
|
|
118
|
+
|
|
116
119
|
```ruby
|
|
117
|
-
|
|
120
|
+
[String] # any-length list of Strings
|
|
121
|
+
[{ id: Integer, name: String }] # any-length list of this object shape
|
|
122
|
+
[Integer, String] # an exact two-element tuple
|
|
118
123
|
```
|
|
119
|
-
|
|
124
|
+
|
|
125
|
+
The one-element shorthand applies only to a Class or Hash. For example, `[Types::UUID]` means an exact one-element array because `Types::UUID` is a Regexp.
|
|
126
|
+
|
|
127
|
+
### Constraint Procs
|
|
128
|
+
|
|
129
|
+
A constraint Proc takes no arguments and returns an options Hash:
|
|
130
|
+
|
|
120
131
|
```ruby
|
|
121
|
-
|
|
132
|
+
schema = {
|
|
133
|
+
age: -> { { type: Integer, min: 18, max: 120 } },
|
|
134
|
+
role: -> { { inclusion: %w[admin member] } },
|
|
135
|
+
code: -> { { regex: /\A[A-Z]{3}\z/ } },
|
|
136
|
+
even: -> { { lambda: ->(value) { value.even? } } },
|
|
137
|
+
nickname: -> { { type: String, allow_blank: true } }
|
|
138
|
+
}
|
|
122
139
|
```
|
|
123
|
-
|
|
140
|
+
|
|
141
|
+
Supported options are `allow_blank`, `type`, `value`, `min`, `max`, `inclusion`, `regex`, and `lambda`. All supplied constraints must pass. Unknown options, a non-Hash return value, or a Proc that declares an argument raises `ArgumentError` with usage guidance.
|
|
142
|
+
|
|
143
|
+
`allow_blank: true` accepts `null`, `false`, empty strings, whitespace-only strings, empty arrays, and empty objects. The key itself remains required.
|
|
144
|
+
|
|
145
|
+
## Built-in Types
|
|
146
|
+
|
|
147
|
+
The built-in types are anchored regular expressions:
|
|
148
|
+
|
|
124
149
|
```ruby
|
|
150
|
+
RSpec::JsonApi::Types::EMAIL
|
|
151
|
+
RSpec::JsonApi::Types::URI
|
|
125
152
|
RSpec::JsonApi::Types::UUID
|
|
126
153
|
```
|
|
127
154
|
|
|
155
|
+
`URI` accepts schemes supported by Ruby's standard URI parser, not only HTTP and HTTPS.
|
|
128
156
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
157
|
+
Generate a custom type with Rails:
|
|
158
|
+
|
|
159
|
+
```sh
|
|
160
|
+
rails generate rspec:json_api:type color_hex
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Or define one directly:
|
|
132
164
|
|
|
165
|
+
```ruby
|
|
133
166
|
module RSpec
|
|
134
167
|
module JsonApi
|
|
135
168
|
module Types
|
|
136
|
-
COLOR_HEX =
|
|
169
|
+
COLOR_HEX = /\A#(?:[0-9a-fA-F]{3}){1,2}\z/
|
|
137
170
|
end
|
|
138
171
|
end
|
|
139
172
|
end
|
|
140
|
-
|
|
141
|
-
RSpec::JsonApi::Types::COLOR_HEX
|
|
142
173
|
```
|
|
143
174
|
|
|
144
|
-
|
|
145
|
-
## Matching methods
|
|
146
|
-
The gem offers variety of possible matching methods.
|
|
175
|
+
## Interfaces
|
|
147
176
|
|
|
148
|
-
|
|
149
|
-
- `match_json_schema` always require full keys match.
|
|
177
|
+
Interfaces are reusable strict object schemas:
|
|
150
178
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
179
|
+
```ruby
|
|
180
|
+
module RSpec
|
|
181
|
+
module JsonApi
|
|
182
|
+
module Interfaces
|
|
183
|
+
PERSON = {
|
|
184
|
+
id: Types::UUID,
|
|
156
185
|
name: String,
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
let(:actual) do
|
|
162
|
-
{
|
|
163
|
-
id: "0a2f911f-3767-4cc7-9c19-049f4350e38c",
|
|
164
|
-
name: "Mikel",
|
|
165
|
-
}
|
|
186
|
+
active: -> { { inclusion: [true, false] } }
|
|
187
|
+
}.freeze
|
|
166
188
|
end
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
Success Example:
|
|
170
|
-
```ruby
|
|
171
|
-
let(:expected_schema) do
|
|
172
|
-
{
|
|
173
|
-
id: RSpec::JsonApi::Types::UUID,
|
|
174
|
-
name: String,
|
|
175
|
-
age: Integer
|
|
176
|
-
}
|
|
177
189
|
end
|
|
178
|
-
|
|
179
|
-
let(:actual) do
|
|
180
|
-
{
|
|
181
|
-
id: "0a2f911f-3767-4cc7-9c19-049f4350e38c",
|
|
182
|
-
name: "John",
|
|
183
|
-
age: 24
|
|
184
|
-
}
|
|
185
|
-
end
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
### Value match
|
|
189
|
-
```ruby
|
|
190
|
-
let(:expected_schema) do
|
|
191
|
-
{
|
|
192
|
-
id: "e0067346-4d24-4aa6-b303-f927a410a001",
|
|
193
|
-
name: "John",
|
|
194
|
-
age: 24,
|
|
195
|
-
favoriteColorHex: "#FF5733"
|
|
196
|
-
}
|
|
197
190
|
end
|
|
198
191
|
```
|
|
199
192
|
|
|
200
|
-
|
|
201
|
-
```ruby
|
|
202
|
-
let(:expected_schema) do
|
|
203
|
-
{
|
|
204
|
-
id: Integer,
|
|
205
|
-
name: String,
|
|
206
|
-
age: Integer,
|
|
207
|
-
notes: [String]
|
|
208
|
-
}
|
|
209
|
-
end
|
|
210
|
-
```
|
|
193
|
+
Generate one with:
|
|
211
194
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
let(:expected_schema) do
|
|
215
|
-
{
|
|
216
|
-
id: RSpec::JsonApi::Types::UUID,
|
|
217
|
-
email: RSpec::JsonApi::Types::EMAIL,
|
|
218
|
-
}
|
|
219
|
-
end
|
|
195
|
+
```sh
|
|
196
|
+
rails generate rspec:json_api:interface person
|
|
220
197
|
```
|
|
221
198
|
|
|
222
|
-
|
|
223
|
-
```ruby
|
|
224
|
-
let(:expected_schema) do
|
|
225
|
-
{
|
|
226
|
-
color: /^\#([a-fA-F]|[0-9]){3,6}$/
|
|
227
|
-
}
|
|
228
|
-
end
|
|
229
|
-
```
|
|
199
|
+
Use an interface directly or as a homogeneous list schema:
|
|
230
200
|
|
|
231
|
-
### Interface match
|
|
232
201
|
```ruby
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
end
|
|
202
|
+
expect(response.body).to match_json_schema(RSpec::JsonApi::Interfaces::PERSON)
|
|
203
|
+
expect(response.body).to match_json_schema([RSpec::JsonApi::Interfaces::PERSON])
|
|
236
204
|
```
|
|
237
205
|
|
|
238
|
-
|
|
239
|
-
Proc match allows to customize schema according needs using lambda shorthand notation `->`
|
|
206
|
+
## Development
|
|
240
207
|
|
|
241
|
-
|
|
242
|
-
- #### type
|
|
243
|
-
```ruby
|
|
244
|
-
let(:expected_schema) do
|
|
245
|
-
{
|
|
246
|
-
name: -> { { type: String } }
|
|
247
|
-
}
|
|
248
|
-
end
|
|
249
|
-
```
|
|
250
|
-
- #### value
|
|
251
|
-
```ruby
|
|
252
|
-
let(:expected_schema) do
|
|
253
|
-
{
|
|
254
|
-
name: -> { { value: "John" } }
|
|
255
|
-
}
|
|
256
|
-
end
|
|
257
|
-
```
|
|
258
|
-
- #### min
|
|
259
|
-
```ruby
|
|
260
|
-
let(:expected_schema) do
|
|
261
|
-
{
|
|
262
|
-
age: -> { { min: 15 } }
|
|
263
|
-
}
|
|
264
|
-
end
|
|
265
|
-
```
|
|
266
|
-
- #### max
|
|
267
|
-
```ruby
|
|
268
|
-
let(:expected_schema) do
|
|
269
|
-
{
|
|
270
|
-
age: -> { { max: 25 } }
|
|
271
|
-
}
|
|
272
|
-
end
|
|
273
|
-
```
|
|
274
|
-
- #### inclusion
|
|
275
|
-
```ruby
|
|
276
|
-
let(:expected_schema) do
|
|
277
|
-
{
|
|
278
|
-
letter: -> { { inclusion: %w[A B C] } }
|
|
279
|
-
}
|
|
280
|
-
end
|
|
281
|
-
```
|
|
282
|
-
- #### regex
|
|
283
|
-
```ruby
|
|
284
|
-
let(:expected_schema) do
|
|
285
|
-
{
|
|
286
|
-
hex: -> { { regex: /^\#([a-fA-F]|[0-9]){3,6}$/ } }
|
|
287
|
-
}
|
|
288
|
-
end
|
|
289
|
-
```
|
|
290
|
-
- #### lambda
|
|
291
|
-
```ruby
|
|
292
|
-
let(:expected_schema) do
|
|
293
|
-
{
|
|
294
|
-
number: -> { { lambda: lambda(&:even?) } }
|
|
295
|
-
}
|
|
296
|
-
end
|
|
297
|
-
```
|
|
298
|
-
- #### allow_blank
|
|
208
|
+
Use the Ruby version in `.ruby-version` and Bundler 4.0.4:
|
|
299
209
|
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
210
|
+
```sh
|
|
211
|
+
gem install bundler -v 4.0.4
|
|
212
|
+
bundle install
|
|
213
|
+
bundle exec rspec
|
|
214
|
+
bundle exec rubocop
|
|
215
|
+
bundle exec bundle-audit check --update
|
|
306
216
|
```
|
|
307
|
-
_Note: Default value is `false`_
|
|
308
|
-
|
|
309
|
-
## Contributing
|
|
310
217
|
|
|
311
|
-
|
|
218
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for compatibility and contribution guidance. Please report vulnerabilities using [GitHub's private security advisory form](https://github.com/nomtek/rspec-json_api/security/advisories/new), as described in [SECURITY.md](SECURITY.md).
|
|
312
219
|
|
|
313
220
|
## License
|
|
314
221
|
|
|
315
|
-
The gem is available
|
|
222
|
+
The gem is available under the terms of the [MIT License](LICENSE.txt).
|
|
316
223
|
|
|
317
224
|
## Code of Conduct
|
|
318
225
|
|
|
319
|
-
Everyone
|
|
226
|
+
Everyone participating in this project is expected to follow the [code of conduct](CODE_OF_CONDUCT.md).
|
data/SECURITY.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported Versions
|
|
4
|
+
|
|
5
|
+
Security fixes are made on the latest released version.
|
|
6
|
+
|
|
7
|
+
## Reporting a Vulnerability
|
|
8
|
+
|
|
9
|
+
Please do not open a public issue for a suspected vulnerability. Use [GitHub's private security advisory form](https://github.com/nomtek/rspec-json_api/security/advisories/new) and include reproduction steps, affected versions, and the expected impact.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RSpec
|
|
4
|
+
module JsonApi
|
|
5
|
+
module Blank
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
BLANK_STRING = /\A[[:space:]]*\z/
|
|
9
|
+
|
|
10
|
+
def blank?(value)
|
|
11
|
+
case value
|
|
12
|
+
when nil, false
|
|
13
|
+
true
|
|
14
|
+
when String
|
|
15
|
+
BLANK_STRING.match?(value)
|
|
16
|
+
else
|
|
17
|
+
value.respond_to?(:empty?) && value.empty?
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -21,7 +21,7 @@ module RSpec
|
|
|
21
21
|
def match(value, options)
|
|
22
22
|
validate!(options)
|
|
23
23
|
|
|
24
|
-
return true if
|
|
24
|
+
return true if Blank.blank?(value) && options[:allow_blank]
|
|
25
25
|
|
|
26
26
|
options.except(:allow_blank).all? do |option, condition|
|
|
27
27
|
satisfies?(value, option, condition)
|
|
@@ -29,6 +29,8 @@ module RSpec
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
def validate!(options)
|
|
32
|
+
raise ArgumentError, "options must be a Hash, got #{options.class}" unless options.is_a?(Hash)
|
|
33
|
+
|
|
32
34
|
unknown = options.keys - SUPPORTED_OPTIONS
|
|
33
35
|
return if unknown.empty?
|
|
34
36
|
|
|
@@ -40,7 +42,7 @@ module RSpec
|
|
|
40
42
|
when :type then value.instance_of?(condition)
|
|
41
43
|
when :value then value == condition
|
|
42
44
|
when :inclusion then condition.include?(value)
|
|
43
|
-
when :regex then
|
|
45
|
+
when :regex then matches_regexp?(value, condition)
|
|
44
46
|
when :lambda then condition.call(value)
|
|
45
47
|
when :min, :max then within_bound?(value, option, condition)
|
|
46
48
|
end
|
|
@@ -51,6 +53,10 @@ module RSpec
|
|
|
51
53
|
|
|
52
54
|
option == :min ? value >= condition : value <= condition
|
|
53
55
|
end
|
|
56
|
+
|
|
57
|
+
def matches_regexp?(value, condition)
|
|
58
|
+
value.is_a?(String) && condition.match?(value)
|
|
59
|
+
end
|
|
54
60
|
end
|
|
55
61
|
end
|
|
56
62
|
end
|