diff --git a/json/README.md b/json/README.md index 21f73322a..80441892a 100644 --- a/json/README.md +++ b/json/README.md @@ -155,6 +155,7 @@ for more information. ### PostgreSQL ### * [postgres-json-schema](https://github.com/gavinwahl/postgres-json-schema) +* [is_jsonb_valid](https://github.com/furstenheim/is_jsonb_valid) If you use it as well, please fork and send a pull request adding yourself to the list :). diff --git a/json/tests/draft4/optional/format.json b/json/tests/draft4/optional/format.json index 80373bdf1..32db8def7 100644 --- a/json/tests/draft4/optional/format.json +++ b/json/tests/draft4/optional/format.json @@ -25,15 +25,80 @@ "schema": {"format": "uri"}, "tests": [ { - "description": "a valid URI", + "description": "a valid URL with anchor tag", "data": "http://foo.bar/?baz=qux#quux", "valid": true }, + { + "description": "a valid URL with anchor tag and parantheses", + "data": "http://foo.com/blah_(wikipedia)_blah#cite-1", + "valid": true + }, + { + "description": "a valid URL with URL-encoded stuff", + "data": "http://foo.bar/?q=Test%20URL-encoded%20stuff", + "valid": true + }, + { + "description": "a valid puny-coded URL ", + "data": "http://xn--nw2a.xn--j6w193g/", + "valid": true + }, + { + "description": "a valid URL with many special characters", + "data": "http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com", + "valid": true + }, + { + "description": "a valid URL based on IPv4", + "data": "http://223.255.255.254", + "valid": true + }, + { + "description": "a valid URL with ftp scheme", + "data": "ftp://ftp.is.co.za/rfc/rfc1808.txt", + "valid": true + }, + { + "description": "a valid URL for a simple text file", + "data": "http://www.ietf.org/rfc/rfc2396.txt", + "valid": true + }, + { + "description": "a valid URL ", + "data": "ldap://[2001:db8::7]/c=GB?objectClass?one", + "valid": true + }, + { + "description": "a valid mailto URI", + "data": "mailto:John.Doe@example.com", + "valid": true + }, + { + "description": "a valid newsgroup URI", + "data": "news:comp.infosystems.www.servers.unix", + "valid": true + }, + { + "description": "a valid tel URI", + "data": "tel:+1-816-555-1212", + "valid": true + }, + { + "description": "a valid URN", + "data": "urn:oasis:names:specification:docbook:dtd:xml:4.1.2", + "valid": true + }, { "description": "an invalid protocol-relative URI Reference", "data": "//foo.bar/?baz=qux#quux", "valid": false }, + { + "description": "an invalid relative URI Reference", + "data": "/abc", + "valid": false + }, { "description": "an invalid URI", "data": "\\\\WINDOWS\\fileshare", @@ -43,6 +108,16 @@ "description": "an invalid URI though valid URI reference", "data": "abc", "valid": false + }, + { + "description": "an invalid URI with spaces", + "data": "http:// shouldfail.com", + "valid": false + }, + { + "description": "an invalid URI with spaces and missing scheme", + "data": ":// should fail", + "valid": false } ] }, diff --git a/json/tests/draft6/const.json b/json/tests/draft6/const.json index 0e533e0a0..0fe00f21f 100644 --- a/json/tests/draft6/const.json +++ b/json/tests/draft6/const.json @@ -46,6 +46,27 @@ } ] }, + { + "description": "const with array", + "schema": {"const": [{ "foo": "bar" }]}, + "tests": [ + { + "description": "same array is valid", + "data": [{"foo": "bar"}], + "valid": true + }, + { + "description": "another array item is invalid", + "data": [2], + "valid": false + }, + { + "description": "array with additional items is invalid", + "data": [1, 2, 3], + "valid": false + } + ] + }, { "description": "const with null", "schema": {"const": null}, diff --git a/json/tests/draft6/optional/format.json b/json/tests/draft6/optional/format.json index 41f6ac45e..67f2fe630 100644 --- a/json/tests/draft6/optional/format.json +++ b/json/tests/draft6/optional/format.json @@ -25,10 +25,70 @@ "schema": {"format": "uri"}, "tests": [ { - "description": "a valid URI", + "description": "a valid URL with anchor tag", "data": "http://foo.bar/?baz=qux#quux", "valid": true }, + { + "description": "a valid URL with anchor tag and parantheses", + "data": "http://foo.com/blah_(wikipedia)_blah#cite-1", + "valid": true + }, + { + "description": "a valid URL with URL-encoded stuff", + "data": "http://foo.bar/?q=Test%20URL-encoded%20stuff", + "valid": true + }, + { + "description": "a valid puny-coded URL ", + "data": "http://xn--nw2a.xn--j6w193g/", + "valid": true + }, + { + "description": "a valid URL with many special characters", + "data": "http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com", + "valid": true + }, + { + "description": "a valid URL based on IPv4", + "data": "http://223.255.255.254", + "valid": true + }, + { + "description": "a valid URL with ftp scheme", + "data": "ftp://ftp.is.co.za/rfc/rfc1808.txt", + "valid": true + }, + { + "description": "a valid URL for a simple text file", + "data": "http://www.ietf.org/rfc/rfc2396.txt", + "valid": true + }, + { + "description": "a valid URL ", + "data": "ldap://[2001:db8::7]/c=GB?objectClass?one", + "valid": true + }, + { + "description": "a valid mailto URI", + "data": "mailto:John.Doe@example.com", + "valid": true + }, + { + "description": "a valid newsgroup URI", + "data": "news:comp.infosystems.www.servers.unix", + "valid": true + }, + { + "description": "a valid tel URI", + "data": "tel:+1-816-555-1212", + "valid": true + }, + { + "description": "a valid URN", + "data": "urn:oasis:names:specification:docbook:dtd:xml:4.1.2", + "valid": true + }, { "description": "an invalid protocol-relative URI Reference", "data": "//foo.bar/?baz=qux#quux", @@ -48,6 +108,16 @@ "description": "an invalid URI though valid URI reference", "data": "abc", "valid": false + }, + { + "description": "an invalid URI with spaces", + "data": "http:// shouldfail.com", + "valid": false + }, + { + "description": "an invalid URI with spaces and missing scheme", + "data": ":// should fail", + "valid": false } ] }, diff --git a/jsonschema/_format.py b/jsonschema/_format.py index f680ecb2c..7d46a677f 100644 --- a/jsonschema/_format.py +++ b/jsonschema/_format.py @@ -296,6 +296,8 @@ def is_css3_color(instance): draft6="json-pointer", raises=jsonpointer.JsonPointerException, ) def is_json_pointer(instance): + if not isinstance(instance, str_types): + return True return jsonpointer.JsonPointer(instance) diff --git a/jsonschema/_validators.py b/jsonschema/_validators.py index c1ecc102c..f07f19f87 100644 --- a/jsonschema/_validators.py +++ b/jsonschema/_validators.py @@ -391,6 +391,15 @@ def type(validator, types, instance, schema): yield ValidationError(_utils.types_msg(instance, types)) +def type_draft6(validator, types, instance, schema): + # Draft 6 changes integer type to include floats with no fractional part + if isinstance(instance, float) and "integer" in types and instance.is_integer(): + return + + for e in type(validator, types, instance, schema): + yield e + + def properties(validator, properties, instance, schema): if not validator.is_type(instance, "object"): return diff --git a/jsonschema/validators.py b/jsonschema/validators.py index 8977812c0..bf174cc6b 100644 --- a/jsonschema/validators.py +++ b/jsonschema/validators.py @@ -56,7 +56,7 @@ def _validates(cls): return _validates -def create(meta_schema, validators=(), version=None, default_types=None): +def create(meta_schema, validators=(), version=None, default_types=None, scope_key="id"): if default_types is None: default_types = { u"array": list, u"boolean": bool, u"integer": int_types, @@ -68,6 +68,7 @@ class Validator(object): VALIDATORS = dict(validators) META_SCHEMA = dict(meta_schema) DEFAULT_TYPES = dict(default_types) + SCOPE_KEY = scope_key def __init__( self, schema, types=(), resolver=None, format_checker=None, @@ -76,12 +77,13 @@ def __init__( self._types.update(types) if resolver is None: + _schema = schema if schema is True: - resolver = RefResolver.from_schema({}) + _schema = {} elif schema is False: - resolver = RefResolver.from_schema({"not": {}}) - else: - resolver = RefResolver.from_schema(schema) + _schema = {"not": {}} + + resolver = RefResolver(_schema.get(scope_key, u""), _schema) self.resolver = resolver self.format_checker = format_checker @@ -101,7 +103,8 @@ def iter_errors(self, instance, _schema=None): elif _schema is False: _schema = {"not": {}} - scope = _schema.get(u"$id") + scope = _schema.get(scope_key) + if scope: self.resolver.push_scope(scope) try: @@ -149,11 +152,8 @@ def is_type(self, instance, type): raise UnknownType(type, instance, self.schema) pytypes = self._types[type] - # FIXME: draft < 6 - if isinstance(instance, float) and type == "integer": - return instance.is_integer() # bool inherits from int, so ensure bools aren't reported as ints - elif isinstance(instance, bool): + if isinstance(instance, bool): pytypes = _utils.flatten(pytypes) is_number = any( issubclass(pytype, numbers.Number) for pytype in pytypes @@ -181,6 +181,7 @@ def extend(validator, validators, version=None): validators=all_validators, version=version, default_types=validator.DEFAULT_TYPES, + scope_key=validator.SCOPE_KEY ) @@ -279,10 +280,11 @@ def extend(validator, validators, version=None): u"properties": _validators.properties, u"propertyNames": _validators.propertyNames, u"required": _validators.required, - u"type": _validators.type, + u"type": _validators.type_draft6, u"uniqueItems": _validators.uniqueItems, }, version="draft6", + scope_key="$id", ) @@ -356,7 +358,7 @@ def __init__( self._remote_cache = remote_cache @classmethod - def from_schema(cls, schema, *args, **kwargs): + def from_schema(cls, schema, scope_key="id", *args, **kwargs): """ Construct a resolver from a JSON schema object. @@ -366,13 +368,17 @@ def from_schema(cls, schema, *args, **kwargs): the referring schema + scope_key: + + the attribute used to adjust the scope + Returns: :class:`RefResolver` """ - return cls(schema.get(u"$id", u""), schema, *args, **kwargs) + return cls(schema.get(scope_key, u""), schema, *args, **kwargs) def push_scope(self, scope): self._scopes_stack.append( @@ -458,7 +464,6 @@ def resolve_fragment(self, document, fragment): a URI fragment to resolve within it """ - fragment = fragment.lstrip(u"/") parts = unquote(fragment).split(u"/") if fragment else [] diff --git a/setup.py b/setup.py index d1ea21990..a1af394c1 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ "jsonpointer>1.13", "rfc3987", "strict-rfc3339", - "uritemplate>3.0.0", + "uritemplate>=3.0.0", "webcolors", ], ":python_version=='2.7'": ["functools32"],