-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
10 changed files
with
144 additions
and
221 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
jschon.formats | ||
============== | ||
.. automodule:: jschon.formats |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,77 +1,54 @@ | ||
import ipaddress | ||
import pprint | ||
import re | ||
|
||
from jschon import create_catalog, JSON, JSONSchema | ||
from jschon import JSON, JSONSchema, create_catalog | ||
from jschon.vocabulary.format import format_validator | ||
|
||
|
||
# define a "hostname" format validation function | ||
def validate_hostname(value): | ||
hostname_regex = re.compile( | ||
r"^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$") | ||
if not hostname_regex.match(value): | ||
raise ValueError(f"'{value}' is not a valid hostname") | ||
# register an 'ipv4' format validator | ||
@format_validator('ipv4') | ||
def validate_ipv4(value: str) -> None: | ||
if isinstance(value, str): | ||
ipaddress.IPv4Address(value) # raises ValueError for an invalid IPv4 address | ||
|
||
|
||
# create a catalog with support for JSON Schema version 2020-12 | ||
# register an 'ipv6' format validator | ||
@format_validator('ipv6') | ||
def validate_ipv6(value: str) -> None: | ||
if isinstance(value, str): | ||
ipaddress.IPv6Address(value) # raises ValueError for an invalid IPv6 address | ||
|
||
|
||
# initialize the catalog, with JSON Schema 2020-12 vocabulary support | ||
catalog = create_catalog('2020-12') | ||
|
||
# register IP address and hostname format validators | ||
catalog.add_format_validators({ | ||
"ipv4": ipaddress.IPv4Address, | ||
"ipv6": ipaddress.IPv6Address, | ||
"hostname": validate_hostname, | ||
}) | ||
# enable validation with the 'ipv4' and 'ipv6' format validators | ||
catalog.enable_formats('ipv4', 'ipv6') | ||
|
||
# create a schema for validating an array of host records | ||
hosts_schema = JSONSchema({ | ||
# create a schema for validating an array of IP addresses | ||
schema = JSONSchema({ | ||
"$schema": "https://json-schema.org/draft/2020-12/schema", | ||
"$id": "https://example.com/hosts-schema", | ||
"$id": "https://example.com/schema", | ||
"type": "array", | ||
"items": { | ||
"type": "object", | ||
"properties": { | ||
"ipaddress": { | ||
"type": "string", | ||
"oneOf": [ | ||
{"format": "ipv4"}, | ||
{"format": "ipv6"} | ||
] | ||
}, | ||
"hostname": { | ||
"type": "string", | ||
"format": "hostname" | ||
} | ||
}, | ||
"required": ["ipaddress", "hostname"] | ||
"type": "string", | ||
"anyOf": [ | ||
{"format": "ipv4"}, | ||
{"format": "ipv6"} | ||
] | ||
} | ||
}) | ||
|
||
# declare a host record array containing valid IP addresses and hostnames | ||
valid_host_records = JSON([ | ||
{"ipaddress": "127.0.0.1", "hostname": "localhost"}, | ||
{"ipaddress": "10.0.0.8", "hostname": "server.local"}, | ||
]) | ||
|
||
# declare a host record array containing some values that are invalid | ||
# per the registered format validators | ||
invalid_host_records = JSON([ | ||
{"ipaddress": "127.0.0.1", "hostname": "~localhost"}, | ||
{"ipaddress": "10.0.0", "hostname": "server.local"}, | ||
]) | ||
|
||
# evaluate the valid array | ||
valid_result = hosts_schema.evaluate(valid_host_records) | ||
# evaluate a valid array | ||
valid_result = schema.evaluate(JSON(['127.0.0.1', '::1'])) | ||
|
||
# evaluate the invalid array | ||
invalid_result = hosts_schema.evaluate(invalid_host_records) | ||
# evaluate an invalid array | ||
invalid_result = schema.evaluate(JSON(['127.0.1', '::1'])) | ||
|
||
# print output for the valid case | ||
print(f'Valid array result: {valid_result.valid}') | ||
print('Valid array basic output:') | ||
print('Valid case output:') | ||
pprint.pp(valid_result.output('basic')) | ||
|
||
# print output for the invalid case | ||
print(f'Invalid array result: {invalid_result.valid}') | ||
print('Invalid array detailed output:') | ||
pprint.pp(invalid_result.output('detailed')) | ||
print('Invalid case output:') | ||
pprint.pp(invalid_result.output('basic')) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,62 +1,35 @@ | ||
Valid array result: True | ||
Valid array basic output: | ||
Valid case output: | ||
{'valid': True, | ||
'annotations': [{'instanceLocation': '', | ||
'keywordLocation': '/items', | ||
'absoluteKeywordLocation': 'https://example.com/hosts-schema#/items', | ||
'absoluteKeywordLocation': 'https://example.com/schema#/items', | ||
'annotation': True}, | ||
{'instanceLocation': '/0', | ||
'keywordLocation': '/items/properties', | ||
'absoluteKeywordLocation': 'https://example.com/hosts-schema#/items/properties', | ||
'annotation': ['ipaddress', 'hostname']}, | ||
{'instanceLocation': '/0/ipaddress', | ||
'keywordLocation': '/items/properties/ipaddress/oneOf/0/format', | ||
'absoluteKeywordLocation': 'https://example.com/hosts-schema#/items/properties/ipaddress/oneOf/0/format', | ||
'keywordLocation': '/items/anyOf/0/format', | ||
'absoluteKeywordLocation': 'https://example.com/schema#/items/anyOf/0/format', | ||
'annotation': 'ipv4'}, | ||
{'instanceLocation': '/0/hostname', | ||
'keywordLocation': '/items/properties/hostname/format', | ||
'absoluteKeywordLocation': 'https://example.com/hosts-schema#/items/properties/hostname/format', | ||
'annotation': 'hostname'}, | ||
{'instanceLocation': '/1', | ||
'keywordLocation': '/items/properties', | ||
'absoluteKeywordLocation': 'https://example.com/hosts-schema#/items/properties', | ||
'annotation': ['ipaddress', 'hostname']}, | ||
{'instanceLocation': '/1/ipaddress', | ||
'keywordLocation': '/items/properties/ipaddress/oneOf/0/format', | ||
'absoluteKeywordLocation': 'https://example.com/hosts-schema#/items/properties/ipaddress/oneOf/0/format', | ||
'annotation': 'ipv4'}, | ||
{'instanceLocation': '/1/hostname', | ||
'keywordLocation': '/items/properties/hostname/format', | ||
'absoluteKeywordLocation': 'https://example.com/hosts-schema#/items/properties/hostname/format', | ||
'annotation': 'hostname'}]} | ||
Invalid array result: False | ||
Invalid array detailed output: | ||
'keywordLocation': '/items/anyOf/1/format', | ||
'absoluteKeywordLocation': 'https://example.com/schema#/items/anyOf/1/format', | ||
'annotation': 'ipv6'}]} | ||
Invalid case output: | ||
{'valid': False, | ||
'instanceLocation': '', | ||
'keywordLocation': '', | ||
'absoluteKeywordLocation': 'https://example.com/hosts-schema#', | ||
'errors': [{'instanceLocation': '', | ||
'keywordLocation': '/items', | ||
'absoluteKeywordLocation': 'https://example.com/hosts-schema#/items', | ||
'errors': [{'instanceLocation': '/0/hostname', | ||
'keywordLocation': '/items/properties/hostname/format', | ||
'absoluteKeywordLocation': 'https://example.com/hosts-schema#/items/properties/hostname/format', | ||
'error': 'The instance is invalid against the ' | ||
'"hostname" format: \'~localhost\' is not a ' | ||
'valid hostname'}, | ||
{'instanceLocation': '/1/ipaddress', | ||
'keywordLocation': '/items/properties/ipaddress/oneOf', | ||
'absoluteKeywordLocation': 'https://example.com/hosts-schema#/items/properties/ipaddress/oneOf', | ||
'errors': [{'instanceLocation': '/1/ipaddress', | ||
'keywordLocation': '/items/properties/ipaddress/oneOf/0/format', | ||
'absoluteKeywordLocation': 'https://example.com/hosts-schema#/items/properties/ipaddress/oneOf/0/format', | ||
'error': 'The instance is invalid against ' | ||
'the "ipv4" format: Expected 4 ' | ||
"octets in '10.0.0'"}, | ||
{'instanceLocation': '/1/ipaddress', | ||
'keywordLocation': '/items/properties/ipaddress/oneOf/1/format', | ||
'absoluteKeywordLocation': 'https://example.com/hosts-schema#/items/properties/ipaddress/oneOf/1/format', | ||
'error': 'The instance is invalid against ' | ||
'the "ipv6" format: At least 3 ' | ||
'parts expected in ' | ||
"'10.0.0'"}]}]}]} | ||
'absoluteKeywordLocation': 'https://example.com/schema#/items', | ||
'error': [0]}, | ||
{'instanceLocation': '/0', | ||
'keywordLocation': '/items/anyOf', | ||
'absoluteKeywordLocation': 'https://example.com/schema#/items/anyOf', | ||
'error': 'The instance must be valid against at least one ' | ||
'subschema'}, | ||
{'instanceLocation': '/0', | ||
'keywordLocation': '/items/anyOf/0/format', | ||
'absoluteKeywordLocation': 'https://example.com/schema#/items/anyOf/0/format', | ||
'error': 'The instance is invalid against the "ipv4" format: ' | ||
"Expected 4 octets in '127.0.1'"}, | ||
{'instanceLocation': '/0', | ||
'keywordLocation': '/items/anyOf/1/format', | ||
'absoluteKeywordLocation': 'https://example.com/schema#/items/anyOf/1/format', | ||
'error': 'The instance is invalid against the "ipv6" format: At ' | ||
"least 3 parts expected in '127.0.1'"}]} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
from jschon.jsonpointer import JSONPointer | ||
from jschon.vocabulary.format import format_validator | ||
|
||
|
||
@format_validator('json-pointer') | ||
def validate_json_pointer(value: str) -> None: | ||
if isinstance(value, str): | ||
if not JSONPointer._json_pointer_re.fullmatch(value): | ||
raise ValueError |
Oops, something went wrong.