Skip to content

Commit ac972c1

Browse files
committed
feat: implement compatibility check for JSON value type changes
1 parent 1fafe25 commit ac972c1

File tree

6 files changed

+453
-17
lines changed

6 files changed

+453
-17
lines changed

Examples.generated.md

Lines changed: 181 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
---
88
## Compatibility
9-
### Compatibility when there are no JSON schema differences
9+
### No JSON schema differences
1010
When there is not JSON schema differences, schema change is fully compatible.
1111
#### Input
1212
##### JSON schema differences
@@ -18,6 +18,165 @@ no differences
1818
full
1919
```
2020

21+
### ► Expected JSON value type changes from null to boolean
22+
Because no boolean value can satisfy null JSON type constraint, and vice versa, such a change is incompatible.
23+
#### Input
24+
##### JSON schema differences
25+
```
26+
-
27+
Schema path: #
28+
Change of accepted JSON value types from
29+
- null
30+
to
31+
- boolean
32+
```
33+
#### Output
34+
```
35+
none
36+
```
37+
38+
### ► Expected JSON value type changes from integer to number
39+
Because every integer is a number, but not vice versa, such a change is backward compatible.
40+
#### Input
41+
##### JSON schema differences
42+
```
43+
-
44+
Schema path: #
45+
Change of accepted JSON value types from
46+
- integer
47+
to
48+
- number
49+
```
50+
#### Output
51+
```
52+
backward
53+
```
54+
55+
### ► Expected JSON value type changes from number to integer
56+
Because every integer is a number, but not vice versa, such a change is forward compatible.
57+
#### Input
58+
##### JSON schema differences
59+
```
60+
-
61+
Schema path: #
62+
Change of accepted JSON value types from
63+
- number
64+
to
65+
- integer
66+
```
67+
#### Output
68+
```
69+
forward
70+
```
71+
72+
### ► Expected JSON value types is extended
73+
Because more value types than before are accepted, this change is backward compatible.
74+
#### Input
75+
##### JSON schema differences
76+
```
77+
-
78+
Schema path: #
79+
Change of accepted JSON value types from
80+
- null
81+
to
82+
- boolean
83+
- null
84+
```
85+
#### Output
86+
```
87+
backward
88+
```
89+
90+
### ► Expected JSON value types is reduced
91+
Because less value types than before are accepted, this change is forward compatible.
92+
#### Input
93+
##### JSON schema differences
94+
```
95+
-
96+
Schema path: #
97+
Change of accepted JSON value types from
98+
- boolean
99+
- null
100+
to
101+
- null
102+
```
103+
#### Output
104+
```
105+
forward
106+
```
107+
108+
### ► Expected JSON value types including number is extended by integer
109+
Because every integer is a number, such a change is fully compatible.
110+
#### Input
111+
##### JSON schema differences
112+
```
113+
-
114+
Schema path: #
115+
Change of accepted JSON value types from
116+
- number
117+
to
118+
- integer
119+
- number
120+
```
121+
#### Output
122+
```
123+
full
124+
```
125+
126+
### ► Expected JSON value types including integer is extended by number
127+
Because not every integer is a number, such a change is backward compatible.
128+
#### Input
129+
##### JSON schema differences
130+
```
131+
-
132+
Schema path: #
133+
Change of accepted JSON value types from
134+
- integer
135+
to
136+
- integer
137+
- number
138+
```
139+
#### Output
140+
```
141+
backward
142+
```
143+
144+
### ► Expected JSON value types including integer and number is reduced by integer
145+
Because every integer is a number, such a change is fully compatible.
146+
#### Input
147+
##### JSON schema differences
148+
```
149+
-
150+
Schema path: #
151+
Change of accepted JSON value types from
152+
- integer
153+
- number
154+
to
155+
- number
156+
```
157+
#### Output
158+
```
159+
full
160+
```
161+
162+
### ► Expected JSON value types including integer and number is reuced by number
163+
Because not every integer is a number, such a change is forward compatible.
164+
#### Input
165+
##### JSON schema differences
166+
```
167+
-
168+
Schema path: #
169+
Change of accepted JSON value types from
170+
- integer
171+
- number
172+
to
173+
- integer
174+
```
175+
#### Output
176+
```
177+
forward
178+
```
179+
21180
---
22181
## Diff
23182
### ► Comparing identical schemata
@@ -36,6 +195,27 @@ false
36195
no differences
37196
```
38197

198+
### ► Changing expected JSON value type from null to boolean
199+
Any change in expected JSON value type should be accounted as a difference.
200+
#### Input
201+
##### Previous JSON schema
202+
```json
203+
{"type":["null"]}
204+
```
205+
##### Next JSON schema
206+
```json
207+
{"type":["boolean"]}
208+
```
209+
#### Output
210+
```
211+
-
212+
Schema path: #
213+
Change of accepted JSON value types from
214+
- null
215+
to
216+
- boolean
217+
```
218+
39219
---
40220
## Validation
41221
### ► A null value against a schema accepting only null values

src/purs/JsonSchema/Compatibility.purs

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,16 @@ module JsonSchema.Compatibility
66

77
import Prelude
88

9+
import Data.Array as Array
10+
import Data.Foldable (foldl)
911
import Data.Generic.Rep (class Generic)
12+
import Data.List as List
13+
import Data.Maybe (Maybe(..))
1014
import Data.Set (Set)
1115
import Data.Set as Set
1216
import Data.Show.Generic (genericShow)
13-
import JsonSchema.Diff (Difference)
17+
import JsonSchema (JsonValueType(..))
18+
import JsonSchema.Diff (Difference, DifferenceType(..))
1419

1520
data Compatibility = Backward | Forward | Full | None
1621

@@ -21,7 +26,65 @@ instance Show Compatibility where
2126
show = genericShow
2227

2328
calculate Set Difference Compatibility
24-
calculate differences = if Set.isEmpty differences then Full else None
29+
calculate differences =
30+
if Set.isEmpty differences then Full
31+
else foldl mergeCompatibility Full
32+
((f <<< _.differenceType) <$> List.fromFoldable differences)
33+
where
34+
f DifferenceType Compatibility
35+
f = case _ of
36+
TypeChange (Just previousTypes) (Just nextTypes) →
37+
let
38+
typesAdded = nextTypes `Set.difference` previousTypes
39+
typesRemoved = previousTypes `Set.difference` nextTypes
40+
in
41+
case Set.size typesAdded, Set.size typesRemoved of
42+
0, 0
43+
Full
44+
0, _ →
45+
case Array.fromFoldable typesRemoved of
46+
[ JsonInteger ] →
47+
if JsonNumber `Set.member` previousTypes then
48+
Full
49+
else Forward
50+
_ →
51+
Forward
52+
_, 0
53+
case Array.fromFoldable typesAdded of
54+
[ JsonInteger ] →
55+
if JsonNumber `Set.member` previousTypes then
56+
Full
57+
else Backward
58+
_ →
59+
Backward
60+
_, _ →
61+
case
62+
Array.fromFoldable typesAdded,
63+
Array.fromFoldable typesRemoved
64+
of
65+
[ JsonInteger ], [ JsonNumber ] →
66+
Forward
67+
[ JsonNumber ], [ JsonInteger ] →
68+
Backward
69+
_, _ →
70+
None
71+
TypeChange _ Nothing
72+
Full
73+
TypeChange Nothing _ →
74+
None
75+
_ →
76+
None
77+
78+
mergeCompatibility Compatibility Compatibility Compatibility
79+
mergeCompatibility = case _, _ of
80+
Backward, Backward
81+
Backward
82+
Forward, Forward
83+
Forward
84+
Full, other →
85+
other
86+
_, _ →
87+
None
2588

2689
renderCompatibility Compatibility String
2790
renderCompatibility = case _ of

src/purs/JsonSchema/Diff.purs

Lines changed: 80 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,92 @@
1-
module JsonSchema.Diff (Difference(..), calculate, renderDifference) where
1+
module JsonSchema.Diff
2+
( Difference
3+
, DifferenceType(..)
4+
, calculate
5+
, renderDifference
6+
) where
27

38
import Prelude
49

10+
import Data.Foldable (foldMap)
11+
import Data.Generic.Rep (class Generic)
12+
import Data.List (List(..))
13+
import Data.Maybe (Maybe, maybe)
514
import Data.Set (Set)
615
import Data.Set as Set
7-
import JsonSchema (JsonSchema)
16+
import Data.Show.Generic (genericShow)
17+
import JsonSchema (JsonSchema(..), JsonValueType, Keywords)
18+
import JsonSchema as Schema
19+
import JsonSchema.SchemaPath (SchemaPath)
20+
import JsonSchema.SchemaPath as SchemaPath
821

9-
data Difference = Difference
22+
type Difference = { differenceType DifferenceType, path SchemaPath }
1023

11-
derive instance Eq Difference
24+
data DifferenceType
25+
= BooleanSchemaChange Boolean
26+
| SchemaChangeFromBooleanToObject Boolean Keywords
27+
| SchemaChangeFromObjectToBoolean Keywords Boolean
28+
| TypeChange (Maybe (Set JsonValueType)) (Maybe (Set JsonValueType))
1229

13-
instance Show Difference where
14-
show _ = ""
30+
derive instance Eq DifferenceType
31+
derive instance Generic DifferenceType _
32+
derive instance Ord DifferenceType
33+
34+
instance Show DifferenceType where
35+
show = genericShow
1536

1637
calculate JsonSchema JsonSchema Set Difference
17-
calculate _ _ = Set.empty
38+
calculate = go Nil
39+
where
40+
go SchemaPath JsonSchema JsonSchema Set Difference
41+
go path previousSchema nextSchema = case previousSchema, nextSchema of
42+
BooleanSchema previousBool, BooleanSchema nextBool →
43+
Set.empty
44+
BooleanSchema previousBool, ObjectSchema nextKeywords →
45+
Set.empty
46+
ObjectSchema previousKeywords, BooleanSchema nextBool →
47+
Set.empty
48+
ObjectSchema previousKeywords, ObjectSchema nextKeywords →
49+
calculateObjectSchemataDiff path previousKeywords nextKeywords
50+
51+
calculateObjectSchemataDiff
52+
SchemaPath Keywords Keywords Set Difference
53+
calculateObjectSchemataDiff path previousKeywords nextKeywords =
54+
if previousKeywords.typeKeyword == nextKeywords.typeKeyword then
55+
Set.empty
56+
else
57+
Set.singleton
58+
{ differenceType: TypeChange
59+
previousKeywords.typeKeyword
60+
nextKeywords.typeKeyword
61+
, path
62+
}
1863

1964
renderDifference Difference Array String
20-
renderDifference _ = [ "<TODO>" ]
65+
renderDifference { differenceType, path } =
66+
[ "Schema path: " <> SchemaPath.render path ]
67+
<> renderDifferenceType
68+
where
69+
renderDifferenceType Array String
70+
renderDifferenceType = case differenceType of
71+
BooleanSchemaChange false
72+
[ "Boolean schema changed to reject-all" ]
73+
BooleanSchemaChange true
74+
[ "Boolean schema changed to allow-all" ]
75+
SchemaChangeFromBooleanToObject _ _ →
76+
[ "Boolean schema changed to object schema" ]
77+
SchemaChangeFromObjectToBoolean _ _ →
78+
[ "Object schema changed to boolean schema" ]
79+
TypeChange typesBefore typesAfter →
80+
[ "Change of accepted JSON value types from " ]
81+
<> renderJsonValueTypes typesBefore
82+
<> [ "to" ]
83+
<> renderJsonValueTypes typesAfter
84+
85+
renderJsonValueTypes Maybe (Set JsonValueType) Array String
86+
renderJsonValueTypes = maybe
87+
[ "unspecified" ]
88+
( foldMap
89+
( \jsonValueType →
90+
[ "- " <> Schema.renderJsonValueType jsonValueType ]
91+
)
92+
)

0 commit comments

Comments
 (0)