├── doc ├── usage.rst ├── reference │ ├── misc.rst │ ├── errors.rst │ ├── schema.rst │ ├── shortcuts.rst │ └── validator.rst ├── reference.rst ├── installation.rst ├── hacking.rst ├── index.rst ├── changes.rst └── conf.py ├── MANIFEST.in ├── requirements └── versiontools.txt ├── .gitignore ├── setup.cfg ├── .travis.yml ├── .bzrignore ├── tox.ini ├── json_schema_validator ├── __init__.py ├── misc.py ├── tests │ ├── test_extensions.py │ ├── __init__.py │ ├── test_schema.py │ └── test_validator.py ├── errors.py ├── shortcuts.py ├── extensions.py ├── schema.py └── validator.py ├── README.md ├── setup.py ├── COPYING └── schema-spec └── draft-zyp-json-schema-03.txt /doc/usage.rst: -------------------------------------------------------------------------------- 1 | Usage 2 | ***** 3 | 4 | TODO 5 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include doc/conf.py 2 | recursive-include doc *.rst 3 | -------------------------------------------------------------------------------- /requirements/versiontools.txt: -------------------------------------------------------------------------------- 1 | versiontools >= 1.8.2 2 | GitPython >= 0.1.6 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg 2 | *.egg-info 3 | *.pyc 4 | *.swp 5 | .tox 6 | build 7 | dist 8 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [upload_docs] 2 | upload-dir=build/sphinx/html 3 | 4 | [upload] 5 | sign=True 6 | -------------------------------------------------------------------------------- /doc/reference/misc.rst: -------------------------------------------------------------------------------- 1 | Misc module 2 | ^^^^^^^^^^^ 3 | 4 | .. automodule:: json_schema_validator.misc 5 | :members: 6 | -------------------------------------------------------------------------------- /doc/reference/errors.rst: -------------------------------------------------------------------------------- 1 | Errors module 2 | ^^^^^^^^^^^^^ 3 | 4 | .. automodule:: json_schema_validator.errors 5 | :members: 6 | -------------------------------------------------------------------------------- /doc/reference/schema.rst: -------------------------------------------------------------------------------- 1 | Schema module 2 | ^^^^^^^^^^^^^ 3 | 4 | .. automodule:: json_schema_validator.schema 5 | :members: 6 | -------------------------------------------------------------------------------- /doc/reference/shortcuts.rst: -------------------------------------------------------------------------------- 1 | Shortcuts module 2 | ^^^^^^^^^^^^^^^^ 3 | 4 | .. automodule:: json_schema_validator.shortcuts 5 | :members: 6 | -------------------------------------------------------------------------------- /doc/reference/validator.rst: -------------------------------------------------------------------------------- 1 | Validator module 2 | ^^^^^^^^^^^^^^^^ 3 | 4 | .. automodule:: json_schema_validator.validator 5 | :members: 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.6" 4 | - "2.7" 5 | - "3.3" 6 | - "3.4" 7 | - "3.5" 8 | - "nightly" 9 | - "pypy" 10 | - "pypy3" 11 | script: python setup.py test 12 | -------------------------------------------------------------------------------- /.bzrignore: -------------------------------------------------------------------------------- 1 | .coverage 2 | dashboard_server/local_settings.py 3 | dashboard_server/database.db 4 | dashboard_server/media/attachments/ 5 | dashboard_server/media/bundles/ 6 | *.egg-info 7 | *.diff 8 | build 9 | dist 10 | *.egg 11 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist=py26,py27,py33,py34,py35 3 | 4 | [testenv] 5 | commands= 6 | unit2 discover [] 7 | sphinx-build -b doctest doc html 8 | sphinx-build doc html 9 | deps= 10 | PyYAML 11 | sphinx 12 | testscenarios 13 | testtools 14 | unittest2 15 | versiontools 16 | -------------------------------------------------------------------------------- /doc/reference.rst: -------------------------------------------------------------------------------- 1 | Code documentation 2 | ****************** 3 | 4 | .. automodule:: json_schema_validator 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | 9 | reference/errors.rst 10 | reference/misc.rst 11 | reference/schema.rst 12 | reference/shortcuts.rst 13 | reference/validator.rst 14 | -------------------------------------------------------------------------------- /doc/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | Prerequisites 5 | ^^^^^^^^^^^^^ 6 | 7 | This package has the following prerequisites: 8 | 9 | * versiontools 10 | 11 | To run the test suite you will also need: 12 | 13 | * testtools 14 | * testscenarios 15 | 16 | To build the documentation from source you will also need: 17 | 18 | * sphinx 19 | 20 | Installation Options 21 | ^^^^^^^^^^^^^^^^^^^^ 22 | 23 | This package is being actively maintained and published in the `Python Package 24 | Index `_. You can install it if you have `pip 25 | `_ tool using just one line:: 26 | 27 | pip install json-schema-validator 28 | -------------------------------------------------------------------------------- /json_schema_validator/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2010, 2011 Linaro Limited 2 | # Copyright (C) 2016 Zygmunt Krynicki 3 | # 4 | # Author: Zygmunt Krynicki 5 | # 6 | # This file is part of json-schema-validator. 7 | # 8 | # json-schema-validator is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU Lesser General Public License version 3 10 | # as published by the Free Software Foundation 11 | # 12 | # json-schema-validator is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU Lesser General Public License 18 | # along with json-schema-validator. If not, see . 19 | 20 | """JSON Schema Validator.""" 21 | 22 | __version__ = (2, 4, 0, "final", 0) 23 | -------------------------------------------------------------------------------- /json_schema_validator/misc.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2010, 2011 Linaro Limited 2 | # Copyright (C) 2016 Zygmunt Krynicki 3 | # 4 | # Author: Zygmunt Krynicki 5 | # 6 | # This file is part of json-schema-validator. 7 | # 8 | # json-schema-validator is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU Lesser General Public License version 3 10 | # as published by the Free Software Foundation 11 | # 12 | # json-schema-validator is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU Lesser General Public License 18 | # along with json-schema-validator. If not, see . 19 | 20 | """Stuff that does not belong anywhere else.""" 21 | 22 | import decimal 23 | 24 | 25 | # List of types recognized as numeric 26 | NUMERIC_TYPES = (int, float, decimal.Decimal) 27 | -------------------------------------------------------------------------------- /doc/hacking.rst: -------------------------------------------------------------------------------- 1 | 2 | Hacking 3 | ******* 4 | 5 | The project is hosted on github 6 | (http://github.com/zyga/json-schema-validator/), feel free to fork it and 7 | propose a pull request. 8 | 9 | Goals 10 | ----- 11 | 12 | The goal of this project is to construct a complete and fast implementation of the 13 | JSON Schema as defined by http://json-schema.org/. 14 | 15 | JSON is powerful because of the simplicity. Unlike the baroque YAML it thrives 16 | on being easy to implement in any language, correctly, completely, with 17 | confidence and test. Python has a good built-in implementation of the 18 | serializer (decoder and encoder) but lacks more advanced tools. Working with 19 | JSON as a backend for your application, whether it's configuration, application 20 | data or envelope for your RPC system, almost always requires data validation 21 | and integrity checking. 22 | 23 | Infrastructure 24 | -------------- 25 | 26 | Github is used for: 27 | 28 | * Hosting source code (in git) 29 | * Reporting and tracking bugs 30 | 31 | Launchpad.net is used for: 32 | 33 | * Hosting source code (as bzr mirror) 34 | * Packaging aid for Ubuntu 35 | 36 | PyPi is used for: 37 | 38 | * Hosting released tarballs 39 | * Hosting built documentation 40 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | JSON Schema Validator 2 | ********************* 3 | 4 | About 5 | ===== 6 | 7 | This package contains an implementation of JSON Schema validator. 8 | 9 | .. note:: 10 | This project is just getting started. While the code relatively 11 | feature-complete, rather well tested and used in production daily the 12 | *documentation* is lacking. 13 | 14 | .. warning:: 15 | This implementation was based on the *second draft* of the specification 16 | A third draft was published on the 22nd Nov 2010. This draft introduced 17 | several important changes that are not yet implemented. 18 | 19 | .. note:: 20 | Only a subset of schema features are currently supported. 21 | Unsupported features are detected and raise a NotImplementedError 22 | when you call :func:`json_schema_validator.schema.Validator.validate` 23 | 24 | .. seealso:: 25 | http://json-schema.org/ for details about the schema 26 | 27 | Table of contents 28 | ================= 29 | 30 | .. toctree:: 31 | :maxdepth: 2 32 | 33 | installation.rst 34 | usage.rst 35 | reference.rst 36 | changes.rst 37 | hacking.rst 38 | 39 | 40 | Indices and tables 41 | ================== 42 | 43 | * :ref:`genindex` 44 | * :ref:`modindex` 45 | * :ref:`search` 46 | 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/zyga/json-schema-validator.svg?branch=master)](https://travis-ci.org/zyga/json-schema-validator) 2 | 3 | About 4 | ===== 5 | 6 | This package contains an implementation of JSON Schema validator as defined by 7 | http://json-schema.org/ 8 | 9 | Installation 10 | ============ 11 | 12 | $ pip install json-schema-validator 13 | 14 | Testing 15 | ======= 16 | 17 | You will need tox (get it from pip) as python2.7 18 | 19 | $ tox 20 | 21 | Generating Documentation 22 | ======================== 23 | 24 | Python package 'Sphinx' can be used to generate documentation for the module, giving details as to the mechanics of each module component, their subsequent classes and functions. To install sphinx use: 25 | `pip3 install sphinx` 26 | 27 | If you don't already have it, sphinx requires the 'versiontools' package which is not included in the install for sphinx itself. Install this with: 28 | `pip3 install versiontools` 29 | 30 | Now you can generate documentation. Navigate to the root of your json_schema_validator module directory (you can find this path by using: `pip3 show json_schema_validator`, the path will be under 'location in the output). From your shell (not the python interpreter) type `sphinx-build -html doc [dest]` (where [dest] is a directory name you would like to build your documentation to) 31 | 32 | Once your documentation is built you will need to host it to see it. `cd` into the directory you used as '[dest]' and use `pydoc -p 8080`. Open your browser and type `pydoc -p 8080` 33 | 34 | 35 | -------------------------------------------------------------------------------- /json_schema_validator/tests/test_extensions.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2010, 2011 Linaro Limited 2 | # Copyright (C) 2016 Zygmunt Krynicki 3 | # 4 | # Author: Zygmunt Krynicki 5 | # 6 | # This file is part of json-schema-validator. 7 | # 8 | # json-schema-validator is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU Lesser General Public License version 3 10 | # as published by the Free Software Foundation 11 | # 12 | # json-schema-validator is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU Lesser General Public License 18 | # along with json-schema-validator. If not, see . 19 | 20 | """ 21 | Unit tests for JSON extensions 22 | """ 23 | 24 | from testtools import TestCase 25 | from datetime import datetime, timedelta 26 | 27 | from json_schema_validator.extensions import datetime_extension, timedelta_extension 28 | 29 | 30 | class ExtensionTests(object): 31 | 32 | def test_to_json(self): 33 | text = self.extension.to_json(self.reference_obj) 34 | self.assertEqual(text, self.reference_text) 35 | 36 | def test_from_json(self): 37 | obj = self.extension.from_json(self.reference_text) 38 | self.assertEqual(obj, self.reference_obj) 39 | 40 | 41 | class DatetimeExtensionTests(TestCase, ExtensionTests): 42 | 43 | reference_obj = datetime(2010, 12, 7, 23, 59, 58) 44 | reference_text = "2010-12-07T23:59:58Z" 45 | extension = datetime_extension 46 | 47 | 48 | class TimedeltaExtensionTests(TestCase, ExtensionTests): 49 | 50 | reference_obj = timedelta(days=1, seconds=2, microseconds=3) 51 | reference_text = "1d 2s 3us" 52 | extension = timedelta_extension 53 | -------------------------------------------------------------------------------- /doc/changes.rst: -------------------------------------------------------------------------------- 1 | Version History 2 | *************** 3 | 4 | Version 2.4 5 | =========== 6 | 7 | * Python 3 support (Thanks to John Vandenberg) 8 | * Small documentation tweaks. 9 | 10 | Version 2.3.1 11 | ============= 12 | 13 | * Maintenance release 14 | 15 | Version 2.3 16 | =========== 17 | 18 | * Add support for minimum, maximum, minLength, maxLength, minItems, maxItems 19 | validators 20 | 21 | Version 2.2 22 | =========== 23 | 24 | * Maintenance release 25 | 26 | Version 2.1 27 | =========== 28 | 29 | * Renamed from linaro-json to json-schema-validator and moved to github. 30 | * Reorganized the package into more sub-modules 31 | * Updated documentation a bit 32 | 33 | Version 2.0.1 34 | ============= 35 | 36 | * Make the package installable via pip by using new versiontools 37 | * Fix test suite to be agnostic to python's rounding of floats 38 | 39 | Version 2.0 40 | =========== 41 | 42 | * New major release, incompatible with past releases 43 | * Drop everything apart from schema validator as other elements have lost their significance 44 | * Tweak requirements to support current Ubuntu LTS (10.04 aka Lucid) 45 | * Refresh installation instructions to point to the new PPA, provide links to 46 | lp.net project page and pypi project page. 47 | 48 | Version 1.2.3 49 | ============= 50 | 51 | * Change how setup.py finds the version of the code to make it pip install okay 52 | when simplejson is not installed yet. 53 | 54 | Version 1.2.2 55 | ============= 56 | 57 | * Fix another problem with pip and versiontools (removed unconditional import 58 | of versiontools from __init__.py) 59 | 60 | Version 1.2.1 61 | ============= 62 | 63 | * Fix installation problem with pip due to versiontools not being available 64 | when parsing initial setup.py 65 | 66 | Version 1.2 67 | =========== 68 | 69 | * Change name to json-schema-validator 70 | * Add dependency on versiontools 71 | * Register on pypi 72 | * Add ReST documentation 73 | 74 | 75 | Version 1.1 76 | =========== 77 | 78 | * Add support for retrieving default values from the schema 79 | 80 | 81 | Version 1.0 82 | =========== 83 | 84 | * Initial release 85 | -------------------------------------------------------------------------------- /json_schema_validator/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2010, 2011 Linaro Limited 2 | # Copyright (C) 2016 Zygmunt Krynicki 3 | # 4 | # Author: Zygmunt Krynicki 5 | # 6 | # This file is part of json-schema-validator. 7 | # 8 | # json-schema-validator is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU Lesser General Public License version 3 10 | # as published by the Free Software Foundation 11 | # 12 | # json-schema-validator is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU Lesser General Public License 18 | # along with json-schema-validator. If not, see . 19 | 20 | """ 21 | Package with unit tests for json-schema-validator 22 | """ 23 | 24 | import doctest 25 | import unittest 26 | 27 | 28 | def app_modules(): 29 | return [ 30 | 'json_schema_validator', 31 | 'json_schema_validator.errors', 32 | 'json_schema_validator.extensions', 33 | 'json_schema_validator.misc', 34 | 'json_schema_validator.schema', 35 | 'json_schema_validator.shortcuts', 36 | 'json_schema_validator.validator', 37 | ] 38 | 39 | 40 | def test_modules(): 41 | return [ 42 | 'json_schema_validator.tests.test_extensions', 43 | 'json_schema_validator.tests.test_schema', 44 | 'json_schema_validator.tests.test_validator', 45 | ] 46 | 47 | 48 | def test_suite(): 49 | """ 50 | Build an unittest.TestSuite() object with all the tests in _modules. 51 | Each module is harvested for both regular unittests and doctests 52 | """ 53 | modules = app_modules() + test_modules() 54 | suite = unittest.TestSuite() 55 | loader = unittest.TestLoader() 56 | for name in modules: 57 | __import__(name, fromlist=['']) 58 | tests = loader.loadTestsFromName(name) 59 | suite.addTests(tests) 60 | doctests = doctest.DocTestSuite(name) 61 | suite.addTests(doctests) 62 | return suite 63 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (C) 2010, 2011 Linaro Limited 4 | # Copyright (C) 2014, Canonical Ltd. 5 | # 6 | # Author: Zygmunt Krynicki 7 | # 8 | # This file is part of json-schema-validator. 9 | # 10 | # json-schema-validator is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU Lesser General Public License version 3 12 | # as published by the Free Software Foundation 13 | # 14 | # json-schema-validator is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU Lesser General Public License 20 | # along with json-schema-validator. If not, see . 21 | 22 | import sys 23 | 24 | from setuptools import setup, find_packages 25 | 26 | test_dependencies = [ 27 | 'testscenarios >= 0.1', 28 | 'testtools >= 0.9.2' 29 | ] 30 | 31 | if sys.version_info[0] == 2: 32 | test_dependencies.append('PyYaml') 33 | 34 | setup( 35 | name='json-schema-validator', 36 | version=":versiontools:json_schema_validator:__version__", 37 | author="Zygmunt Krynicki", 38 | author_email="me@zygoon.pl", 39 | description="JSON Schema Validator", 40 | packages=find_packages(), 41 | url='https://github.com/zyga/json-schema-validator', 42 | test_suite='json_schema_validator.tests.test_suite', 43 | classifiers=[ 44 | "Development Status :: 4 - Beta", 45 | "Intended Audience :: Developers", 46 | ("License :: OSI Approved :: GNU Library or Lesser General Public" 47 | " License (LGPL)"), 48 | "Operating System :: OS Independent", 49 | "Programming Language :: Python :: 2.6", 50 | "Programming Language :: Python :: 2.7", 51 | "Programming Language :: Python :: 3", 52 | "Programming Language :: Python :: 3.3", 53 | "Programming Language :: Python :: 3.4", 54 | "Programming Language :: Python :: 3.5", 55 | "Programming Language :: Python :: Implementation :: CPython", 56 | "Programming Language :: Python :: Implementation :: PyPy", 57 | ], 58 | setup_requires=[ 59 | 'versiontools >= 1.3.1'], 60 | tests_require=test_dependencies, 61 | zip_safe=True) 62 | -------------------------------------------------------------------------------- /json_schema_validator/errors.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2010, 2011 Linaro Limited 2 | # Copyright (C) 2016 Zygmunt Krynicki 3 | # 4 | # Author: Zygmunt Krynicki 5 | # 6 | # This file is part of json-schema-validator. 7 | # 8 | # json-schema-validator is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU Lesser General Public License version 3 10 | # as published by the Free Software Foundation 11 | # 12 | # json-schema-validator is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU Lesser General Public License 18 | # along with json-schema-validator. If not, see . 19 | 20 | """Error classes used by this package.""" 21 | 22 | 23 | class SchemaError(ValueError): 24 | """Exception raised when there is a problem with the schema itself.""" 25 | 26 | 27 | class ValidationError(ValueError): 28 | """ 29 | Exception raised on mismatch between the validated object and the schema. 30 | 31 | .. attribute:: message 32 | 33 | Old and verbose message that contains less helpful message and lots of 34 | JSON data (deprecated). 35 | 36 | .. attribute:: new_message 37 | 38 | Short and concise message about the problem. 39 | 40 | .. attribute:: object_expr 41 | 42 | A JavaScript expression that evaluates to the object that failed to 43 | validate. The expression always starts with a root object called 44 | ``'object'``. 45 | 46 | .. attribute:: schema_expr 47 | 48 | A JavaScript expression that evaluates to the schema that was checked 49 | at the time validation failed. The expression always starts with a root 50 | object called ``'schema'``. 51 | """ 52 | 53 | def __init__(self, message, new_message=None, 54 | object_expr=None, schema_expr=None): 55 | self.message = message 56 | self.new_message = new_message 57 | self.object_expr = object_expr 58 | self.schema_expr = schema_expr 59 | 60 | def __str__(self): 61 | return ("ValidationError: {0} " 62 | "object_expr={1!r}, " 63 | "schema_expr={2!r})").format( 64 | self.new_message, self.object_expr, 65 | self.schema_expr) 66 | -------------------------------------------------------------------------------- /json_schema_validator/shortcuts.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2010, 2011 Linaro Limited 2 | # Copyright (C) 2016 Zygmunt Krynicki 3 | # 4 | # Author: Zygmunt Krynicki 5 | # 6 | # This file is part of json-schema-validator. 7 | # 8 | # json-schema-validator is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU Lesser General Public License version 3 10 | # as published by the Free Software Foundation 11 | # 12 | # json-schema-validator is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU Lesser General Public License 18 | # along with json-schema-validator. If not, see . 19 | 20 | """One liners that make the code shorter.""" 21 | 22 | try: 23 | import simplejson as json 24 | except ImportError: 25 | import json 26 | 27 | from json_schema_validator.schema import Schema 28 | from json_schema_validator.validator import Validator 29 | 30 | _default_deserializer = json.loads 31 | 32 | 33 | def validate(schema_text, data_text, deserializer=_default_deserializer): 34 | """ 35 | Validate specified JSON text with specified schema. 36 | 37 | Both arguments are converted to JSON objects with :func:`simplejson.loads`, 38 | if present, or :func:`json.loads`. 39 | 40 | :param schema_text: 41 | Text of the JSON schema to check against 42 | :type schema_text: 43 | :class:`str` 44 | :param data_text: 45 | Text of the JSON object to check 46 | :type data_text: 47 | :class:`str` 48 | :param deserializer: 49 | Function to convert the schema and data to JSON objects 50 | :type deserializer: 51 | :class:`callable` 52 | :returns: 53 | Same as :meth:`json_schema_validator.validator.Validator.validate` 54 | :raises: 55 | Whatever may be raised by simplejson (in particular 56 | :class:`simplejson.decoder.JSONDecoderError`, a subclass of 57 | :class:`ValueError`) or json 58 | :raises: 59 | Whatever may be raised by 60 | :meth:`json_schema_validator.validator.Validator.validate`. In particular 61 | :class:`json_schema_validator.errors.ValidationError` and 62 | :class:`json_schema_validator.errors.SchemaError` 63 | """ 64 | schema = Schema(deserializer(schema_text)) 65 | data = deserializer(data_text) 66 | return Validator.validate(schema, data) 67 | -------------------------------------------------------------------------------- /json_schema_validator/extensions.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2010, 2011 Linaro Limited 2 | # Copyright (C) 2016 Zygmunt Krynicki 3 | # 4 | # Author: Zygmunt Krynicki 5 | # 6 | # This file is part of json-schema-validator. 7 | # 8 | # json-schema-validator is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU Lesser General Public License version 3 10 | # as published by the Free Software Foundation 11 | # 12 | # json-schema-validator is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU Lesser General Public License 18 | # along with json-schema-validator. If not, see . 19 | 20 | """ 21 | Date-time extension, allows to serialize and deserialize datetime 22 | objects in a consistent way. Implements equivalent of schema: 23 | 24 | { 25 | "type": "string", 26 | "format": "date-time" 27 | } 28 | """ 29 | 30 | import re 31 | import sys 32 | 33 | from datetime import datetime, timedelta 34 | 35 | if sys.version_info[0] > 2: 36 | basestring = (str, ) 37 | 38 | 39 | class datetime_extension(object): 40 | """ 41 | Proxy class for serializing datetime.datetime objects. 42 | 43 | The serialization is a JSON string. Date is encoded 44 | using the ISO 8601 format: 45 | YYYY-MM-DDThh:mm:ssZ 46 | 47 | That is: 48 | * Four digit year code 49 | * Dash 50 | * Two digit month code 51 | * Dash 52 | * Two digit day code 53 | * Capital letter 'T' - time stamp indicator 54 | * Two digit hour code 55 | * Colon 56 | * Two digit minute code 57 | * Colon 58 | * Two digit seconds code 59 | * Capital letter 'Z' - Zulu (UTC) time zone indicator 60 | """ 61 | 62 | FORMAT = "%Y-%m-%dT%H:%M:%SZ" 63 | 64 | @classmethod 65 | def to_json(cls, obj): 66 | return obj.strftime(cls.FORMAT) 67 | 68 | @classmethod 69 | def from_json(cls, doc): 70 | return datetime.strptime(doc, cls.FORMAT) 71 | 72 | 73 | class timedelta_extension(object): 74 | """ 75 | Proxy for serializing datetime.timedelta instances 76 | """ 77 | PATTERN = re.compile("^(\d+)d (\d+)s (\d+)us$") 78 | 79 | @classmethod 80 | def to_json(cls, obj): 81 | """ 82 | Serialize wrapped datetime.timedelta instance to a string the 83 | with the following format: 84 | [DAYS]d [SECONDS]s [MICROSECONDS]us 85 | """ 86 | return "{0}d {1}s {2}us".format( 87 | obj.days, obj.seconds, obj.microseconds) 88 | 89 | @classmethod 90 | def from_json(cls, doc): 91 | """ 92 | Deserialize JSON document (string) to datetime.timedelta instance 93 | """ 94 | if not isinstance(doc, basestring): 95 | raise TypeError("JSON document must be a string") 96 | match = cls.PATTERN.match(doc) 97 | if not match: 98 | raise ValueError("JSON document must match expected pattern") 99 | days, seconds, microseconds = map(int, match.groups()) 100 | return timedelta(days, seconds, microseconds) 101 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # JSON Schema Validator documentation build configuration file, created by 4 | # sphinx-quickstart on Mon Dec 27 16:39:47 2010. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys 15 | import os 16 | 17 | # If extensions (or modules to document with autodoc) are in another directory, 18 | # add these directories to sys.path here. If the directory is relative to the 19 | # documentation root, use os.path.abspath to make it absolute, like shown here. 20 | sys.path.append(os.path.abspath('..')) 21 | 22 | # -- General configuration ----------------------------------------------------- 23 | 24 | # Add any Sphinx extension module names here, as strings. They can be extensions 25 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 26 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage'] 27 | 28 | # Add any paths that contain templates here, relative to this directory. 29 | templates_path = [] 30 | 31 | # The suffix of source filenames. 32 | source_suffix = '.rst' 33 | 34 | # The encoding of source files. 35 | #source_encoding = 'utf-8' 36 | 37 | # The master toctree document. 38 | master_doc = 'index' 39 | 40 | # General information about the project. 41 | project = u'JSON Schema Validator' 42 | copyright = u'2010, 2011 Linaro Limited; 2016 Zygmunt Krynicki' 43 | 44 | # The version info for the project you're documenting, acts as replacement for 45 | # |version| and |release|, also used in various other places throughout the 46 | # built documents. 47 | # 48 | # The short X.Y version. 49 | import json_schema_validator 50 | import versiontools 51 | version = "%d.%d" % json_schema_validator.__version__[0:2] 52 | # The full version, including alpha/beta/rc tags. 53 | release = versiontools.format_version(json_schema_validator.__version__) 54 | 55 | # The language for content autogenerated by Sphinx. Refer to documentation 56 | # for a list of supported languages. 57 | #language = None 58 | 59 | # There are two options for replacing |today|: either, you set today to some 60 | # non-false value, then it is used: 61 | #today = '' 62 | # Else, today_fmt is used as the format for a strftime call. 63 | #today_fmt = '%B %d, %Y' 64 | 65 | # List of documents that shouldn't be included in the build. 66 | #unused_docs = [] 67 | 68 | # List of directories, relative to source directory, that shouldn't be searched 69 | # for source files. 70 | exclude_trees = [] 71 | 72 | # The reST default role (used for this markup: `text`) to use for all documents. 73 | #default_role = None 74 | 75 | # If true, '()' will be appended to :func: etc. cross-reference text. 76 | #add_function_parentheses = True 77 | 78 | # If true, the current module name will be prepended to all description 79 | # unit titles (such as .. function::). 80 | #add_module_names = True 81 | 82 | # If true, sectionauthor and moduleauthor directives will be shown in the 83 | # output. They are ignored by default. 84 | #show_authors = False 85 | 86 | # The name of the Pygments (syntax highlighting) style to use. 87 | pygments_style = 'sphinx' 88 | 89 | # A list of ignored prefixes for module index sorting. 90 | #modindex_common_prefix = [] 91 | 92 | 93 | # -- Options for HTML output --------------------------------------------------- 94 | 95 | # The theme to use for HTML and HTML Help pages. Major themes that come with 96 | # Sphinx are currently 'default' and 'sphinxdoc'. 97 | html_theme = 'default' 98 | 99 | # Theme options are theme-specific and customize the look and feel of a theme 100 | # further. For a list of options available for each theme, see the 101 | # documentation. 102 | #html_theme_options = {} 103 | 104 | # Add any paths that contain custom themes here, relative to this directory. 105 | #html_theme_path = [] 106 | 107 | # The name for this set of Sphinx documents. If None, it defaults to 108 | # " v documentation". 109 | #html_title = None 110 | 111 | # A shorter title for the navigation bar. Default is the same as html_title. 112 | #html_short_title = None 113 | 114 | # The name of an image file (relative to this directory) to place at the top 115 | # of the sidebar. 116 | #html_logo = None 117 | 118 | # The name of an image file (within the static path) to use as favicon of the 119 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 120 | # pixels large. 121 | #html_favicon = None 122 | 123 | # Add any paths that contain custom static files (such as style sheets) here, 124 | # relative to this directory. They are copied after the builtin static files, 125 | # so a file named "default.css" will overwrite the builtin "default.css". 126 | html_static_path = [] 127 | 128 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 129 | # using the given strftime format. 130 | #html_last_updated_fmt = '%b %d, %Y' 131 | 132 | # If true, SmartyPants will be used to convert quotes and dashes to 133 | # typographically correct entities. 134 | #html_use_smartypants = True 135 | 136 | # Custom sidebar templates, maps document names to template names. 137 | #html_sidebars = {} 138 | 139 | # Additional templates that should be rendered to pages, maps page names to 140 | # template names. 141 | #html_additional_pages = {} 142 | 143 | # If false, no module index is generated. 144 | #html_use_modindex = True 145 | 146 | # If false, no index is generated. 147 | #html_use_index = True 148 | 149 | # If true, the index is split into individual pages for each letter. 150 | #html_split_index = False 151 | 152 | # If true, links to the reST sources are added to the pages. 153 | #html_show_sourcelink = True 154 | 155 | # If true, an OpenSearch description file will be output, and all pages will 156 | # contain a tag referring to it. The value of this option must be the 157 | # base URL from which the finished HTML is served. 158 | #html_use_opensearch = '' 159 | 160 | # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). 161 | #html_file_suffix = '' 162 | 163 | # Output file base name for HTML help builder. 164 | htmlhelp_basename = 'JSONSchemaValidatorDoc' 165 | 166 | 167 | # -- Options for LaTeX output -------------------------------------------------- 168 | 169 | # The paper size ('letter' or 'a4'). 170 | #latex_paper_size = 'letter' 171 | 172 | # The font size ('10pt', '11pt' or '12pt'). 173 | #latex_font_size = '10pt' 174 | 175 | # Grouping the document tree into LaTeX files. List of tuples 176 | # (source start file, target name, title, author, documentclass [howto/manual]). 177 | latex_documents = [ 178 | ('index', 'json-schema-validator.tex', u'JSON Schema Validator Documentation', 179 | u'Zygmunt Krynicki', 'manual'), 180 | ] 181 | 182 | # The name of an image file (relative to this directory) to place at the top of 183 | # the title page. 184 | #latex_logo = None 185 | 186 | # For "manual" documents, if this is true, then toplevel headings are parts, 187 | # not chapters. 188 | #latex_use_parts = False 189 | 190 | # Additional stuff for the LaTeX preamble. 191 | #latex_preamble = '' 192 | 193 | # Documents to append as an appendix to all manuals. 194 | #latex_appendices = [] 195 | 196 | # If false, no module index is generated. 197 | #latex_use_modindex = True 198 | 199 | 200 | # Example configuration for intersphinx: refer to the Python standard library. 201 | intersphinx_mapping = {'http://docs.python.org/': None} 202 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /json_schema_validator/schema.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2010, 2011 Linaro Limited 2 | # Copyright (C) 2016 Zygmunt Krynicki 3 | # 4 | # Author: Zygmunt Krynicki 5 | # 6 | # This file is part of json-schema-validator. 7 | # 8 | # json-schema-validator is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU Lesser General Public License version 3 10 | # as published by the Free Software Foundation 11 | # 12 | # json-schema-validator is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU Lesser General Public License 18 | # along with json-schema-validator. If not, see . 19 | 20 | """Helper module to work with raw JSON Schema.""" 21 | 22 | import re 23 | import sys 24 | 25 | from json_schema_validator.errors import SchemaError 26 | from json_schema_validator.misc import NUMERIC_TYPES 27 | 28 | if sys.version_info[0] > 2: 29 | basestring = (str, ) 30 | 31 | 32 | class Schema(object): 33 | """ 34 | JSON schema object. 35 | 36 | Schema describes aspects of a valid object. Upon validation each object has 37 | an associated schema. Various properties of the object are tested against 38 | rules described by the schema. 39 | """ 40 | 41 | def __init__(self, json_obj): 42 | """ 43 | Initialize a schema with a schema representation. 44 | 45 | :param json_obj: 46 | A JSON object (python dictionary) describing the schema. 47 | """ 48 | if not isinstance(json_obj, dict): 49 | raise SchemaError("Schema definition must be a JSON object") 50 | self._schema = json_obj 51 | 52 | def __repr__(self): 53 | return "Schema({0!r})".format(self._schema) 54 | 55 | @property 56 | def type(self): 57 | """ 58 | Type of a valid object. 59 | 60 | Type may be a JSON type name or a list of such names. Valid JSON type 61 | names are ``string``, ``number``, ``integer``, ``boolean``, ``object``, 62 | ``array``, ``any`` (default). 63 | """ 64 | value = self._schema.get("type", "any") 65 | if not isinstance(value, (basestring, dict, list)): 66 | raise SchemaError( 67 | "type value {0!r} is not a simple type name, nested " 68 | "schema nor a list of those".format(value)) 69 | if isinstance(value, list): 70 | type_list = value 71 | # Union types have to have at least two alternatives 72 | if len(type_list) < 2: 73 | raise SchemaError( 74 | "union type {0!r} is too short".format(value)) 75 | else: 76 | type_list = [value] 77 | seen = set() 78 | for js_type in type_list: 79 | if isinstance(js_type, dict): 80 | # no nested validation here 81 | pass 82 | elif isinstance(js_type, list): 83 | # no nested validation here 84 | pass 85 | else: 86 | if js_type in seen: 87 | raise SchemaError( 88 | ("type value {0!r} contains duplicate element" 89 | " {1!r}").format(value, js_type)) 90 | else: 91 | seen.add(js_type) 92 | if js_type not in ( 93 | "string", "number", "integer", "boolean", "object", 94 | "array", "null", "any"): 95 | raise SchemaError( 96 | "type value {0!r} is not a simple type " 97 | "name".format(js_type)) 98 | return value 99 | 100 | @property 101 | def properties(self): 102 | """Schema for particular properties of the object.""" 103 | value = self._schema.get("properties", {}) 104 | if not isinstance(value, dict): 105 | raise SchemaError( 106 | "properties value {0!r} is not an object".format(value)) 107 | return value 108 | 109 | @property 110 | def items(self): 111 | """ 112 | Schema or a list of schemas describing particular elements of the object. 113 | 114 | A single schema applies to all the elements. Each element of the object 115 | must match that schema. A list of schemas describes particular elements 116 | of the object. 117 | """ 118 | value = self._schema.get("items", {}) 119 | if not isinstance(value, (list, dict)): 120 | raise SchemaError( 121 | "items value {0!r} is neither a list nor an object". 122 | format(value)) 123 | return value 124 | 125 | @property 126 | def optional(self): 127 | """Flag indicating an optional property.""" 128 | value = self._schema.get("optional", False) 129 | if value is not False and value is not True: 130 | raise SchemaError( 131 | "optional value {0!r} is not a boolean".format(value)) 132 | return value 133 | 134 | @property 135 | def additionalProperties(self): 136 | """Schema for all additional properties, or False.""" 137 | value = self._schema.get("additionalProperties", {}) 138 | if not isinstance(value, dict) and value is not False: 139 | raise SchemaError( 140 | "additionalProperties value {0!r} is neither false nor" 141 | " an object".format(value)) 142 | return value 143 | 144 | @property 145 | def requires(self): 146 | """Additional object or objects required by this object.""" 147 | # NOTE: spec says this can also be a list of strings 148 | value = self._schema.get("requires", {}) 149 | if not isinstance(value, (basestring, dict)): 150 | raise SchemaError( 151 | "requires value {0!r} is neither a string nor an" 152 | " object".format(value)) 153 | return value 154 | 155 | @property 156 | def minimum(self): 157 | """Minimum value of the object.""" 158 | value = self._schema.get("minimum", None) 159 | if value is None: 160 | return 161 | if not isinstance(value, NUMERIC_TYPES): 162 | raise SchemaError( 163 | "minimum value {0!r} is not a numeric type".format( 164 | value)) 165 | return value 166 | 167 | @property 168 | def maximum(self): 169 | """Maximum value of the object.""" 170 | value = self._schema.get("maximum", None) 171 | if value is None: 172 | return 173 | if not isinstance(value, NUMERIC_TYPES): 174 | raise SchemaError( 175 | "maximum value {0!r} is not a numeric type".format( 176 | value)) 177 | return value 178 | 179 | @property 180 | def minimumCanEqual(self): 181 | """Flag indicating if maximum value is inclusive or exclusive.""" 182 | if self.minimum is None: 183 | raise SchemaError("minimumCanEqual requires presence of minimum") 184 | value = self._schema.get("minimumCanEqual", True) 185 | if value is not True and value is not False: 186 | raise SchemaError( 187 | "minimumCanEqual value {0!r} is not a boolean".format( 188 | value)) 189 | return value 190 | 191 | @property 192 | def maximumCanEqual(self): 193 | """Flag indicating if the minimum value is inclusive or exclusive.""" 194 | if self.maximum is None: 195 | raise SchemaError("maximumCanEqual requires presence of maximum") 196 | value = self._schema.get("maximumCanEqual", True) 197 | if value is not True and value is not False: 198 | raise SchemaError( 199 | "maximumCanEqual value {0!r} is not a boolean".format( 200 | value)) 201 | return value 202 | 203 | @property 204 | def minItems(self): 205 | """Minimum number of items in the collection.""" 206 | value = self._schema.get("minItems", 0) 207 | if not isinstance(value, int): 208 | raise SchemaError( 209 | "minItems value {0!r} is not an integer".format(value)) 210 | if value < 0: 211 | raise SchemaError( 212 | "minItems value {0!r} cannot be negative".format(value)) 213 | return value 214 | 215 | @property 216 | def maxItems(self): 217 | """Maximum number of items in the collection.""" 218 | value = self._schema.get("maxItems", None) 219 | if value is None: 220 | return 221 | if not isinstance(value, int): 222 | raise SchemaError( 223 | "maxItems value {0!r} is not an integer".format(value)) 224 | return value 225 | 226 | @property 227 | def uniqueItems(self): 228 | """Flag indicating that valid is a collection without duplicates.""" 229 | value = self._schema.get("uniqueItems", False) 230 | if value is not True and value is not False: 231 | raise SchemaError( 232 | "uniqueItems value {0!r} is not a boolean".format(value)) 233 | return value 234 | 235 | @property 236 | def pattern(self): 237 | """ 238 | Regular expression describing valid objects. 239 | 240 | .. note:: 241 | JSON schema specifications says that this value SHOULD 242 | follow the ``EMCA 262/Perl 5`` format. We cannot support 243 | this so we support python regular expressions instead. This 244 | is still valid but should be noted for clarity. 245 | 246 | :returns: 247 | None or compiled regular expression 248 | """ 249 | value = self._schema.get("pattern", None) 250 | if value is None: 251 | return 252 | try: 253 | return re.compile(value) 254 | except re.error as ex: 255 | raise SchemaError( 256 | "pattern value {0!r} is not a valid regular expression:" 257 | " {1}".format(value, str(ex))) 258 | 259 | @property 260 | def minLength(self): 261 | """Minimum length of object.""" 262 | value = self._schema.get("minLength", 0) 263 | if not isinstance(value, int): 264 | raise SchemaError( 265 | "minLength value {0!r} is not an integer".format(value)) 266 | if value < 0: 267 | raise SchemaError( 268 | "minLength value {0!r} cannot be negative".format(value)) 269 | return value 270 | 271 | @property 272 | def maxLength(self): 273 | """Maximum length of object.""" 274 | value = self._schema.get("maxLength", None) 275 | if value is None: 276 | return 277 | if not isinstance(value, int): 278 | raise SchemaError( 279 | "maxLength value {0!r} is not an integer".format(value)) 280 | return value 281 | 282 | @property 283 | def enum(self): 284 | """ 285 | Enumeration of allowed object values. 286 | 287 | The enumeration must not contain duplicates. 288 | """ 289 | value = self._schema.get("enum", None) 290 | if value is None: 291 | return 292 | if not isinstance(value, list): 293 | raise SchemaError( 294 | "enum value {0!r} is not a list".format(value)) 295 | if len(value) == 0: 296 | raise SchemaError( 297 | "enum value {0!r} does not contain any" 298 | " elements".format(value)) 299 | seen = set() 300 | for item in value: 301 | if item in seen: 302 | raise SchemaError( 303 | "enum value {0!r} contains duplicate element" 304 | " {1!r}".format(value, item)) 305 | else: 306 | seen.add(item) 307 | return value 308 | 309 | @property 310 | def title(self): 311 | """ 312 | Title of the object. 313 | 314 | This schema element is purely informative. 315 | """ 316 | value = self._schema.get("title", None) 317 | if value is None: 318 | return 319 | if not isinstance(value, basestring): 320 | raise SchemaError( 321 | "title value {0!r} is not a string".format(value)) 322 | return value 323 | 324 | @property 325 | def description(self): 326 | """ 327 | Description of the object. 328 | 329 | This schema element is purely informative. 330 | """ 331 | value = self._schema.get("description", None) 332 | if value is None: 333 | return 334 | if not isinstance(value, basestring): 335 | raise SchemaError( 336 | "description value {0!r} is not a string".format(value)) 337 | return value 338 | 339 | @property 340 | def format(self): 341 | """Format of the (string) object.""" 342 | value = self._schema.get("format", None) 343 | if value is None: 344 | return 345 | if not isinstance(value, basestring): 346 | raise SchemaError( 347 | "format value {0!r} is not a string".format(value)) 348 | if value in [ 349 | 'date-time', 350 | 'regex', 351 | ]: 352 | return value 353 | raise NotImplementedError( 354 | "format value {0!r} is not supported".format(value)) 355 | 356 | @property 357 | def contentEncoding(self): 358 | value = self._schema.get("contentEncoding", None) 359 | if value is None: 360 | return 361 | if value.lower() not in [ 362 | "7bit", "8bit", "binary", "quoted-printable", "base64", 363 | "ietf-token", "x-token"]: 364 | raise SchemaError( 365 | "contentEncoding value {0!r} is not" 366 | " valid".format(value)) 367 | if value.lower() != "base64": 368 | raise NotImplementedError( 369 | "contentEncoding value {0!r} is not supported".format( 370 | value)) 371 | return value 372 | 373 | @property 374 | def divisibleBy(self): 375 | """Integer that divides the object without reminder.""" 376 | value = self._schema.get("divisibleBy", 1) 377 | if value is None: 378 | return 379 | if not isinstance(value, NUMERIC_TYPES): 380 | raise SchemaError( 381 | "divisibleBy value {0!r} is not a numeric type". 382 | format(value)) 383 | if value < 0: 384 | raise SchemaError( 385 | "divisibleBy value {0!r} cannot be" 386 | " negative".format(value)) 387 | return value 388 | 389 | @property 390 | def disallow(self): 391 | """ 392 | Description of disallowed objects. 393 | 394 | Disallow must be a type name, a nested schema or a list of those. Type 395 | name must be one of ``string``, ``number``, ``integer``, ``boolean``, 396 | ``object``, ``array``, ``null`` or ``any``. 397 | """ 398 | value = self._schema.get("disallow", None) 399 | if value is None: 400 | return 401 | if not isinstance(value, (basestring, dict, list)): 402 | raise SchemaError( 403 | "disallow value {0!r} is not a simple type name, nested " 404 | "schema nor a list of those".format(value)) 405 | if isinstance(value, list): 406 | disallow_list = value 407 | else: 408 | disallow_list = [value] 409 | seen = set() 410 | for js_disallow in disallow_list: 411 | if isinstance(js_disallow, dict): 412 | # no nested validation here 413 | pass 414 | else: 415 | if js_disallow in seen: 416 | raise SchemaError( 417 | "disallow value {0!r} contains duplicate element" 418 | " {1!r}".format(value, js_disallow)) 419 | else: 420 | seen.add(js_disallow) 421 | if js_disallow not in ( 422 | "string", "number", "integer", "boolean", "object", 423 | "array", "null", "any"): 424 | raise SchemaError( 425 | "disallow value {0!r} is not a simple type" 426 | " name".format(js_disallow)) 427 | return disallow_list 428 | 429 | @property 430 | def extends(self): 431 | raise NotImplementedError("extends property is not supported") 432 | 433 | @property 434 | def default(self): 435 | """Default value for an object.""" 436 | try: 437 | return self._schema["default"] 438 | except KeyError: 439 | raise SchemaError("There is no schema default for this item") 440 | -------------------------------------------------------------------------------- /json_schema_validator/validator.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2010, 2011 Linaro Limited 2 | # Copyright (C) 2016 Zygmunt Krynicki 3 | # 4 | # Author: Zygmunt Krynicki 5 | # 6 | # This file is part of json-schema-validator. 7 | # 8 | # json-schema-validator is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU Lesser General Public License version 3 10 | # as published by the Free Software Foundation 11 | # 12 | # json-schema-validator is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU Lesser General Public License 18 | # along with json-schema-validator. If not, see . 19 | 20 | """Validator implementation.""" 21 | 22 | import re 23 | import datetime 24 | import itertools 25 | import types 26 | import sys 27 | 28 | from json_schema_validator.errors import ValidationError 29 | from json_schema_validator.misc import NUMERIC_TYPES 30 | from json_schema_validator.schema import Schema 31 | 32 | if sys.version_info[0] > 2: 33 | basestring = (str, ) 34 | zip_longest = itertools.zip_longest 35 | else: 36 | zip_longest = itertools.izip_longest 37 | 38 | 39 | class Validator(object): 40 | """ 41 | JSON Schema validator. 42 | 43 | Can be used to validate any JSON document against a 44 | :class:`json_schema_validator.schema.Schema`. 45 | """ 46 | 47 | JSON_TYPE_MAP = { 48 | "string": basestring, 49 | "number": NUMERIC_TYPES, 50 | "integer": int, 51 | "object": dict, 52 | "array": list, 53 | "null": None.__class__, 54 | } 55 | 56 | def __init__(self): 57 | self._schema_stack = [] 58 | self._object_stack = [] 59 | 60 | def _push_object(self, obj, path): 61 | self._object_stack.append((obj, path)) 62 | 63 | def _pop_object(self): 64 | self._object_stack.pop() 65 | 66 | def _push_schema(self, schema, path): 67 | self._schema_stack.append((schema, path)) 68 | 69 | def _pop_schema(self): 70 | self._schema_stack.pop() 71 | 72 | @property 73 | def _object(self): 74 | return self._object_stack[-1][0] 75 | 76 | @property 77 | def _schema(self): 78 | return self._schema_stack[-1][0] 79 | 80 | @classmethod 81 | def validate(cls, schema, obj): 82 | """ 83 | Validate specified JSON object obj with specified schema. 84 | 85 | :param schema: 86 | Schema to validate against 87 | :type schema: 88 | :class:`json_schema_validator.schema.Schema` 89 | :param obj: 90 | JSON object to validate 91 | :rtype: 92 | bool 93 | :returns: 94 | True on success 95 | :raises `json_schema_validator.errors.ValidationError`: 96 | if the object does not match schema. 97 | :raises `json_schema_validator.errors.SchemaError`: 98 | if the schema itself is wrong. 99 | """ 100 | if not isinstance(schema, Schema): 101 | raise ValueError( 102 | "schema value {0!r} is not a Schema" 103 | " object".format(schema)) 104 | self = cls() 105 | self.validate_toplevel(schema, obj) 106 | return True 107 | 108 | def _get_object_expression(self): 109 | return "".join(map(lambda x: x[1], self._object_stack)) 110 | 111 | def _get_schema_expression(self): 112 | return "".join(map(lambda x: x[1], self._schema_stack)) 113 | 114 | def validate_toplevel(self, schema, obj): 115 | self._object_stack = [] 116 | self._schema_stack = [] 117 | self._push_schema(schema, "schema") 118 | self._push_object(obj, "object") 119 | self._validate() 120 | self._pop_schema() 121 | self._pop_object() 122 | 123 | def _validate(self): 124 | obj = self._object 125 | self._validate_type() 126 | self._validate_requires() 127 | if isinstance(obj, dict): 128 | self._validate_properties() 129 | self._validate_additional_properties() 130 | elif isinstance(obj, list): 131 | self._validate_items() 132 | else: 133 | self._validate_enum() 134 | self._validate_format() 135 | self._validate_pattern() 136 | if isinstance(obj, basestring): 137 | self._validate_length() 138 | elif isinstance(obj, NUMERIC_TYPES): 139 | self._validate_range() 140 | self._report_unsupported() 141 | 142 | def _report_error(self, legacy_message, new_message=None, 143 | schema_suffix=None): 144 | """ 145 | Report an error during validation. 146 | 147 | There are two error messages. The legacy message is used for backwards 148 | compatibility and usually contains the object (possibly very large) 149 | that failed to validate. The new message is much better as it contains 150 | just a short message on what went wrong. User code can inspect 151 | object_expr and schema_expr to see which part of the object failed to 152 | validate against which part of the schema. 153 | 154 | The schema_suffix, if provided, is appended to the schema_expr. This 155 | is quite handy to specify the bit that the validator looked at (such as 156 | the type or optional flag, etc). object_suffix serves the same purpose 157 | but is used for object expressions instead. 158 | """ 159 | object_expr = self._get_object_expression() 160 | schema_expr = self._get_schema_expression() 161 | if schema_suffix: 162 | schema_expr += schema_suffix 163 | raise ValidationError(legacy_message, new_message, object_expr, 164 | schema_expr) 165 | 166 | def _push_property_schema(self, prop): 167 | """Construct a sub-schema from a property of the current schema.""" 168 | schema = Schema(self._schema.properties[prop]) 169 | self._push_schema(schema, ".properties." + prop) 170 | 171 | def _push_additional_property_schema(self): 172 | schema = Schema(self._schema.additionalProperties) 173 | self._push_schema(schema, ".additionalProperties") 174 | 175 | def _push_array_schema(self): 176 | schema = Schema(self._schema.items) 177 | self._push_schema(schema, ".items") 178 | 179 | def _push_array_item_object(self, index): 180 | self._push_object(self._object[index], "[%d]" % index) 181 | 182 | def _push_property_object(self, prop): 183 | self._push_object(self._object[prop], "." + prop) 184 | 185 | def _report_unsupported(self): 186 | schema = self._schema 187 | if schema.contentEncoding is not None: 188 | raise NotImplementedError("contentEncoding is not supported") 189 | if schema.divisibleBy != 1: 190 | raise NotImplementedError("divisibleBy is not supported") 191 | if schema.disallow is not None: 192 | raise NotImplementedError("disallow is not supported") 193 | 194 | def _validate_type(self): 195 | schema = self._schema 196 | json_type = schema.type 197 | if json_type == "any": 198 | return 199 | obj = self._object 200 | if json_type == "boolean": 201 | # Bool is special cased because in python there is no 202 | # way to test for isinstance(something, bool) that would 203 | # not catch isinstance(1, bool) :/ 204 | if obj is not True and obj is not False: 205 | self._report_error( 206 | "{obj!r} does not match type {type!r}".format( 207 | obj=obj, type=json_type), 208 | "Object has incorrect type (expected boolean)", 209 | schema_suffix=".type") 210 | elif isinstance(json_type, dict): 211 | # Nested type check. This is pretty odd case. Here we 212 | # don't change our object stack (it's the same object). 213 | self._push_schema(Schema(json_type), ".type") 214 | self._validate() 215 | self._pop_schema() 216 | elif isinstance(json_type, list): 217 | # Alternative type check, here we may match _any_ of the types 218 | # in the list to be considered valid. 219 | json_type_list = json_type 220 | if json_type == []: 221 | return 222 | for index, json_type in enumerate(json_type_list): 223 | # Aww, ugly. The level of packaging around Schema is annoying 224 | self._push_schema( 225 | Schema({'type': json_type}), 226 | ".type.%d" % index) 227 | try: 228 | self._validate() 229 | except ValidationError: 230 | # Ignore errors, we just want one thing to match 231 | pass 232 | else: 233 | # We've got a match - break the loop 234 | break 235 | finally: 236 | # Pop the schema regardless of match/mismatch 237 | self._pop_schema() 238 | else: 239 | # We were not interupted (no break) so we did not match 240 | self._report_error( 241 | "{obj!r} does not match any of the types in {type!r}".format( 242 | obj=obj, type=json_type_list), 243 | "Object has incorrect type (multiple types possible)", 244 | schema_suffix=".type") 245 | else: 246 | # Simple type check 247 | if not isinstance(obj, self.JSON_TYPE_MAP[json_type]): 248 | self._report_error( 249 | "{obj!r} does not match type {type!r}".format( 250 | obj=obj, type=json_type), 251 | "Object has incorrect type (expected {type})".format( 252 | type=json_type), 253 | schema_suffix=".type") 254 | 255 | def _validate_pattern(self): 256 | ptn = self._schema.pattern 257 | obj = self._object 258 | 259 | if ptn is None: 260 | return 261 | if not isinstance(obj, basestring): 262 | return 263 | if re.match(ptn, obj): 264 | return 265 | 266 | self._report_error( 267 | "{obj!r} does not match pattern {ptn!r}".format( 268 | obj=obj,ptn=ptn), 269 | "Object does not match pattern (expected {ptn})".format( 270 | ptn=ptn), 271 | schema_suffix=".pattern" 272 | ) 273 | 274 | def _validate_format(self): 275 | fmt = self._schema.format 276 | obj = self._object 277 | if fmt is None: 278 | return 279 | if fmt == 'date-time': 280 | try: 281 | DATE_TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ" 282 | datetime.datetime.strptime(obj, DATE_TIME_FORMAT) 283 | except ValueError: 284 | self._report_error( 285 | "{obj!r} is not a string representing JSON date-time".format( 286 | obj=obj), 287 | "Object is not a string representing JSON date-time", 288 | schema_suffix=".format") 289 | elif fmt == 'regex': 290 | try: 291 | re.compile(obj) 292 | except: 293 | self._report_error( 294 | "{obj!r} is not a string representing a regex".format( 295 | obj=obj), 296 | "Object is not a string representing a regex", 297 | schema_suffix=".format") 298 | else: 299 | raise NotImplementedError("format {0!r} is not supported".format(fmt)) 300 | 301 | def _validate_properties(self): 302 | obj = self._object 303 | schema = self._schema 304 | assert isinstance(obj, dict) 305 | for prop in schema.properties.keys(): 306 | self._push_property_schema(prop) 307 | if prop in obj: 308 | self._push_property_object(prop) 309 | self._validate() 310 | self._pop_object() 311 | else: 312 | if not self._schema.optional: 313 | self._report_error( 314 | "{obj!r} does not have property {prop!r}".format( 315 | obj=obj, prop=prop), 316 | "Object lacks property {prop!r}".format( 317 | prop=prop), 318 | schema_suffix=".optional") 319 | self._pop_schema() 320 | 321 | def _validate_additional_properties(self): 322 | obj = self._object 323 | assert isinstance(obj, dict) 324 | if self._schema.additionalProperties is False: 325 | # Additional properties are disallowed 326 | # Report exception for each unknown property 327 | for prop in obj.keys(): 328 | if prop not in self._schema.properties: 329 | self._report_error( 330 | "{obj!r} has unknown property {prop!r} and" 331 | " additionalProperties is false".format( 332 | obj=obj, prop=prop), 333 | "Object has unknown property {prop!r} but" 334 | " additional properties are disallowed".format( 335 | prop=prop), 336 | schema_suffix=".additionalProperties") 337 | else: 338 | # Check each property against this object 339 | self._push_additional_property_schema() 340 | for prop in obj.keys(): 341 | self._push_property_object(prop) 342 | self._validate() 343 | self._pop_object() 344 | self._pop_schema() 345 | 346 | def _validate_enum(self): 347 | obj = self._object 348 | schema = self._schema 349 | if schema.enum is not None: 350 | for allowed_value in schema.enum: 351 | if obj == allowed_value: 352 | break 353 | else: 354 | self._report_error( 355 | "{obj!r} does not match any value in enumeration" 356 | " {enum!r}".format(obj=obj, enum=schema.enum), 357 | "Object does not match any value in enumeration", 358 | schema_suffix=".enum") 359 | 360 | def _validate_length(self): 361 | obj = self._object 362 | schema = self._schema 363 | if schema.minLength is not None: 364 | if len(obj) < schema.minLength: 365 | self._report_error( 366 | "{obj!r} does not meet the minimum length" 367 | " {minLength!r}".format(obj=obj, minLength=schema.minLength), 368 | "Object does not meet the minimum length", 369 | schema_suffix=".minLength") 370 | if schema.maxLength is not None: 371 | if len(obj) > schema.maxLength: 372 | self._report_error( 373 | "{obj!r} exceeds the maximum length" 374 | " {maxLength!r}".format(obj=obj, maxLength=schema.maxLength), 375 | "Object exceeds the maximum length", 376 | schema_suffix=".maxLength") 377 | 378 | def _validate_range(self): 379 | obj = self._object 380 | schema = self._schema 381 | if schema.minimum is not None: 382 | if obj < schema.minimum or (obj == schema.minimum and not schema.minimumCanEqual): 383 | self._report_error( 384 | "{obj!r} is less than the minimum" 385 | " {minimum!r}".format(obj=obj, minimum=schema.minimum), 386 | "Object is less than the minimum", 387 | schema_suffix=".minimum") 388 | if schema.maximum is not None: 389 | if obj > schema.maximum or (obj == schema.maximum and not schema.maximumCanEqual): 390 | self._report_error( 391 | "{obj!r} is greater than the maximum" 392 | " {maximum!r}".format(obj=obj, maximum=schema.maximum), 393 | "Object is greater than the maximum", 394 | schema_suffix=".maximum") 395 | 396 | def _validate_items(self): 397 | obj = self._object 398 | schema = self._schema 399 | assert isinstance(obj, list) 400 | items_schema_json = schema.items 401 | if items_schema_json == {}: 402 | # default value, don't do anything 403 | return 404 | if isinstance(obj, list) and schema.uniqueItems is True and len(set(obj)) != len(obj): 405 | # If we want a list of unique items and the length of unique 406 | # elements is different from the length of the full list 407 | # then validation fails. 408 | # This implementation isn't strictly compatible with the specs, because 409 | # we are not checking unique dicts. 410 | self._report_error( 411 | "Repeated items found in {obj!r}".format(obj=obj), 412 | "Repeated items found in array", 413 | schema_suffix=".items") 414 | if schema.minItems: 415 | if len(obj) < schema.minItems: 416 | self._report_error( 417 | "{obj!r} has fewer than the minimum number of items" 418 | " {minItems!r}".format(obj=obj, minimum=schema.minItems), 419 | "Object has fewer than the minimum number of items", 420 | schema_suffix=".minItems") 421 | if schema.maxItems is not None: 422 | if len(obj) > schema.maxItems: 423 | self._report_error( 424 | "{obj!r} has more than the maximum number of items" 425 | " {maxItems!r}".format(obj=obj, minimum=schema.maxItems), 426 | "Object has more than the maximum number of items", 427 | schema_suffix=".maxItems") 428 | if isinstance(items_schema_json, dict): 429 | self._push_array_schema() 430 | for index, item in enumerate(obj): 431 | self._push_array_item_object(index) 432 | self._validate() 433 | self._pop_object() 434 | self._pop_schema() 435 | elif isinstance(items_schema_json, list): 436 | if len(obj) < len(items_schema_json): 437 | # If our data array is shorter than the schema then 438 | # validation fails. Longer arrays are okay (during this 439 | # step) as they are validated based on 440 | # additionalProperties schema 441 | self._report_error( 442 | "{obj!r} is shorter than array schema {schema!r}". 443 | format(obj=obj, schema=items_schema_json), 444 | "Object array is shorter than schema array", 445 | schema_suffix=".items") 446 | if len(obj) != len(items_schema_json) and schema.additionalProperties is False: 447 | # If our array is not exactly the same size as the 448 | # schema and additional properties are disallowed then 449 | # validation fails 450 | self._report_error( 451 | "{obj!r} is not of the same length as array schema" 452 | " {schema!r} and additionalProperties is" 453 | " false".format(obj=obj, schema=items_schema_json), 454 | "Object array is not of the same length as schema array", 455 | schema_suffix=".items") 456 | # Validate each array element using schema for the 457 | # corresponding array index, fill missing values (since 458 | # there may be more items in our array than in the schema) 459 | # with additionalProperties which by now is not False 460 | for index, (item, item_schema_json) in enumerate( 461 | zip_longest( 462 | obj, items_schema_json, 463 | fillvalue=schema.additionalProperties)): 464 | item_schema = Schema(item_schema_json) 465 | if index < len(items_schema_json): 466 | self._push_schema(item_schema, "items[%d]" % index) 467 | else: 468 | self._push_schema(item_schema, ".additionalProperties") 469 | self._push_array_item_object(index) 470 | self._validate() 471 | self._pop_schema() 472 | self._pop_object() 473 | 474 | def _validate_requires(self): 475 | obj = self._object 476 | schema = self._schema 477 | requires_json = schema.requires 478 | if requires_json == {}: 479 | # default value, don't do anything 480 | return 481 | # Find our enclosing object in the object stack 482 | if len(self._object_stack) < 2: 483 | self._report_error( 484 | "{obj!r} requires that enclosing object matches" 485 | " schema {schema!r} but there is no enclosing" 486 | " object".format(obj=obj, schema=requires_json), 487 | "Object has no enclosing object that matches schema", 488 | schema_suffix=".requires") 489 | # Note: Parent object can be None, (e.g. a null property) 490 | parent_obj = self._object_stack[-2][0] 491 | if isinstance(requires_json, basestring): 492 | # This is a simple property test 493 | if (not isinstance(parent_obj, dict) 494 | or requires_json not in parent_obj): 495 | self._report_error( 496 | "{obj!r} requires presence of property {requires!r}" 497 | " in the same object".format( 498 | obj=obj, requires=requires_json), 499 | "Enclosing object does not have property" 500 | " {prop!r}".format(prop=requires_json), 501 | schema_suffix=".requires") 502 | elif isinstance(requires_json, dict): 503 | # Requires designates a whole schema, the enclosing object 504 | # must match against that schema. 505 | # Here we resort to a small hack. Proper implementation 506 | # would require us to validate the parent object from its 507 | # own context (own list of parent objects). Since doing that 508 | # and restoring the state would be very complicated we just 509 | # instantiate a new validator with a subset of our current 510 | # history here. 511 | sub_validator = Validator() 512 | sub_validator._object_stack = self._object_stack[:-1] 513 | sub_validator._schema_stack = self._schema_stack[:] 514 | sub_validator._push_schema( 515 | Schema(requires_json), ".requires") 516 | sub_validator._validate() 517 | -------------------------------------------------------------------------------- /json_schema_validator/tests/test_schema.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2010, 2011 Linaro Limited 2 | # Copyright (C) 2016 Zygmunt Krynicki 3 | # 4 | # Author: Zygmunt Krynicki 5 | # 6 | # This file is part of json-schema-validator. 7 | # 8 | # json-schema-validator is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU Lesser General Public License version 3 10 | # as published by the Free Software Foundation 11 | # 12 | # json-schema-validator is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU Lesser General Public License 18 | # along with json-schema-validator. If not, see . 19 | 20 | """ 21 | Unit tests for JSON schema 22 | """ 23 | 24 | import json 25 | import sys 26 | 27 | from testscenarios import TestWithScenarios 28 | from testtools import TestCase 29 | 30 | from json_schema_validator.errors import SchemaError 31 | from json_schema_validator.schema import Schema 32 | 33 | PY2 = sys.version_info[0] == 2 34 | PY35 = sys.version_info[0:2] >= (3, 5) 35 | 36 | if PY2: 37 | import yaml 38 | deserializer = yaml.safe_load 39 | else: 40 | deserializer = json.loads 41 | 42 | 43 | class SchemaTests(TestWithScenarios, TestCase): 44 | 45 | scenarios = [ 46 | ('type_default', { 47 | 'schema': '{}', 48 | 'expected': { 49 | 'type': 'any' 50 | }, 51 | }), 52 | ('type_string', { 53 | 'schema': '{"type": "string"}', 54 | 'expected': { 55 | 'type': 'string' 56 | }, 57 | }), 58 | ('type_number', { 59 | 'schema': '{"type": "number"}', 60 | 'expected': { 61 | 'type': 'number' 62 | }, 63 | }), 64 | ('type_integer', { 65 | 'schema': '{"type": "integer"}', 66 | 'expected': { 67 | 'type': 'integer' 68 | }, 69 | }), 70 | ('type_boolean', { 71 | 'schema': '{"type": "boolean"}', 72 | 'expected': { 73 | 'type': 'boolean' 74 | }, 75 | }), 76 | ('type_object', { 77 | 'schema': '{"type": "object"}', 78 | 'expected': { 79 | 'type': 'object' 80 | }, 81 | }), 82 | ('type_array', { 83 | 'schema': '{"type": "array"}', 84 | 'expected': { 85 | 'type': 'array' 86 | }, 87 | }), 88 | ('type_complex_subtype', { 89 | 'schema': '{"type": {}}', 90 | 'expected': { 91 | 'type': {}, 92 | }, 93 | }), 94 | ("type_empty_list", { 95 | 'schema': '{"type": []}', 96 | 'access': 'type', 97 | 'raises': SchemaError("union type [] is too short") 98 | }), 99 | ("type_list_with_one_item", { 100 | 'schema': '{"type": ["number"]}', 101 | 'access': 'type', 102 | 'raises': SchemaError("union type ['number'] is too short") 103 | }), 104 | ('type_list', { 105 | 'schema': '{"type": ["string", "number"]}', 106 | 'expected': { 107 | 'type': ["string", "number"], 108 | }, 109 | }), 110 | ('type_wrong_type', { 111 | 'schema': '{"type": 5}', 112 | 'access': 'type', 113 | 'raises': SchemaError( 114 | "type value 5 is not a simple type name," 115 | " nested schema nor a list of those"), 116 | }), 117 | ('type_not_a_simple_type_name', { 118 | 'schema': '{"type": "foobar"}', 119 | 'access': 'type', 120 | 'raises': SchemaError( 121 | "type value 'foobar' is not a simple type name"), 122 | }), 123 | ('type_list_duplicates', { 124 | 'schema': '{"type": ["string", "string"]}', 125 | 'access': 'type', 126 | 'raises': SchemaError( 127 | "type value ['string', 'string'] contains duplicate" 128 | " element 'string'") 129 | }), 130 | ('properties_default', { 131 | 'schema': '{}', 132 | 'expected': { 133 | 'properties': {}, 134 | }, 135 | }), 136 | ('properties_example', { 137 | 'schema': '{"properties": {"prop": {"type": "number"}}}', 138 | 'expected': { 139 | 'properties': {"prop": {"type": "number"}}, 140 | }, 141 | }), 142 | ('properties_wrong_type', { 143 | 'schema': '{"properties": 5}', 144 | 'access': 'properties', 145 | 'raises': SchemaError( 146 | 'properties value 5 is not an object'), 147 | }), 148 | ('items_default', { 149 | 'schema': '{}', 150 | 'expected': { 151 | 'items': {}, 152 | }, 153 | }), 154 | ('items_tuple', { 155 | 'schema': '{"items": [{}, {}]}', 156 | 'expected': { 157 | 'items': [{}, {}], 158 | }, 159 | }), 160 | ('items_each', { 161 | 'schema': '{"items": {"type": "number"}}', 162 | 'expected': { 163 | 'items': {"type": "number"}, 164 | }, 165 | }), 166 | ('items_wrong_type', { 167 | 'schema': '{"items": 5}', 168 | 'access': 'items', 169 | 'raises': SchemaError( 170 | 'items value 5 is neither a list nor an object'), 171 | }), 172 | ('optional_default', { 173 | 'schema': '{}', 174 | 'expected': { 175 | 'optional': False, 176 | }, 177 | }), 178 | ('optional_true', { 179 | 'schema': '{"optional": true}', 180 | 'expected': { 181 | 'optional': True, 182 | }, 183 | }), 184 | ('optional_false', { 185 | 'schema': '{"optional": false}', 186 | 'expected': { 187 | 'optional': False, 188 | }, 189 | }), 190 | ('optional_wrong_type', { 191 | 'schema': '{"optional": 5}', 192 | 'access': 'optional', 193 | 'raises': SchemaError( 194 | 'optional value 5 is not a boolean'), 195 | }), 196 | ('additionalProperties_default', { 197 | 'schema': '{}', 198 | 'expected': { 199 | 'additionalProperties': {} 200 | }, 201 | }), 202 | ('additionalProperties_false', { 203 | 'schema': '{"additionalProperties": false}', 204 | 'expected': { 205 | "additionalProperties": False, 206 | }, 207 | }), 208 | ('additionalProperties_object', { 209 | 'schema': '{"additionalProperties": {"type": "number"}}', 210 | 'expected': { 211 | "additionalProperties": {"type": "number"}, 212 | }, 213 | }), 214 | ('additionalProperties_wrong_type', { 215 | 'schema': '{"additionalProperties": 5}', 216 | 'access': 'additionalProperties', 217 | 'raises': SchemaError( 218 | 'additionalProperties value 5 is neither false nor an' 219 | ' object'), 220 | }), 221 | ('requires_default', { 222 | 'schema': '{}', 223 | 'expected': { 224 | 'requires': {}, 225 | }, 226 | }), 227 | ('requires_property_name', { 228 | 'schema': '{"requires": "other"}', 229 | 'expected': { 230 | 'requires': "other", 231 | }, 232 | }), 233 | ('requires_schema', { 234 | 'schema': '{"requires": {"properties": {"other": {"type": "number"}}}}', 235 | 'expected': { 236 | 'requires': { 237 | 'properties': { 238 | 'other': { 239 | 'type': 'number' 240 | }, 241 | }, 242 | }, 243 | }, 244 | }), 245 | ('requires_wrong_value', { 246 | 'schema': '{"requires": 5}', 247 | 'access': 'requires', 248 | 'raises': SchemaError( 249 | 'requires value 5 is neither a string nor an object'), 250 | }), 251 | ('minimum_default', { 252 | 'schema': '{}', 253 | 'expected': { 254 | 'minimum': None 255 | }, 256 | }), 257 | ('minimum_integer', { 258 | 'schema': '{"minimum": 5}', 259 | 'expected': { 260 | 'minimum': 5 261 | }, 262 | }), 263 | ('minimum_float', { 264 | 'schema': '{"minimum": 3.5}', 265 | 'expected': { 266 | 'minimum': 3.5 267 | }, 268 | }), 269 | ('minimum_wrong_type', { 270 | 'schema': '{"minimum": "foobar"}', 271 | 'access': 'minimum', 272 | 'raises': SchemaError( 273 | 'minimum value \'foobar\' is not a numeric type') 274 | }), 275 | ('maximum_default', { 276 | 'schema': '{}', 277 | 'expected': { 278 | 'maximum': None 279 | }, 280 | }), 281 | ('maximum_integer', { 282 | 'schema': '{"maximum": 5}', 283 | 'expected': { 284 | 'maximum': 5 285 | }, 286 | }), 287 | ('maximum_float', { 288 | 'schema': '{"maximum": 3.5}', 289 | 'expected': { 290 | 'maximum': 3.5 291 | }, 292 | }), 293 | ('maximum_wrong_type', { 294 | 'schema': '{"maximum": "foobar"}', 295 | 'access': 'maximum', 296 | 'raises': SchemaError( 297 | 'maximum value \'foobar\' is not a numeric type') 298 | }), 299 | ('minimumCanEqual_default', { 300 | 'schema': '{"minimum": 5}', 301 | 'expected': { 302 | 'minimum': 5, 303 | 'minimumCanEqual': True 304 | }, 305 | }), 306 | ('minimumCanEqual_false', { 307 | 'schema': '{"minimum": 5, "minimumCanEqual": false}', 308 | 'expected': { 309 | 'minimum': 5, 310 | 'minimumCanEqual': False, 311 | }, 312 | }), 313 | ('minimumCanEqual_true', { 314 | 'schema': '{"minimum": 5, "minimumCanEqual": true}', 315 | 'expected': { 316 | 'minimum': 5, 317 | 'minimumCanEqual': True 318 | }, 319 | }), 320 | ('minimumCanEqual_without_minimum', { 321 | 'schema': '{}', 322 | 'access': 'minimumCanEqual', 323 | 'raises': SchemaError( 324 | "minimumCanEqual requires presence of minimum"), 325 | }), 326 | ('minimumCanEqual_wrong_type', { 327 | 'schema': '{"minimum": 5, "minimumCanEqual": 5}', 328 | 'access': 'minimumCanEqual', 329 | 'raises': SchemaError( 330 | "minimumCanEqual value 5 is not a boolean"), 331 | }), 332 | ('maximumCanEqual_default', { 333 | 'schema': '{"maximum": 5}', 334 | 'expected': { 335 | 'maximum': 5, 336 | 'maximumCanEqual': True 337 | }, 338 | }), 339 | ('maximumCanEqual_false', { 340 | 'schema': '{"maximum": 5, "maximumCanEqual": false}', 341 | 'expected': { 342 | 'maximum': 5, 343 | 'maximumCanEqual': False, 344 | }, 345 | }), 346 | ('maximumCanEqual_true', { 347 | 'schema': '{"maximum": 5, "maximumCanEqual": true}', 348 | 'expected': { 349 | 'maximum': 5, 350 | 'maximumCanEqual': True 351 | }, 352 | }), 353 | ('maximumCanEqual_without_maximum', { 354 | 'schema': '{}', 355 | 'access': 'maximumCanEqual', 356 | 'raises': SchemaError( 357 | "maximumCanEqual requires presence of maximum"), 358 | }), 359 | ('maximumCanEqual_wrong_type', { 360 | 'schema': '{"maximum": 5, "maximumCanEqual": 5}', 361 | 'access': 'maximumCanEqual', 362 | 'raises': SchemaError( 363 | "maximumCanEqual value 5 is not a boolean"), 364 | }), 365 | ("minItems_default", { 366 | 'schema': '{}', 367 | 'expected': { 368 | 'minItems': 0, 369 | }, 370 | }), 371 | ("minItems_integer", { 372 | 'schema': '{"minItems": 13}', 373 | 'expected': { 374 | 'minItems': 13, 375 | }, 376 | }), 377 | ("minItems_zero", { 378 | 'schema': '{"minItems": 0}', 379 | 'expected': { 380 | 'minItems': 0, 381 | }, 382 | }), 383 | ("minItems_minus_one", { 384 | 'schema': '{"minItems": -1}', 385 | 'access': 'minItems', 386 | 'raises': SchemaError( 387 | "minItems value -1 cannot be negative"), 388 | }), 389 | ("minItems_wrong_type", { 390 | 'schema': '{"minItems": "foobar"}', 391 | 'access': 'minItems', 392 | 'raises': SchemaError( 393 | "minItems value 'foobar' is not an integer"), 394 | }), 395 | ("maxItems_default", { 396 | 'schema': '{}', 397 | 'expected': { 398 | 'maxItems': None, 399 | }, 400 | }), 401 | ("maxItems_integer", { 402 | 'schema': '{"maxItems": 13}', 403 | 'expected': { 404 | 'maxItems': 13, 405 | }, 406 | }), 407 | ("maxItems_zero", { 408 | 'schema': '{"maxItems": 0}', 409 | 'expected': { 410 | 'maxItems': 0, 411 | }, 412 | }), 413 | ("maxItems_minus_one", { 414 | 'schema': '{"maxItems": -1}', 415 | 'expected': { 416 | 'maxItems': -1 417 | }, 418 | }), 419 | ("maxItems_wrong_type", { 420 | 'schema': '{"maxItems": "foobar"}', 421 | 'access': 'maxItems', 422 | 'raises': SchemaError( 423 | "maxItems value 'foobar' is not an integer"), 424 | }), 425 | ("uniqueItems_default", { 426 | 'schema': '{}', 427 | 'expected': { 428 | 'uniqueItems': False 429 | } 430 | }), 431 | ("uniqueItems_true", { 432 | 'schema': '{"uniqueItems": true}', 433 | 'expected': { 434 | 'uniqueItems': True 435 | } 436 | }), 437 | ("uniqueItems_false", { 438 | 'schema': '{"uniqueItems": false}', 439 | 'expected': { 440 | 'uniqueItems': False 441 | } 442 | }), 443 | ("uniqueItems_wrong_type", { 444 | 'schema': '{"uniqueItems": 5}', 445 | 'access': 'uniqueItems', 446 | 'raises': SchemaError( 447 | "uniqueItems value 5 is not a boolean") 448 | }), 449 | ("pattern_default", { 450 | 'schema': '{}', 451 | 'expected': { 452 | 'pattern': None, 453 | }, 454 | }), 455 | #("pattern_simple", { 456 | # 'schema': '{"pattern": "foo|bar"}', 457 | # 'expected': { 458 | # 'pattern': re.compile('foo|bar'), 459 | # }, 460 | #}), 461 | ("pattern_broken", { 462 | 'schema': '{"pattern": "[unterminated"}', 463 | 'access': 'pattern', 464 | 'raises': SchemaError( 465 | "pattern value '[unterminated' is not a valid regular" 466 | " expression: " + 467 | ("unexpected end of regular expression" if not PY35 else 468 | "unterminated character set at position 0" 469 | )), 470 | }), 471 | ("minLength_default", { 472 | 'schema': '{}', 473 | 'expected': { 474 | 'minLength': 0, 475 | }, 476 | }), 477 | ("minLength_integer", { 478 | 'schema': '{"minLength": 13}', 479 | 'expected': { 480 | 'minLength': 13, 481 | }, 482 | }), 483 | ("minLength_zero", { 484 | 'schema': '{"minLength": 0}', 485 | 'expected': { 486 | 'minLength': 0, 487 | }, 488 | }), 489 | ("minLength_minus_one", { 490 | 'schema': '{"minLength": -1}', 491 | 'access': 'minLength', 492 | 'raises': SchemaError( 493 | "minLength value -1 cannot be negative"), 494 | }), 495 | ("minLength_wrong_type", { 496 | 'schema': '{"minLength": "foobar"}', 497 | 'access': 'minLength', 498 | 'raises': SchemaError( 499 | "minLength value 'foobar' is not an integer"), 500 | }), 501 | ("maxLength_default", { 502 | 'schema': '{}', 503 | 'expected': { 504 | 'maxLength': None, 505 | }, 506 | }), 507 | ("maxLength_integer", { 508 | 'schema': '{"maxLength": 13}', 509 | 'expected': { 510 | 'maxLength': 13, 511 | }, 512 | }), 513 | ("maxLength_zero", { 514 | 'schema': '{"maxLength": 0}', 515 | 'expected': { 516 | 'maxLength': 0, 517 | }, 518 | }), 519 | ("maxLength_minus_one", { 520 | 'schema': '{"maxLength": -1}', 521 | 'expected': { 522 | 'maxLength': -1 523 | }, 524 | }), 525 | ("maxLength_wrong_type", { 526 | 'schema': '{"maxLength": "foobar"}', 527 | 'access': 'maxLength', 528 | 'raises': SchemaError( 529 | "maxLength value 'foobar' is not an integer"), 530 | }), 531 | ("enum_default", { 532 | 'schema': '{}', 533 | 'expected': { 534 | 'enum': None, 535 | } 536 | }), 537 | ("enum_simple", { 538 | 'schema': '{"enum": ["foo", "bar"]}', 539 | 'expected': { 540 | 'enum': ["foo", "bar"], 541 | } 542 | }), 543 | ("enum_mixed", { 544 | 'schema': '{"enum": [5, false, "foobar"]}', 545 | 'expected': { 546 | 'enum':[5, False, "foobar"] 547 | } 548 | }), 549 | ("enum_wrong_type", { 550 | 'schema': '{"enum": "foobar"}', 551 | 'access': 'enum', 552 | 'raises': SchemaError( 553 | "enum value 'foobar' is not a list"), 554 | }), 555 | ("enum_too_short", { 556 | 'schema': '{"enum": []}', 557 | 'access': 'enum', 558 | 'raises': SchemaError( 559 | "enum value [] does not contain any elements"), 560 | }), 561 | ("enum_duplicates", { 562 | 'schema': '{"enum": ["foo", "foo"]}', 563 | 'access': 'enum', 564 | 'raises': SchemaError( 565 | "enum value ['foo', 'foo'] contains duplicate element" 566 | " 'foo'"), 567 | }), 568 | ("title_default", { 569 | 'schema': '{}', 570 | 'expected': { 571 | 'title': None, 572 | }, 573 | }), 574 | ("title_simple", { 575 | 'schema': '{"title": "foobar"}', 576 | 'expected': { 577 | 'title': "foobar", 578 | }, 579 | }), 580 | ("title_wrong_type", { 581 | 'schema': '{"title": 5}', 582 | 'access': 'title', 583 | 'raises': SchemaError('title value 5 is not a string') 584 | }), 585 | ("description_default", { 586 | 'schema': '{}', 587 | 'expected': { 588 | 'description': None, 589 | }, 590 | }), 591 | ("description_simple", { 592 | 'schema': '{"description": "foobar"}', 593 | 'expected': { 594 | 'description': "foobar", 595 | }, 596 | }), 597 | ("description_wrong_type", { 598 | 'schema': '{"description": 5}', 599 | 'access': 'description', 600 | 'raises': SchemaError('description value 5 is not a string') 601 | }), 602 | ("format_default", { 603 | 'schema': '{}', 604 | 'expected': { 605 | 'format': None 606 | }, 607 | }), 608 | ("format_date_time", { 609 | 'schema': '{"format": "date-time"}', 610 | 'expected': { 611 | 'format': "date-time" 612 | }, 613 | }), 614 | ("format_regex", { 615 | 'schema': '{"format": "regex"}', 616 | 'expected': { 617 | 'format': "regex" 618 | }, 619 | }), 620 | ("format_wrong_type", { 621 | 'schema': '{"format": 5}', 622 | 'access': 'format', 623 | 'raises': SchemaError('format value 5 is not a string') 624 | }), 625 | ("format_not_implemented", { 626 | 'schema': '{"format": "color"}', 627 | 'access': 'format', 628 | 'raises': NotImplementedError( 629 | "format value 'color' is not supported") 630 | }), 631 | ("contentEncoding_default", { 632 | 'schema': '{}', 633 | 'expected': { 634 | 'contentEncoding': None, 635 | } 636 | }), 637 | ("contentEncoding_base64", { 638 | 'schema': '{"contentEncoding": "base64"}', 639 | 'expected': { 640 | 'contentEncoding': "base64", 641 | }, 642 | }), 643 | ("contentEncoding_base64_mixed_case", { 644 | 'schema': '{"contentEncoding": "BAsE64"}', 645 | 'expected': { 646 | 'contentEncoding': 'BAsE64', 647 | }, 648 | }), 649 | ("contentEncoding_unsupported_value", { 650 | 'schema': '{"contentEncoding": "x-token"}', 651 | 'access': 'contentEncoding', 652 | 'raises': NotImplementedError( 653 | "contentEncoding value 'x-token' is not supported") 654 | }), 655 | ("contentEncoding_unknown_value", { 656 | 'schema': '{"contentEncoding": "bogus"}', 657 | 'access': 'contentEncoding', 658 | 'raises': SchemaError( 659 | "contentEncoding value 'bogus' is not valid") 660 | }), 661 | ("divisibleBy_default", { 662 | 'schema': '{}', 663 | 'expected': { 664 | 'divisibleBy': 1 665 | } 666 | }), 667 | ("divisibleBy_int", { 668 | 'schema': '{"divisibleBy": 5}', 669 | 'expected': { 670 | 'divisibleBy': 5 671 | } 672 | }), 673 | ("divisibleBy_float", { 674 | 'schema': '{"divisibleBy": 3.5}', 675 | 'expected': { 676 | 'divisibleBy': 3.5 677 | } 678 | }), 679 | ("divisibleBy_wrong_type", { 680 | 'schema': '{"divisibleBy": "foobar"}', 681 | 'access': 'divisibleBy', 682 | 'raises': SchemaError( 683 | "divisibleBy value 'foobar' is not a numeric type") 684 | }), 685 | ("divisibleBy_minus_one", { 686 | 'schema': '{"divisibleBy": -1}', 687 | 'access': 'divisibleBy', 688 | 'raises': SchemaError( 689 | "divisibleBy value -1 cannot be negative") 690 | }), 691 | ('disallow_default', { 692 | 'schema': '{}', 693 | 'expected': { 694 | 'disallow': None 695 | }, 696 | }), 697 | ('disallow_string', { 698 | 'schema': '{"disallow": "string"}', 699 | 'expected': { 700 | 'disallow': ['string'] 701 | }, 702 | }), 703 | ('disallow_number', { 704 | 'schema': '{"disallow": "number"}', 705 | 'expected': { 706 | 'disallow': ['number'] 707 | }, 708 | }), 709 | ('disallow_integer', { 710 | 'schema': '{"disallow": "integer"}', 711 | 'expected': { 712 | 'disallow': ['integer'] 713 | }, 714 | }), 715 | ('disallow_boolean', { 716 | 'schema': '{"disallow": "boolean"}', 717 | 'expected': { 718 | 'disallow': ['boolean'] 719 | }, 720 | }), 721 | ('disallow_object', { 722 | 'schema': '{"disallow": "object"}', 723 | 'expected': { 724 | 'disallow': ['object'] 725 | }, 726 | }), 727 | ('disallow_array', { 728 | 'schema': '{"disallow": "array"}', 729 | 'expected': { 730 | 'disallow': ['array'] 731 | }, 732 | }), 733 | ('disallow_complex_subtype', { 734 | 'schema': '{"disallow": {}}', 735 | 'expected': { 736 | 'disallow': [{}], 737 | }, 738 | }), 739 | ('disallow_list', { 740 | 'schema': '{"disallow": ["string", "number"]}', 741 | 'expected': { 742 | 'disallow': ["string", "number"], 743 | }, 744 | }), 745 | ('disallow_wrong_type', { 746 | 'schema': '{"disallow": 5}', 747 | 'access': 'disallow', 748 | 'raises': SchemaError( 749 | "disallow value 5 is not a simple type name," 750 | " nested schema nor a list of those"), 751 | }), 752 | ('disallow_not_a_simple_disallow_name', { 753 | 'schema': '{"disallow": "foobar"}', 754 | 'access': 'disallow', 755 | 'raises': SchemaError( 756 | "disallow value 'foobar' is not a simple type name") 757 | }), 758 | ('disallow_list_duplicates', { 759 | 'schema': '{"disallow": ["string", "string"]}', 760 | 'access': 'disallow', 761 | 'raises': SchemaError( 762 | "disallow value ['string', 'string'] contains" 763 | " duplicate element 'string'") 764 | }), 765 | ('extends_not_supported', { 766 | 'schema': '{}', 767 | 'access': 'extends', 768 | 'raises': NotImplementedError( 769 | "extends property is not supported"), 770 | }), 771 | ('default_with_value', { 772 | 'schema': '{"default": 5}', 773 | 'expected': { 774 | 'default': 5 775 | } 776 | }), 777 | ('default_without_value', { 778 | 'schema': '{}', 779 | 'access': 'default', 780 | 'raises': SchemaError("There is no schema default for this item"), 781 | }), 782 | ] 783 | 784 | def test_schema_attribute(self): 785 | if deserializer != json.loads: 786 | # Always check the serialised JSON using the native JSON loader 787 | # so that any error messages are consistent and appropriate. 788 | json.loads(self.schema) 789 | 790 | schema = Schema(deserializer(self.schema)) 791 | if hasattr(self, 'expected'): 792 | for attr, expected_value in self.expected.items(): 793 | self.assertEqual( 794 | expected_value, getattr(schema, attr)) 795 | elif hasattr(self, 'access') and hasattr(self, 'raises'): 796 | self.assertRaises( 797 | type(self.raises), 798 | getattr, schema, self.access) 799 | try: 800 | getattr(schema, self.access) 801 | except type(self.raises) as ex: 802 | self.assertEqual(str(ex), str(self.raises)) 803 | except Exception as ex: 804 | self.fail("Raised exception {0!r} instead of {1!r}".format( 805 | ex, self.raises)) 806 | else: 807 | self.fail("Broken test definition, must define 'expected' " 808 | "or 'access' and 'raises' scenario attributes") 809 | -------------------------------------------------------------------------------- /json_schema_validator/tests/test_validator.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2010, 2011 Linaro Limited 2 | # Copyright (C) 2016 Zygmunt Krynicki 3 | # 4 | # Author: Zygmunt Krynicki 5 | # 6 | # This file is part of json-schema-validator. 7 | # 8 | # json-schema-validator is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU Lesser General Public License version 3 10 | # as published by the Free Software Foundation 11 | # 12 | # json-schema-validator is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU Lesser General Public License 18 | # along with json-schema-validator. If not, see . 19 | 20 | """ 21 | Unit tests for JSON schema 22 | """ 23 | 24 | import functools 25 | import json 26 | import sys 27 | 28 | from testscenarios import TestWithScenarios 29 | from testtools import TestCase 30 | 31 | from json_schema_validator.errors import ValidationError 32 | from json_schema_validator.shortcuts import validate 33 | from json_schema_validator.validator import Validator 34 | 35 | PY2 = sys.version_info[0] == 2 36 | if PY2: 37 | import yaml 38 | 39 | def deserializer(json_string): 40 | # Always check the serialised JSON using the native JSON loader 41 | # so that any error messages are consistent and appropriate. 42 | json.loads(json_string) 43 | return yaml.safe_load(json_string) 44 | 45 | validate = functools.partial(validate, deserializer=deserializer) 46 | 47 | 48 | class ValidatorFailureTests(TestWithScenarios, TestCase): 49 | 50 | scenarios = [ 51 | ("type_string_got_null", { 52 | 'schema': '{"type": "string"}', 53 | 'data': 'null', 54 | 'raises': ValidationError( 55 | "None does not match type 'string'", 56 | "Object has incorrect type (expected string)"), 57 | 'object_expr': 'object', 58 | 'schema_expr': 'schema.type', 59 | }), 60 | ("type_string_got_integer", { 61 | 'schema': '{"type": "string"}', 62 | 'data': '5', 63 | 'raises': ValidationError( 64 | "5 does not match type 'string'", 65 | "Object has incorrect type (expected string)"), 66 | 'object_expr': 'object', 67 | 'schema_expr': 'schema.type', 68 | }), 69 | ("type_number_got_null", { 70 | 'schema': '{"type": "number"}', 71 | 'data': 'null', 72 | 'raises': ValidationError( 73 | "None does not match type 'number'", 74 | "Object has incorrect type (expected number)"), 75 | 'object_expr': 'object', 76 | 'schema_expr': 'schema.type', 77 | }), 78 | ("type_number_got_string", { 79 | 'schema': '{"type": "number"}', 80 | 'data': '"foobar"', 81 | 'raises': ValidationError( 82 | "'foobar' does not match type 'number'", 83 | "Object has incorrect type (expected number)"), 84 | 'object_expr': 'object', 85 | 'schema_expr': 'schema.type', 86 | }), 87 | ("type_number_got_string_that_looks_like_number", { 88 | 'schema': '{"type": "number"}', 89 | 'data': '"3"', 90 | 'raises': ValidationError( 91 | "'3' does not match type 'number'", 92 | "Object has incorrect type (expected number)"), 93 | 'object_expr': 'object', 94 | 'schema_expr': 'schema.type', 95 | }), 96 | ("type_integer_got_float", { 97 | 'schema': '{"type": "integer"}', 98 | 'data': '1.5', 99 | 'raises': ValidationError( 100 | "1.5 does not match type 'integer'", 101 | "Object has incorrect type (expected integer)"), 102 | 'object_expr': 'object', 103 | 'schema_expr': 'schema.type', 104 | }), 105 | ("type_integer_got_null", { 106 | 'schema': '{"type": "integer"}', 107 | 'data': 'null', 108 | 'raises': ValidationError( 109 | "None does not match type 'integer'", 110 | "Object has incorrect type (expected integer)"), 111 | 'object_expr': 'object', 112 | 'schema_expr': 'schema.type', 113 | }), 114 | ("type_boolean_got_null", { 115 | 'schema': '{"type": "boolean"}', 116 | 'data': 'null', 117 | 'raises': ValidationError( 118 | "None does not match type 'boolean'", 119 | "Object has incorrect type (expected boolean)"), 120 | 'object_expr': 'object', 121 | 'schema_expr': 'schema.type', 122 | }), 123 | ("type_boolean_got_empty_string", { 124 | 'schema': '{"type": "boolean"}', 125 | 'data': '""', 126 | 'raises': ValidationError( 127 | "'' does not match type 'boolean'", 128 | "Object has incorrect type (expected boolean)"), 129 | 'object_expr': 'object', 130 | 'schema_expr': 'schema.type', 131 | }), 132 | ("type_boolean_got_empty_list", { 133 | 'schema': '{"type": "boolean"}', 134 | 'data': '[]', 135 | 'raises': ValidationError( 136 | "[] does not match type 'boolean'", 137 | "Object has incorrect type (expected boolean)"), 138 | 'object_expr': 'object', 139 | 'schema_expr': 'schema.type', 140 | }), 141 | ("type_boolean_got_empty_object", { 142 | 'schema': '{"type": "boolean"}', 143 | 'data': '{}', 144 | 'raises': ValidationError( 145 | "{} does not match type 'boolean'", 146 | "Object has incorrect type (expected boolean)"), 147 | 'object_expr': 'object', 148 | 'schema_expr': 'schema.type', 149 | }), 150 | ("type_object_got_integer", { 151 | 'schema': '{"type": "object"}', 152 | 'data': '1', 153 | 'raises': ValidationError( 154 | "1 does not match type 'object'", 155 | "Object has incorrect type (expected object)"), 156 | 'object_expr': 'object', 157 | 'schema_expr': 'schema.type', 158 | }), 159 | ("type_object_got_null", { 160 | 'schema': '{"type": "object"}', 161 | 'data': 'null', 162 | 'raises': ValidationError( 163 | "None does not match type 'object'", 164 | "Object has incorrect type (expected object)"), 165 | 'object_expr': 'object', 166 | 'schema_expr': 'schema.type', 167 | }), 168 | ("type_array_got_null", { 169 | 'schema': '{"type": "array"}', 170 | 'data': 'null', 171 | 'raises': ValidationError( 172 | "None does not match type 'array'", 173 | "Object has incorrect type (expected array)"), 174 | 'object_expr': 'object', 175 | 'schema_expr': 'schema.type', 176 | }), 177 | ("type_array_got_integer", { 178 | 'schema': '{"type": "array"}', 179 | 'data': '1', 180 | 'raises': ValidationError( 181 | "1 does not match type 'array'", 182 | "Object has incorrect type (expected array)"), 183 | 'object_expr': 'object', 184 | 'schema_expr': 'schema.type', 185 | }), 186 | ("type_null_got_empty_string", { 187 | 'schema': '{"type": "null"}', 188 | 'data': '""', 189 | 'raises': ValidationError( 190 | "'' does not match type 'null'", 191 | "Object has incorrect type (expected null)"), 192 | 'object_expr': 'object', 193 | 'schema_expr': 'schema.type', 194 | }), 195 | ("type_null_got_zero", { 196 | 'schema': '{"type": "null"}', 197 | 'data': '0', 198 | 'raises': ValidationError( 199 | "0 does not match type 'null'", 200 | "Object has incorrect type (expected null)"), 201 | 'object_expr': 'object', 202 | 'schema_expr': 'schema.type', 203 | }), 204 | ("type_null_got_empty_list", { 205 | 'schema': '{"type": "null"}', 206 | 'data': '[]', 207 | 'raises': ValidationError( 208 | "[] does not match type 'null'", 209 | "Object has incorrect type (expected null)"), 210 | 'object_expr': 'object', 211 | 'schema_expr': 'schema.type', 212 | }), 213 | ("type_null_got_empty_object", { 214 | 'schema': '{"type": "null"}', 215 | 'data': '{}', 216 | 'raises': ValidationError( 217 | "{} does not match type 'null'", 218 | "Object has incorrect type (expected null)"), 219 | 'object_expr': 'object', 220 | 'schema_expr': 'schema.type', 221 | }), 222 | ("type_list_got_mismatching_item", { 223 | 'schema': '{"type": ["string", "number"]}', 224 | 'data': '{}', 225 | 'raises': ValidationError( 226 | "{} does not match any of the types in ['string', 'number']", 227 | "Object has incorrect type (multiple types possible)", 228 | ".type"), 229 | 'object_expr': 'object', 230 | 'schema_expr': 'schema.type', 231 | }), 232 | ("property_check_is_not_primitive", { 233 | 'schema': """ 234 | { 235 | "type": "object", 236 | "properties": { 237 | "foo": { 238 | "type": "number" 239 | } 240 | } 241 | }""", 242 | 'data': '{"foo": "foobar"}', 243 | 'raises': ValidationError( 244 | "'foobar' does not match type 'number'", 245 | "Object has incorrect type (expected number)"), 246 | 'object_expr': 'object.foo', 247 | 'schema_expr': 'schema.properties.foo.type', 248 | }), 249 | ("property_check_validates_optional_properties", { 250 | 'schema': """ 251 | { 252 | "type": "object", 253 | "properties": { 254 | "foo": { 255 | "type": "number", 256 | "optional": true 257 | } 258 | } 259 | }""", 260 | 'data': '{"foo": null}', 261 | 'raises': ValidationError( 262 | "None does not match type 'number'", 263 | "Object has incorrect type (expected number)"), 264 | 'object_expr': 'object.foo', 265 | 'schema_expr': 'schema.properties.foo.type', 266 | }), 267 | ("property_check_reports_missing_non_optional_properties", { 268 | 'schema': """ 269 | { 270 | "type": "object", 271 | "properties": { 272 | "foo": { 273 | "type": "number", 274 | "optional": false 275 | } 276 | } 277 | }""", 278 | 'data': '{}', 279 | 'raises': ValidationError( 280 | "{} does not have property 'foo'", 281 | "Object lacks property 'foo'"), 282 | 'object_expr': 'object', 283 | 'schema_expr': 'schema.properties.foo.optional', 284 | }), 285 | ("property_check_reports_unknown_properties_when_additionalProperties_is_false", { 286 | 'schema': """ 287 | { 288 | "type": "object", 289 | "additionalProperties": false 290 | }""", 291 | 'data': '{"foo": 5}', 292 | 'raises': ValidationError( 293 | "{'foo': 5} has unknown property 'foo' and" 294 | " additionalProperties is false", 295 | "Object has unknown property 'foo' but additional " 296 | "properties are disallowed"), 297 | 'object_expr': 'object', 298 | 'schema_expr': 'schema.additionalProperties', 299 | }), 300 | ("property_check_validates_additional_properties_using_specified_type_when_additionalProperties_is_an_object_violation", { 301 | 'schema': """ 302 | { 303 | "type": "object", 304 | "additionalProperties": { 305 | "type": "string" 306 | } 307 | }""", 308 | 'data': '{"foo": "aaa", "bar": 5}', 309 | 'raises': ValidationError( 310 | "5 does not match type 'string'", 311 | "Object has incorrect type (expected string)"), 312 | 'object_expr': 'object.bar', 313 | 'schema_expr': 'schema.additionalProperties.type', 314 | }), 315 | ("enum_check_reports_unlisted_values", { 316 | 'schema': '{"enum": [1, 2, 3]}', 317 | 'data': '5', 318 | 'raises': ValidationError( 319 | '5 does not match any value in enumeration [1, 2, 3]', 320 | "Object does not match any value in enumeration"), 321 | 'object_expr': 'object', 322 | 'schema_expr': 'schema.enum', 323 | }), 324 | ("items_with_single_schema_finds_problems", { 325 | 'schema': '{"items": {"type": "string"}}', 326 | 'data': '["foo", null, "froz"]', 327 | 'raises': ValidationError( 328 | "None does not match type 'string'", 329 | "Object has incorrect type (expected string)"), 330 | 'object_expr': 'object[1]', 331 | 'schema_expr': 'schema.items.type', 332 | }), 333 | ("items_with_array_schema_checks_for_too_short_data", { 334 | 'schema': """ 335 | { 336 | "items": [ 337 | {"type": "string"}, 338 | {"type": "boolean"} 339 | ] 340 | }""", 341 | 'data': '["foo"]', 342 | 'raises': ValidationError( 343 | "['foo'] is shorter than array schema [{'type':" 344 | " 'string'}, {'type': 'boolean'}]", 345 | "Object array is shorter than schema array"), 346 | 'object_expr': 'object', 347 | 'schema_expr': 'schema.items', 348 | }), 349 | ("items_with_array_schema_and_additionalProperties_of_false_checks_for_too_much_data", { 350 | 'schema': """ 351 | { 352 | "items": [ 353 | {"type": "string"}, 354 | {"type": "boolean"} 355 | ], 356 | "additionalProperties": false 357 | }""", 358 | 'data': '["foo", false, 5]', 359 | 'raises': ValidationError( 360 | "['foo', False, 5] is not of the same length as array" 361 | " schema [{'type': 'string'}, {'type': 'boolean'}] and" 362 | " additionalProperties is false", 363 | "Object array is not of the same length as schema array"), 364 | 'object_expr': 'object', 365 | 'schema_expr': 'schema.items', 366 | }), 367 | ("items_with_array_schema_and_additionalProperties_can_find_problems", { 368 | 'schema': """ 369 | { 370 | "items": [ 371 | {"type": "string"}, 372 | {"type": "boolean"} 373 | ], 374 | "additionalProperties": { 375 | "type": "number" 376 | } 377 | }""", 378 | 'data': '["foo", false, 5, 7.9, null]', 379 | 'raises': ValidationError( 380 | "None does not match type 'number'", 381 | "Object has incorrect type (expected number)"), 382 | 'object_expr': 'object[4]', 383 | 'schema_expr': 'schema.additionalProperties.type', 384 | }), 385 | ("array_with_array_schema_and_uniqueItems_is_True", { 386 | 'schema': """ 387 | { 388 | "type": "array", 389 | "items": {"type": "string"}, 390 | "uniqueItems": true 391 | }""", 392 | 'data': '["foo", "bar", "foo"]', 393 | 'raises': ValidationError( 394 | "Repeated items found in ['foo', 'bar', 'foo']", 395 | "Repeated items found in array"), 396 | 'object_expr': 'object', 397 | 'schema_expr': 'schema.items', 398 | }), 399 | ("requires_with_simple_property_name_can_report_problems", { 400 | 'schema': """ 401 | { 402 | "properties": { 403 | "foo": { 404 | "optional": true 405 | }, 406 | "bar": { 407 | "requires": "foo", 408 | "optional": true 409 | } 410 | } 411 | } 412 | """, 413 | 'data': '{"bar": null}', 414 | 'raises': ValidationError( 415 | "None requires presence of property 'foo' in the same" 416 | " object", 417 | "Enclosing object does not have property 'foo'"), 418 | 'object_expr': 'object.bar', 419 | 'schema_expr': 'schema.properties.bar.requires', 420 | }), 421 | ("requires_with_simple_property_name_can_report_problems_while_nested", { 422 | 'schema': """ 423 | { 424 | "type": "object", 425 | "properties": { 426 | "nested": { 427 | "properties": { 428 | "foo": { 429 | "optional": true 430 | }, 431 | "bar": { 432 | "requires": "foo", 433 | "optional": true 434 | } 435 | } 436 | } 437 | } 438 | } 439 | """, 440 | 'data': '{"nested": {"bar": null}}', 441 | 'raises': ValidationError( 442 | "None requires presence of property 'foo' in the same" 443 | " object", 444 | "Enclosing object does not have property 'foo'"), 445 | 'object_expr': 'object.nested.bar', 446 | 'schema_expr': 'schema.properties.nested.properties.bar.requires', 447 | }), 448 | ("requires_with_schema_can_report_problems", { 449 | 'schema': """ 450 | { 451 | "properties": { 452 | "foo": { 453 | "optional": true 454 | }, 455 | "bar": { 456 | "requires": { 457 | "properties": { 458 | "foo": { 459 | "type": "number" 460 | } 461 | } 462 | }, 463 | "optional": true 464 | } 465 | } 466 | } 467 | """, 468 | 'data': '{"bar": null}', 469 | 'raises': ValidationError( 470 | "{'bar': None} does not have property 'foo'", 471 | "Object lacks property 'foo'"), 472 | 'object_expr': 'object', 473 | 'schema_expr': 'schema.properties.bar.requires.properties.foo.optional', 474 | }), 475 | ("requires_with_schema_can_report_subtle_problems", { 476 | # In this test presence of "bar" requires that "foo" is 477 | # present and has a type "number" 478 | 'schema': """ 479 | { 480 | "properties": { 481 | "foo": { 482 | "optional": true 483 | }, 484 | "bar": { 485 | "requires": { 486 | "properties": { 487 | "foo": { 488 | "type": "number" 489 | } 490 | } 491 | }, 492 | "optional": true 493 | } 494 | } 495 | } 496 | """, 497 | 'data': '{"bar": null, "foo": "not a number"}', 498 | 'raises': ValidationError( 499 | "'not a number' does not match type 'number'", 500 | "Object has incorrect type (expected number)"), 501 | 'object_expr': 'object.foo', 502 | 'schema_expr': 'schema.properties.bar.requires.properties.foo.type' 503 | }), 504 | ("format_date_time_finds_problems", { 505 | 'schema': '{"format": "date-time"}', 506 | 'data': '"broken"', 507 | 'raises': ValidationError( 508 | "'broken' is not a string representing JSON date-time", 509 | "Object is not a string representing JSON date-time"), 510 | 'object_expr': 'object', 511 | 'schema_expr': 'schema.format' 512 | }), 513 | ] 514 | 515 | def test_validation_error_has_proper_message(self): 516 | ex = self.assertRaises(ValidationError, 517 | validate, self.schema, self.data) 518 | self.assertEqual(ex.message, self.raises.message) 519 | 520 | def test_validation_error_has_proper_new_message(self): 521 | ex = self.assertRaises(ValidationError, 522 | validate, self.schema, self.data) 523 | self.assertEqual(ex.new_message, self.raises.new_message) 524 | 525 | def test_validation_error_has_proper_object_expr(self): 526 | ex = self.assertRaises(ValidationError, 527 | validate, self.schema, self.data) 528 | self.assertEqual(ex.object_expr, self.object_expr) 529 | 530 | def test_validation_error_has_proper_schema_expr(self): 531 | ex = self.assertRaises(ValidationError, 532 | validate, self.schema, self.data) 533 | self.assertEqual(ex.schema_expr, self.schema_expr) 534 | 535 | 536 | class ValidatorSuccessTests(TestWithScenarios, TestCase): 537 | 538 | scenarios = [ 539 | ("type_string_got_string", { 540 | 'schema': '{"type": "string"}', 541 | 'data': '"foobar"' 542 | }), 543 | ("type_number_got_integer", { 544 | 'schema': '{"type": "number"}', 545 | 'data': '1', 546 | }), 547 | ("type_number_number_float", { 548 | 'schema': '{"type": "number"}', 549 | 'data': '1.1', 550 | }), 551 | ("type_integer_got_integer_one", { 552 | 'schema': '{"type": "integer"}', 553 | 'data': '1' 554 | }), 555 | ("type_integer_got_integer", { 556 | 'schema': '{"type": "integer"}', 557 | 'data': '5' 558 | }), 559 | ("type_boolean_got_true", { 560 | 'schema': '{"type": "boolean"}', 561 | 'data': 'true', 562 | }), 563 | ("type_boolean_got_false", { 564 | 'schema': '{"type": "boolean"}', 565 | 'data': 'true', 566 | }), 567 | ("type_object_got_object", { 568 | 'schema': '{"type": "object"}', 569 | 'data': '{}' 570 | }), 571 | ("type_array_got_array", { 572 | 'schema': '{"type": "array"}', 573 | 'data': '[]' 574 | }), 575 | ("type_null_got_null", { 576 | 'schema': '{"type": "null"}', 577 | 'data': 'null', 578 | }), 579 | ("type_any_got_null", { 580 | 'schema': '{"type": "any"}', 581 | 'data': 'null', 582 | }), 583 | ("type_any_got_integer", { 584 | 'schema': '{"type": "any"}', 585 | 'data': '5', 586 | }), 587 | ("type_any_got_boolean", { 588 | 'schema': '{"type": "any"}', 589 | 'data': 'false', 590 | }), 591 | ("type_any_got_string", { 592 | 'schema': '{"type": "any"}', 593 | 'data': '"foobar"', 594 | }), 595 | ("type_any_got_array", { 596 | 'schema': '{"type": "any"}', 597 | 'data': '[]', 598 | }), 599 | ("type_any_got_object", { 600 | 'schema': '{"type": "any"}', 601 | 'data': '{}', 602 | }), 603 | ("type_nested_schema_check", { 604 | 'schema': '{"type": {"type": "number"}}', 605 | 'data': '5', 606 | }), 607 | ("type_list_with_two_values_got_first_value", { 608 | 'schema': '{"type": ["number", "string"]}', 609 | 'data': '1', 610 | }), 611 | ("type_list_with_two_values_got_second_value", { 612 | 'schema': '{"type": ["number", "string"]}', 613 | 'data': '"string"', 614 | }), 615 | ("property_ignored_on_non_objects", { 616 | 'schema': '{"properties": {"foo": {"type": "number"}}}', 617 | 'data': '"foobar"', 618 | }), 619 | ("property_checks_known_props", { 620 | 'schema': """ 621 | { 622 | "type": "object", 623 | "properties": { 624 | "foo": { 625 | "type": "number" 626 | }, 627 | "bar": { 628 | "type": "boolean" 629 | } 630 | } 631 | }""", 632 | 'data': """ 633 | { 634 | "foo": 5, 635 | "bar": false 636 | }""" 637 | }), 638 | ("property_check_ignores_missing_optional_properties", { 639 | 'schema': """ 640 | { 641 | "type": "object", 642 | "properties": { 643 | "foo": { 644 | "type": "number", 645 | "optional": true 646 | } 647 | } 648 | }""", 649 | 'data': '{}', 650 | }), 651 | ("property_check_ignores_normal_properties_when_additionalProperties_is_false", { 652 | 'schema': """ 653 | { 654 | "type": "object", 655 | "properties": { 656 | "foo": {} 657 | }, 658 | "additionalProperties": false 659 | }""", 660 | 'data': '{"foo": 5}', 661 | }), 662 | ("property_check_validates_additional_properties_using_specified_type_when_additionalProperties_is_an_object", { 663 | 'schema': """ 664 | { 665 | "type": "object", 666 | "additionalProperties": { 667 | "type": "string" 668 | } 669 | }""", 670 | 'data': '{"foo": "aaa", "bar": "bbb"}', 671 | }), 672 | ("enum_check_does_nothing_by_default", { 673 | 'schema': '{}', 674 | 'data': '5', 675 | }), 676 | ("enum_check_verifies_possible_values", { 677 | 'schema': '{"enum": [1, 2, 3]}', 678 | 'data': '2', 679 | }), 680 | ("items_check_does_nothing_for_non_arrays", { 681 | 'schema': '{"items": {"type": "string"}}', 682 | 'data': '5', 683 | }), 684 | ("items_with_single_schema_applies_to_each_item", { 685 | 'schema': '{"items": {"type": "string"}}', 686 | 'data': '["foo", "bar", "froz"]', 687 | }), 688 | ("items_with_array_schema_applies_to_corresponding_items", { 689 | 'schema': """ 690 | { 691 | "items": [ 692 | {"type": "string"}, 693 | {"type": "boolean"} 694 | ] 695 | }""", 696 | 'data': '["foo", true]', 697 | }), 698 | ("items_with_array_schema_and_additionalProperties", { 699 | 'schema': """ 700 | { 701 | "items": [ 702 | {"type": "string"}, 703 | {"type": "boolean"} 704 | ], 705 | "additionalProperties": { 706 | "type": "number" 707 | } 708 | }""", 709 | 'data': '["foo", false, 5, 7.9]', 710 | }), 711 | ("requires_with_simple_property_name_does_nothing_when_parent_property_is_not_used", { 712 | 'schema': """ 713 | { 714 | "properties": { 715 | "foo": { 716 | "optional": true 717 | }, 718 | "bar": { 719 | "requires": "foo", 720 | "optional": true 721 | } 722 | } 723 | } 724 | """, 725 | 'data': '{}', 726 | }), 727 | ("requires_with_simple_property_name_works_when_condition_satisfied", { 728 | 'schema': """ 729 | { 730 | "properties": { 731 | "foo": { 732 | "optional": true 733 | }, 734 | "bar": { 735 | "requires": "foo", 736 | "optional": true 737 | } 738 | } 739 | } 740 | """, 741 | 'data': '{"foo": null, "bar": null}', 742 | }), 743 | ("requires_with_schema_name_does_nothing_when_parent_property_is_not_used", { 744 | 'schema': """ 745 | { 746 | "properties": { 747 | "foo": { 748 | "optional": true 749 | }, 750 | "bar": { 751 | "requires": { 752 | "properties": { 753 | "foo": { 754 | "type": "number" 755 | } 756 | } 757 | }, 758 | "optional": true 759 | } 760 | } 761 | } 762 | """, 763 | 'data': '{}', 764 | }), 765 | ("format_date_time_works", { 766 | 'schema': '{"format": "date-time"}', 767 | 'data': '"2010-11-12T14:38:55Z"', 768 | }), 769 | ("array_with_array_schema_and_uniqueItems_is_True", { 770 | 'schema': """ 771 | { 772 | "type": "array", 773 | "items": {"type": "string"}, 774 | "uniqueItems": true 775 | }""", 776 | 'data': '["foo", "bar", "baz"]', 777 | }), 778 | ] 779 | 780 | def test_validator_does_not_raise_an_exception(self): 781 | self.assertEqual( 782 | True, validate(self.schema, self.data)) 783 | -------------------------------------------------------------------------------- /schema-spec/draft-zyp-json-schema-03.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Internet Engineering Task Force K. Zyp, Ed. 5 | Internet-Draft SitePen (USA) 6 | Intended status: Informational G. Court 7 | Expires: May 26, 2011 November 22, 2010 8 | 9 | 10 | A JSON Media Type for Describing the Structure and Meaning of JSON 11 | Documents 12 | draft-zyp-json-schema-03 13 | 14 | Abstract 15 | 16 | JSON (JavaScript Object Notation) Schema defines the media type 17 | "application/schema+json", a JSON based format for defining the 18 | structure of JSON data. JSON Schema provides a contract for what 19 | JSON data is required for a given application and how to interact 20 | with it. JSON Schema is intended to define validation, 21 | documentation, hyperlink navigation, and interaction control of JSON 22 | data. 23 | 24 | Status of This Memo 25 | 26 | This Internet-Draft is submitted in full conformance with the 27 | provisions of BCP 78 and BCP 79. 28 | 29 | Internet-Drafts are working documents of the Internet Engineering 30 | Task Force (IETF). Note that other groups may also distribute 31 | working documents as Internet-Drafts. The list of current Internet- 32 | Drafts is at http://datatracker.ietf.org/drafts/current/. 33 | 34 | Internet-Drafts are draft documents valid for a maximum of six months 35 | and may be updated, replaced, or obsoleted by other documents at any 36 | time. It is inappropriate to use Internet-Drafts as reference 37 | material or to cite them other than as "work in progress." 38 | 39 | This Internet-Draft will expire on May 26, 2011. 40 | 41 | Copyright Notice 42 | 43 | Copyright (c) 2010 IETF Trust and the persons identified as the 44 | document authors. All rights reserved. 45 | 46 | This document is subject to BCP 78 and the IETF Trust's Legal 47 | Provisions Relating to IETF Documents 48 | (http://trustee.ietf.org/license-info) in effect on the date of 49 | publication of this document. Please review these documents 50 | carefully, as they describe your rights and restrictions with respect 51 | to this document. Code Components extracted from this document must 52 | 53 | 54 | 55 | Zyp & Court Expires May 26, 2011 [Page 1] 56 | 57 | Internet-Draft JSON Schema Media Type November 2010 58 | 59 | 60 | include Simplified BSD License text as described in Section 4.e of 61 | the Trust Legal Provisions and are provided without warranty as 62 | described in the Simplified BSD License. 63 | 64 | Table of Contents 65 | 66 | 1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 4 67 | 2. Conventions . . . . . . . . . . . . . . . . . . . . . . . . . 4 68 | 3. Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 69 | 3.1. Terminology . . . . . . . . . . . . . . . . . . . . . . . 6 70 | 3.2. Design Considerations . . . . . . . . . . . . . . . . . . 6 71 | 4. Schema/Instance Association . . . . . . . . . . . . . . . . . 6 72 | 4.1. Self-Descriptive Schema . . . . . . . . . . . . . . . . . 7 73 | 5. Core Schema Definition . . . . . . . . . . . . . . . . . . . . 7 74 | 5.1. type . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 75 | 5.2. properties . . . . . . . . . . . . . . . . . . . . . . . . 9 76 | 5.3. patternProperties . . . . . . . . . . . . . . . . . . . . 9 77 | 5.4. additionalProperties . . . . . . . . . . . . . . . . . . . 9 78 | 5.5. items . . . . . . . . . . . . . . . . . . . . . . . . . . 10 79 | 5.6. additionalItems . . . . . . . . . . . . . . . . . . . . . 10 80 | 5.7. required . . . . . . . . . . . . . . . . . . . . . . . . . 10 81 | 5.8. dependencies . . . . . . . . . . . . . . . . . . . . . . . 10 82 | 5.9. minimum . . . . . . . . . . . . . . . . . . . . . . . . . 11 83 | 5.10. maximum . . . . . . . . . . . . . . . . . . . . . . . . . 11 84 | 5.11. exclusiveMinimum . . . . . . . . . . . . . . . . . . . . . 11 85 | 5.12. exclusiveMaximum . . . . . . . . . . . . . . . . . . . . . 11 86 | 5.13. minItems . . . . . . . . . . . . . . . . . . . . . . . . . 11 87 | 5.14. maxItems . . . . . . . . . . . . . . . . . . . . . . . . . 11 88 | 5.15. uniqueItems . . . . . . . . . . . . . . . . . . . . . . . 11 89 | 5.16. pattern . . . . . . . . . . . . . . . . . . . . . . . . . 12 90 | 5.17. minLength . . . . . . . . . . . . . . . . . . . . . . . . 12 91 | 5.18. maxLength . . . . . . . . . . . . . . . . . . . . . . . . 12 92 | 5.19. enum . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 93 | 5.20. default . . . . . . . . . . . . . . . . . . . . . . . . . 12 94 | 5.21. title . . . . . . . . . . . . . . . . . . . . . . . . . . 12 95 | 5.22. description . . . . . . . . . . . . . . . . . . . . . . . 13 96 | 5.23. format . . . . . . . . . . . . . . . . . . . . . . . . . . 13 97 | 5.24. divisibleBy . . . . . . . . . . . . . . . . . . . . . . . 14 98 | 5.25. disallow . . . . . . . . . . . . . . . . . . . . . . . . . 14 99 | 5.26. extends . . . . . . . . . . . . . . . . . . . . . . . . . 14 100 | 5.27. id . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 101 | 5.28. $ref . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 102 | 5.29. $schema . . . . . . . . . . . . . . . . . . . . . . . . . 15 103 | 6. Hyper Schema . . . . . . . . . . . . . . . . . . . . . . . . . 15 104 | 6.1. links . . . . . . . . . . . . . . . . . . . . . . . . . . 16 105 | 6.1.1. Link Description Object . . . . . . . . . . . . . . . 16 106 | 6.2. fragmentResolution . . . . . . . . . . . . . . . . . . . . 20 107 | 6.2.1. slash-delimited fragment resolution . . . . . . . . . 20 108 | 109 | 110 | 111 | Zyp & Court Expires May 26, 2011 [Page 2] 112 | 113 | Internet-Draft JSON Schema Media Type November 2010 114 | 115 | 116 | 6.2.2. dot-delimited fragment resolution . . . . . . . . . . 21 117 | 6.3. readonly . . . . . . . . . . . . . . . . . . . . . . . . . 21 118 | 6.4. contentEncoding . . . . . . . . . . . . . . . . . . . . . 22 119 | 6.5. pathStart . . . . . . . . . . . . . . . . . . . . . . . . 22 120 | 6.6. mediaType . . . . . . . . . . . . . . . . . . . . . . . . 22 121 | 7. Security Considerations . . . . . . . . . . . . . . . . . . . 22 122 | 8. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 23 123 | 8.1. Registry of Link Relations . . . . . . . . . . . . . . . . 24 124 | 9. References . . . . . . . . . . . . . . . . . . . . . . . . . . 24 125 | 9.1. Normative References . . . . . . . . . . . . . . . . . . . 24 126 | 9.2. Informative References . . . . . . . . . . . . . . . . . . 24 127 | Appendix A. Change Log . . . . . . . . . . . . . . . . . . . . . 26 128 | Appendix B. Open Issues . . . . . . . . . . . . . . . . . . . . . 27 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | Zyp & Court Expires May 26, 2011 [Page 3] 168 | 169 | Internet-Draft JSON Schema Media Type November 2010 170 | 171 | 172 | 1. Introduction 173 | 174 | JSON (JavaScript Object Notation) Schema is a JSON media type for 175 | defining the structure of JSON data. JSON Schema provides a contract 176 | for what JSON data is required for a given application and how to 177 | interact with it. JSON Schema is intended to define validation, 178 | documentation, hyperlink navigation, and interaction control of JSON 179 | data. 180 | 181 | 2. Conventions 182 | 183 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", 184 | "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this 185 | document are to be interpreted as described in RFC 2119 [RFC2119]. 186 | 187 | 3. Overview 188 | 189 | JSON Schema defines the media type "application/schema+json" for 190 | describing the structure of other JSON documents. JSON Schema is 191 | JSON-based and includes facilities for describing the structure of 192 | JSON documents in terms of allowable values, descriptions, and 193 | interpreting relations with other resources. 194 | 195 | JSON Schema format is organized into several separate definitions. 196 | The first definition is the core schema specification. This 197 | definition is primary concerned with describing a JSON structure and 198 | specifying valid elements in the structure. The second definition is 199 | the Hyper Schema specification which is intended define elements in a 200 | structure that can be interpreted as hyperlinks. Hyper Schema builds 201 | on JSON Schema to describe the hyperlink structure of other JSON 202 | documents and elements of interaction. This allows user agents to be 203 | able to successfully navigate JSON documents based on their schemas. 204 | 205 | Cumulatively JSON Schema acts as a meta-document that can be used to 206 | define the required type and constraints on property values, as well 207 | as define the meaning of the property values for the purpose of 208 | describing a resource and determining hyperlinks within the 209 | representation. 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | Zyp & Court Expires May 26, 2011 [Page 4] 224 | 225 | Internet-Draft JSON Schema Media Type November 2010 226 | 227 | 228 | An example JSON Schema that describes products might look like: 229 | 230 | { 231 | "name":"Product", 232 | "properties":{ 233 | "id":{ 234 | "type":"number", 235 | "description":"Product identifier", 236 | "required":true 237 | }, 238 | "name":{ 239 | "description":"Name of the product", 240 | "type":"string", 241 | "required":true 242 | }, 243 | "price":{ 244 | "required":true, 245 | "type": "number", 246 | "minimum":0, 247 | "required":true 248 | }, 249 | "tags":{ 250 | "type":"array", 251 | "items":{ 252 | "type":"string" 253 | } 254 | } 255 | }, 256 | "links":[ 257 | { 258 | "rel":"full", 259 | "href":"{id}" 260 | }, 261 | { 262 | "rel":"comments", 263 | "href":"comments/?id={id}" 264 | } 265 | ] 266 | } 267 | 268 | This schema defines the properties of the instance JSON documents, 269 | the required properties (id, name, and price), as well as an optional 270 | property (tags). This also defines the link relations of the 271 | instance JSON documents. 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | Zyp & Court Expires May 26, 2011 [Page 5] 280 | 281 | Internet-Draft JSON Schema Media Type November 2010 282 | 283 | 284 | 3.1. Terminology 285 | 286 | For this specification, *schema* will be used to denote a JSON Schema 287 | definition, and an *instance* refers to a JSON value that the schema 288 | will be describing and validating. 289 | 290 | 3.2. Design Considerations 291 | 292 | The JSON Schema media type does not attempt to dictate the structure 293 | of JSON representations that contain data, but rather provides a 294 | separate format for flexibly communicating how a JSON representation 295 | should be interpreted and validated, such that user agents can 296 | properly understand acceptable structures and extrapolate hyperlink 297 | information with the JSON document. It is acknowledged that JSON 298 | documents come in a variety of structures, and JSON is unique in that 299 | the structure of stored data structures often prescribes a non- 300 | ambiguous definite JSON representation. Attempting to force a 301 | specific structure is generally not viable, and therefore JSON Schema 302 | allows for a great flexibility in the structure of the JSON data that 303 | it describes. 304 | 305 | This specification is protocol agnostic. The underlying protocol 306 | (such as HTTP) should sufficiently define the semantics of the 307 | client-server interface, the retrieval of resource representations 308 | linked to by JSON representations, and modification of those 309 | resources. The goal of this format is to sufficiently describe JSON 310 | structures such that one can utilize existing information available 311 | in existing JSON representations from a large variety of services 312 | that leverage a representational state transfer architecture using 313 | existing protocols. 314 | 315 | 4. Schema/Instance Association 316 | 317 | JSON Schema instances are correlated to their schema by the 318 | "describedby" relation, where the schema is defined to be the target 319 | of the relation. Instance representations may be of the 320 | "application/json" media type or any other subtype. Consequently, 321 | dictating how an instance representation should specify the relation 322 | to the schema is beyond the normative scope of this document (since 323 | this document specifically defines the JSON Schema media type, and no 324 | other), but it is recommended that instances specify their schema so 325 | that user agents can interpret the instance representation and 326 | messages may retain the self-descriptive characteristic, avoiding the 327 | need for out-of-band information about instance data. Two approaches 328 | are recommended for declaring the relation to the schema that 329 | describes the meaning of a JSON instance's (or collection of 330 | instances) structure. A MIME type parameter named "profile" or a 331 | relation of "describedby" (which could be defined by a Link header) 332 | 333 | 334 | 335 | Zyp & Court Expires May 26, 2011 [Page 6] 336 | 337 | Internet-Draft JSON Schema Media Type November 2010 338 | 339 | 340 | may be used: 341 | 342 | Content-Type: application/my-media-type+json; 343 | profile=http://json.com/my-hyper-schema 344 | 345 | or if the content is being transferred by a protocol (such as HTTP) 346 | that provides headers, a Link header can be used: 347 | 348 | Link: ; rel="describedby" 349 | 350 | Instances MAY specify multiple schemas, to indicate all the schemas 351 | that are applicable to the data, and the data SHOULD be valid by all 352 | the schemas. The instance data MAY have multiple schemas that it is 353 | defined by (the instance data SHOULD be valid for those schemas). Or 354 | if the document is a collection of instances, the collection MAY 355 | contain instances from different schemas. When collections contain 356 | heterogeneous instances, the "pathStart" attribute MAY be specified 357 | in the schema to disambiguate which schema should be applied for each 358 | item in the collection. However, ultimately, the mechanism for 359 | referencing a schema is up to the media type of the instance 360 | documents (if they choose to specify that schemas can be referenced). 361 | 362 | 4.1. Self-Descriptive Schema 363 | 364 | JSON Schemas can themselves be described using JSON Schemas. A self- 365 | describing JSON Schema for the core JSON Schema can be found at 366 | http://json-schema.org/schema for the latest version or 367 | http://json-schema.org/draft-03/schema for the draft-03 version. The 368 | hyper schema self-description can be found at 369 | http://json-schema.org/hyper-schema or 370 | http://json-schema.org/draft-03/hyper-schema. All schemas used 371 | within a protocol with media type definitions SHOULD include a MIME 372 | parameter that refers to the self-descriptive hyper schema or another 373 | schema that extends this hyper schema: 374 | 375 | Content-Type: application/json; 376 | profile=http://json-schema.org/draft-03/hyper-schema 377 | 378 | 5. Core Schema Definition 379 | 380 | A JSON Schema is a JSON Object that defines various attributes 381 | (including usage and valid values) of a JSON value. JSON Schema has 382 | recursive capabilities; there are a number of elements in the 383 | structure that allow for nested JSON Schemas. 384 | 385 | An example JSON Schema definition could look like: 386 | 387 | 388 | 389 | 390 | 391 | Zyp & Court Expires May 26, 2011 [Page 7] 392 | 393 | Internet-Draft JSON Schema Media Type November 2010 394 | 395 | 396 | { 397 | "description":"A person", 398 | "type":"object", 399 | 400 | "properties":{ 401 | "name":{"type":"string"}, 402 | "age" :{ 403 | "type":"integer", 404 | "maximum":125 405 | } 406 | } 407 | } 408 | 409 | A JSON Schema object may have any of the following properties, called 410 | schema attributes (all attributes are optional): 411 | 412 | 5.1. type 413 | 414 | This attribute defines what the primitive type or the schema of the 415 | instance MUST be in order to validate. This attribute can take one 416 | of two forms: 417 | 418 | Simple Types A string indicating a primitive or simple type. The 419 | following are acceptable string values: 420 | 421 | string Value MUST be a string. 422 | 423 | number Value MUST be a number, floating point numbers are 424 | allowed. 425 | 426 | integer Value MUST be an integer, no floating point numbers are 427 | allowed. This is a subset of the number type. 428 | 429 | boolean Value MUST be a boolean. 430 | 431 | object Value MUST be an object. 432 | 433 | array Value MUST be an array. 434 | 435 | null Value MUST be null. Note this is mainly for purpose of 436 | being able use union types to define nullability. If this type 437 | is not included in a union, null values are not allowed (the 438 | primitives listed above do not allow nulls on their own). 439 | 440 | any Value MAY be of any type including null. 441 | 442 | If the property is not defined or is not in this list, then any 443 | type of value is acceptable. Other type values MAY be used for 444 | 445 | 446 | 447 | Zyp & Court Expires May 26, 2011 [Page 8] 448 | 449 | Internet-Draft JSON Schema Media Type November 2010 450 | 451 | 452 | custom purposes, but minimal validators of the specification 453 | implementation can allow any instance value on unknown type 454 | values. 455 | 456 | Union Types An array of two or more simple type definitions. Each 457 | item in the array MUST be a simple type definition or a schema. 458 | The instance value is valid if it is of the same type as one of 459 | the simple type definitions, or valid by one of the schemas, in 460 | the array. 461 | 462 | For example, a schema that defines if an instance can be a string or 463 | a number would be: 464 | 465 | {"type":["string","number"]} 466 | 467 | 5.2. properties 468 | 469 | This attribute is an object with property definitions that define the 470 | valid values of instance object property values. When the instance 471 | value is an object, the property values of the instance object MUST 472 | conform to the property definitions in this object. In this object, 473 | each property definition's value MUST be a schema, and the property's 474 | name MUST be the name of the instance property that it defines. The 475 | instance property value MUST be valid according to the schema from 476 | the property definition. Properties are considered unordered, the 477 | order of the instance properties MAY be in any order. 478 | 479 | 5.3. patternProperties 480 | 481 | This attribute is an object that defines the schema for a set of 482 | property names of an object instance. The name of each property of 483 | this attribute's object is a regular expression pattern in the ECMA 484 | 262/Perl 5 format, while the value is a schema. If the pattern 485 | matches the name of a property on the instance object, the value of 486 | the instance's property MUST be valid against the pattern name's 487 | schema value. 488 | 489 | 5.4. additionalProperties 490 | 491 | This attribute defines a schema for all properties that are not 492 | explicitly defined in an object type definition. If specified, the 493 | value MUST be a schema or a boolean. If false is provided, no 494 | additional properties are allowed beyond the properties defined in 495 | the schema. The default value is an empty schema which allows any 496 | value for additional properties. 497 | 498 | 499 | 500 | 501 | 502 | 503 | Zyp & Court Expires May 26, 2011 [Page 9] 504 | 505 | Internet-Draft JSON Schema Media Type November 2010 506 | 507 | 508 | 5.5. items 509 | 510 | This attribute defines the allowed items in an instance array, and 511 | MUST be a schema or an array of schemas. The default value is an 512 | empty schema which allows any value for items in the instance array. 513 | 514 | When this attribute value is a schema and the instance value is an 515 | array, then all the items in the array MUST be valid according to the 516 | schema. 517 | 518 | When this attribute value is an array of schemas and the instance 519 | value is an array, each position in the instance array MUST conform 520 | to the schema in the corresponding position for this array. This 521 | called tuple typing. When tuple typing is used, additional items are 522 | allowed, disallowed, or constrained by the "additionalItems" 523 | (Section 5.6) attribute using the same rules as 524 | "additionalProperties" (Section 5.4) for objects. 525 | 526 | 5.6. additionalItems 527 | 528 | This provides a definition for additional items in an array instance 529 | when tuple definitions of the items is provided. This can be false 530 | to indicate additional items in the array are not allowed, or it can 531 | be a schema that defines the schema of the additional items. 532 | 533 | 5.7. required 534 | 535 | This attribute indicates if the instance must have a value, and not 536 | be undefined. This is false by default, making the instance 537 | optional. 538 | 539 | 5.8. dependencies 540 | 541 | This attribute is an object that defines the requirements of a 542 | property on an instance object. If an object instance has a property 543 | with the same name as a property in this attribute's object, then the 544 | instance must be valid against the attribute's property value 545 | (hereafter referred to as the "dependency value"). 546 | 547 | The dependency value can take one of two forms: 548 | 549 | Simple Dependency If the dependency value is a string, then the 550 | instance object MUST have a property with the same name as the 551 | dependency value. If the dependency value is an array of strings, 552 | then the instance object MUST have a property with the same name 553 | as each string in the dependency value's array. 554 | 555 | 556 | 557 | 558 | 559 | Zyp & Court Expires May 26, 2011 [Page 10] 560 | 561 | Internet-Draft JSON Schema Media Type November 2010 562 | 563 | 564 | Schema Dependency If the dependency value is a schema, then the 565 | instance object MUST be valid against the schema. 566 | 567 | 5.9. minimum 568 | 569 | This attribute defines the minimum value of the instance property 570 | when the type of the instance value is a number. 571 | 572 | 5.10. maximum 573 | 574 | This attribute defines the maximum value of the instance property 575 | when the type of the instance value is a number. 576 | 577 | 5.11. exclusiveMinimum 578 | 579 | This attribute indicates if the value of the instance (if the 580 | instance is a number) can not equal the number defined by the 581 | "minimum" attribute. This is false by default, meaning the instance 582 | value can be greater then or equal to the minimum value. 583 | 584 | 5.12. exclusiveMaximum 585 | 586 | This attribute indicates if the value of the instance (if the 587 | instance is a number) can not equal the number defined by the 588 | "maximum" attribute. This is false by default, meaning the instance 589 | value can be less then or equal to the maximum value. 590 | 591 | 5.13. minItems 592 | 593 | This attribute defines the minimum number of values in an array when 594 | the array is the instance value. 595 | 596 | 5.14. maxItems 597 | 598 | This attribute defines the maximum number of values in an array when 599 | the array is the instance value. 600 | 601 | 5.15. uniqueItems 602 | 603 | This attribute indicates that all items in an array instance MUST be 604 | unique (contains no two identical values). 605 | 606 | Two instance are consider equal if they are both of the same type 607 | and: 608 | 609 | are null; or 610 | 611 | 612 | 613 | 614 | 615 | Zyp & Court Expires May 26, 2011 [Page 11] 616 | 617 | Internet-Draft JSON Schema Media Type November 2010 618 | 619 | 620 | are booleans/numbers/strings and have the same value; or 621 | 622 | are arrays, contains the same number of items, and each item in 623 | the array is equal to the corresponding item in the other array; 624 | or 625 | 626 | are objects, contains the same property names, and each property 627 | in the object is equal to the corresponding property in the other 628 | object. 629 | 630 | 5.16. pattern 631 | 632 | When the instance value is a string, this provides a regular 633 | expression that a string instance MUST match in order to be valid. 634 | Regular expressions SHOULD follow the regular expression 635 | specification from ECMA 262/Perl 5 636 | 637 | 5.17. minLength 638 | 639 | When the instance value is a string, this defines the minimum length 640 | of the string. 641 | 642 | 5.18. maxLength 643 | 644 | When the instance value is a string, this defines the maximum length 645 | of the string. 646 | 647 | 5.19. enum 648 | 649 | This provides an enumeration of all possible values that are valid 650 | for the instance property. This MUST be an array, and each item in 651 | the array represents a possible value for the instance value. If 652 | this attribute is defined, the instance value MUST be one of the 653 | values in the array in order for the schema to be valid. Comparison 654 | of enum values uses the same algorithm as defined in "uniqueItems" 655 | (Section 5.15). 656 | 657 | 5.20. default 658 | 659 | This attribute defines the default value of the instance when the 660 | instance is undefined. 661 | 662 | 5.21. title 663 | 664 | This attribute is a string that provides a short description of the 665 | instance property. 666 | 667 | 668 | 669 | 670 | 671 | Zyp & Court Expires May 26, 2011 [Page 12] 672 | 673 | Internet-Draft JSON Schema Media Type November 2010 674 | 675 | 676 | 5.22. description 677 | 678 | This attribute is a string that provides a full description of the of 679 | purpose the instance property. 680 | 681 | 5.23. format 682 | 683 | This property defines the type of data, content type, or microformat 684 | to be expected in the instance property values. A format attribute 685 | MAY be one of the values listed below, and if so, SHOULD adhere to 686 | the semantics describing for the format. A format SHOULD only be 687 | used to give meaning to primitive types (string, integer, number, or 688 | boolean). Validators MAY (but are not required to) validate that the 689 | instance values conform to a format. The following formats are 690 | predefined: 691 | 692 | date-time This SHOULD be a date in ISO 8601 format of YYYY-MM- 693 | DDThh:mm:ssZ in UTC time. This is the recommended form of date/ 694 | timestamp. 695 | 696 | date This SHOULD be a date in the format of YYYY-MM-DD. It is 697 | recommended that you use the "date-time" format instead of "date" 698 | unless you need to transfer only the date part. 699 | 700 | time This SHOULD be a time in the format of hh:mm:ss. It is 701 | recommended that you use the "date-time" format instead of "time" 702 | unless you need to transfer only the time part. 703 | 704 | utc-millisec This SHOULD be the difference, measured in 705 | milliseconds, between the specified time and midnight, 00:00 of 706 | January 1, 1970 UTC. The value SHOULD be a number (integer or 707 | float). 708 | 709 | regex A regular expression, following the regular expression 710 | specification from ECMA 262/Perl 5. 711 | 712 | color This is a CSS color (like "#FF0000" or "red"), based on CSS 713 | 2.1 [W3C.CR-CSS21-20070719]. 714 | 715 | style This is a CSS style definition (like "color: red; background- 716 | color:#FFF"), based on CSS 2.1 [W3C.CR-CSS21-20070719]. 717 | 718 | phone This SHOULD be a phone number (format MAY follow E.123). 719 | 720 | uri This value SHOULD be a URI.. 721 | 722 | 723 | 724 | 725 | 726 | 727 | Zyp & Court Expires May 26, 2011 [Page 13] 728 | 729 | Internet-Draft JSON Schema Media Type November 2010 730 | 731 | 732 | email This SHOULD be an email address. 733 | 734 | ip-address This SHOULD be an ip version 4 address. 735 | 736 | ipv6 This SHOULD be an ip version 6 address. 737 | 738 | host-name This SHOULD be a host-name. 739 | 740 | Additional custom formats MAY be created. These custom formats MAY 741 | be expressed as an URI, and this URI MAY reference a schema of that 742 | format. 743 | 744 | 5.24. divisibleBy 745 | 746 | This attribute defines what value the number instance must be 747 | divisible by with no remainder (the result of the division must be an 748 | integer.) The value of this attribute SHOULD NOT be 0. 749 | 750 | 5.25. disallow 751 | 752 | This attribute takes the same values as the "type" attribute, however 753 | if the instance matches the type or if this value is an array and the 754 | instance matches any type or schema in the array, then this instance 755 | is not valid. 756 | 757 | 5.26. extends 758 | 759 | The value of this property MUST be another schema which will provide 760 | a base schema which the current schema will inherit from. The 761 | inheritance rules are such that any instance that is valid according 762 | to the current schema MUST be valid according to the referenced 763 | schema. This MAY also be an array, in which case, the instance MUST 764 | be valid for all the schemas in the array. A schema that extends 765 | another schema MAY define additional attributes, constrain existing 766 | attributes, or add other constraints. 767 | 768 | Conceptually, the behavior of extends can be seen as validating an 769 | instance against all constraints in the extending schema as well as 770 | the extended schema(s). More optimized implementations that merge 771 | schemas are possible, but are not required. An example of using 772 | "extends": 773 | 774 | { 775 | "description":"An adult", 776 | "properties":{"age":{"minimum": 21}}, 777 | "extends":"person" 778 | } 779 | 780 | 781 | 782 | 783 | Zyp & Court Expires May 26, 2011 [Page 14] 784 | 785 | Internet-Draft JSON Schema Media Type November 2010 786 | 787 | 788 | { 789 | "description":"Extended schema", 790 | "properties":{"deprecated":{"type": "boolean"}}, 791 | "extends":"http://json-schema.org/draft-03/schema" 792 | } 793 | 794 | 5.27. id 795 | 796 | This attribute defines the current URI of this schema (this attribute 797 | is effectively a "self" link). This URI MAY be relative or absolute. 798 | If the URI is relative it is resolved against the current URI of the 799 | parent schema it is contained in. If this schema is not contained in 800 | any parent schema, the current URI of the parent schema is held to be 801 | the URI under which this schema was addressed. If id is missing, the 802 | current URI of a schema is defined to be that of the parent schema. 803 | The current URI of the schema is also used to construct relative 804 | references such as for $ref. 805 | 806 | 5.28. $ref 807 | 808 | This attribute defines a URI of a schema that contains the full 809 | representation of this schema. When a validator encounters this 810 | attribute, it SHOULD replace the current schema with the schema 811 | referenced by the value's URI (if known and available) and re- 812 | validate the instance. This URI MAY be relative or absolute, and 813 | relative URIs SHOULD be resolved against the URI of the current 814 | schema. 815 | 816 | 5.29. $schema 817 | 818 | This attribute defines a URI of a JSON Schema that is the schema of 819 | the current schema. When this attribute is defined, a validator 820 | SHOULD use the schema referenced by the value's URI (if known and 821 | available) when resolving Hyper Schema (Section 6) links 822 | (Section 6.1). 823 | 824 | A validator MAY use this attribute's value to determine which version 825 | of JSON Schema the current schema is written in, and provide the 826 | appropriate validation features and behavior. Therefore, it is 827 | RECOMMENDED that all schema authors include this attribute in their 828 | schemas to prevent conflicts with future JSON Schema specification 829 | changes. 830 | 831 | 6. Hyper Schema 832 | 833 | The following attributes are specified in addition to those 834 | attributes that already provided by the core schema with the specific 835 | purpose of informing user agents of relations between resources based 836 | 837 | 838 | 839 | Zyp & Court Expires May 26, 2011 [Page 15] 840 | 841 | Internet-Draft JSON Schema Media Type November 2010 842 | 843 | 844 | on JSON data. Just as with JSON schema attributes, all the 845 | attributes in hyper schemas are optional. Therefore, an empty object 846 | is a valid (non-informative) schema, and essentially describes plain 847 | JSON (no constraints on the structures). Addition of attributes 848 | provides additive information for user agents. 849 | 850 | 6.1. links 851 | 852 | The value of the links property MUST be an array, where each item in 853 | the array is a link description object which describes the link 854 | relations of the instances. 855 | 856 | 6.1.1. Link Description Object 857 | 858 | A link description object is used to describe link relations. In the 859 | context of a schema, it defines the link relations of the instances 860 | of the schema, and can be parameterized by the instance values. The 861 | link description format can be used on its own in regular (non-schema 862 | documents), and use of this format can be declared by referencing the 863 | normative link description schema as the the schema for the data 864 | structure that uses the links. The URI of the normative link 865 | description schema is: http://json-schema.org/links (latest version) 866 | or http://json-schema.org/draft-03/links (draft-03 version). 867 | 868 | 6.1.1.1. href 869 | 870 | The value of the "href" link description property indicates the 871 | target URI of the related resource. The value of the instance 872 | property SHOULD be resolved as a URI-Reference per RFC 3986 [RFC3986] 873 | and MAY be a relative URI. The base URI to be used for relative 874 | resolution SHOULD be the URI used to retrieve the instance object 875 | (not the schema) when used within a schema. Also, when links are 876 | used within a schema, the URI SHOULD be parametrized by the property 877 | values of the instance object, if property values exist for the 878 | corresponding variables in the template (otherwise they MAY be 879 | provided from alternate sources, like user input). 880 | 881 | Instance property values SHOULD be substituted into the URIs where 882 | matching braces ('{', '}') are found surrounding zero or more 883 | characters, creating an expanded URI. Instance property value 884 | substitutions are resolved by using the text between the braces to 885 | denote the property name from the instance to get the value to 886 | substitute. For example, if an href value is defined: 887 | 888 | http://somesite.com/{id} 889 | 890 | Then it would be resolved by replace the value of the "id" property 891 | value from the instance object. If the value of the "id" property 892 | 893 | 894 | 895 | Zyp & Court Expires May 26, 2011 [Page 16] 896 | 897 | Internet-Draft JSON Schema Media Type November 2010 898 | 899 | 900 | was "45", the expanded URI would be: 901 | 902 | http://somesite.com/45 903 | 904 | If matching braces are found with the string "@" (no quotes) between 905 | the braces, then the actual instance value SHOULD be used to replace 906 | the braces, rather than a property value. This should only be used 907 | in situations where the instance is a scalar (string, boolean, or 908 | number), and not for objects or arrays. 909 | 910 | 6.1.1.2. rel 911 | 912 | The value of the "rel" property indicates the name of the relation to 913 | the target resource. The relation to the target SHOULD be 914 | interpreted as specifically from the instance object that the schema 915 | (or sub-schema) applies to, not just the top level resource that 916 | contains the object within its hierarchy. If a resource JSON 917 | representation contains a sub object with a property interpreted as a 918 | link, that sub-object holds the relation with the target. A relation 919 | to target from the top level resource MUST be indicated with the 920 | schema describing the top level JSON representation. 921 | 922 | Relationship definitions SHOULD NOT be media type dependent, and 923 | users are encouraged to utilize existing accepted relation 924 | definitions, including those in existing relation registries (see RFC 925 | 4287 [RFC4287]). However, we define these relations here for clarity 926 | of normative interpretation within the context of JSON hyper schema 927 | defined relations: 928 | 929 | self If the relation value is "self", when this property is 930 | encountered in the instance object, the object represents a 931 | resource and the instance object is treated as a full 932 | representation of the target resource identified by the specified 933 | URI. 934 | 935 | full This indicates that the target of the link is the full 936 | representation for the instance object. The object that contains 937 | this link possibly may not be the full representation. 938 | 939 | describedby This indicates the target of the link is the schema for 940 | the instance object. This MAY be used to specifically denote the 941 | schemas of objects within a JSON object hierarchy, facilitating 942 | polymorphic type data structures. 943 | 944 | root This relation indicates that the target of the link SHOULD be 945 | treated as the root or the body of the representation for the 946 | purposes of user agent interaction or fragment resolution. All 947 | other properties of the instance objects can be regarded as meta- 948 | 949 | 950 | 951 | Zyp & Court Expires May 26, 2011 [Page 17] 952 | 953 | Internet-Draft JSON Schema Media Type November 2010 954 | 955 | 956 | data descriptions for the data. 957 | 958 | The following relations are applicable for schemas (the schema as the 959 | "from" resource in the relation): 960 | 961 | instances This indicates the target resource that represents 962 | collection of instances of a schema. 963 | 964 | create This indicates a target to use for creating new instances of 965 | a schema. This link definition SHOULD be a submission link with a 966 | non-safe method (like POST). 967 | 968 | For example, if a schema is defined: 969 | 970 | { 971 | "links": [ 972 | { 973 | "rel": "self" 974 | "href": "{id}" 975 | }, 976 | { 977 | "rel": "up" 978 | "href": "{upId}" 979 | }, 980 | { 981 | "rel": "children" 982 | "href": "?upId={id}" 983 | } 984 | ] 985 | } 986 | 987 | And if a collection of instance resource's JSON representation was 988 | retrieved: 989 | 990 | GET /Resource/ 991 | 992 | [ 993 | { 994 | "id": "thing", 995 | "upId": "parent" 996 | }, 997 | { 998 | "id": "thing2", 999 | "upId": "parent" 1000 | } 1001 | ] 1002 | 1003 | This would indicate that for the first item in the collection, its 1004 | 1005 | 1006 | 1007 | Zyp & Court Expires May 26, 2011 [Page 18] 1008 | 1009 | Internet-Draft JSON Schema Media Type November 2010 1010 | 1011 | 1012 | own (self) URI would resolve to "/Resource/thing" and the first 1013 | item's "up" relation SHOULD be resolved to the resource at 1014 | "/Resource/parent". The "children" collection would be located at 1015 | "/Resource/?upId=thing". 1016 | 1017 | 6.1.1.3. targetSchema 1018 | 1019 | This property value is a schema that defines the expected structure 1020 | of the JSON representation of the target of the link. 1021 | 1022 | 6.1.1.4. Submission Link Properties 1023 | 1024 | The following properties also apply to link definition objects, and 1025 | provide functionality analogous to HTML forms, in providing a means 1026 | for submitting extra (often user supplied) information to send to a 1027 | server. 1028 | 1029 | 6.1.1.4.1. method 1030 | 1031 | This attribute defines which method can be used to access the target 1032 | resource. In an HTTP environment, this would be "GET" or "POST" 1033 | (other HTTP methods such as "PUT" and "DELETE" have semantics that 1034 | are clearly implied by accessed resources, and do not need to be 1035 | defined here). This defaults to "GET". 1036 | 1037 | 6.1.1.4.2. enctype 1038 | 1039 | If present, this property indicates a query media type format that 1040 | the server supports for querying or posting to the collection of 1041 | instances at the target resource. The query can be suffixed to the 1042 | target URI to query the collection with property-based constraints on 1043 | the resources that SHOULD be returned from the server or used to post 1044 | data to the resource (depending on the method). For example, with 1045 | the following schema: 1046 | 1047 | { 1048 | "links":[ 1049 | { 1050 | "enctype":"application/x-www-form-urlencoded", 1051 | "method":"GET", 1052 | "href":"/Product/", 1053 | "properties":{ 1054 | "name":{"description":"name of the product"} 1055 | } 1056 | } 1057 | ] 1058 | } 1059 | 1060 | 1061 | 1062 | 1063 | Zyp & Court Expires May 26, 2011 [Page 19] 1064 | 1065 | Internet-Draft JSON Schema Media Type November 2010 1066 | 1067 | 1068 | This indicates that the client can query the server for instances 1069 | that have a specific name: 1070 | 1071 | /Product/?name=Slinky 1072 | 1073 | If no enctype or method is specified, only the single URI specified 1074 | by the href property is defined. If the method is POST, 1075 | "application/json" is the default media type. 1076 | 1077 | 6.1.1.4.3. schema 1078 | 1079 | This attribute contains a schema which defines the acceptable 1080 | structure of the submitted request (for a GET request, this schema 1081 | would define the properties for the query string and for a POST 1082 | request, this would define the body). 1083 | 1084 | 6.2. fragmentResolution 1085 | 1086 | This property indicates the fragment resolution protocol to use for 1087 | resolving fragment identifiers in URIs within the instance 1088 | representations. This applies to the instance object URIs and all 1089 | children of the instance object's URIs. The default fragment 1090 | resolution protocol is "slash-delimited", which is defined below. 1091 | Other fragment resolution protocols MAY be used, but are not defined 1092 | in this document. 1093 | 1094 | The fragment identifier is based on RFC 2396, Sec 5 [RFC2396], and 1095 | defines the mechanism for resolving references to entities within a 1096 | document. 1097 | 1098 | 6.2.1. slash-delimited fragment resolution 1099 | 1100 | With the slash-delimited fragment resolution protocol, the fragment 1101 | identifier is interpreted as a series of property reference tokens 1102 | that start with and are delimited by the "/" character (\x2F). Each 1103 | property reference token is a series of unreserved or escaped URI 1104 | characters. Each property reference token SHOULD be interpreted, 1105 | starting from the beginning of the fragment identifier, as a path 1106 | reference in the target JSON structure. The final target value of 1107 | the fragment can be determined by starting with the root of the JSON 1108 | structure from the representation of the resource identified by the 1109 | pre-fragment URI. If the target is a JSON object, then the new 1110 | target is the value of the property with the name identified by the 1111 | next property reference token in the fragment. If the target is a 1112 | JSON array, then the target is determined by finding the item in 1113 | array the array with the index defined by the next property reference 1114 | token (which MUST be a number). The target is successively updated 1115 | for each property reference token, until the entire fragment has been 1116 | 1117 | 1118 | 1119 | Zyp & Court Expires May 26, 2011 [Page 20] 1120 | 1121 | Internet-Draft JSON Schema Media Type November 2010 1122 | 1123 | 1124 | traversed. 1125 | 1126 | Property names SHOULD be URI-encoded. In particular, any "/" in a 1127 | property name MUST be encoded to avoid being interpreted as a 1128 | property delimiter. 1129 | 1130 | For example, for the following JSON representation: 1131 | 1132 | { 1133 | "foo":{ 1134 | "anArray":[ 1135 | {"prop":44} 1136 | ], 1137 | "another prop":{ 1138 | "baz":"A string" 1139 | } 1140 | } 1141 | } 1142 | 1143 | The following fragment identifiers would be resolved: 1144 | 1145 | fragment identifier resolution 1146 | ------------------- ---------- 1147 | # self, the root of the resource itself 1148 | #/foo the object referred to by the foo property 1149 | #/foo/another%20prop the object referred to by the "another prop" 1150 | property of the object referred to by the 1151 | "foo" property 1152 | #/foo/another%20prop/baz the string referred to by the value of "baz" 1153 | property of the "another prop" property of 1154 | the object referred to by the "foo" property 1155 | #/foo/anArray/0 the first object in the "anArray" array 1156 | 1157 | 6.2.2. dot-delimited fragment resolution 1158 | 1159 | The dot-delimited fragment resolution protocol is the same as slash- 1160 | delimited fragment resolution protocol except that the "." character 1161 | (\x2E) is used as the delimiter between property names (instead of 1162 | "/") and the path does not need to start with a ".". For example, 1163 | #.foo and #foo are a valid fragment identifiers for referencing the 1164 | value of the foo propery. 1165 | 1166 | 6.3. readonly 1167 | 1168 | This attribute indicates that the instance property SHOULD NOT be 1169 | changed. Attempts by a user agent to modify the value of this 1170 | property are expected to be rejected by a server. 1171 | 1172 | 1173 | 1174 | 1175 | Zyp & Court Expires May 26, 2011 [Page 21] 1176 | 1177 | Internet-Draft JSON Schema Media Type November 2010 1178 | 1179 | 1180 | 6.4. contentEncoding 1181 | 1182 | If the instance property value is a string, this attribute defines 1183 | that the string SHOULD be interpreted as binary data and decoded 1184 | using the encoding named by this schema property. RFC 2045, Sec 6.1 1185 | [RFC2045] lists the possible values for this property. 1186 | 1187 | 6.5. pathStart 1188 | 1189 | This attribute is a URI that defines what the instance's URI MUST 1190 | start with in order to validate. The value of the "pathStart" 1191 | attribute MUST be resolved as per RFC 3986, Sec 5 [RFC3986], and is 1192 | relative to the instance's URI. 1193 | 1194 | When multiple schemas have been referenced for an instance, the user 1195 | agent can determine if this schema is applicable for a particular 1196 | instance by determining if the URI of the instance begins with the 1197 | the value of the "pathStart" attribute. If the URI of the instance 1198 | does not start with this URI, or if another schema specifies a 1199 | starting URI that is longer and also matches the instance, this 1200 | schema SHOULD NOT be applied to the instance. Any schema that does 1201 | not have a pathStart attribute SHOULD be considered applicable to all 1202 | the instances for which it is referenced. 1203 | 1204 | 6.6. mediaType 1205 | 1206 | This attribute defines the media type of the instance representations 1207 | that this schema is defining. 1208 | 1209 | 7. Security Considerations 1210 | 1211 | This specification is a sub-type of the JSON format, and consequently 1212 | the security considerations are generally the same as RFC 4627 1213 | [RFC4627]. However, an additional issue is that when link relation 1214 | of "self" is used to denote a full representation of an object, the 1215 | user agent SHOULD NOT consider the representation to be the 1216 | authoritative representation of the resource denoted by the target 1217 | URI if the target URI is not equivalent to or a sub-path of the the 1218 | URI used to request the resource representation which contains the 1219 | target URI with the "self" link. For example, if a hyper schema was 1220 | defined: 1221 | 1222 | 1223 | 1224 | 1225 | 1226 | 1227 | 1228 | 1229 | 1230 | 1231 | Zyp & Court Expires May 26, 2011 [Page 22] 1232 | 1233 | Internet-Draft JSON Schema Media Type November 2010 1234 | 1235 | 1236 | { 1237 | "links":[ 1238 | { 1239 | "rel":"self", 1240 | "href":"{id}" 1241 | } 1242 | ] 1243 | } 1244 | 1245 | And a resource was requested from somesite.com: 1246 | 1247 | GET /foo/ 1248 | 1249 | With a response of: 1250 | 1251 | Content-Type: application/json; profile=/schema-for-this-data 1252 | [ 1253 | {"id":"bar", "name":"This representation can be safely treated \ 1254 | as authoritative "}, 1255 | {"id":"/baz", "name":"This representation should not be treated as \ 1256 | authoritative the user agent should make request the resource\ 1257 | from "/baz" to ensure it has the authoritative representation"}, 1258 | {"id":"http://othersite.com/something", "name":"This representation\ 1259 | should also not be treated as authoritative and the target\ 1260 | resource representation should be retrieved for the\ 1261 | authoritative representation"} 1262 | ] 1263 | 1264 | 8. IANA Considerations 1265 | 1266 | The proposed MIME media type for JSON Schema is "application/ 1267 | schema+json". 1268 | 1269 | Type name: application 1270 | 1271 | Subtype name: schema+json 1272 | 1273 | Required parameters: profile 1274 | 1275 | The value of the profile parameter SHOULD be a URI (relative or 1276 | absolute) that refers to the schema used to define the structure of 1277 | this structure (the meta-schema). Normally the value would be 1278 | http://json-schema.org/draft-03/hyper-schema, but it is allowable to 1279 | use other schemas that extend the hyper schema's meta- schema. 1280 | 1281 | Optional parameters: pretty 1282 | 1283 | The value of the pretty parameter MAY be true or false to indicate if 1284 | 1285 | 1286 | 1287 | Zyp & Court Expires May 26, 2011 [Page 23] 1288 | 1289 | Internet-Draft JSON Schema Media Type November 2010 1290 | 1291 | 1292 | additional whitespace has been included to make the JSON 1293 | representation easier to read. 1294 | 1295 | 8.1. Registry of Link Relations 1296 | 1297 | This registry is maintained by IANA per RFC 4287 [RFC4287] and this 1298 | specification adds four values: "full", "create", "instances", 1299 | "root". New assignments are subject to IESG Approval, as outlined in 1300 | RFC 5226 [RFC5226]. Requests should be made by email to IANA, which 1301 | will then forward the request to the IESG, requesting approval. 1302 | 1303 | 9. References 1304 | 1305 | 9.1. Normative References 1306 | 1307 | [RFC2045] Freed, N. and N. Borenstein, 1308 | "Multipurpose Internet Mail 1309 | Extensions (MIME) Part One: Format 1310 | of Internet Message Bodies", 1311 | RFC 2045, November 1996. 1312 | 1313 | [RFC2119] Bradner, S., "Key words for use in 1314 | RFCs to Indicate Requirement 1315 | Levels", BCP 14, RFC 2119, 1316 | March 1997. 1317 | 1318 | [RFC2396] Berners-Lee, T., Fielding, R., and 1319 | L. Masinter, "Uniform Resource 1320 | Identifiers (URI): Generic 1321 | Syntax", RFC 2396, August 1998. 1322 | 1323 | [RFC3339] Klyne, G., Ed. and C. Newman, 1324 | "Date and Time on the Internet: 1325 | Timestamps", RFC 3339, July 2002. 1326 | 1327 | [RFC3986] Berners-Lee, T., Fielding, R., and 1328 | L. Masinter, "Uniform Resource 1329 | Identifier (URI): Generic Syntax", 1330 | STD 66, RFC 3986, January 2005. 1331 | 1332 | [RFC4287] Nottingham, M., Ed. and R. Sayre, 1333 | Ed., "The Atom Syndication 1334 | Format", RFC 4287, December 2005. 1335 | 1336 | 9.2. Informative References 1337 | 1338 | [RFC2616] Fielding, R., Gettys, J., Mogul, 1339 | J., Frystyk, H., Masinter, L., 1340 | 1341 | 1342 | 1343 | Zyp & Court Expires May 26, 2011 [Page 24] 1344 | 1345 | Internet-Draft JSON Schema Media Type November 2010 1346 | 1347 | 1348 | Leach, P., and T. Berners-Lee, 1349 | "Hypertext Transfer Protocol -- 1350 | HTTP/1.1", RFC 2616, June 1999. 1351 | 1352 | [RFC4627] Crockford, D., "The application/ 1353 | json Media Type for JavaScript 1354 | Object Notation (JSON)", RFC 4627, 1355 | July 2006. 1356 | 1357 | [RFC5226] Narten, T. and H. Alvestrand, 1358 | "Guidelines for Writing an IANA 1359 | Considerations Section in RFCs", 1360 | BCP 26, RFC 5226, May 2008. 1361 | 1362 | [I-D.hammer-discovery] Hammer-Lahav, E., "LRDD: Link- 1363 | based Resource Descriptor 1364 | Discovery", 1365 | draft-hammer-discovery-06 (work in 1366 | progress), May 2010. 1367 | 1368 | [I-D.gregorio-uritemplate] Gregorio, J., Fielding, R., 1369 | Hadley, M., and M. Nottingham, 1370 | "URI Template", 1371 | draft-gregorio-uritemplate-04 1372 | (work in progress), March 2010. 1373 | 1374 | [I-D.nottingham-http-link-header] Nottingham, M., "Web Linking", dra 1375 | ft-nottingham-http-link-header-10 1376 | (work in progress), May 2010. 1377 | 1378 | [W3C.REC-html401-19991224] Raggett, D., Hors, A., and I. 1379 | Jacobs, "HTML 4.01 Specification", 1380 | World Wide Web Consortium Recommen 1381 | dation REC-html401-19991224, 1382 | December 1999, . 1384 | 1385 | [W3C.CR-CSS21-20070719] Hickson, I., Lie, H., Celik, T., 1386 | and B. Bos, "Cascading Style 1387 | Sheets Level 2 Revision 1 (CSS 1388 | 2.1) Specification", World Wide 1389 | Web Consortium CR CR-CSS21- 1390 | 20070719, July 2007, . 1393 | 1394 | 1395 | 1396 | 1397 | 1398 | 1399 | Zyp & Court Expires May 26, 2011 [Page 25] 1400 | 1401 | Internet-Draft JSON Schema Media Type November 2010 1402 | 1403 | 1404 | Appendix A. Change Log 1405 | 1406 | draft-03 1407 | 1408 | * Added example and verbiage to "extends" attribute. 1409 | 1410 | * Defined slash-delimited to use a leading slash. 1411 | 1412 | * Made "root" a relation instead of an attribute. 1413 | 1414 | * Removed address values, and MIME media type from format to 1415 | reduce confusion (mediaType already exists, so it can be used 1416 | for MIME types). 1417 | 1418 | * Added more explanation of nullability. 1419 | 1420 | * Removed "alternate" attribute. 1421 | 1422 | * Upper cased many normative usages of must, may, and should. 1423 | 1424 | * Replaced the link submission "properties" attribute to "schema" 1425 | attribute. 1426 | 1427 | * Replaced "optional" attribute with "required" attribute. 1428 | 1429 | * Replaced "maximumCanEqual" attribute with "exclusiveMaximum" 1430 | attribute. 1431 | 1432 | * Replaced "minimumCanEqual" attribute with "exclusiveMinimum" 1433 | attribute. 1434 | 1435 | * Replaced "requires" attribute with "dependencies" attribute. 1436 | 1437 | * Moved "contentEncoding" attribute to hyper schema. 1438 | 1439 | * Added "additionalItems" attribute. 1440 | 1441 | * Added "id" attribute. 1442 | 1443 | * Switched self-referencing variable substitution from "-this" to 1444 | "@" to align with reserved characters in URI template. 1445 | 1446 | * Added "patternProperties" attribute. 1447 | 1448 | * Schema URIs are now namespace versioned. 1449 | 1450 | * Added "$ref" and "$schema" attributes. 1451 | 1452 | 1453 | 1454 | 1455 | Zyp & Court Expires May 26, 2011 [Page 26] 1456 | 1457 | Internet-Draft JSON Schema Media Type November 2010 1458 | 1459 | 1460 | draft-02 1461 | 1462 | * Replaced "maxDecimal" attribute with "divisibleBy" attribute. 1463 | 1464 | * Added slash-delimited fragment resolution protocol and made it 1465 | the default. 1466 | 1467 | * Added language about using links outside of schemas by 1468 | referencing its normative URI. 1469 | 1470 | * Added "uniqueItems" attribute. 1471 | 1472 | * Added "targetSchema" attribute to link description object. 1473 | 1474 | draft-01 1475 | 1476 | * Fixed category and updates from template. 1477 | 1478 | draft-00 1479 | 1480 | * Initial draft. 1481 | 1482 | Appendix B. Open Issues 1483 | 1484 | Should we give a preference to MIME headers over Link headers (or 1485 | only use one)? 1486 | 1487 | Should "root" be a MIME parameter? 1488 | 1489 | Should "format" be renamed to "mediaType" or "contentType" to 1490 | reflect the usage MIME media types that are allowed? 1491 | 1492 | How should dates be handled? 1493 | 1494 | Authors' Addresses 1495 | 1496 | Kris Zyp (editor) 1497 | SitePen (USA) 1498 | 530 Lytton Avenue 1499 | Palo Alto, CA 94301 1500 | USA 1501 | 1502 | Phone: +1 650 968 8787 1503 | EMail: kris@sitepen.com 1504 | 1505 | 1506 | 1507 | 1508 | 1509 | 1510 | 1511 | Zyp & Court Expires May 26, 2011 [Page 27] 1512 | 1513 | Internet-Draft JSON Schema Media Type November 2010 1514 | 1515 | 1516 | Gary Court 1517 | Calgary, AB 1518 | Canada 1519 | 1520 | EMail: gary.court@gmail.com 1521 | 1522 | 1523 | 1524 | 1525 | 1526 | 1527 | 1528 | 1529 | 1530 | 1531 | 1532 | 1533 | 1534 | 1535 | 1536 | 1537 | 1538 | 1539 | 1540 | 1541 | 1542 | 1543 | 1544 | 1545 | 1546 | 1547 | 1548 | 1549 | 1550 | 1551 | 1552 | 1553 | 1554 | 1555 | 1556 | 1557 | 1558 | 1559 | 1560 | 1561 | 1562 | 1563 | 1564 | 1565 | 1566 | 1567 | Zyp & Court Expires May 26, 2011 [Page 28] 1568 | 1569 | 1570 | --------------------------------------------------------------------------------