Skip to content

Commit 1b7d08a

Browse files
author
Clemens Vasters
committed
feat: ensure all JSON Structure types are covered in samples and validators
- Updated schema and instance validators to support all primitive types: - Added int8, uint8, int16, uint16, int128, uint128, float8 validation - Added integer type (alias for int32 per spec PR #16) - Moved 'any' from primitive to compound types - Updated 03-financial-types sample to demonstrate all types: - Added number, integer, double, null, int16, int32 type usage - Updated examples with new fields (weight, discountPercent, paymentTermsDays, cancelledDate, sequenceNumber) All 34 JSON Structure types are now covered across the samples.
1 parent 00000a4 commit 1b7d08a

File tree

6 files changed

+108
-32
lines changed

6 files changed

+108
-32
lines changed

samples/core/03-financial-types/example1.json

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,35 @@
33
"invoiceNumber": "INV-2023-001",
44
"issueDate": "2023-11-13",
55
"dueDate": "2023-12-13",
6+
"paymentTermsDays": 30,
67
"lineItems": [
78
{
89
"description": "Professional Services",
9-
"quantity": "10.000",
10+
"quantity": 10,
1011
"unitPrice": {
1112
"amount": "150.00",
1213
"currency": "USD"
1314
},
1415
"totalPrice": {
1516
"amount": "1500.00",
1617
"currency": "USD"
17-
}
18+
},
19+
"weight": 0.0,
20+
"discountPercent": 0
1821
},
1922
{
2023
"description": "Software License",
21-
"quantity": "1.000",
24+
"quantity": 1,
2225
"unitPrice": {
2326
"amount": "299.99",
2427
"currency": "USD"
2528
},
2629
"totalPrice": {
2730
"amount": "299.99",
2831
"currency": "USD"
29-
}
32+
},
33+
"weight": 0.5,
34+
"discountPercent": 10.5
3035
}
3136
],
3237
"subtotal": {
@@ -41,5 +46,6 @@
4146
"totalAmount": {
4247
"amount": "1957.49",
4348
"currency": "USD"
44-
}
49+
},
50+
"cancelledDate": null
4551
}

samples/core/03-financial-types/example2.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,21 @@
33
"invoiceNumber": "INV-2023-002",
44
"issueDate": "2023-11-14",
55
"dueDate": "2023-11-28",
6+
"paymentTermsDays": 14,
67
"lineItems": [
78
{
89
"description": "Monthly Subscription",
9-
"quantity": "1.000",
10+
"quantity": 1,
1011
"unitPrice": {
1112
"amount": "49.99",
1213
"currency": "EUR"
1314
},
1415
"totalPrice": {
1516
"amount": "49.99",
1617
"currency": "EUR"
17-
}
18+
},
19+
"weight": 0.0,
20+
"discountPercent": 0
1821
}
1922
],
2023
"subtotal": {
@@ -24,5 +27,6 @@
2427
"totalAmount": {
2528
"amount": "49.99",
2629
"currency": "EUR"
27-
}
30+
},
31+
"cancelledDate": "2023-11-20"
2832
}

samples/core/03-financial-types/example3.json

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,43 +3,49 @@
33
"invoiceNumber": "INV-2023-003",
44
"issueDate": "2023-11-15",
55
"dueDate": "2023-12-15",
6+
"paymentTermsDays": 30,
67
"lineItems": [
78
{
89
"description": "Consulting Hours",
9-
"quantity": "25.500",
10+
"quantity": 26,
1011
"unitPrice": {
1112
"amount": "200.00",
1213
"currency": "GBP"
1314
},
1415
"totalPrice": {
15-
"amount": "5100.00",
16+
"amount": "5200.00",
1617
"currency": "GBP"
17-
}
18+
},
19+
"weight": 0.0,
20+
"discountPercent": 5
1821
},
1922
{
2023
"description": "Travel Expenses",
21-
"quantity": "1.000",
24+
"quantity": 1,
2225
"unitPrice": {
2326
"amount": "450.75",
2427
"currency": "GBP"
2528
},
2629
"totalPrice": {
2730
"amount": "450.75",
2831
"currency": "GBP"
29-
}
32+
},
33+
"weight": 15.5,
34+
"discountPercent": 0
3035
}
3136
],
3237
"subtotal": {
33-
"amount": "5550.75",
38+
"amount": "5650.75",
3439
"currency": "GBP"
3540
},
3641
"taxRate": "0.2000",
3742
"taxAmount": {
38-
"amount": "1110.15",
43+
"amount": "1130.15",
3944
"currency": "GBP"
4045
},
4146
"totalAmount": {
42-
"amount": "6660.90",
47+
"amount": "6780.90",
4348
"currency": "GBP"
44-
}
49+
},
50+
"cancelledDate": null
4551
}

samples/core/03-financial-types/schema.struct.json

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,8 @@
3434
"maxLength": 200
3535
},
3636
"quantity": {
37-
"type": "decimal",
38-
"description": "Quantity of items",
39-
"precision": 10,
40-
"scale": 3
37+
"type": "integer",
38+
"description": "Quantity of items (whole number)"
4139
},
4240
"unitPrice": {
4341
"type": { "$ref": "#/definitions/Money" },
@@ -46,6 +44,14 @@
4644
"totalPrice": {
4745
"type": { "$ref": "#/definitions/Money" },
4846
"description": "Total price for this line item"
47+
},
48+
"weight": {
49+
"type": "double",
50+
"description": "Weight of the item in kilograms (double precision)"
51+
},
52+
"discountPercent": {
53+
"type": "number",
54+
"description": "Discount percentage as a raw JSON number"
4955
}
5056
},
5157
"required": ["description", "quantity", "unitPrice", "totalPrice"]
@@ -60,8 +66,12 @@
6066
"maxLength": 50
6167
},
6268
"sequenceNumber": {
69+
"type": "int32",
70+
"description": "Sequential invoice number for internal tracking"
71+
},
72+
"globalSequenceNumber": {
6373
"type": "int64",
64-
"description": "Sequential invoice number for audit trail"
74+
"description": "Global sequential invoice number for audit trail"
6575
},
6676
"issueDate": {
6777
"type": "date",
@@ -73,8 +83,7 @@
7383
},
7484
"paymentTermsDays": {
7585
"type": "int16",
76-
"description": "Number of days for payment terms",
77-
"default": 30
86+
"description": "Number of days for payment terms (1-365 typical range)"
7887
},
7988
"lineItems": {
8089
"type": "array",
@@ -106,6 +115,10 @@
106115
"blockchainHash": {
107116
"type": "uint128",
108117
"description": "Unsigned 128-bit blockchain transaction hash for immutable record"
118+
},
119+
"cancelledDate": {
120+
"type": ["date", "null"],
121+
"description": "Date the invoice was cancelled, or null if not cancelled"
109122
}
110123
},
111124
"required": ["invoiceNumber", "issueDate", "dueDate", "lineItems", "subtotal", "totalAmount"]

samples/py/json_structure_instance_validator.py

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -258,11 +258,31 @@ def validate_instance(self, instance, schema=None, path="#", meta=None):
258258
elif schema_type == "null":
259259
if instance is not None:
260260
self.errors.append(f"Expected null at {path}, got {type(instance).__name__}")
261-
elif schema_type == "int32":
261+
elif schema_type == "int8":
262262
if not isinstance(instance, int):
263-
self.errors.append(f"Expected int32 at {path}, got {type(instance).__name__}")
263+
self.errors.append(f"Expected int8 at {path}, got {type(instance).__name__}")
264+
elif not (-2**7 <= instance <= 2**7 - 1):
265+
self.errors.append(f"int8 value at {path} out of range")
266+
elif schema_type == "uint8":
267+
if not isinstance(instance, int):
268+
self.errors.append(f"Expected uint8 at {path}, got {type(instance).__name__}")
269+
elif not (0 <= instance <= 2**8 - 1):
270+
self.errors.append(f"uint8 value at {path} out of range")
271+
elif schema_type == "int16":
272+
if not isinstance(instance, int):
273+
self.errors.append(f"Expected int16 at {path}, got {type(instance).__name__}")
274+
elif not (-2**15 <= instance <= 2**15 - 1):
275+
self.errors.append(f"int16 value at {path} out of range")
276+
elif schema_type == "uint16":
277+
if not isinstance(instance, int):
278+
self.errors.append(f"Expected uint16 at {path}, got {type(instance).__name__}")
279+
elif not (0 <= instance <= 2**16 - 1):
280+
self.errors.append(f"uint16 value at {path} out of range")
281+
elif schema_type == "int32" or schema_type == "integer":
282+
if not isinstance(instance, int):
283+
self.errors.append(f"Expected {schema_type} at {path}, got {type(instance).__name__}")
264284
elif not (-2**31 <= instance <= 2**31 - 1):
265-
self.errors.append(f"int32 value at {path} out of range")
285+
self.errors.append(f"{schema_type} value at {path} out of range")
266286
elif schema_type == "uint32":
267287
if not isinstance(instance, int):
268288
self.errors.append(f"Expected uint32 at {path}, got {type(instance).__name__}")
@@ -288,6 +308,29 @@ def validate_instance(self, instance, schema=None, path="#", meta=None):
288308
self.errors.append(f"uint64 value at {path} out of range")
289309
except ValueError:
290310
self.errors.append(f"Invalid uint64 format at {path}")
311+
elif schema_type == "int128":
312+
if not isinstance(instance, str):
313+
self.errors.append(f"Expected int128 as string at {path}, got {type(instance).__name__}")
314+
else:
315+
try:
316+
value = int(instance)
317+
if not (-2**127 <= value <= 2**127 - 1):
318+
self.errors.append(f"int128 value at {path} out of range")
319+
except ValueError:
320+
self.errors.append(f"Invalid int128 format at {path}")
321+
elif schema_type == "uint128":
322+
if not isinstance(instance, str):
323+
self.errors.append(f"Expected uint128 as string at {path}, got {type(instance).__name__}")
324+
else:
325+
try:
326+
value = int(instance)
327+
if not (0 <= value <= 2**128 - 1):
328+
self.errors.append(f"uint128 value at {path} out of range")
329+
except ValueError:
330+
self.errors.append(f"Invalid uint128 format at {path}")
331+
elif schema_type == "float8":
332+
if not isinstance(instance, (int, float)):
333+
self.errors.append(f"Expected float8 at {path}, got {type(instance).__name__}")
291334
elif schema_type in ("float", "double"):
292335
if not isinstance(instance, (int, float)):
293336
self.errors.append(f"Expected {schema_type} at {path}, got {type(instance).__name__}")
@@ -548,7 +591,9 @@ def validate(self, instance, schema=None):
548591
import re
549592
if not re.match(schema["pattern"], instance):
550593
errors.append(f"String does not match pattern {schema['pattern']}")
551-
if t == "number" or t == "int32" or t == "float" or t == "double":
594+
if t in ("number", "integer", "float", "double", "float8", "decimal",
595+
"int8", "uint8", "int16", "uint16", "int32", "uint32",
596+
"int64", "uint64", "int128", "uint128"):
552597
if "minimum" in schema and instance < schema["minimum"]:
553598
errors.append(f"Number less than minimum {schema['minimum']}")
554599
if "maximum" in schema and instance > schema["maximum"]:
@@ -675,7 +720,9 @@ def _validate_validation_addins(self, schema, instance, path):
675720
[Metaschema: JSON Structure JSONStructureValidation]
676721
"""
677722
# Numeric constraints.
678-
if schema.get("type") in ("number", "integer", "float", "double", "decimal", "int32", "uint32", "int64", "uint64", "int128", "uint128"):
723+
if schema.get("type") in ("number", "integer", "float", "double", "decimal",
724+
"int8", "uint8", "int16", "uint16", "int32", "uint32",
725+
"int64", "uint64", "int128", "uint128", "float8"):
679726
if "minimum" in schema:
680727
try:
681728
if instance < schema["minimum"]:

samples/py/json_structure_schema_validator.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,12 @@ class JSONStructureSchemaCoreValidator:
4242
"values", "choices", "selector", "tuple"
4343
}
4444
PRIMITIVE_TYPES = {
45-
"string", "number", "boolean", "null", "int8", "uint8", "int16", "uint16",
45+
"string", "number", "integer", "boolean", "null", "int8", "uint8", "int16", "uint16",
4646
"int32", "uint32", "int64", "uint64", "int128", "uint128", "float8",
4747
"float", "double", "decimal", "date", "datetime", "time", "duration",
48-
"uuid", "uri", "binary", "jsonpointer", "any"
48+
"uuid", "uri", "binary", "jsonpointer"
4949
}
50-
COMPOUND_TYPES = {"object", "array", "set", "map", "tuple", "choice"}
50+
COMPOUND_TYPES = {"object", "array", "set", "map", "tuple", "choice", "any"}
5151

5252
# Extended keywords for conditional composition
5353
COMPOSITION_KEYWORDS = {"allOf", "anyOf", "oneOf", "not", "if", "then", "else"}

0 commit comments

Comments
 (0)