philiprehberger-json_schema 0.2.3 → 0.3.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 +9 -0
- data/README.md +38 -1
- data/lib/philiprehberger/json_schema/validator.rb +54 -0
- data/lib/philiprehberger/json_schema/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2a67bbf9af3550800b697cffb7061d297e1cc4d4d658b1db1f4c3eb3133c5125
|
|
4
|
+
data.tar.gz: a805669bc1f806de5fd76ac8ff9b28414676b2772ef71c67bdbad40ba5ebf66d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f14027b317fc2d9b1f36dd79351e5da1ef2b43ebe14a00877cff83221db2e2e63d5802f5142389147285fd020992d5338583850947edb49f244383157710aa17
|
|
7
|
+
data.tar.gz: 4290e58110711d0ad8e9ed236d4d7dbea255621ca7dd985a48e63fe2c20b100545fbca3b5437406653fd448a54b74f44cfcdbb45a4ed22ffcd1c4f49b8fad3fe
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.0] - 2026-04-17
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `exclusiveMinimum` and `exclusiveMaximum` keywords for strict numeric bounds
|
|
14
|
+
- `multipleOf` keyword, using BigDecimal arithmetic to avoid IEEE-754 drift on fractional multiples
|
|
15
|
+
- `uniqueItems` keyword for arrays (deep equality on nested values)
|
|
16
|
+
- `minProperties` and `maxProperties` keywords for object property-count constraints
|
|
17
|
+
- README: Usage examples for the new keywords and expanded Supported Keywords table
|
|
18
|
+
|
|
10
19
|
## [0.2.3] - 2026-04-09
|
|
11
20
|
|
|
12
21
|
### Fixed
|
data/README.md
CHANGED
|
@@ -141,6 +141,39 @@ Philiprehberger::JsonSchema.valid?('fixed_value', schema) # => true
|
|
|
141
141
|
Philiprehberger::JsonSchema.valid?('other', schema) # => false
|
|
142
142
|
```
|
|
143
143
|
|
|
144
|
+
### Numeric Bounds (exclusive and multipleOf)
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
# exclusive bounds
|
|
148
|
+
schema = { type: 'integer', exclusiveMinimum: 0, exclusiveMaximum: 10 }
|
|
149
|
+
Philiprehberger::JsonSchema.valid?(5, schema) # => true
|
|
150
|
+
Philiprehberger::JsonSchema.valid?(0, schema) # => false (equal to exclusiveMinimum)
|
|
151
|
+
Philiprehberger::JsonSchema.valid?(10, schema) # => false (equal to exclusiveMaximum)
|
|
152
|
+
|
|
153
|
+
# multipleOf (BigDecimal-backed, safe for fractions)
|
|
154
|
+
schema = { type: 'number', multipleOf: 0.1 }
|
|
155
|
+
Philiprehberger::JsonSchema.valid?(0.3, schema) # => true
|
|
156
|
+
Philiprehberger::JsonSchema.valid?(0.25, schema) # => false
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Unique Array Items
|
|
160
|
+
|
|
161
|
+
```ruby
|
|
162
|
+
schema = { type: 'array', uniqueItems: true }
|
|
163
|
+
Philiprehberger::JsonSchema.valid?([1, 2, 3], schema) # => true
|
|
164
|
+
Philiprehberger::JsonSchema.valid?([1, 2, 2], schema) # => false
|
|
165
|
+
Philiprehberger::JsonSchema.valid?([{ 'a' => 1 }, { 'a' => 1 }], schema) # => false (deep equality)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Object Property Counts
|
|
169
|
+
|
|
170
|
+
```ruby
|
|
171
|
+
schema = { type: 'object', minProperties: 1, maxProperties: 3 }
|
|
172
|
+
Philiprehberger::JsonSchema.valid?({ 'a' => 1 }, schema) # => true
|
|
173
|
+
Philiprehberger::JsonSchema.valid?({}, schema) # => false
|
|
174
|
+
Philiprehberger::JsonSchema.valid?({ 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4 }, schema) # => false
|
|
175
|
+
```
|
|
176
|
+
|
|
144
177
|
### Compiled Schemas
|
|
145
178
|
|
|
146
179
|
```ruby
|
|
@@ -182,11 +215,15 @@ compiled.validate({ 'id' => 'x' }) # => ["$.id: expected type integer..."]
|
|
|
182
215
|
| `patternProperties` | Regex-keyed sub-schemas for matching property names |
|
|
183
216
|
| `pattern` | Regex match for strings |
|
|
184
217
|
| `minLength` / `maxLength` | String length constraints |
|
|
185
|
-
| `minimum` / `maximum` | Numeric range constraints |
|
|
218
|
+
| `minimum` / `maximum` | Numeric range constraints (inclusive) |
|
|
219
|
+
| `exclusiveMinimum` / `exclusiveMaximum` | Numeric range constraints (exclusive) |
|
|
220
|
+
| `multipleOf` | Value must be a multiple of the given number (BigDecimal-backed) |
|
|
186
221
|
| `enum` | Allowed value list |
|
|
187
222
|
| `const` | Exact value match |
|
|
188
223
|
| `items` | Schema for array elements |
|
|
189
224
|
| `minItems` / `maxItems` | Array length constraints |
|
|
225
|
+
| `uniqueItems` | Require all array items to be unique (deep equality) |
|
|
226
|
+
| `minProperties` / `maxProperties` | Object property-count constraints |
|
|
190
227
|
| `allOf` | Data must match all sub-schemas |
|
|
191
228
|
| `anyOf` | Data must match at least one sub-schema |
|
|
192
229
|
| `oneOf` | Data must match exactly one sub-schema |
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'bigdecimal'
|
|
4
|
+
|
|
3
5
|
module Philiprehberger
|
|
4
6
|
module JsonSchema
|
|
5
7
|
# Core validation engine that checks data against a JSON Schema (draft-07 subset)
|
|
@@ -18,15 +20,19 @@ module Philiprehberger
|
|
|
18
20
|
validate_type(data, schema, path, errors)
|
|
19
21
|
validate_const(data, schema, path, errors)
|
|
20
22
|
validate_required(data, schema, path, errors)
|
|
23
|
+
validate_object_size(data, schema, path, errors)
|
|
21
24
|
validate_properties(data, schema, path, errors, root_schema)
|
|
22
25
|
validate_additional_properties(data, schema, path, errors, root_schema)
|
|
23
26
|
validate_pattern_properties(data, schema, path, errors, root_schema)
|
|
24
27
|
validate_pattern(data, schema, path, errors)
|
|
25
28
|
validate_string_length(data, schema, path, errors)
|
|
26
29
|
validate_numeric_range(data, schema, path, errors)
|
|
30
|
+
validate_exclusive_range(data, schema, path, errors)
|
|
31
|
+
validate_multiple_of(data, schema, path, errors)
|
|
27
32
|
validate_enum(data, schema, path, errors)
|
|
28
33
|
validate_items(data, schema, path, errors, root_schema)
|
|
29
34
|
validate_array_length(data, schema, path, errors)
|
|
35
|
+
validate_unique_items(data, schema, path, errors)
|
|
30
36
|
validate_ref(data, schema, path, errors, root_schema)
|
|
31
37
|
validate_all_of(data, schema, path, errors, root_schema)
|
|
32
38
|
validate_any_of(data, schema, path, errors, root_schema)
|
|
@@ -176,6 +182,31 @@ module Philiprehberger
|
|
|
176
182
|
errors << "#{path}: value #{data} is greater than maximum #{max}" if max && data > max
|
|
177
183
|
end
|
|
178
184
|
|
|
185
|
+
def validate_exclusive_range(data, schema, path, errors)
|
|
186
|
+
return unless data.is_a?(Numeric) && !data.is_a?(Complex)
|
|
187
|
+
|
|
188
|
+
excl_min = schema_key(schema, :exclusiveMinimum)
|
|
189
|
+
excl_max = schema_key(schema, :exclusiveMaximum)
|
|
190
|
+
|
|
191
|
+
min_violated = excl_min.is_a?(Numeric) && data <= excl_min
|
|
192
|
+
max_violated = excl_max.is_a?(Numeric) && data >= excl_max
|
|
193
|
+
|
|
194
|
+
errors << "#{path}: value #{data} is not greater than exclusiveMinimum #{excl_min}" if min_violated
|
|
195
|
+
errors << "#{path}: value #{data} is not less than exclusiveMaximum #{excl_max}" if max_violated
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def validate_multiple_of(data, schema, path, errors)
|
|
199
|
+
return unless data.is_a?(Numeric) && !data.is_a?(Complex)
|
|
200
|
+
|
|
201
|
+
multiple = schema_key(schema, :multipleOf)
|
|
202
|
+
return unless multiple.is_a?(Numeric) && multiple.positive? && multiple.finite?
|
|
203
|
+
|
|
204
|
+
# Use BigDecimal via string representation to sidestep IEEE-754 drift
|
|
205
|
+
# (e.g. 0.3 % 0.1 != 0 in Float arithmetic).
|
|
206
|
+
remainder = BigDecimal(data.to_s) % BigDecimal(multiple.to_s)
|
|
207
|
+
errors << "#{path}: value #{data} is not a multiple of #{multiple}" unless remainder.zero?
|
|
208
|
+
end
|
|
209
|
+
|
|
179
210
|
def validate_enum(data, schema, path, errors)
|
|
180
211
|
allowed = schema_key(schema, :enum)
|
|
181
212
|
return unless allowed.is_a?(Array)
|
|
@@ -204,6 +235,29 @@ module Philiprehberger
|
|
|
204
235
|
errors << "#{path}: array length #{data.length} is greater than maxItems #{max}" if max && data.length > max
|
|
205
236
|
end
|
|
206
237
|
|
|
238
|
+
def validate_unique_items(data, schema, path, errors)
|
|
239
|
+
return unless data.is_a?(Array)
|
|
240
|
+
|
|
241
|
+
unique = schema_key(schema, :uniqueItems)
|
|
242
|
+
return unless unique == true
|
|
243
|
+
|
|
244
|
+
errors << "#{path}: array items are not unique" if data.length != data.uniq.length
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def validate_object_size(data, schema, path, errors)
|
|
248
|
+
return unless data.is_a?(Hash)
|
|
249
|
+
|
|
250
|
+
min = schema_key(schema, :minProperties)
|
|
251
|
+
max = schema_key(schema, :maxProperties)
|
|
252
|
+
size = data.size
|
|
253
|
+
|
|
254
|
+
under_min = min.is_a?(Integer) && size < min
|
|
255
|
+
over_max = max.is_a?(Integer) && size > max
|
|
256
|
+
|
|
257
|
+
errors << "#{path}: object has #{size} properties, expected at least #{min}" if under_min
|
|
258
|
+
errors << "#{path}: object has #{size} properties, expected at most #{max}" if over_max
|
|
259
|
+
end
|
|
260
|
+
|
|
207
261
|
def validate_ref(data, schema, path, errors, root_schema)
|
|
208
262
|
ref = schema_key(schema, '$ref')
|
|
209
263
|
return unless ref.is_a?(String)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: philiprehberger-json_schema
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Philip Rehberger
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-04-
|
|
11
|
+
date: 2026-04-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Validate Ruby data structures against JSON Schema definitions with support
|
|
14
14
|
for type checking, required properties, pattern matching, numeric ranges, enums,
|