rspec-json_api 1.6.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 +26 -1
- data/CONTRIBUTING.md +24 -0
- data/README.md +142 -236
- data/SECURITY.md +9 -0
- data/lib/rspec/json_api/blank.rb +22 -0
- data/lib/rspec/json_api/constraints.rb +6 -2
- data/lib/rspec/json_api/matchers/match_json_schema.rb +4 -0
- data/lib/rspec/json_api/schema_match.rb +7 -10
- data/lib/rspec/json_api/traversal.rb +16 -0
- data/lib/rspec/json_api/version.rb +1 -1
- data/lib/rspec/json_api.rb +2 -1
- metadata +10 -36
- data/lib/rspec/json_api/interfaces/example_interface.rb +0 -14
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,4 +1,29 @@
|
|
|
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.
|
|
2
27
|
|
|
3
28
|
## [1.6.0] - 2026-09-03
|
|
4
29
|
|
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,135 +1,168 @@
|
|
|
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\#([a-fA-F]|[0-9]){3,6}\z/,
|
|
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
|
|
@@ -137,184 +170,57 @@ module RSpec
|
|
|
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\#([a-fA-F]|[0-9]){3,6}\z/
|
|
227
|
-
}
|
|
228
|
-
end
|
|
229
|
-
```
|
|
230
|
-
_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._
|
|
199
|
+
Use an interface directly or as a homogeneous list schema:
|
|
231
200
|
|
|
232
|
-
### Interface match
|
|
233
201
|
```ruby
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
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])
|
|
237
204
|
```
|
|
238
205
|
|
|
239
|
-
|
|
240
|
-
Proc match allows to customize schema according needs using lambda shorthand notation `->`
|
|
206
|
+
## Development
|
|
241
207
|
|
|
242
|
-
|
|
243
|
-
- #### type
|
|
244
|
-
```ruby
|
|
245
|
-
let(:expected_schema) do
|
|
246
|
-
{
|
|
247
|
-
name: -> { { type: String } }
|
|
248
|
-
}
|
|
249
|
-
end
|
|
250
|
-
```
|
|
251
|
-
- #### value
|
|
252
|
-
```ruby
|
|
253
|
-
let(:expected_schema) do
|
|
254
|
-
{
|
|
255
|
-
name: -> { { value: "John" } }
|
|
256
|
-
}
|
|
257
|
-
end
|
|
258
|
-
```
|
|
259
|
-
- #### min
|
|
260
|
-
```ruby
|
|
261
|
-
let(:expected_schema) do
|
|
262
|
-
{
|
|
263
|
-
age: -> { { min: 15 } }
|
|
264
|
-
}
|
|
265
|
-
end
|
|
266
|
-
```
|
|
267
|
-
- #### max
|
|
268
|
-
```ruby
|
|
269
|
-
let(:expected_schema) do
|
|
270
|
-
{
|
|
271
|
-
age: -> { { max: 25 } }
|
|
272
|
-
}
|
|
273
|
-
end
|
|
274
|
-
```
|
|
275
|
-
- #### inclusion
|
|
276
|
-
```ruby
|
|
277
|
-
let(:expected_schema) do
|
|
278
|
-
{
|
|
279
|
-
letter: -> { { inclusion: %w[A B C] } }
|
|
280
|
-
}
|
|
281
|
-
end
|
|
282
|
-
```
|
|
283
|
-
- #### regex
|
|
284
|
-
```ruby
|
|
285
|
-
let(:expected_schema) do
|
|
286
|
-
{
|
|
287
|
-
hex: -> { { regex: /^\#([a-fA-F]|[0-9]){3,6}$/ } }
|
|
288
|
-
}
|
|
289
|
-
end
|
|
290
|
-
```
|
|
291
|
-
- #### lambda
|
|
292
|
-
```ruby
|
|
293
|
-
let(:expected_schema) do
|
|
294
|
-
{
|
|
295
|
-
number: -> { { lambda: lambda(&:even?) } }
|
|
296
|
-
}
|
|
297
|
-
end
|
|
298
|
-
```
|
|
299
|
-
- #### allow_blank
|
|
208
|
+
Use the Ruby version in `.ruby-version` and Bundler 4.0.4:
|
|
300
209
|
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
|
307
216
|
```
|
|
308
|
-
_Note: Default value is `false`_
|
|
309
|
-
|
|
310
|
-
## Contributing
|
|
311
217
|
|
|
312
|
-
|
|
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).
|
|
313
219
|
|
|
314
220
|
## License
|
|
315
221
|
|
|
316
|
-
The gem is available
|
|
222
|
+
The gem is available under the terms of the [MIT License](LICENSE.txt).
|
|
317
223
|
|
|
318
224
|
## Code of Conduct
|
|
319
225
|
|
|
320
|
-
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)
|
|
@@ -42,7 +42,7 @@ module RSpec
|
|
|
42
42
|
when :type then value.instance_of?(condition)
|
|
43
43
|
when :value then value == condition
|
|
44
44
|
when :inclusion then condition.include?(value)
|
|
45
|
-
when :regex then
|
|
45
|
+
when :regex then matches_regexp?(value, condition)
|
|
46
46
|
when :lambda then condition.call(value)
|
|
47
47
|
when :min, :max then within_bound?(value, option, condition)
|
|
48
48
|
end
|
|
@@ -53,6 +53,10 @@ module RSpec
|
|
|
53
53
|
|
|
54
54
|
option == :min ? value >= condition : value <= condition
|
|
55
55
|
end
|
|
56
|
+
|
|
57
|
+
def matches_regexp?(value, condition)
|
|
58
|
+
value.is_a?(String) && condition.match?(value)
|
|
59
|
+
end
|
|
56
60
|
end
|
|
57
61
|
end
|
|
58
62
|
end
|
|
@@ -45,6 +45,10 @@ module RSpec
|
|
|
45
45
|
!matches?(actual) && !@type_error
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
+
def description
|
|
49
|
+
"match JSON schema"
|
|
50
|
+
end
|
|
51
|
+
|
|
48
52
|
# Provides a failure message for when the JSON data does not match the expected schema.
|
|
49
53
|
# @return [String] A descriptive message detailing the mismatch between expected and actual JSON.
|
|
50
54
|
def failure_message
|
|
@@ -10,31 +10,28 @@ module RSpec
|
|
|
10
10
|
module SchemaMatch
|
|
11
11
|
module_function
|
|
12
12
|
|
|
13
|
-
# Top-level comparison. Applies
|
|
14
|
-
#
|
|
13
|
+
# Top-level comparison. Applies shape guards to objects and collections,
|
|
14
|
+
# then uses the same value dispatch as nested schema values.
|
|
15
15
|
def match(actual, expected)
|
|
16
|
-
return false unless actual.instance_of?(expected.class)
|
|
17
|
-
|
|
18
16
|
case expected
|
|
19
17
|
when Array
|
|
20
18
|
compare_array(actual, expected)
|
|
21
19
|
when Hash
|
|
20
|
+
return false unless actual.is_a?(Hash)
|
|
22
21
|
return false unless same_key_structure?(actual, expected)
|
|
23
22
|
|
|
24
23
|
compare(actual, expected)
|
|
25
24
|
else
|
|
26
|
-
|
|
25
|
+
compare_values(actual, expected)
|
|
27
26
|
end
|
|
28
27
|
end
|
|
29
28
|
|
|
30
29
|
def same_key_structure?(actual, expected)
|
|
31
|
-
Traversal.
|
|
32
|
-
Traversal.deep_sort(Traversal.deep_keys(expected))
|
|
30
|
+
Traversal.same_key_structure?(actual, expected)
|
|
33
31
|
end
|
|
34
32
|
|
|
35
33
|
def compare(actual, expected)
|
|
36
34
|
return false unless actual.is_a?(Hash)
|
|
37
|
-
return false if actual.blank? && expected.present?
|
|
38
35
|
|
|
39
36
|
keys = Traversal.deep_key_paths(expected) | Traversal.deep_key_paths(actual)
|
|
40
37
|
|
|
@@ -77,7 +74,7 @@ module RSpec
|
|
|
77
74
|
end
|
|
78
75
|
|
|
79
76
|
def compare_regexp(actual_value, expected_value)
|
|
80
|
-
expected_value.match?(actual_value
|
|
77
|
+
actual_value.is_a?(String) && expected_value.match?(actual_value)
|
|
81
78
|
end
|
|
82
79
|
|
|
83
80
|
# A schema Proc describes the constraints for a value; it is called without
|
|
@@ -142,7 +139,7 @@ module RSpec
|
|
|
142
139
|
return false if actual_value.size != expected_value.size
|
|
143
140
|
|
|
144
141
|
expected_value.each_with_index.all? do |elem, index|
|
|
145
|
-
elem.is_a?(Hash) ?
|
|
142
|
+
elem.is_a?(Hash) ? match(actual_value[index], elem) : compare_values(actual_value[index], elem)
|
|
146
143
|
end
|
|
147
144
|
end
|
|
148
145
|
|
|
@@ -44,6 +44,22 @@ module RSpec
|
|
|
44
44
|
.map { |element| element.is_a?(Array) ? deep_sort(element) : element }
|
|
45
45
|
.sort_by { |element| element.is_a?(Array) ? element.first.to_s : element.to_s }
|
|
46
46
|
end
|
|
47
|
+
|
|
48
|
+
# Whether two hashes have the same keys under the same parent objects.
|
|
49
|
+
def same_key_structure?(actual, expected)
|
|
50
|
+
same_keys?(actual, expected) && actual.all? do |key, actual_value|
|
|
51
|
+
same_nested_key_structure?(actual_value, expected[key])
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def same_keys?(actual, expected)
|
|
56
|
+
actual.size == expected.size && actual.each_key.all? { |key| expected.key?(key) }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def same_nested_key_structure?(actual, expected)
|
|
60
|
+
nested = actual.is_a?(Hash) || expected.is_a?(Hash)
|
|
61
|
+
!nested || (actual.is_a?(Hash) && expected.is_a?(Hash) && same_key_structure?(actual, expected))
|
|
62
|
+
end
|
|
47
63
|
end
|
|
48
64
|
end
|
|
49
65
|
end
|
data/lib/rspec/json_api.rb
CHANGED
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
require "json"
|
|
5
5
|
require "uri"
|
|
6
6
|
require "diffy"
|
|
7
|
-
require "
|
|
7
|
+
require "rspec/expectations"
|
|
8
8
|
|
|
9
9
|
# Load the json_api parts
|
|
10
10
|
require "rspec/json_api/version"
|
|
11
|
+
require "rspec/json_api/blank"
|
|
11
12
|
require "rspec/json_api/traversal"
|
|
12
13
|
require "rspec/json_api/constraints"
|
|
13
14
|
require "rspec/json_api/schema_match"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rspec-json_api
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michal Gajowiak
|
|
@@ -9,20 +9,6 @@ bindir: bin
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
-
- !ruby/object:Gem::Dependency
|
|
13
|
-
name: activesupport
|
|
14
|
-
requirement: !ruby/object:Gem::Requirement
|
|
15
|
-
requirements:
|
|
16
|
-
- - ">="
|
|
17
|
-
- !ruby/object:Gem::Version
|
|
18
|
-
version: 6.1.4.1
|
|
19
|
-
type: :runtime
|
|
20
|
-
prerelease: false
|
|
21
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
-
requirements:
|
|
23
|
-
- - ">="
|
|
24
|
-
- !ruby/object:Gem::Version
|
|
25
|
-
version: 6.1.4.1
|
|
26
12
|
- !ruby/object:Gem::Dependency
|
|
27
13
|
name: diffy
|
|
28
14
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -38,33 +24,19 @@ dependencies:
|
|
|
38
24
|
- !ruby/object:Gem::Version
|
|
39
25
|
version: 3.4.2
|
|
40
26
|
- !ruby/object:Gem::Dependency
|
|
41
|
-
name:
|
|
42
|
-
requirement: !ruby/object:Gem::Requirement
|
|
43
|
-
requirements:
|
|
44
|
-
- - ">="
|
|
45
|
-
- !ruby/object:Gem::Version
|
|
46
|
-
version: 6.1.4.1
|
|
47
|
-
type: :runtime
|
|
48
|
-
prerelease: false
|
|
49
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
-
requirements:
|
|
51
|
-
- - ">="
|
|
52
|
-
- !ruby/object:Gem::Version
|
|
53
|
-
version: 6.1.4.1
|
|
54
|
-
- !ruby/object:Gem::Dependency
|
|
55
|
-
name: rspec-rails
|
|
27
|
+
name: rspec-expectations
|
|
56
28
|
requirement: !ruby/object:Gem::Requirement
|
|
57
29
|
requirements:
|
|
58
|
-
- - "
|
|
30
|
+
- - "~>"
|
|
59
31
|
- !ruby/object:Gem::Version
|
|
60
|
-
version:
|
|
32
|
+
version: '3.0'
|
|
61
33
|
type: :runtime
|
|
62
34
|
prerelease: false
|
|
63
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
64
36
|
requirements:
|
|
65
|
-
- - "
|
|
37
|
+
- - "~>"
|
|
66
38
|
- !ruby/object:Gem::Version
|
|
67
|
-
version:
|
|
39
|
+
version: '3.0'
|
|
68
40
|
email:
|
|
69
41
|
- m.gajowiak@nomtek.com
|
|
70
42
|
executables: []
|
|
@@ -72,16 +44,18 @@ extensions: []
|
|
|
72
44
|
extra_rdoc_files: []
|
|
73
45
|
files:
|
|
74
46
|
- CHANGELOG.md
|
|
47
|
+
- CONTRIBUTING.md
|
|
75
48
|
- LICENSE.txt
|
|
76
49
|
- README.md
|
|
50
|
+
- SECURITY.md
|
|
77
51
|
- lib/generators/rspec/json_api/install/install_generator.rb
|
|
78
52
|
- lib/generators/rspec/json_api/install/templates/rspec/json_api/interfaces/.empty_directory
|
|
79
53
|
- lib/generators/rspec/json_api/install/templates/rspec/json_api/types/.empty_directory
|
|
80
54
|
- lib/generators/rspec/json_api/interface/interface_generator.rb
|
|
81
55
|
- lib/generators/rspec/json_api/type/type_generator.rb
|
|
82
56
|
- lib/rspec/json_api.rb
|
|
57
|
+
- lib/rspec/json_api/blank.rb
|
|
83
58
|
- lib/rspec/json_api/constraints.rb
|
|
84
|
-
- lib/rspec/json_api/interfaces/example_interface.rb
|
|
85
59
|
- lib/rspec/json_api/matchers.rb
|
|
86
60
|
- lib/rspec/json_api/matchers/have_no_content.rb
|
|
87
61
|
- lib/rspec/json_api/matchers/match_json_schema.rb
|
|
@@ -113,7 +87,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
113
87
|
- !ruby/object:Gem::Version
|
|
114
88
|
version: '0'
|
|
115
89
|
requirements: []
|
|
116
|
-
rubygems_version:
|
|
90
|
+
rubygems_version: 3.6.9
|
|
117
91
|
specification_version: 4
|
|
118
92
|
summary: RSpec extension to test JSON API response.
|
|
119
93
|
test_files: []
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RSpec
|
|
4
|
-
module JsonApi
|
|
5
|
-
module Interfaces
|
|
6
|
-
EXAMPLE_INTERFACE = {
|
|
7
|
-
id: Types::UUID,
|
|
8
|
-
name: String,
|
|
9
|
-
number: Integer,
|
|
10
|
-
color: -> { { inclusion: %w[black red white], allow_blank: true } }
|
|
11
|
-
}.freeze
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|