Skip to content

Commit c46b9b2

Browse files
committed
feat: implement the multipleOf keyword
1 parent a17258e commit c46b9b2

16 files changed

+459
-111
lines changed

docs/src/examples/json-schema-change-compatibility-checks.md

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
---
21
## JSON Schema Change Compatibility Checks
32
**Backward compatibility** - an ability of a system to understand input intended for previous versions of itself
43

@@ -39,6 +38,10 @@ Examples:
3938
- [Expected JSON value types including integer is extended by number](#expected-json-value-types-including-integer-is-extended-by-number)
4039
- [Expected JSON value types including integer and number is reduced by integer](#expected-json-value-types-including-integer-and-number-is-reduced-by-integer)
4140
- [Expected JSON value types including integer and number is reuced by number](#expected-json-value-types-including-integer-and-number-is-reuced-by-number)
41+
- [the new value of multipleOf is divisible by the old one](#the-new-value-of-multipleof-is-divisible-by-the-old-one)
42+
- [the old value of multipleOf is divisible by the new one](#the-old-value-of-multipleof-is-divisible-by-the-new-one)
43+
- [old and new value of multipleOf are not each other's factors](#old-and-new-value-of-multipleof-are-not-each-other's-factors)
44+
---
4245
### No JSON schema differences
4346
When there is not JSON schema differences, schema change is fully compatible.
4447

@@ -51,6 +54,7 @@ no differences
5154
```
5255
full
5356
```
57+
---
5458
### Expected JSON value type changes from null to boolean
5559
Because no boolean value can satisfy null JSON type constraint, and vice versa, such a change is incompatible.
5660

@@ -68,6 +72,7 @@ Because no boolean value can satisfy null JSON type constraint, and vice versa,
6872
```
6973
none
7074
```
75+
---
7176
### Expected JSON value type changes from integer to number
7277
Because every integer is a number, but not vice versa, such a change is backward compatible.
7378

@@ -85,6 +90,7 @@ Because every integer is a number, but not vice versa, such a change is backward
8590
```
8691
backward
8792
```
93+
---
8894
### Expected JSON value type changes from number to integer
8995
Because every integer is a number, but not vice versa, such a change is forward compatible.
9096

@@ -102,6 +108,7 @@ Because every integer is a number, but not vice versa, such a change is forward
102108
```
103109
forward
104110
```
111+
---
105112
### Expected JSON value types is extended
106113
Because more value types than before are accepted, this change is backward compatible.
107114

@@ -120,6 +127,7 @@ Because more value types than before are accepted, this change is backward compa
120127
```
121128
backward
122129
```
130+
---
123131
### Expected JSON value types is reduced
124132
Because less value types than before are accepted, this change is forward compatible.
125133

@@ -138,6 +146,7 @@ Because less value types than before are accepted, this change is forward compat
138146
```
139147
forward
140148
```
149+
---
141150
### Expected JSON value types including number is extended by integer
142151
Because every integer is a number, such a change is fully compatible.
143152

@@ -156,6 +165,7 @@ Because every integer is a number, such a change is fully compatible.
156165
```
157166
full
158167
```
168+
---
159169
### Expected JSON value types including integer is extended by number
160170
Because not every integer is a number, such a change is backward compatible.
161171

@@ -174,6 +184,7 @@ Because not every integer is a number, such a change is backward compatible.
174184
```
175185
backward
176186
```
187+
---
177188
### Expected JSON value types including integer and number is reduced by integer
178189
Because every integer is a number, such a change is fully compatible.
179190

@@ -192,6 +203,7 @@ Because every integer is a number, such a change is fully compatible.
192203
```
193204
full
194205
```
206+
---
195207
### Expected JSON value types including integer and number is reuced by number
196208
Because not every integer is a number, such a change is forward compatible.
197209

@@ -210,3 +222,48 @@ Because not every integer is a number, such a change is forward compatible.
210222
```
211223
forward
212224
```
225+
---
226+
### the new value of multipleOf is divisible by the old one
227+
Because every multiple the new value is also a multiple of the old value, such a change is backward compatible
228+
229+
#### Input
230+
##### JSON schema differences
231+
```
232+
-
233+
Schema path: #
234+
multipleOf changed from 2.0 to 4.0
235+
```
236+
#### Output
237+
```
238+
backward
239+
```
240+
---
241+
### the old value of multipleOf is divisible by the new one
242+
Because every multiple the old value is also a multiple of the new value, such a change is forward compatible
243+
244+
#### Input
245+
##### JSON schema differences
246+
```
247+
-
248+
Schema path: #
249+
multipleOf changed from 4.0 to 2.0
250+
```
251+
#### Output
252+
```
253+
forward
254+
```
255+
---
256+
### old and new value of multipleOf are not each other's factors
257+
In this situation, there are potentially some numbers that are not divisible by neither of multipleOf values. Therefore, such a change is incompatible.
258+
259+
#### Input
260+
##### JSON schema differences
261+
```
262+
-
263+
Schema path: #
264+
multipleOf changed from 2.0 to 5.0
265+
```
266+
#### Output
267+
```
268+
none
269+
```

docs/src/examples/json-schema-difference-calculation.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
---
21
## JSON Schema Difference Calculation
32
TODO
43

@@ -8,6 +7,8 @@ Examples:
87

98
- [Comparing identical schemata](#comparing-identical-schemata)
109
- [Changing expected JSON value type from null to boolean](#changing-expected-json-value-type-from-null-to-boolean)
10+
- [Changing multipleOf value](#changing-multipleof-value)
11+
---
1112
### Comparing identical schemata
1213
When two identical schemata are compared, no difference should be found.
1314

@@ -24,6 +25,7 @@ false
2425
```
2526
no differences
2627
```
28+
---
2729
### Changing expected JSON value type from null to boolean
2830
Any change in expected JSON value type should be accounted as a difference.
2931

@@ -45,3 +47,22 @@ Any change in expected JSON value type should be accounted as a difference.
4547
to
4648
- boolean
4749
```
50+
---
51+
### Changing multipleOf value
52+
TODO
53+
54+
#### Input
55+
##### Previous JSON schema
56+
```json
57+
{"multipleOf":2,"type":["number"]}
58+
```
59+
##### Next JSON schema
60+
```json
61+
{"multipleOf":4,"type":["number"]}
62+
```
63+
#### Output
64+
```
65+
-
66+
Schema path: #
67+
multipleOf changed from 2.0 to 4.0
68+
```

docs/src/examples/json-values-validation.md

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
---
21
## JSON Values Validation
32
TODO
43

@@ -8,9 +7,14 @@ Examples:
87

98
- [A null value against a schema accepting only null values](#a-null-value-against-a-schema-accepting-only-null-values)
109
- [A boolean value against a schema accepting only null values](#a-boolean-value-against-a-schema-accepting-only-null-values)
10+
- [An whole number value against a schema accepting only numbers](#an-whole-number-value-against-a-schema-accepting-only-numbers)
11+
- [A fractional number value against a schema accepting only integers](#a-fractional-number-value-against-a-schema-accepting-only-integers)
1112
- [A boolean value against a schema accepting only null and string values](#a-boolean-value-against-a-schema-accepting-only-null-and-string-values)
1213
- [An array with 2 out of 5 items not matching the desired item type](#an-array-with-2-out-of-5-items-not-matching-the-desired-item-type)
1314
- [An array with forbidden duplicate value.](#an-array-with-forbidden-duplicate-value.)
15+
- [Number 7.5 against a schema accepting only multiples of 2.5](#number-7.5-against-a-schema-accepting-only-multiples-of-2.5)
16+
- [Number 7 against a schema accepting only multiples of 2.5](#number-7-against-a-schema-accepting-only-multiples-of-2.5)
17+
---
1418
### A null value against a schema accepting only null values
1519
A null value conforms to the schema.
1620

@@ -27,6 +31,7 @@ null
2731
```
2832
✓ no violations
2933
```
34+
---
3035
### A boolean value against a schema accepting only null values
3136
A boolean value does not conform to the schema as only null values do.
3237

@@ -46,6 +51,44 @@ true
4651
JSON path: $
4752
Invalid type. Expected null but got boolean.
4853
```
54+
---
55+
### An whole number value against a schema accepting only numbers
56+
All whole number values conform to the schema as every integer is a number.
57+
58+
#### Input
59+
##### JSON schema
60+
```json
61+
{"type":["number"]}
62+
```
63+
##### JSON
64+
```json
65+
1
66+
```
67+
#### Output
68+
```
69+
✓ no violations
70+
```
71+
---
72+
### A fractional number value against a schema accepting only integers
73+
Not all number values conform to the schema as not every number is a integer.
74+
75+
#### Input
76+
##### JSON schema
77+
```json
78+
{"type":["integer"]}
79+
```
80+
##### JSON
81+
```json
82+
1.5
83+
```
84+
#### Output
85+
```
86+
87+
Schema path: #/type
88+
JSON path: $
89+
Invalid type. Expected integer but got number.
90+
```
91+
---
4992
### A boolean value against a schema accepting only null and string values
5093
A boolean value does not conform to the schema as only null or string values do.
5194

@@ -65,6 +108,7 @@ true
65108
JSON path: $
66109
Invalid type. Expected null or string but got boolean.
67110
```
111+
---
68112
### An array with 2 out of 5 items not matching the desired item type
69113
When schema requires items to conform to a certain schema, every single value in the array has to.
70114

@@ -92,6 +136,7 @@ When schema requires items to conform to a certain schema, every single value in
92136
JSON path: $[3]
93137
Invalid type. Expected null but got boolean.
94138
```
139+
---
95140
### An array with forbidden duplicate value.
96141
When schema requires items to be unique, any duplicate occurrence of any value will cause a validation failure.
97142

@@ -127,3 +172,40 @@ When schema requires items to be unique, any duplicate occurrence of any value w
127172
JSON path: $[5]
128173
Non-unique array item.
129174
```
175+
---
176+
### Number 7.5 against a schema accepting only multiples of 2.5
177+
Number 7.5 conforms to the schema as 7.5 is 2.5 times 3.
178+
179+
#### Input
180+
##### JSON schema
181+
```json
182+
{"multipleOf":2.5,"type":["number"]}
183+
```
184+
##### JSON
185+
```json
186+
7.5
187+
```
188+
#### Output
189+
```
190+
✓ no violations
191+
```
192+
---
193+
### Number 7 against a schema accepting only multiples of 2.5
194+
Number 7 does not conform the schema as 7.5 is not a multiple of 2.5.
195+
196+
#### Input
197+
##### JSON schema
198+
```json
199+
{"multipleOf":2.5,"type":["number"]}
200+
```
201+
##### JSON
202+
```json
203+
7
204+
```
205+
#### Output
206+
```
207+
208+
Schema path: #/multipleOf
209+
JSON path: $
210+
7.0 is not a multiple of 2.5
211+
```

src/purs/JsonSchema.purs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ instance Show JsonSchema where
2727

2828
type Keywords =
2929
{ items Maybe JsonSchema
30+
, multipleOf Maybe Number
3031
, not Maybe JsonSchema
3132
, required Set String
3233
, typeKeyword Maybe (Set JsonValueType)
@@ -36,6 +37,7 @@ type Keywords =
3637
defaultKeywords Keywords
3738
defaultKeywords =
3839
{ items: Nothing
40+
, multipleOf: Nothing
3941
, not: Nothing
4042
, required: Set.empty
4143
, typeKeyword: Nothing

src/purs/JsonSchema/Codec/Parsing.purs

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ import Data.Maybe (Maybe(..), maybe)
1111
import Data.Set (Set)
1212
import Data.Set as Set
1313
import Data.Traversable (traverse)
14+
import Foreign.Object (Object)
1415
import Foreign.Object as Object
1516
import JsonSchema (JsonSchema(..), JsonValueType(..))
17+
import JsonSchema as Schema
1618

1719
parseSchema Json String \/ JsonSchema
1820
parseSchema json = parseBooleanSchema json <|> parseObjectSchema json
@@ -30,33 +32,51 @@ parseObjectSchema keywordsJson = do
3032
(parsingErrorMessage "schema is not a JSON object")
3133
(A.toObject keywordsJson)
3234

33-
items ← maybe (Right Nothing)
35+
items ← maybe (Right Schema.defaultKeywords.items)
3436
(map Just <<< parseSchema)
3537
(Object.lookup "items" schemaObject)
3638

37-
not ← maybe (Right Nothing)
39+
multipleOf ← parseMultipleOf schemaObject
40+
41+
not ← maybe (Right Schema.defaultKeywords.not)
3842
(map Just <<< parseSchema)
3943
(Object.lookup "not" schemaObject)
4044

41-
required ← maybe (Right Set.empty)
45+
required ← maybe (Right Schema.defaultKeywords.required)
4246
parseRequiredKeywordSpec
4347
(Object.lookup "required" schemaObject)
4448

4549
typeKeyword ← case Object.lookup "type" schemaObject of
4650
Just typeKeywordSpecJson →
4751
Just <$> parseTypeKeywordSpec typeKeywordSpecJson
4852
Nothing
49-
Right Nothing
53+
Right Schema.defaultKeywords.typeKeyword
5054

51-
uniqueItems ← maybe (Right false)
55+
uniqueItems ← maybe (Right Schema.defaultKeywords.uniqueItems)
5256
( \json →
53-
if A.isNull json then Right false
57+
if A.isNull json then Right Schema.defaultKeywords.uniqueItems
5458
else note "Unique items is not a boolean." $ A.toBoolean json
5559
)
5660
(Object.lookup "uniqueItems" schemaObject)
5761

5862
pure $ ObjectSchema
59-
{ items, not, required, typeKeyword, uniqueItems }
63+
{ items, multipleOf, not, required, typeKeyword, uniqueItems }
64+
65+
parseMultipleOf Object Json String \/ Maybe Number
66+
parseMultipleOf = Object.lookup "multipleOf" >>> case _ of
67+
Just json →
68+
if A.isNull json then Right default
69+
else case A.toNumber json of
70+
Just x →
71+
if x > zero then Right $ Just x
72+
else Left "multipleOf must be greater than zero."
73+
Nothing
74+
Left "multipleOf is not a number."
75+
Nothing
76+
Right default
77+
where
78+
default Maybe Number
79+
default = Schema.defaultKeywords.multipleOf
6080

6181
parseTypeKeywordSpec Json String \/ Set JsonValueType
6282
parseTypeKeywordSpec specJson = do

0 commit comments

Comments
 (0)