diff --git a/CMakeLists.txt b/CMakeLists.txt index 33f3ef9..fcacbd3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,9 @@ set(LIB_PATCH_VERSION "2") set(LIB_VERSION_STRING "${LIB_MAJOR_VERSION}.${LIB_MINOR_VERSION}.${LIB_PATCH_VERSION}") # compile in release with debug info mode by default -SET(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Build Type") +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE) +endif() # Build all binaries in a separate directory SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) diff --git a/bin/draft-04/schema b/bin/draft-04/schema new file mode 100644 index 0000000..85eb502 --- /dev/null +++ b/bin/draft-04/schema @@ -0,0 +1,150 @@ +{ + "id": "http://json-schema.org/draft-04/schema#", + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#" } + }, + "positiveInteger": { + "type": "integer", + "minimum": 0 + }, + "positiveIntegerDefault0": { + "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ] + }, + "simpleTypes": { + "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1, + "uniqueItems": true + } + }, + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uri" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": {}, + "multipleOf": { + "type": "number", + "minimum": 0, + "exclusiveMinimum": true + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "boolean", + "default": false + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "boolean", + "default": false + }, + "maxLength": { "$ref": "#/definitions/positiveInteger" }, + "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { + "anyOf": [ + { "type": "boolean" }, + { "$ref": "#" } + ], + "default": {} + }, + "items": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/schemaArray" } + ], + "default": {} + }, + "maxItems": { "$ref": "#/definitions/positiveInteger" }, + "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "maxProperties": { "$ref": "#/definitions/positiveInteger" }, + "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "required": { "$ref": "#/definitions/stringArray" }, + "additionalProperties": { + "anyOf": [ + { "type": "boolean" }, + { "$ref": "#" } + ], + "default": {} + }, + "definitions": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "properties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/stringArray" } + ] + } + }, + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + }, + "type": { + "anyOf": [ + { "$ref": "#/definitions/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/definitions/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "allOf": { "$ref": "#/definitions/schemaArray" }, + "anyOf": { "$ref": "#/definitions/schemaArray" }, + "oneOf": { "$ref": "#/definitions/schemaArray" }, + "not": { "$ref": "#" } + }, + "dependencies": { + "exclusiveMaximum": [ "maximum" ], + "exclusiveMinimum": [ "minimum" ] + }, + "default": {} +} diff --git a/bin/jsonschema/.gitignore b/bin/jsonschema/.gitignore new file mode 100644 index 0000000..1333ed7 --- /dev/null +++ b/bin/jsonschema/.gitignore @@ -0,0 +1 @@ +TODO diff --git a/bin/jsonschema/.travis.yml b/bin/jsonschema/.travis.yml new file mode 100644 index 0000000..deecd61 --- /dev/null +++ b/bin/jsonschema/.travis.yml @@ -0,0 +1,4 @@ +language: python +python: "2.7" +install: pip install jsonschema +script: bin/jsonschema_suite check diff --git a/bin/jsonschema/LICENSE b/bin/jsonschema/LICENSE new file mode 100644 index 0000000..c28adba --- /dev/null +++ b/bin/jsonschema/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2012 Julian Berman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/bin/jsonschema/README.md b/bin/jsonschema/README.md new file mode 100644 index 0000000..6d9da94 --- /dev/null +++ b/bin/jsonschema/README.md @@ -0,0 +1,148 @@ +JSON Schema Test Suite [![Build Status](https://travis-ci.org/json-schema/JSON-Schema-Test-Suite.png?branch=develop)](https://travis-ci.org/json-schema/JSON-Schema-Test-Suite) +====================== + +This repository contains a set of JSON objects that implementors of JSON Schema +validation libraries can use to test their validators. + +It is meant to be language agnostic and should require only a JSON parser. + +The conversion of the JSON objects into tests within your test framework of +choice is still the job of the validator implementor. + +Structure of a Test +------------------- + +If you're going to use this suite, you need to know how tests are laid out. The +tests are contained in the `tests` directory at the root of this repository. + +Inside that directory is a subdirectory for each draft or version of the +schema. We'll use `draft3` as an example. + +If you look inside the draft directory, there are a number of `.json` files, +which logically group a set of test cases together. Often the grouping is by +property under test, but not always, especially within optional test files +(discussed below). + +Inside each `.json` file is a single array containing objects. It's easiest to +illustrate the structure of these with an example: + +```json + { + "description": "the description of the test case", + "schema": {"the schema that should" : "be validated against"}, + "tests": [ + { + "description": "a specific test of a valid instance", + "data": "the instance", + "valid": true + }, + { + "description": "another specific test this time, invalid", + "data": 15, + "valid": false + } + ] + } +``` + +So a description, a schema, and some tests, where tests is an array containing +one or more objects with descriptions, data, and a boolean indicating whether +they should be valid or invalid. + +Coverage +-------- + +Draft 3 and 4 should have full coverage. If you see anything missing or think +there is a useful test missing, please send a pull request or open an issue. + +Who Uses the Test Suite +----------------------- + +This suite is being used by: + +### Coffeescript ### + +* [jsck](https://github.com/pandastrike/jsck) + +### Dart ### + +* [json_schema](https://github.com/patefacio/json_schema) + +### Erlang ### + +* [jesse](https://github.com/klarna/jesse) + +### Go ### + +* [gojsonschema](https://github.com/sigu-399/gojsonschema) +* [validate-json](https://github.com/cesanta/validate-json) + +### Haskell ### + +* [aeson-schema](https://github.com/timjb/aeson-schema) +* [hjsonschema](https://github.com/seagreen/hjsonschema) + +### Java ### + +* [json-schema-validator](https://github.com/fge/json-schema-validator) + +### JavaScript ### + +* [json-schema-benchmark](https://github.com/Muscula/json-schema-benchmark) +* [direct-schema](https://github.com/IreneKnapp/direct-schema) +* [is-my-json-valid](https://github.com/mafintosh/is-my-json-valid) +* [jassi](https://github.com/iclanzan/jassi) +* [JaySchema](https://github.com/natesilva/jayschema) +* [json-schema-valid](https://github.com/ericgj/json-schema-valid) +* [Jsonary](https://github.com/jsonary-js/jsonary) +* [jsonschema](https://github.com/tdegrunt/jsonschema) +* [request-validator](https://github.com/bugventure/request-validator) +* [skeemas](https://github.com/Prestaul/skeemas) +* [tv4](https://github.com/geraintluff/tv4) +* [z-schema](https://github.com/zaggino/z-schema) +* [jsen](https://github.com/bugventure/jsen) +* [ajv](https://github.com/epoberezkin/ajv) + +### Node.js ### + +The JSON Schema Test Suite is also available as an +[npm](https://www.npmjs.com/package/json-schema-test-suite) package. +Node-specific support is maintained on the [node branch](https://github.com/json-schema/JSON-Schema-Test-Suite/tree/node). +See [NODE-README.md](https://github.com/json-schema/JSON-Schema-Test-Suite/blob/node/NODE-README.md) +for more information. + +### .NET ### + +* [Newtonsoft.Json.Schema](https://github.com/JamesNK/Newtonsoft.Json.Schema) + +### PHP ### + +* [json-schema](https://github.com/justinrainbow/json-schema) + +### Python ### + +* [jsonschema](https://github.com/Julian/jsonschema) + +### Ruby ### + +* [json-schema](https://github.com/hoxworth/json-schema) + +### Rust ### + +* [valico](https://github.com/rustless/valico) + +### Swift ### + +* [JSONSchema](https://github.com/kylef/JSONSchema.swift) + +If you use it as well, please fork and send a pull request adding yourself to +the list :). + +Contributing +------------ + +If you see something missing or incorrect, a pull request is most welcome! + +There are some sanity checks in place for testing the test suite. You can run +them with `bin/jsonschema_suite check` or `tox`. They will be run automatically by +[Travis CI](https://travis-ci.org/) as well. diff --git a/bin/jsonschema/bin/jsonschema_suite b/bin/jsonschema/bin/jsonschema_suite new file mode 100755 index 0000000..96108c8 --- /dev/null +++ b/bin/jsonschema/bin/jsonschema_suite @@ -0,0 +1,283 @@ +#! /usr/bin/env python +from __future__ import print_function +import sys +import textwrap + +try: + import argparse +except ImportError: + print(textwrap.dedent(""" + The argparse library could not be imported. jsonschema_suite requires + either Python 2.7 or for you to install argparse. You can do so by + running `pip install argparse`, `easy_install argparse` or by + downloading argparse and running `python2.6 setup.py install`. + + See https://pypi.python.org/pypi/argparse for details. + """.strip("\n"))) + sys.exit(1) + +import errno +import fnmatch +import json +import os +import random +import shutil +import unittest +import warnings + +if getattr(unittest, "skipIf", None) is None: + unittest.skipIf = lambda cond, msg : lambda fn : fn + +try: + import jsonschema +except ImportError: + jsonschema = None +else: + validators = getattr( + jsonschema.validators, "validators", jsonschema.validators + ) + + +ROOT_DIR = os.path.join( + os.path.dirname(__file__), os.pardir).rstrip("__pycache__") +SUITE_ROOT_DIR = os.path.join(ROOT_DIR, "tests") + +REMOTES = { + "integer.json": {"type": "integer"}, + "subSchemas.json": { + "integer": {"type": "integer"}, + "refToInteger": {"$ref": "#/integer"}, + }, + "folder/folderInteger.json": {"type": "integer"} +} +REMOTES_DIR = os.path.join(ROOT_DIR, "remotes") + +TESTSUITE_SCHEMA = { + "$schema": "http://json-schema.org/draft-03/schema#", + "type": "array", + "items": { + "type": "object", + "properties": { + "description": {"type": "string", "required": True}, + "schema": {"required": True}, + "tests": { + "type": "array", + "items": { + "type": "object", + "properties": { + "description": {"type": "string", "required": True}, + "data": {"required": True}, + "valid": {"type": "boolean", "required": True} + }, + "additionalProperties": False + }, + "minItems": 1 + } + }, + "additionalProperties": False, + "minItems": 1 + } +} + + +def files(paths): + for path in paths: + with open(path) as test_file: + yield json.load(test_file) + + +def groups(paths): + for test_file in files(paths): + for group in test_file: + yield group + + +def cases(paths): + for test_group in groups(paths): + for test in test_group["tests"]: + test["schema"] = test_group["schema"] + yield test + + +def collect(root_dir): + for root, dirs, files in os.walk(root_dir): + for filename in fnmatch.filter(files, "*.json"): + yield os.path.join(root, filename) + + +class SanityTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + print("Looking for tests in %s" % SUITE_ROOT_DIR) + cls.test_files = list(collect(SUITE_ROOT_DIR)) + print("Found %s test files" % len(cls.test_files)) + assert cls.test_files, "Didn't find the test files!" + + def test_all_files_are_valid_json(self): + for path in self.test_files: + with open(path) as test_file: + try: + json.load(test_file) + except ValueError as error: + self.fail("%s contains invalid JSON (%s)" % (path, error)) + + def test_all_descriptions_have_reasonable_length(self): + for case in cases(self.test_files): + descript = case["description"] + self.assertLess( + len(descript), + 60, + "%r is too long! (keep it to less than 60 chars)" % (descript,) + ) + + def test_all_descriptions_are_unique(self): + for group in groups(self.test_files): + descriptions = set(test["description"] for test in group["tests"]) + self.assertEqual( + len(descriptions), + len(group["tests"]), + "%r contains a duplicate description" % (group,) + ) + + @unittest.skipIf(jsonschema is None, "Validation library not present!") + def test_all_schemas_are_valid(self): + for schema in os.listdir(SUITE_ROOT_DIR): + schema_validator = validators.get(schema) + if schema_validator is not None: + test_files = collect(os.path.join(SUITE_ROOT_DIR, schema)) + for case in cases(test_files): + try: + schema_validator.check_schema(case["schema"]) + except jsonschema.SchemaError as error: + self.fail("%s contains an invalid schema (%s)" % + (case, error)) + else: + warnings.warn("No schema validator for %s" % schema) + + @unittest.skipIf(jsonschema is None, "Validation library not present!") + def test_suites_are_valid(self): + validator = jsonschema.Draft3Validator(TESTSUITE_SCHEMA) + for tests in files(self.test_files): + try: + validator.validate(tests) + except jsonschema.ValidationError as error: + self.fail(str(error)) + + def test_remote_schemas_are_updated(self): + for url, schema in REMOTES.items(): + filepath = os.path.join(REMOTES_DIR, url) + with open(filepath) as schema_file: + self.assertEqual(json.load(schema_file), schema) + + +def main(arguments): + if arguments.command == "check": + suite = unittest.TestLoader().loadTestsFromTestCase(SanityTests) + result = unittest.TextTestRunner(verbosity=2).run(suite) + sys.exit(not result.wasSuccessful()) + elif arguments.command == "flatten": + selected_cases = [case for case in cases(collect(arguments.version))] + + if arguments.randomize: + random.shuffle(selected_cases) + + json.dump(selected_cases, sys.stdout, indent=4, sort_keys=True) + elif arguments.command == "remotes": + json.dump(REMOTES, sys.stdout, indent=4, sort_keys=True) + elif arguments.command == "dump_remotes": + if arguments.update: + shutil.rmtree(arguments.out_dir, ignore_errors=True) + + try: + os.makedirs(arguments.out_dir) + except OSError as e: + if e.errno == errno.EEXIST: + print("%s already exists. Aborting." % arguments.out_dir) + sys.exit(1) + raise + + for url, schema in REMOTES.items(): + filepath = os.path.join(arguments.out_dir, url) + + try: + os.makedirs(os.path.dirname(filepath)) + except OSError as e: + if e.errno != errno.EEXIST: + raise + + with open(filepath, "wb") as out_file: + json.dump(schema, out_file, indent=4, sort_keys=True) + elif arguments.command == "serve": + try: + from flask import Flask, jsonify + except ImportError: + print(textwrap.dedent(""" + The Flask library is required to serve the remote schemas. + + You can install it by running `pip install Flask`. + + Alternatively, see the `jsonschema_suite remotes` or + `jsonschema_suite dump_remotes` commands to create static files + that can be served with your own web server. + """.strip("\n"))) + sys.exit(1) + + app = Flask(__name__) + + @app.route("/") + def serve_path(path): + if path in REMOTES: + return jsonify(REMOTES[path]) + return "Document does not exist.", 404 + + app.run(port=1234) + + +parser = argparse.ArgumentParser( + description="JSON Schema Test Suite utilities", +) +subparsers = parser.add_subparsers(help="utility commands", dest="command") + +check = subparsers.add_parser("check", help="Sanity check the test suite.") + +flatten = subparsers.add_parser( + "flatten", + help="Output a flattened file containing a selected version's test cases." +) +flatten.add_argument( + "--randomize", + action="store_true", + help="Randomize the order of the outputted cases.", +) +flatten.add_argument( + "version", help="The directory containing the version to output", +) + +remotes = subparsers.add_parser( + "remotes", + help="Output the expected URLs and their associated schemas for remote " + "ref tests as a JSON object." +) + +dump_remotes = subparsers.add_parser( + "dump_remotes", help="Dump the remote ref schemas into a file tree", +) +dump_remotes.add_argument( + "--update", + action="store_true", + help="Update the remotes in an existing directory.", +) +dump_remotes.add_argument( + "--out-dir", + default=REMOTES_DIR, + type=os.path.abspath, + help="The output directory to create as the root of the file tree", +) + +serve = subparsers.add_parser( + "serve", + help="Start a webserver to serve schemas used by remote ref tests." +) + +if __name__ == "__main__": + main(parser.parse_args()) diff --git a/bin/jsonschema/remotes/.DS_Store b/bin/jsonschema/remotes/.DS_Store new file mode 100644 index 0000000..1d098a4 Binary files /dev/null and b/bin/jsonschema/remotes/.DS_Store differ diff --git a/bin/jsonschema/remotes/folder/folderInteger.json b/bin/jsonschema/remotes/folder/folderInteger.json new file mode 100644 index 0000000..dbe5c75 Binary files /dev/null and b/bin/jsonschema/remotes/folder/folderInteger.json differ diff --git a/bin/jsonschema/remotes/integer.json b/bin/jsonschema/remotes/integer.json new file mode 100644 index 0000000..dbe5c75 Binary files /dev/null and b/bin/jsonschema/remotes/integer.json differ diff --git a/bin/jsonschema/remotes/subSchemas.json b/bin/jsonschema/remotes/subSchemas.json new file mode 100644 index 0000000..8b6d8f8 Binary files /dev/null and b/bin/jsonschema/remotes/subSchemas.json differ diff --git a/bin/jsonschema/tests/.DS_Store b/bin/jsonschema/tests/.DS_Store new file mode 100644 index 0000000..dae9b18 Binary files /dev/null and b/bin/jsonschema/tests/.DS_Store differ diff --git a/bin/jsonschema/tests/draft3/additionalItems.json b/bin/jsonschema/tests/draft3/additionalItems.json new file mode 100644 index 0000000..6d4bff5 Binary files /dev/null and b/bin/jsonschema/tests/draft3/additionalItems.json differ diff --git a/bin/jsonschema/tests/draft3/additionalProperties.json b/bin/jsonschema/tests/draft3/additionalProperties.json new file mode 100644 index 0000000..40831f9 Binary files /dev/null and b/bin/jsonschema/tests/draft3/additionalProperties.json differ diff --git a/bin/jsonschema/tests/draft3/default.json b/bin/jsonschema/tests/draft3/default.json new file mode 100644 index 0000000..1762977 Binary files /dev/null and b/bin/jsonschema/tests/draft3/default.json differ diff --git a/bin/jsonschema/tests/draft3/dependencies.json b/bin/jsonschema/tests/draft3/dependencies.json new file mode 100644 index 0000000..2f6ae48 Binary files /dev/null and b/bin/jsonschema/tests/draft3/dependencies.json differ diff --git a/bin/jsonschema/tests/draft3/disallow.json b/bin/jsonschema/tests/draft3/disallow.json new file mode 100644 index 0000000..a5c9d90 Binary files /dev/null and b/bin/jsonschema/tests/draft3/disallow.json differ diff --git a/bin/jsonschema/tests/draft3/divisibleBy.json b/bin/jsonschema/tests/draft3/divisibleBy.json new file mode 100644 index 0000000..ef7cc14 Binary files /dev/null and b/bin/jsonschema/tests/draft3/divisibleBy.json differ diff --git a/bin/jsonschema/tests/draft3/enum.json b/bin/jsonschema/tests/draft3/enum.json new file mode 100644 index 0000000..0c83f08 Binary files /dev/null and b/bin/jsonschema/tests/draft3/enum.json differ diff --git a/bin/jsonschema/tests/draft3/extends.json b/bin/jsonschema/tests/draft3/extends.json new file mode 100644 index 0000000..909bce5 Binary files /dev/null and b/bin/jsonschema/tests/draft3/extends.json differ diff --git a/bin/jsonschema/tests/draft3/items.json b/bin/jsonschema/tests/draft3/items.json new file mode 100644 index 0000000..f5e18a1 Binary files /dev/null and b/bin/jsonschema/tests/draft3/items.json differ diff --git a/bin/jsonschema/tests/draft3/maxItems.json b/bin/jsonschema/tests/draft3/maxItems.json new file mode 100644 index 0000000..3b53a6b Binary files /dev/null and b/bin/jsonschema/tests/draft3/maxItems.json differ diff --git a/bin/jsonschema/tests/draft3/maxLength.json b/bin/jsonschema/tests/draft3/maxLength.json new file mode 100644 index 0000000..4de42bc Binary files /dev/null and b/bin/jsonschema/tests/draft3/maxLength.json differ diff --git a/bin/jsonschema/tests/draft3/maximum.json b/bin/jsonschema/tests/draft3/maximum.json new file mode 100644 index 0000000..86c7b89 Binary files /dev/null and b/bin/jsonschema/tests/draft3/maximum.json differ diff --git a/bin/jsonschema/tests/draft3/minItems.json b/bin/jsonschema/tests/draft3/minItems.json new file mode 100644 index 0000000..ed51188 Binary files /dev/null and b/bin/jsonschema/tests/draft3/minItems.json differ diff --git a/bin/jsonschema/tests/draft3/minLength.json b/bin/jsonschema/tests/draft3/minLength.json new file mode 100644 index 0000000..3f09158 Binary files /dev/null and b/bin/jsonschema/tests/draft3/minLength.json differ diff --git a/bin/jsonschema/tests/draft3/minimum.json b/bin/jsonschema/tests/draft3/minimum.json new file mode 100644 index 0000000..d5bf000 Binary files /dev/null and b/bin/jsonschema/tests/draft3/minimum.json differ diff --git a/bin/jsonschema/tests/draft3/optional/bignum.json b/bin/jsonschema/tests/draft3/optional/bignum.json new file mode 100644 index 0000000..ccc7c17 Binary files /dev/null and b/bin/jsonschema/tests/draft3/optional/bignum.json differ diff --git a/bin/jsonschema/tests/draft3/optional/format.json b/bin/jsonschema/tests/draft3/optional/format.json new file mode 100644 index 0000000..3ca7319 Binary files /dev/null and b/bin/jsonschema/tests/draft3/optional/format.json differ diff --git a/bin/jsonschema/tests/draft3/optional/jsregex.json b/bin/jsonschema/tests/draft3/optional/jsregex.json new file mode 100644 index 0000000..03fe977 Binary files /dev/null and b/bin/jsonschema/tests/draft3/optional/jsregex.json differ diff --git a/bin/jsonschema/tests/draft3/optional/zeroTerminatedFloats.json b/bin/jsonschema/tests/draft3/optional/zeroTerminatedFloats.json new file mode 100644 index 0000000..9b50ea2 Binary files /dev/null and b/bin/jsonschema/tests/draft3/optional/zeroTerminatedFloats.json differ diff --git a/bin/jsonschema/tests/draft3/pattern.json b/bin/jsonschema/tests/draft3/pattern.json new file mode 100644 index 0000000..25e7299 Binary files /dev/null and b/bin/jsonschema/tests/draft3/pattern.json differ diff --git a/bin/jsonschema/tests/draft3/patternProperties.json b/bin/jsonschema/tests/draft3/patternProperties.json new file mode 100644 index 0000000..18586e5 Binary files /dev/null and b/bin/jsonschema/tests/draft3/patternProperties.json differ diff --git a/bin/jsonschema/tests/draft3/properties.json b/bin/jsonschema/tests/draft3/properties.json new file mode 100644 index 0000000..cd1644d Binary files /dev/null and b/bin/jsonschema/tests/draft3/properties.json differ diff --git a/bin/jsonschema/tests/draft3/ref.json b/bin/jsonschema/tests/draft3/ref.json new file mode 100644 index 0000000..903ecb6 Binary files /dev/null and b/bin/jsonschema/tests/draft3/ref.json differ diff --git a/bin/jsonschema/tests/draft3/refRemote.json b/bin/jsonschema/tests/draft3/refRemote.json new file mode 100644 index 0000000..4ca8047 Binary files /dev/null and b/bin/jsonschema/tests/draft3/refRemote.json differ diff --git a/bin/jsonschema/tests/draft3/required.json b/bin/jsonschema/tests/draft3/required.json new file mode 100644 index 0000000..aaaf024 Binary files /dev/null and b/bin/jsonschema/tests/draft3/required.json differ diff --git a/bin/jsonschema/tests/draft3/type.json b/bin/jsonschema/tests/draft3/type.json new file mode 100644 index 0000000..337da12 Binary files /dev/null and b/bin/jsonschema/tests/draft3/type.json differ diff --git a/bin/jsonschema/tests/draft3/uniqueItems.json b/bin/jsonschema/tests/draft3/uniqueItems.json new file mode 100644 index 0000000..c1f4ab9 Binary files /dev/null and b/bin/jsonschema/tests/draft3/uniqueItems.json differ diff --git a/bin/jsonschema/tests/draft4/.DS_Store b/bin/jsonschema/tests/draft4/.DS_Store new file mode 100644 index 0000000..ef14229 Binary files /dev/null and b/bin/jsonschema/tests/draft4/.DS_Store differ diff --git a/bin/jsonschema/tests/draft4/additionalItems.json b/bin/jsonschema/tests/draft4/additionalItems.json new file mode 100644 index 0000000..521745c Binary files /dev/null and b/bin/jsonschema/tests/draft4/additionalItems.json differ diff --git a/bin/jsonschema/tests/draft4/additionalProperties.json b/bin/jsonschema/tests/draft4/additionalProperties.json new file mode 100644 index 0000000..40831f9 Binary files /dev/null and b/bin/jsonschema/tests/draft4/additionalProperties.json differ diff --git a/bin/jsonschema/tests/draft4/allOf.json b/bin/jsonschema/tests/draft4/allOf.json new file mode 100644 index 0000000..bbb5f89 Binary files /dev/null and b/bin/jsonschema/tests/draft4/allOf.json differ diff --git a/bin/jsonschema/tests/draft4/anyOf.json b/bin/jsonschema/tests/draft4/anyOf.json new file mode 100644 index 0000000..a58714a Binary files /dev/null and b/bin/jsonschema/tests/draft4/anyOf.json differ diff --git a/bin/jsonschema/tests/draft4/default.json b/bin/jsonschema/tests/draft4/default.json new file mode 100644 index 0000000..1762977 Binary files /dev/null and b/bin/jsonschema/tests/draft4/default.json differ diff --git a/bin/jsonschema/tests/draft4/definitions.json b/bin/jsonschema/tests/draft4/definitions.json new file mode 100644 index 0000000..cf935a3 Binary files /dev/null and b/bin/jsonschema/tests/draft4/definitions.json differ diff --git a/bin/jsonschema/tests/draft4/dependencies.json b/bin/jsonschema/tests/draft4/dependencies.json new file mode 100644 index 0000000..7b9b16a Binary files /dev/null and b/bin/jsonschema/tests/draft4/dependencies.json differ diff --git a/bin/jsonschema/tests/draft4/enum.json b/bin/jsonschema/tests/draft4/enum.json new file mode 100644 index 0000000..f124436 Binary files /dev/null and b/bin/jsonschema/tests/draft4/enum.json differ diff --git a/bin/jsonschema/tests/draft4/items.json b/bin/jsonschema/tests/draft4/items.json new file mode 100644 index 0000000..f5e18a1 Binary files /dev/null and b/bin/jsonschema/tests/draft4/items.json differ diff --git a/bin/jsonschema/tests/draft4/maxItems.json b/bin/jsonschema/tests/draft4/maxItems.json new file mode 100644 index 0000000..3b53a6b Binary files /dev/null and b/bin/jsonschema/tests/draft4/maxItems.json differ diff --git a/bin/jsonschema/tests/draft4/maxLength.json b/bin/jsonschema/tests/draft4/maxLength.json new file mode 100644 index 0000000..811d35b Binary files /dev/null and b/bin/jsonschema/tests/draft4/maxLength.json differ diff --git a/bin/jsonschema/tests/draft4/maxProperties.json b/bin/jsonschema/tests/draft4/maxProperties.json new file mode 100644 index 0000000..d282446 Binary files /dev/null and b/bin/jsonschema/tests/draft4/maxProperties.json differ diff --git a/bin/jsonschema/tests/draft4/maximum.json b/bin/jsonschema/tests/draft4/maximum.json new file mode 100644 index 0000000..86c7b89 Binary files /dev/null and b/bin/jsonschema/tests/draft4/maximum.json differ diff --git a/bin/jsonschema/tests/draft4/minItems.json b/bin/jsonschema/tests/draft4/minItems.json new file mode 100644 index 0000000..ed51188 Binary files /dev/null and b/bin/jsonschema/tests/draft4/minItems.json differ diff --git a/bin/jsonschema/tests/draft4/minLength.json b/bin/jsonschema/tests/draft4/minLength.json new file mode 100644 index 0000000..3f09158 Binary files /dev/null and b/bin/jsonschema/tests/draft4/minLength.json differ diff --git a/bin/jsonschema/tests/draft4/minProperties.json b/bin/jsonschema/tests/draft4/minProperties.json new file mode 100644 index 0000000..a72c7d2 Binary files /dev/null and b/bin/jsonschema/tests/draft4/minProperties.json differ diff --git a/bin/jsonschema/tests/draft4/minimum.json b/bin/jsonschema/tests/draft4/minimum.json new file mode 100644 index 0000000..d5bf000 Binary files /dev/null and b/bin/jsonschema/tests/draft4/minimum.json differ diff --git a/bin/jsonschema/tests/draft4/multipleOf.json b/bin/jsonschema/tests/draft4/multipleOf.json new file mode 100644 index 0000000..ca3b761 Binary files /dev/null and b/bin/jsonschema/tests/draft4/multipleOf.json differ diff --git a/bin/jsonschema/tests/draft4/not.json b/bin/jsonschema/tests/draft4/not.json new file mode 100644 index 0000000..cbb7f46 Binary files /dev/null and b/bin/jsonschema/tests/draft4/not.json differ diff --git a/bin/jsonschema/tests/draft4/oneOf.json b/bin/jsonschema/tests/draft4/oneOf.json new file mode 100644 index 0000000..1eaa4e4 Binary files /dev/null and b/bin/jsonschema/tests/draft4/oneOf.json differ diff --git a/bin/jsonschema/tests/draft4/optional/bignum.json b/bin/jsonschema/tests/draft4/optional/bignum.json new file mode 100644 index 0000000..ccc7c17 Binary files /dev/null and b/bin/jsonschema/tests/draft4/optional/bignum.json differ diff --git a/bin/jsonschema/tests/draft4/optional/format.json b/bin/jsonschema/tests/draft4/optional/format.json new file mode 100644 index 0000000..aacfd11 Binary files /dev/null and b/bin/jsonschema/tests/draft4/optional/format.json differ diff --git a/bin/jsonschema/tests/draft4/optional/zeroTerminatedFloats.json b/bin/jsonschema/tests/draft4/optional/zeroTerminatedFloats.json new file mode 100644 index 0000000..9b50ea2 Binary files /dev/null and b/bin/jsonschema/tests/draft4/optional/zeroTerminatedFloats.json differ diff --git a/bin/jsonschema/tests/draft4/pattern.json b/bin/jsonschema/tests/draft4/pattern.json new file mode 100644 index 0000000..25e7299 Binary files /dev/null and b/bin/jsonschema/tests/draft4/pattern.json differ diff --git a/bin/jsonschema/tests/draft4/patternProperties.json b/bin/jsonschema/tests/draft4/patternProperties.json new file mode 100644 index 0000000..18586e5 Binary files /dev/null and b/bin/jsonschema/tests/draft4/patternProperties.json differ diff --git a/bin/jsonschema/tests/draft4/properties.json b/bin/jsonschema/tests/draft4/properties.json new file mode 100644 index 0000000..cd1644d Binary files /dev/null and b/bin/jsonschema/tests/draft4/properties.json differ diff --git a/bin/jsonschema/tests/draft4/ref.json b/bin/jsonschema/tests/draft4/ref.json new file mode 100644 index 0000000..7e80552 Binary files /dev/null and b/bin/jsonschema/tests/draft4/ref.json differ diff --git a/bin/jsonschema/tests/draft4/refRemote.json b/bin/jsonschema/tests/draft4/refRemote.json new file mode 100644 index 0000000..4ca8047 Binary files /dev/null and b/bin/jsonschema/tests/draft4/refRemote.json differ diff --git a/bin/jsonschema/tests/draft4/required.json b/bin/jsonschema/tests/draft4/required.json new file mode 100644 index 0000000..612f73f Binary files /dev/null and b/bin/jsonschema/tests/draft4/required.json differ diff --git a/bin/jsonschema/tests/draft4/type.json b/bin/jsonschema/tests/draft4/type.json new file mode 100644 index 0000000..db42a44 Binary files /dev/null and b/bin/jsonschema/tests/draft4/type.json differ diff --git a/bin/jsonschema/tests/draft4/uniqueItems.json b/bin/jsonschema/tests/draft4/uniqueItems.json new file mode 100644 index 0000000..c1f4ab9 Binary files /dev/null and b/bin/jsonschema/tests/draft4/uniqueItems.json differ diff --git a/bin/jsonschema/tox.ini b/bin/jsonschema/tox.ini new file mode 100644 index 0000000..5301222 --- /dev/null +++ b/bin/jsonschema/tox.ini @@ -0,0 +1,8 @@ +[tox] +minversion = 1.6 +envlist = py27 +skipsdist = True + +[testenv] +deps = jsonschema +commands = {envpython} bin/jsonschema_suite check diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in index b806205..fcb0926 100644 --- a/doc/Doxyfile.in +++ b/doc/Doxyfile.in @@ -774,6 +774,7 @@ INPUT = readme.md \ doc/encoding.md \ doc/dom.md \ doc/sax.md \ + doc/schema.md \ doc/performance.md \ doc/internals.md \ doc/faq.md diff --git a/doc/Doxyfile.zh-cn.in b/doc/Doxyfile.zh-cn.in index 873022a..76d828b 100644 --- a/doc/Doxyfile.zh-cn.in +++ b/doc/Doxyfile.zh-cn.in @@ -774,6 +774,7 @@ INPUT = readme.zh-cn.md \ doc/encoding.zh-cn.md \ doc/dom.zh-cn.md \ doc/sax.zh-cn.md \ + doc/schema.zh-cn.md \ doc/performance.zh-cn.md \ doc/internals.md \ doc/faq.zh-cn.md diff --git a/doc/schema.md b/doc/schema.md new file mode 100644 index 0000000..a310c79 --- /dev/null +++ b/doc/schema.md @@ -0,0 +1,237 @@ +# Schema + +## Status: experimental, shall be included in v1.1 + +JSON Schema is a draft standard for describing format of JSON. The schema itself is also a JSON. By validating a JSON with JSON Schema, your code can safely access the DOM without manually checking types, or whether a key exists, etc. It can also ensure that the serialized JSON conform to a specified schema. + +RapidJSON implemented a JSON Schema validator for [JSON Schema Draft v4](http://json-schema.org/documentation.html). If you do not familiar with JSON Schema, you may refer to [Understanding JSON Schema](http://spacetelescope.github.io/understanding-json-schema/). + +[TOC] + +## Basic Usage + +First of all, you need to parse a JSON Schema into `Document`, and then compile the `Document` into `SchemaDocument`. + +Secondly, construct a `SchemaValidator` with the `SchedmaDocument`. It is similar to a `Writer` in the sense of handling SAX events. So, you can use `document.Accept(validator)` to validate a document, and then check the validity. + +~~~cpp +#include "rapidjson/schema.h" + +// ... + +Document sd; +if (!sd.Parse(schemaJson)) { + // the schema is not a valid JSON. + // ... +} +SchemaDocument schema(sd); // Compile a Document to SchemaDocument +// sd is no longer needed here. + +Document d; +if (!d.Parse(inputJson)) { + // the input is not a valid JSON. + // ... +} + +SchemaValidator validator(schema); +if (!d.Accept(validator)) { + // Input JSON is invalid according to the schema + // Output diagnostic information + StringBuffer sb; + validator.GetInvalidSchemaPointer().StringifyUriFragment(sb); + printf("Invalid schema: %s\n", sb.GetString()); + printf("Invalid keyword: %s\n", validator.GetInvalidSchemaKeyword()); + sb.Clear(); + validator.GetInvalidDocumentPointer().StringifyUriFragment(sb); + printf("Invalid document: %s\n", sb.GetString()); +} +~~~ + +Some notes: + +* One `SchemaDocment` can be referenced by multiple `SchemaValidator`s. It will not be modified by `SchemaValidator`s. +* A `SchemaValidator` may be reused to validate multiple documents. To run it for other documents, call `validator.Reset()` first. + +## Validation during parsing/serialization + +Differ to most JSON Schema validator implementations, RapidJSON provides a SAX-based schema validator. Therefore, you can parse a JSON from a stream while validating it on the fly. If the validator encounters a JSON value that invalidates the supplied schema, the parsing will be terminated immediately. This design is especially useful for parsing large JSON files. + +### DOM parsing + +For using DOM in parsing, `Document` needs some preparation and finalizing tasks, in addition to receiving SAX events, thus it needs some work to route the reader, validator and the document. `SchemaValidatingReader` is a helper class that doing such work. + +~~~cpp +#include "rapidjson/filereadstream.h" + +// ... +SchemaDocument schema(sd); // Compile a Document to SchemaDocument + +// Use reader to parse the JSON +FILE* fp = fopen("big.json", "r"); +FileReadStream is(fp, buffer, sizeof(buffer)); + +// Parse JSON from reader, validate the SAX events, and store in d. +Document d; +SchemaValidatingReader > reader(is, schema); +d.Populate(reader); + +if (!reader.GetParseResult()) { + // Not a valid JSON + // When reader.GetParseResult().Code() == kParseErrorTermination, + // it may be terminated by: + // (1) the validator found that the JSON is invalid according to schema; or + // (2) the input stream has I/O error. + + // Check the validation result + if (!reader.IsValid()) { + // Input JSON is invalid according to the schema + // Output diagnostic information + StringBuffer sb; + reader.GetInvalidSchemaPointer().StringifyUriFragment(sb); + printf("Invalid schema: %s\n", sb.GetString()); + printf("Invalid keyword: %s\n", reader.GetInvalidSchemaKeyword()); + sb.Clear(); + reader.GetInvalidDocumentPointer().StringifyUriFragment(sb); + printf("Invalid document: %s\n", sb.GetString()); + } +} +~~~ + +### SAX parsing + +For using SAX in parsing, it is much simpler. If it only need to validate the JSON without further processing, it is simply: + +~~~ +SchemaValidator validator(schema); +Reader reader; +if (!reader.Parse(stream, validator)) { + if (!validator.IsValid()) { + // ... + } +} +~~~ + +This is exactly the method used in [schemavalidator](example/schemavalidator/schemavalidator.cpp) example. The distinct advantage is low memory usage, no matter how big the JSON was (the memory usage depends on the complexity of the schema). + +If you need to handle the SAX events further, then you need to use the template class `GenericSchemaValidator` to set the output handler of the validator: + +~~~ +MyHandler handler; +GenericSchemaValidator validator(schema, handler); +Reader reader; +if (!reader.Parse(ss, validator)) { + if (!validator.IsValid()) { + // ... + } +} +~~~ + +### Serialization + +It is also possible to do validation during serializing. This can ensure the result JSON is valid according to the JSON schema. + +~~~ +StringBuffer sb; +Writer writer(sb); +GenericSchemaValidator > validator(s, writer); +if (!d.Accept(validator)) { + // Some problem during Accept(), it may be validation or encoding issues. + if (!validator.IsValid()) { + // ... + } +} +~~~ + +Of course, if your application only needs SAX-style serialization, it can simply send SAX events to `SchemaValidator` instead of `Writer`. + +## Remote Schema + +JSON Schema supports [`$ref` keyword](http://spacetelescope.github.io/understanding-json-schema/structuring.html), which is a [JSON pointer](pointer.md) referencing to a local or remote schema. Local pointer is prefixed with `#`, while remote pointer is an relative or absolute URI. For example: + +~~~js +{ "$ref": "definitions.json#/address" } +~~~ + +As `SchemaValidator` does not know how to resolve such URI, it needs a user-provided `IRemoteSchemaDocumentProvider` instance to do so. + +~~~ +class MyRemoteSchemaDocumentProvider : public IRemoteSchemaDocumentProvider { +public: + virtual const SchemaDocument* GetRemoteDocument(const char* uri, SizeTyp length) { + // Resolve the uri and returns a pointer to that schema. + } +}; + +// ... + +MyRemoteSchemaDocumentProvider provider; +SchemaValidator validator(schema, &provider); +~~~ + +## Conformance + +RapidJSON passed 262 out of 263 tests in [JSON Schema Test Suite](https://github.com/json-schema/JSON-Schema-Test-Suite) (Json Schema draft 4). + +The failed test is "changed scope ref invalid" of "change resolution scope" in `refRemote.json`. It is due to that `id` schema keyword and URI combining function are not implemented. + +Besides, the `format` schema keyword for string values is ignored, since it is not required by the specification. + +### Regular Expression + +The schema keyword `pattern` and `patternProperties` uses regular expression to match the required pattern. + +RapidJSON implemented a simple NFA regular expression engine, which is used by default. It supports the following syntax. + +|Syntax|Description| +|------|-----------| +|`ab` | Concatenation +|`a|b` | Alternation +|`a?` | Zero or one +|`a*` | Zero or more +|`a+` | One or more +|`a{3}` | Exactly 3 times +|`a{3,}` | At least 3 times +|`a{3,5}`| 3 to 5 times +|`(ab)` | Grouping +|`^a` | At the beginning +|`a$` | At the end +|`.` | Any character +|`[abc]` | Character classes +|`[a-c]` | Character class range +|`[a-z0-9_]` | Character class combination +|`[^abc]` | Negated character classes +|`[^a-c]` | Negated character class range +|`[\b]` | Backspace (U+0008) +|`\|`, `\\`, ... | Escape characters +|`\f` | Form feed (U+000C) +|`\n` | Line feed (U+000A) +|`\r` | Carriage return (U+000D) +|`\t` | Tab (U+0009) +|`\v` | Vertical tab (U+000B) + +For C++11 compiler, it is also possible to use the `std::regex` by defining `RAPIDJSON_SCHEMA_USE_INTERNALREGEX=0` and `RAPIDJSON_SCHEMA_USE_STDREGEX=1`. If your schemas do not need `pattern` and `patternProperties`, you can set both macros to zero to disable this feature, which will reduce some code size. + +## Performance + +Most C++ JSON libraries have not yet supporting JSON Schema. So we tried to evaluate the performance of RapidJSON's JSON Schema validator according to [json-schema-benchmark](https://github.com/ebdrup/json-schema-benchmark), which tests 11 JavaScript libraries running on Node.js. + +That benchmark runs validations on [JSON Schema Test Suite](https://github.com/json-schema/JSON-Schema-Test-Suite), in which some test suites and tests are excluded. We made the same benchmarking procedure in [`schematest.cpp`](test/perftest/schematest.cpp). + +On a Mac Book Pro (2.8 GHz Intel Core i7), the following results are collected. + +|Validator|Relative speed|Number of test runs per second| +|---------|:------------:|:----------------------------:| +|RapidJSON|36521%|7220217| +|[`ajv`](https://github.com/epoberezkin/ajv)|100%|19770 (± 1.31%)| +|[`is-my-json-valid`](https://github.com/mafintosh/is-my-json-valid)|70%|13835 (± 2.84%)| +|[`jsen`](https://github.com/bugventure/jsen)|57.7%|11411 (± 1.27%)| +|[`schemasaurus`](https://github.com/AlexeyGrishin/schemasaurus)|26%|5145 (± 1.62%)| +|[`themis`](https://github.com/playlyfe/themis)|19.9%|3935 (± 2.69%)| +|[`z-schema`](https://github.com/zaggino/z-schema)|7%|1388 (± 0.84%)| +|[`jsck`](https://github.com/pandastrike/jsck#readme)|3.1%|606 (± 2.84%)| +|[`jsonschema`](https://github.com/tdegrunt/jsonschema#readme)|0.9%|185 (± 1.01%)| +|[`skeemas`](https://github.com/Prestaul/skeemas#readme)|0.8%|154 (± 0.79%)| +|tv4|0.5%|93 (± 0.94%)| +|[`jayschema`](https://github.com/natesilva/jayschema)|0.1%|21 (± 1.14%)| + +That is, RapidJSON is about ~365 times faster than the fastest JavaScript library (ajv). And ~344 thousand times faster than the slowest one. diff --git a/doc/schema.zh-cn.md b/doc/schema.zh-cn.md new file mode 100644 index 0000000..190285c --- /dev/null +++ b/doc/schema.zh-cn.md @@ -0,0 +1,237 @@ +# Schema + +## 状态: 实验性,应该会合进 v1.1 + +JSON Schema 是描述 JSON 格式的一个标准草案。一个 schema 本身也是一个 JSON。使用 JSON Schema 去校验 JSON,可以让你的代码安全地访问 DOM,而无须检查类型或键值是否存在等。这也能确保输出的 JSON 是符合指定的 schema。 + +RapidJSON 实现了一个 [JSON Schema Draft v4](http://json-schema.org/documentation.html) 的校验器。若你不熟悉 JSON Schema,可以参考 [Understanding JSON Schema](http://spacetelescope.github.io/understanding-json-schema/)。 + +[TOC] + +## 基本用法 + +首先,你要把 JSON Schema 解析成 `Document`,再把它编译成一个 `SchemaDocument`。 + +然后,利用该 `SchemaDocument` 创建一个 `SchemaValidator`。它与 `Writer` 相似,都是能够处理 SAX 事件的。因此,你可以用 `document.Accept(validator)` 去校验一个 JSON,然后再获取校验结果。 + +~~~cpp +#include "rapidjson/schema.h" + +// ... + +Document sd; +if (!sd.Parse(schemaJson)) { + // the schema is not a valid JSON. + // ... +} +SchemaDocument schema(sd); // Compile a Document to SchemaDocument +// sd is no longer needed here. + +Document d; +if (!d.Parse(inputJson)) { + // the input is not a valid JSON. + // ... +} + +SchemaValidator validator(schema); +if (!d.Accept(validator)) { + // Input JSON is invalid according to the schema + // Output diagnostic information + StringBuffer sb; + validator.GetInvalidSchemaPointer().StringifyUriFragment(sb); + printf("Invalid schema: %s\n", sb.GetString()); + printf("Invalid keyword: %s\n", validator.GetInvalidSchemaKeyword()); + sb.Clear(); + validator.GetInvalidDocumentPointer().StringifyUriFragment(sb); + printf("Invalid document: %s\n", sb.GetString()); +} +~~~ + +一些注意点: + +* 一个 `SchemaDocment` 能被多个 `SchemaValidator` 吊用。它不会被 `SchemaValidator` 修改。 +* 一个 `SchemaValidator` 可以重复使用来校验多个文件。在校验其他文件前,先调用 `validator.Reset()`。 + +## 在解析/生成时进行校验 + +与大部分 JSON Schema 校验器有所不同,RapidJSON 提供了一个基于 SAX 的 schema 校验器实现。因此,你可以在输入流解析 JSON 的同时进行校验。若校验器遇到一个与 schema 不符的值,就会立即终止解析。这设计对于解析大型 JSON 文件时特别有用。 + +### DOM 解析 + +在使用 DOM 进行解析时,`Document` 除了接收 SAX 事件外,还需做一些准备及结束工作,因此,为了连接 `Reader`、`SchemaValidator` 和 `Document` 要做多一点事情。`SchemaValidatingReader` 是一个辅助类去做那些工作。 + +~~~cpp +#include "rapidjson/filereadstream.h" + +// ... +SchemaDocument schema(sd); // Compile a Document to SchemaDocument + +// Use reader to parse the JSON +FILE* fp = fopen("big.json", "r"); +FileReadStream is(fp, buffer, sizeof(buffer)); + +// Parse JSON from reader, validate the SAX events, and store in d. +Document d; +SchemaValidatingReader > reader(is, schema); +d.Populate(reader); + +if (!reader.GetParseResult()) { + // Not a valid JSON + // When reader.GetParseResult().Code() == kParseErrorTermination, + // it may be terminated by: + // (1) the validator found that the JSON is invalid according to schema; or + // (2) the input stream has I/O error. + + // Check the validation result + if (!reader.IsValid()) { + // Input JSON is invalid according to the schema + // Output diagnostic information + StringBuffer sb; + reader.GetInvalidSchemaPointer().StringifyUriFragment(sb); + printf("Invalid schema: %s\n", sb.GetString()); + printf("Invalid keyword: %s\n", reader.GetInvalidSchemaKeyword()); + sb.Clear(); + reader.GetInvalidDocumentPointer().StringifyUriFragment(sb); + printf("Invalid document: %s\n", sb.GetString()); + } +} +~~~ + +### SAX 解析 + +使用 SAX 解析时,情况就简单得多。若只需要校验 JSON 而无需进一步处理,那么仅需要: + +~~~ +SchemaValidator validator(schema); +Reader reader; +if (!reader.Parse(stream, validator)) { + if (!validator.IsValid()) { + // ... + } +} +~~~ + +这种方式和 [schemavalidator](example/schemavalidator/schemavalidator.cpp) 例子完全相同。这带来的独特优势是,无论 JSON 多巨大,永远维持低内存用量(内存用量只与 Schema 的复杂度相关)。 + +若你需要进一步处理 SAX 事件,便可使用模板类 `GenericSchemaValidator` 去设置校验器的输出 `Handler`: + +~~~ +MyHandler handler; +GenericSchemaValidator validator(schema, handler); +Reader reader; +if (!reader.Parse(ss, validator)) { + if (!validator.IsValid()) { + // ... + } +} +~~~ + +### 生成 + +我们也可以在生成(serialization)的时候进行校验。这能确保输出的 JSON 符合一个 JSON Schema。 + +~~~ +StringBuffer sb; +Writer writer(sb); +GenericSchemaValidator > validator(s, writer); +if (!d.Accept(validator)) { + // Some problem during Accept(), it may be validation or encoding issues. + if (!validator.IsValid()) { + // ... + } +} +~~~ + +当然,如果你的应用仅需要 SAX 风格的生成,那么只需要把 SAX 事件由原来发送到 `Writer`,改为发送到 `SchemaValidator`。 + +## 远程 Schema + +JSON Schema 支持 [`$ref` 关键字](http://spacetelescope.github.io/understanding-json-schema/structuring.html),它是一个[JSON pointer](pointer.md) 引用至一个本地(local)或远程(remote) schema。本地指针的首字符是 `#`,而远程指针是一个相对或绝对 URI。例如: + +~~~js +{ "$ref": "definitions.json#/address" } +~~~ + +由于 `SchemaValidator` 并不知道如何处理那些 URI,它需要使用者提供一个 `IRemoteSchemaDocumentProvider` 的实例去处理。 + +~~~ +class MyRemoteSchemaDocumentProvider : public IRemoteSchemaDocumentProvider { +public: + virtual const SchemaDocument* GetRemoteDocument(const char* uri, SizeTyp length) { + // Resolve the uri and returns a pointer to that schema. + } +}; + +// ... + +MyRemoteSchemaDocumentProvider provider; +SchemaValidator validator(schema, &provider); +~~~ + +## 标准的符合程度 + +RapidJSON 通过了 [JSON Schema Test Suite](https://github.com/json-schema/JSON-Schema-Test-Suite) (Json Schema draft 4) 中 263 个测试的 262 个。 + +没通过的测试是 `refRemote.json` 中的 "change resolution scope" - "changed scope ref invalid"。这是由于未实现 `id` schema 关键字及 URI 合并功能。 + +除此以外,关于字符串类型的 `format` schema 关键字也会被忽略,因为标准中并没需求必须实现。 + +### 正则表达式 + +`pattern` 及 `patternProperties` 这两个 schema 关键字使用了正则表达式去匹配所需的模式。 + +RapidJSON 实现了一个简单的 NFA 正则表达式引擎,并预设使用。它支持以下语法。 + +|语法|描述| +|------|-----------| +|`ab` | 串联 +|`a|b` | 交替 +|`a?` | 零或一次 +|`a*` | 零或多次 +|`a+` | 一或多次 +|`a{3}` | 刚好 3 次 +|`a{3,}` | 至少 3 次 +|`a{3,5}`| 3 至 5 次 +|`(ab)` | 分组 +|`^a` | 在开始处 +|`a$` | 在结束处 +|`.` | 任何字符 +|`[abc]` | 字符组 +|`[a-c]` | 字符组范围 +|`[a-z0-9_]` | 字符组组合 +|`[^abc]` | 字符组取反 +|`[^a-c]` | 字符组范围取反 +|`[\b]` | 退格符 (U+0008) +|`\|`, `\\`, ... | 转义字符 +|`\f` | 馈页 (U+000C) +|`\n` | 馈行 (U+000A) +|`\r` | 回车 (U+000D) +|`\t` | 制表 (U+0009) +|`\v` | 垂直制表 (U+000B) + +对于使用 C++11 编译器的使用者,也可使用 `std::regex`,只需定义 `RAPIDJSON_SCHEMA_USE_INTERNALREGEX=0` 及 `RAPIDJSON_SCHEMA_USE_STDREGEX=1`。若你的 schema 无需使用 `pattern` 或 `patternProperties`,可以把两个宏都设为零,以禁用此功能,这样做可节省一些代码体积。 + +## 性能 + +大部分 C++ JSON 库都未支持 JSON Schema。因此我们尝试按照 [json-schema-benchmark](https://github.com/ebdrup/json-schema-benchmark) 去评估 RapidJSON 的 JSON Schema 校验器。该评测测试了 11 个运行在 node.js 上的 JavaScript 库。 + +该评测校验 [JSON Schema Test Suite](https://github.com/json-schema/JSON-Schema-Test-Suite) 中的测试,当中排除了一些测试套件及个别测试。我们在 [`schematest.cpp`](test/perftest/schematest.cpp) 实现了相同的评测。 + +在 MacBook Pro (2.8 GHz Intel Core i7) 上收集到以下结果。 + +|校验器|相对速度|每秒执行的测试数目| +|---------|:------------:|:----------------------------:| +|RapidJSON|36521%|7220217| +|[`ajv`](https://github.com/epoberezkin/ajv)|100%|19770 (± 1.31%)| +|[`is-my-json-valid`](https://github.com/mafintosh/is-my-json-valid)|70%|13835 (± 2.84%)| +|[`jsen`](https://github.com/bugventure/jsen)|57.7%|11411 (± 1.27%)| +|[`schemasaurus`](https://github.com/AlexeyGrishin/schemasaurus)|26%|5145 (± 1.62%)| +|[`themis`](https://github.com/playlyfe/themis)|19.9%|3935 (± 2.69%)| +|[`z-schema`](https://github.com/zaggino/z-schema)|7%|1388 (± 0.84%)| +|[`jsck`](https://github.com/pandastrike/jsck#readme)|3.1%|606 (± 2.84%)| +|[`jsonschema`](https://github.com/tdegrunt/jsonschema#readme)|0.9%|185 (± 1.01%)| +|[`skeemas`](https://github.com/Prestaul/skeemas#readme)|0.8%|154 (± 0.79%)| +|tv4|0.5%|93 (± 0.94%)| +|[`jayschema`](https://github.com/natesilva/jayschema)|0.1%|21 (± 1.14%)| + +换言之,RapidJSON 比最快的 JavaScript 库(ajv)快约 365 倍。比最慢的快 34 万倍。 diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 127f71e..cfb55dd 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -9,6 +9,7 @@ set(EXAMPLES messagereader pretty prettyauto + schemavalidator serialize simpledom simplereader diff --git a/example/schemavalidator/schemavalidator.cpp b/example/schemavalidator/schemavalidator.cpp new file mode 100644 index 0000000..ce36ea9 --- /dev/null +++ b/example/schemavalidator/schemavalidator.cpp @@ -0,0 +1,72 @@ +// Schema Validator example + +// The example validates JSON text from stdin with a JSON schema specified in the argument. + +#include "rapidjson/error/en.h" +#include "rapidjson/filereadstream.h" +#include "rapidjson/schema.h" +#include "rapidjson/stringbuffer.h" + +using namespace rapidjson; + +int main(int argc, char *argv[]) { + if (argc != 2) { + fprintf(stderr, "Usage: schemavalidator schema.json < input.json\n"); + return EXIT_FAILURE; + } + + // Read a JSON schema from file into Document + Document d; + char buffer[4096]; + + { + FILE *fp = fopen(argv[1], "r"); + if (!fp) { + printf("Schema file '%s' not found\n", argv[1]); + return -1; + } + FileReadStream fs(fp, buffer, sizeof(buffer)); + d.ParseStream(fs); + if (d.HasParseError()) { + fprintf(stderr, "Schema file '%s' is not a valid JSON\n", argv[1]); + fprintf(stderr, "Error(offset %u): %s\n", + static_cast(d.GetErrorOffset()), + GetParseError_En(d.GetParseError())); + fclose(fp); + return EXIT_FAILURE; + } + fclose(fp); + } + + // Then convert the Document into SchemaDocument + SchemaDocument sd(d); + + // Use reader to parse the JSON in stdin, and forward SAX events to validator + SchemaValidator validator(sd); + Reader reader; + FileReadStream is(stdin, buffer, sizeof(buffer)); + if (!reader.Parse(is, validator) && reader.GetParseErrorCode() != kParseErrorTermination) { + // Schema validator error would cause kParseErrorTermination, which will handle it in next step. + fprintf(stderr, "Input is not a valid JSON\n"); + fprintf(stderr, "Error(offset %u): %s\n", + static_cast(reader.GetErrorOffset()), + GetParseError_En(reader.GetParseErrorCode())); + } + + // Check the validation result + if (validator.IsValid()) { + printf("Input JSON is valid.\n"); + return EXIT_SUCCESS; + } + else { + printf("Input JSON is invalid.\n"); + StringBuffer sb; + validator.GetInvalidSchemaPointer().StringifyUriFragment(sb); + fprintf(stderr, "Invalid schema: %s\n", sb.GetString()); + fprintf(stderr, "Invalid keyword: %s\n", validator.GetInvalidSchemaKeyword()); + sb.Clear(); + validator.GetInvalidDocumentPointer().StringifyUriFragment(sb); + fprintf(stderr, "Invalid document: %s\n", sb.GetString()); + return EXIT_FAILURE; + } +} diff --git a/include/rapidjson/document.h b/include/rapidjson/document.h index 12187ea..026bb57 100644 --- a/include/rapidjson/document.h +++ b/include/rapidjson/document.h @@ -1535,22 +1535,22 @@ public: case kTrueType: return handler.Bool(true); case kObjectType: - if (!handler.StartObject()) + if (RAPIDJSON_UNLIKELY(!handler.StartObject())) return false; for (ConstMemberIterator m = MemberBegin(); m != MemberEnd(); ++m) { RAPIDJSON_ASSERT(m->name.IsString()); // User may change the type of name by MemberIterator. - if (!handler.Key(m->name.GetString(), m->name.GetStringLength(), (m->name.flags_ & kCopyFlag) != 0)) + if (RAPIDJSON_UNLIKELY(!handler.Key(m->name.GetString(), m->name.GetStringLength(), (m->name.flags_ & kCopyFlag) != 0))) return false; - if (!m->value.Accept(handler)) + if (RAPIDJSON_UNLIKELY(!m->value.Accept(handler))) return false; } return handler.EndObject(data_.o.size); case kArrayType: - if (!handler.StartArray()) + if (RAPIDJSON_UNLIKELY(!handler.StartArray())) return false; - for (GenericValue* v = data_.a.elements; v != data_.a.elements + data_.a.size; ++v) - if (!v->Accept(handler)) + for (const GenericValue* v = data_.a.elements; v != data_.a.elements + data_.a.size; ++v) + if (RAPIDJSON_UNLIKELY(!v->Accept(handler))) return false; return handler.EndArray(data_.a.size); @@ -1559,11 +1559,11 @@ public: default: RAPIDJSON_ASSERT(GetType() == kNumberType); - if (IsInt()) return handler.Int(data_.n.i.i); + if (IsDouble()) return handler.Double(data_.n.d); + else if (IsInt()) return handler.Int(data_.n.i.i); else if (IsUint()) return handler.Uint(data_.n.u.u); else if (IsInt64()) return handler.Int64(data_.n.i64); - else if (IsUint64()) return handler.Uint64(data_.n.u64); - else return handler.Double(data_.n.d); + else return handler.Uint64(data_.n.u64); } } @@ -1869,6 +1869,21 @@ public: */ friend inline void swap(GenericDocument& a, GenericDocument& b) RAPIDJSON_NOEXCEPT { a.Swap(b); } + //! Populate this document by a generator which produces SAX events. + /*! \tparam Generator A functor with bool f(Handler) prototype. + \param g Generator functor which sends SAX events to the parameter. + \return The document itself for fluent API. + */ + template + GenericDocument& Populate(Generator& g) { + ClearStackOnExit scope(*this); + if (g(*this)) { + RAPIDJSON_ASSERT(stack_.GetSize() == sizeof(ValueType)); // Got one and only one root object + ValueType::operator=(*stack_.template Pop(1));// Move value from stack to document + } + return *this; + } + //!@name Parse from stream //!@{ @@ -2017,9 +2032,10 @@ private: }; // callers of the following private Handler functions - template friend class GenericReader; // for parsing + // template friend class GenericReader; // for parsing template friend class GenericValue; // for deep copying +public: // Implementation of Handler bool Null() { new (stack_.template Push()) ValueType(); return true; } bool Bool(bool b) { new (stack_.template Push()) ValueType(b); return true; } diff --git a/include/rapidjson/internal/regex.h b/include/rapidjson/internal/regex.h new file mode 100644 index 0000000..edcb974 --- /dev/null +++ b/include/rapidjson/internal/regex.h @@ -0,0 +1,677 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_REGEX_H_ +#define RAPIDJSON_INTERNAL_REGEX_H_ + +#include "../rapidjson.h" +#include "stack.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +RAPIDJSON_DIAG_OFF(switch-enum) +RAPIDJSON_DIAG_OFF(implicit-fallthrough) +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#ifndef RAPIDJSON_REGEX_VERBOSE +#define RAPIDJSON_REGEX_VERBOSE 0 +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +/////////////////////////////////////////////////////////////////////////////// +// GenericRegex + +static const SizeType kRegexInvalidState = ~SizeType(0); //!< Represents an invalid index in GenericRegex::State::out, out1 +static const SizeType kRegexInvalidRange = ~SizeType(0); + +//! Regular expression engine with subset of ECMAscript grammar. +/*! + Supported regular expression syntax: + - \c ab Concatenation + - \c a|b Alternation + - \c a? Zero or one + - \c a* Zero or more + - \c a+ One or more + - \c a{3} Exactly 3 times + - \c a{3,} At least 3 times + - \c a{3,5} 3 to 5 times + - \c (ab) Grouping + - \c ^a At the beginning + - \c a$ At the end + - \c . Any character + - \c [abc] Character classes + - \c [a-c] Character class range + - \c [a-z0-9_] Character class combination + - \c [^abc] Negated character classes + - \c [^a-c] Negated character class range + - \c [\b] Backspace (U+0008) + - \c \\| \\\\ ... Escape characters + - \c \\f Form feed (U+000C) + - \c \\n Line feed (U+000A) + - \c \\r Carriage return (U+000D) + - \c \\t Tab (U+0009) + - \c \\v Vertical tab (U+000B) + + \note This is a Thompson NFA engine, implemented with reference to + Cox, Russ. "Regular Expression Matching Can Be Simple And Fast (but is slow in Java, Perl, PHP, Python, Ruby,...).", + https://swtch.com/~rsc/regexp/regexp1.html +*/ +template +class GenericRegex { +public: + typedef typename Encoding::Ch Ch; + + GenericRegex(const Ch* source, Allocator* allocator = 0) : + states_(allocator, 256), ranges_(allocator, 256), root_(kRegexInvalidState), stateCount_(), rangeCount_(), + stateSet_(), state0_(allocator, 0), state1_(allocator, 0), anchorBegin_(), anchorEnd_() + { + GenericStringStream ss(source); + DecodedStream > ds(ss); + Parse(ds); + } + + ~GenericRegex() { + Allocator::Free(stateSet_); + } + + bool IsValid() const { + return root_ != kRegexInvalidState; + } + + template + bool Match(InputStream& is) const { + return SearchWithAnchoring(is, true, true); + } + + bool Match(const Ch* s) const { + GenericStringStream is(s); + return Match(is); + } + + template + bool Search(InputStream& is) const { + return SearchWithAnchoring(is, anchorBegin_, anchorEnd_); + } + + bool Search(const Ch* s) const { + GenericStringStream is(s); + return Search(is); + } + +private: + enum Operator { + kZeroOrOne, + kZeroOrMore, + kOneOrMore, + kConcatenation, + kAlternation, + kLeftParenthesis + }; + + static const unsigned kAnyCharacterClass = 0xFFFFFFFF; //!< For '.' + static const unsigned kRangeCharacterClass = 0xFFFFFFFE; + static const unsigned kRangeNegationFlag = 0x80000000; + + struct Range { + unsigned start; // + unsigned end; + SizeType next; + }; + + struct State { + SizeType out; //!< Equals to kInvalid for matching state + SizeType out1; //!< Equals to non-kInvalid for split + SizeType rangeStart; + unsigned codepoint; + }; + + struct Frag { + Frag(SizeType s, SizeType o, SizeType m) : start(s), out(o), minIndex(m) {} + SizeType start; + SizeType out; //!< link-list of all output states + SizeType minIndex; + }; + + template + class DecodedStream { + public: + DecodedStream(SourceStream& ss) : ss_(ss), codepoint_() { Decode(); } + unsigned Peek() { return codepoint_; } + unsigned Take() { + unsigned c = codepoint_; + if (c) // No further decoding when '\0' + Decode(); + return c; + } + + private: + void Decode() { + if (!Encoding::Decode(ss_, &codepoint_)) + codepoint_ = 0; + } + + SourceStream& ss_; + unsigned codepoint_; + }; + + State& GetState(SizeType index) { + RAPIDJSON_ASSERT(index < stateCount_); + return states_.template Bottom()[index]; + } + + const State& GetState(SizeType index) const { + RAPIDJSON_ASSERT(index < stateCount_); + return states_.template Bottom()[index]; + } + + Range& GetRange(SizeType index) { + RAPIDJSON_ASSERT(index < rangeCount_); + return ranges_.template Bottom()[index]; + } + + const Range& GetRange(SizeType index) const { + RAPIDJSON_ASSERT(index < rangeCount_); + return ranges_.template Bottom()[index]; + } + + template + void Parse(DecodedStream& ds) { + Allocator allocator; + Stack operandStack(&allocator, 256); // Frag + Stack operatorStack(&allocator, 256); // Operator + Stack atomCountStack(&allocator, 256); // unsigned (Atom per parenthesis) + + *atomCountStack.template Push() = 0; + + unsigned codepoint; + while (ds.Peek() != 0) { + switch (codepoint = ds.Take()) { + case '^': + anchorBegin_ = true; + break; + + case '$': + anchorEnd_ = true; + break; + + case '|': + while (!operatorStack.Empty() && *operatorStack.template Top() < kAlternation) + if (!Eval(operandStack, *operatorStack.template Pop(1))) + return; + *operatorStack.template Push() = kAlternation; + *atomCountStack.template Top() = 0; + break; + + case '(': + *operatorStack.template Push() = kLeftParenthesis; + *atomCountStack.template Push() = 0; + break; + + case ')': + while (!operatorStack.Empty() && *operatorStack.template Top() != kLeftParenthesis) + if (!Eval(operandStack, *operatorStack.template Pop(1))) + return; + if (operatorStack.Empty()) + return; + operatorStack.template Pop(1); + atomCountStack.template Pop(1); + ImplicitConcatenation(atomCountStack, operatorStack); + break; + + case '?': + if (!Eval(operandStack, kZeroOrOne)) + return; + break; + + case '*': + if (!Eval(operandStack, kZeroOrMore)) + return; + break; + + case '+': + if (!Eval(operandStack, kOneOrMore)) + return; + break; + + case '{': + { + unsigned n, m; + if (!ParseUnsigned(ds, &n) || n == 0) + return; + + if (ds.Peek() == ',') { + ds.Take(); + if (ds.Peek() == '}') + m = 0; + else if (!ParseUnsigned(ds, &m) || m < n) + return; + } + else + m = n; + + if (!EvalQuantifier(operandStack, n, m) || ds.Peek() != '}') + return; + ds.Take(); + } + break; + + case '.': + PushOperand(operandStack, kAnyCharacterClass); + ImplicitConcatenation(atomCountStack, operatorStack); + break; + + case '[': + { + SizeType range; + if (!ParseRange(ds, &range)) + return; + SizeType s = NewState(kRegexInvalidState, kRegexInvalidState, kRangeCharacterClass); + GetState(s).rangeStart = range; + *operandStack.template Push() = Frag(s, s, s); + } + ImplicitConcatenation(atomCountStack, operatorStack); + break; + + case '\\': // Escape character + if (!CharacterEscape(ds, &codepoint)) + return; // Unsupported escape character + // fall through to default + + default: // Pattern character + PushOperand(operandStack, codepoint); + ImplicitConcatenation(atomCountStack, operatorStack); + } + } + + while (!operatorStack.Empty()) + if (!Eval(operandStack, *operatorStack.template Pop(1))) + return; + + // Link the operand to matching state. + if (operandStack.GetSize() == sizeof(Frag)) { + Frag* e = operandStack.template Pop(1); + Patch(e->out, NewState(kRegexInvalidState, kRegexInvalidState, 0)); + root_ = e->start; + +#if RAPIDJSON_REGEX_VERBOSE + printf("root: %d\n", root_); + for (SizeType i = 0; i < stateCount_ ; i++) { + State& s = GetState(i); + printf("[%2d] out: %2d out1: %2d c: '%c'\n", i, s.out, s.out1, (char)s.codepoint); + } + printf("\n"); +#endif + } + + // Preallocate buffer for SearchWithAnchoring() + RAPIDJSON_ASSERT(stateSet_ == 0); + if (stateCount_ > 0) { + stateSet_ = static_cast(states_.GetAllocator().Malloc(GetStateSetSize())); + state0_.template Reserve(stateCount_); + state1_.template Reserve(stateCount_); + } + } + + SizeType NewState(SizeType out, SizeType out1, unsigned codepoint) { + State* s = states_.template Push(); + s->out = out; + s->out1 = out1; + s->codepoint = codepoint; + s->rangeStart = kRegexInvalidRange; + return stateCount_++; + } + + void PushOperand(Stack& operandStack, unsigned codepoint) { + SizeType s = NewState(kRegexInvalidState, kRegexInvalidState, codepoint); + *operandStack.template Push() = Frag(s, s, s); + } + + void ImplicitConcatenation(Stack& atomCountStack, Stack& operatorStack) { + if (*atomCountStack.template Top()) + *operatorStack.template Push() = kConcatenation; + (*atomCountStack.template Top())++; + } + + SizeType Append(SizeType l1, SizeType l2) { + SizeType old = l1; + while (GetState(l1).out != kRegexInvalidState) + l1 = GetState(l1).out; + GetState(l1).out = l2; + return old; + } + + void Patch(SizeType l, SizeType s) { + for (SizeType next; l != kRegexInvalidState; l = next) { + next = GetState(l).out; + GetState(l).out = s; + } + } + + bool Eval(Stack& operandStack, Operator op) { + switch (op) { + case kConcatenation: + if (operandStack.GetSize() >= sizeof(Frag) * 2) { + Frag e2 = *operandStack.template Pop(1); + Frag e1 = *operandStack.template Pop(1); + Patch(e1.out, e2.start); + *operandStack.template Push() = Frag(e1.start, e2.out, Min(e1.minIndex, e2.minIndex)); + return true; + } + return false; + + case kAlternation: + if (operandStack.GetSize() >= sizeof(Frag) * 2) { + Frag e2 = *operandStack.template Pop(1); + Frag e1 = *operandStack.template Pop(1); + SizeType s = NewState(e1.start, e2.start, 0); + *operandStack.template Push() = Frag(s, Append(e1.out, e2.out), Min(e1.minIndex, e2.minIndex)); + return true; + } + return false; + + case kZeroOrOne: + if (operandStack.GetSize() >= sizeof(Frag)) { + Frag e = *operandStack.template Pop(1); + SizeType s = NewState(kRegexInvalidState, e.start, 0); + *operandStack.template Push() = Frag(s, Append(e.out, s), e.minIndex); + return true; + } + return false; + + case kZeroOrMore: + if (operandStack.GetSize() >= sizeof(Frag)) { + Frag e = *operandStack.template Pop(1); + SizeType s = NewState(kRegexInvalidState, e.start, 0); + Patch(e.out, s); + *operandStack.template Push() = Frag(s, s, e.minIndex); + return true; + } + return false; + + case kOneOrMore: + if (operandStack.GetSize() >= sizeof(Frag)) { + Frag e = *operandStack.template Pop(1); + SizeType s = NewState(kRegexInvalidState, e.start, 0); + Patch(e.out, s); + *operandStack.template Push() = Frag(e.start, s, e.minIndex); + return true; + } + return false; + + default: + return false; + } + } + + bool EvalQuantifier(Stack& operandStack, unsigned n, unsigned m) { + RAPIDJSON_ASSERT(n > 0); + RAPIDJSON_ASSERT(m == 0 || n <= m); // m == 0 means infinity + if (operandStack.GetSize() < sizeof(Frag)) + return false; + + for (unsigned i = 0; i < n - 1; i++) // a{3} -> a a a + CloneTopOperand(operandStack); + + if (m == 0) + Eval(operandStack, kOneOrMore); // a{3,} -> a a a+ + else if (m > n) { + CloneTopOperand(operandStack); // a{3,5} -> a a a a + Eval(operandStack, kZeroOrOne); // a{3,5} -> a a a a? + for (unsigned i = n; i < m - 1; i++) + CloneTopOperand(operandStack); // a{3,5} -> a a a a? a? + for (unsigned i = n; i < m; i++) + Eval(operandStack, kConcatenation); // a{3,5} -> a a aa?a? + } + + for (unsigned i = 0; i < n - 1; i++) + Eval(operandStack, kConcatenation); // a{3} -> aaa, a{3,} -> aaa+, a{3.5} -> aaaa?a? + + return true; + } + + static SizeType Min(SizeType a, SizeType b) { return a < b ? a : b; } + + void CloneTopOperand(Stack& operandStack) { + const Frag *src = operandStack.template Top(); + SizeType count = stateCount_ - src->minIndex; // Assumes top operand contains states in [src->minIndex, stateCount_) + State* s = states_.template Push(count); + memcpy(s, &GetState(src->minIndex), count * sizeof(State)); + for (SizeType j = 0; j < count; j++) { + if (s[j].out != kRegexInvalidState) + s[j].out += count; + if (s[j].out1 != kRegexInvalidState) + s[j].out1 += count; + } + *operandStack.template Push() = Frag(src->start + count, src->out + count, src->minIndex + count); + stateCount_ += count; + } + + template + bool ParseUnsigned(DecodedStream& ds, unsigned* u) { + unsigned r = 0; + while (ds.Peek() >= '0' && ds.Peek() <= '9') { + if (r >= 429496729 && ds.Peek() > '5') // 2^32 - 1 = 4294967295 + return false; // overflow + r = r * 10 + (ds.Take() - '0'); + } + *u = r; + return true; + } + + template + bool ParseRange(DecodedStream& ds, SizeType* range) { + bool isBegin = true; + bool negate = false; + int step = 0; + SizeType start = kRegexInvalidRange; + SizeType current = kRegexInvalidRange; + unsigned codepoint; + while ((codepoint = ds.Take()) != 0) { + if (isBegin) { + isBegin = false; + if (codepoint == '^') { + negate = true; + continue; + } + } + + switch (codepoint) { + case ']': + if (start == kRegexInvalidRange) + return false; // Error: nothing inside [] + if (step == 2) { // Add trailing '-' + SizeType r = NewRange('-'); + RAPIDJSON_ASSERT(current != kRegexInvalidRange); + GetRange(current).next = r; + } + if (negate) + GetRange(start).start |= kRangeNegationFlag; + *range = start; + return true; + + case '\\': + if (ds.Peek() == 'b') { + ds.Take(); + codepoint = 0x0008; // Escape backspace character + } + else if (!CharacterEscape(ds, &codepoint)) + return false; + // fall through to default + + default: + switch (step) { + case 1: + if (codepoint == '-') { + step++; + break; + } + // fall through to step 0 for other characters + + case 0: + { + SizeType r = NewRange(codepoint); + if (current != kRegexInvalidRange) + GetRange(current).next = r; + if (start == kRegexInvalidRange) + start = r; + current = r; + } + step = 1; + break; + + default: + RAPIDJSON_ASSERT(step == 2); + GetRange(current).end = codepoint; + step = 0; + } + } + } + return false; + } + + SizeType NewRange(unsigned codepoint) { + Range* r = ranges_.template Push(); + r->start = r->end = codepoint; + r->next = kRegexInvalidRange; + return rangeCount_++; + } + + template + bool CharacterEscape(DecodedStream& ds, unsigned* escapedCodepoint) { + unsigned codepoint; + switch (codepoint = ds.Take()) { + case '^': + case '$': + case '|': + case '(': + case ')': + case '?': + case '*': + case '+': + case '.': + case '[': + case ']': + case '{': + case '}': + case '\\': + *escapedCodepoint = codepoint; return true; + case 'f': *escapedCodepoint = 0x000C; return true; + case 'n': *escapedCodepoint = 0x000A; return true; + case 'r': *escapedCodepoint = 0x000D; return true; + case 't': *escapedCodepoint = 0x0009; return true; + case 'v': *escapedCodepoint = 0x000B; return true; + default: + return false; // Unsupported escape character + } + } + + template + bool SearchWithAnchoring(InputStream& is, bool anchorBegin, bool anchorEnd) const { + RAPIDJSON_ASSERT(IsValid()); + DecodedStream ds(is); + + state0_.Clear(); + Stack *current = &state0_, *next = &state1_; + const size_t stateSetSize = GetStateSetSize(); + std::memset(stateSet_, 0, stateSetSize); + + bool matched = AddState(*current, root_); + unsigned codepoint; + while (!current->Empty() && (codepoint = ds.Take()) != 0) { + std::memset(stateSet_, 0, stateSetSize); + next->Clear(); + matched = false; + for (const SizeType* s = current->template Bottom(); s != current->template End(); ++s) { + const State& sr = GetState(*s); + if (sr.codepoint == codepoint || + sr.codepoint == kAnyCharacterClass || + (sr.codepoint == kRangeCharacterClass && MatchRange(sr.rangeStart, codepoint))) + { + matched = AddState(*next, sr.out) || matched; + if (!anchorEnd && matched) + return true; + } + if (!anchorBegin) + AddState(*next, root_); + } + internal::Swap(current, next); + } + + return matched; + } + + size_t GetStateSetSize() const { + return (stateCount_ + 31) / 32 * 4; + } + + // Return whether the added states is a match state + bool AddState(Stack& l, SizeType index) const { + if (index == kRegexInvalidState) + return true; + + const State& s = GetState(index); + if (s.out1 != kRegexInvalidState) { // Split + bool matched = AddState(l, s.out); + return AddState(l, s.out1) || matched; + } + else if (!(stateSet_[index >> 5] & (1 << (index & 31)))) { + stateSet_[index >> 5] |= (1 << (index & 31)); + *l.template PushUnsafe() = index; + } + return s.out == kRegexInvalidState; // by using PushUnsafe() above, we can ensure s is not validated due to reallocation. + } + + bool MatchRange(SizeType rangeIndex, unsigned codepoint) const { + bool yes = (GetRange(rangeIndex).start & kRangeNegationFlag) == 0; + while (rangeIndex != kRegexInvalidRange) { + const Range& r = GetRange(rangeIndex); + if (codepoint >= (r.start & ~kRangeNegationFlag) && codepoint <= r.end) + return yes; + rangeIndex = r.next; + } + return !yes; + } + + Stack states_; + Stack ranges_; + SizeType root_; + SizeType stateCount_; + SizeType rangeCount_; + + // For SearchWithAnchoring() + uint32_t* stateSet_; // allocated by states_.GetAllocator() + mutable Stack state0_; + mutable Stack state1_; + bool anchorBegin_; + bool anchorEnd_; +}; + +typedef GenericRegex > Regex; + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_INTERNAL_REGEX_H_ diff --git a/include/rapidjson/internal/stack.h b/include/rapidjson/internal/stack.h index 12f8a8f..6615c46 100644 --- a/include/rapidjson/internal/stack.h +++ b/include/rapidjson/internal/stack.h @@ -38,7 +38,6 @@ public: // Optimization note: Do not allocate memory for stack_ in constructor. // Do it lazily when first Push() -> Expand() -> Resize(). Stack(Allocator* allocator, size_t stackCapacity) : allocator_(allocator), ownAllocator_(0), stack_(0), stackTop_(0), stackEnd_(0), initialCapacity_(stackCapacity) { - RAPIDJSON_ASSERT(stackCapacity > 0); } #if RAPIDJSON_HAS_CXX11_RVALUE_REFS @@ -146,9 +145,24 @@ public: return reinterpret_cast(stackTop_ - sizeof(T)); } + template + const T* Top() const { + RAPIDJSON_ASSERT(GetSize() >= sizeof(T)); + return reinterpret_cast(stackTop_ - sizeof(T)); + } + + template + T* End() { return reinterpret_cast(stackTop_); } + + template + const T* End() const { return reinterpret_cast(stackTop_); } + template T* Bottom() { return reinterpret_cast(stack_); } + template + const T* Bottom() const { return reinterpret_cast(stack_); } + bool HasAllocator() const { return allocator_ != 0; } @@ -157,6 +171,7 @@ public: RAPIDJSON_ASSERT(allocator_); return *allocator_; } + bool Empty() const { return stackTop_ == stack_; } size_t GetSize() const { return static_cast(stackTop_ - stack_); } size_t GetCapacity() const { return static_cast(stackEnd_ - stack_); } diff --git a/include/rapidjson/internal/strfunc.h b/include/rapidjson/internal/strfunc.h index f6c99db..040ca1e 100644 --- a/include/rapidjson/internal/strfunc.h +++ b/include/rapidjson/internal/strfunc.h @@ -33,6 +33,22 @@ inline SizeType StrLen(const Ch* s) { return SizeType(p - s); } +//! Returns number of code points in a encoded string. +template +bool CountStringCodePoint(const typename Encoding::Ch* s, SizeType length, SizeType* outCount) { + GenericStringStream is(s); + const typename Encoding::Ch* end = s + length; + SizeType count = 0; + while (is.src_ < end) { + unsigned codepoint; + if (!Encoding::Decode(is, &codepoint)) + return false; + count++; + } + *outCount = count; + return true; +} + } // namespace internal RAPIDJSON_NAMESPACE_END diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index 7519490..63790cb 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -101,7 +101,7 @@ public: //@{ //! Default constructor. - GenericPointer() : allocator_(), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) {} + GenericPointer(Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) {} //! Constructor that parses a string or URI fragment representation. /*! @@ -160,7 +160,7 @@ public: GenericPointer(const Token* tokens, size_t tokenCount) : allocator_(), ownAllocator_(), nameBuffer_(), tokens_(const_cast(tokens)), tokenCount_(tokenCount), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) {} //! Copy constructor. - GenericPointer(const GenericPointer& rhs) : allocator_(), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + GenericPointer(const GenericPointer& rhs, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { *this = rhs; } @@ -305,6 +305,9 @@ public: //@} + //! Get the allocator of this pointer. + Allocator& GetAllocator() { return *allocator_; } + //!@name Tokens //@{ diff --git a/include/rapidjson/schema.h b/include/rapidjson/schema.h new file mode 100644 index 0000000..a72ace9 --- /dev/null +++ b/include/rapidjson/schema.h @@ -0,0 +1,1979 @@ +// Tencent is pleased to support the open source community by making RapidJSON available-> +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip-> All rights reserved-> +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License-> You may obtain a copy of the License at +// +// http://opensource->org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied-> See the License for the +// specific language governing permissions and limitations under the License-> + +#ifndef RAPIDJSON_SCHEMA_H_ +#define RAPIDJSON_SCHEMA_H_ + +#include "document.h" +#include "pointer.h" +#include // HUGE_VAL, abs, floor + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(weak-vtables) +RAPIDJSON_DIAG_OFF(exit-time-destructors) +RAPIDJSON_DIAG_OFF(c++98-compat-pedantic) +#endif + +#if !defined(RAPIDJSON_SCHEMA_USE_INTERNALREGEX) +#define RAPIDJSON_SCHEMA_USE_INTERNALREGEX 1 +#else +#define RAPIDJSON_SCHEMA_USE_INTERNALREGEX 0 +#endif + +#if !RAPIDJSON_SCHEMA_USE_INTERNALREGEX && !defined(RAPIDJSON_SCHEMA_USE_STDREGEX) && (__cplusplus >=201103L || (defined(_MSC_VER) && _MSC_VER >= 1800)) +#define RAPIDJSON_SCHEMA_USE_STDREGEX 1 +#else +#define RAPIDJSON_SCHEMA_USE_STDREGEX 0 +#endif + +#if RAPIDJSON_SCHEMA_USE_INTERNALREGEX +#include "internal/regex.h" +#elif RAPIDJSON_SCHEMA_USE_STDREGEX +#include +#endif + +#if RAPIDJSON_SCHEMA_USE_INTERNALREGEX || RAPIDJSON_SCHEMA_USE_STDREGEX +#define RAPIDJSON_SCHEMA_HAS_REGEX 1 +#else +#define RAPIDJSON_SCHEMA_HAS_REGEX 0 +#endif + +#ifndef RAPIDJSON_SCHEMA_VERBOSE +#define RAPIDJSON_SCHEMA_VERBOSE 0 +#endif + +#if RAPIDJSON_SCHEMA_VERBOSE +#include "stringbuffer.h" +#endif + +#if defined(__GNUC__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(variadic-macros) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// Verbose Utilities + +#if RAPIDJSON_SCHEMA_VERBOSE + +namespace internal { + +inline void PrintInvalidKeyword(const char* keyword) { + printf("Fail keyword: %s\n", keyword); +} + +inline void PrintInvalidKeyword(const wchar_t* keyword) { + wprintf(L"Fail keyword: %ls\n", keyword); +} + +inline void PrintInvalidDocument(const char* document) { + printf("Fail document: %s\n\n", document); +} + +inline void PrintInvalidDocument(const wchar_t* document) { + wprintf(L"Fail document: %ls\n\n", document); +} + +inline void PrintValidatorPointers(unsigned depth, const char* s, const char* d) { + printf("S: %*s%s\nD: %*s%s\n\n", depth * 4, " ", s, depth * 4, " ", d); +} + +inline void PrintValidatorPointers(unsigned depth, const wchar_t* s, const wchar_t* d) { + wprintf(L"S: %*ls%ls\nD: %*ls%ls\n\n", depth * 4, L" ", s, depth * 4, L" ", d); +} + +} // namespace internal + +#endif // RAPIDJSON_SCHEMA_VERBOSE + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_INVALID_KEYWORD_RETURN + +#if RAPIDJSON_SCHEMA_VERBOSE +#define RAPIDJSON_INVALID_KEYWORD_VERBOSE(keyword) internal::PrintInvalidKeyword(keyword) +#else +#define RAPIDJSON_INVALID_KEYWORD_VERBOSE(keyword) +#endif + +#define RAPIDJSON_INVALID_KEYWORD_RETURN(keyword)\ +RAPIDJSON_MULTILINEMACRO_BEGIN\ + context.invalidKeyword = keyword.GetString();\ + RAPIDJSON_INVALID_KEYWORD_VERBOSE(keyword.GetString());\ + return false;\ +RAPIDJSON_MULTILINEMACRO_END + +/////////////////////////////////////////////////////////////////////////////// +// Forward declarations + +template +class GenericSchemaDocument; + +namespace internal { + +template +class Schema; + +/////////////////////////////////////////////////////////////////////////////// +// ISchemaValidator + +class ISchemaValidator { +public: + virtual ~ISchemaValidator() {} + virtual bool IsValid() const = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// +// ISchemaStateFactory + +template +class ISchemaStateFactory { +public: + virtual ~ISchemaStateFactory() {} + virtual ISchemaValidator* CreateSchemaValidator(const SchemaType&) = 0; + virtual void DestroySchemaValidator(ISchemaValidator* validator) = 0; + virtual void* CreateHasher() = 0; + virtual uint64_t GetHashCode(void* hasher) = 0; + virtual void DestroryHasher(void* hasher) = 0; + virtual void* MallocState(size_t size) = 0; + virtual void* ReallocState(void* originalPtr, size_t originalSize, size_t newSize) = 0; + virtual void FreeState(void* p) = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// +// Hasher + +// For comparison of compound value +template +class Hasher { +public: + typedef typename Encoding::Ch Ch; + + Hasher(Allocator* allocator = 0, size_t stackCapacity = kDefaultSize) : stack_(allocator, stackCapacity) {} + + bool Null() { return WriteType(kNullType); } + bool Bool(bool b) { return WriteType(b ? kTrueType : kFalseType); } + bool Int(int i) { Number n; n.u.i = i; n.d = static_cast(i); return WriteNumber(n); } + bool Uint(unsigned u) { Number n; n.u.u = u; n.d = static_cast(u); return WriteNumber(n); } + bool Int64(int64_t i) { Number n; n.u.i = i; n.d = static_cast(i); return WriteNumber(n); } + bool Uint64(uint64_t u) { Number n; n.u.u = u; n.d = static_cast(u); return WriteNumber(n); } + bool Double(double d) { + Number n; + if (d < 0) n.u.i = static_cast(d); + else n.u.u = static_cast(d); + n.d = d; + return WriteNumber(n); + } + + bool String(const Ch* str, SizeType len, bool) { + WriteBuffer(kStringType, str, len * sizeof(Ch)); + return true; + } + + bool StartObject() { return true; } + bool Key(const Ch* str, SizeType len, bool copy) { return String(str, len, copy); } + bool EndObject(SizeType memberCount) { + uint64_t h = Hash(0, kObjectType); + uint64_t* kv = stack_.template Pop(memberCount * 2); + for (SizeType i = 0; i < memberCount; i++) + h ^= Hash(kv[i * 2], kv[i * 2 + 1]); // Use xor to achieve member order insensitive + *stack_.template Push() = h; + return true; + } + + bool StartArray() { return true; } + bool EndArray(SizeType elementCount) { + uint64_t h = Hash(0, kArrayType); + uint64_t* e = stack_.template Pop(elementCount); + for (SizeType i = 0; i < elementCount; i++) + h = Hash(h, e[i]); // Use hash to achieve element order sensitive + *stack_.template Push() = h; + return true; + } + + bool IsValid() const { return stack_.GetSize() == sizeof(uint64_t); } + + uint64_t GetHashCode() const { + RAPIDJSON_ASSERT(IsValid()); + return *stack_.template Top(); + } + +private: + static const size_t kDefaultSize = 256; + struct Number { + union U { + uint64_t u; + int64_t i; + }u; + double d; + }; + + bool WriteType(Type type) { return WriteBuffer(type, 0, 0); } + + bool WriteNumber(const Number& n) { return WriteBuffer(kNumberType, &n, sizeof(n)); } + + bool WriteBuffer(Type type, const void* data, size_t len) { + // FNV-1a from http://isthe.com/chongo/tech/comp/fnv/ + uint64_t h = Hash(RAPIDJSON_UINT64_C2(0x84222325, 0xcbf29ce4), type); + const unsigned char* d = static_cast(data); + for (size_t i = 0; i < len; i++) + h = Hash(h, d[i]); + *stack_.template Push() = h; + return true; + } + + static uint64_t Hash(uint64_t h, uint64_t d) { + static const uint64_t kPrime = RAPIDJSON_UINT64_C2(0x00000100, 0x000001b3); + h ^= d; + h *= kPrime; + return h; + } + + Stack stack_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// SchemaValidationContext + +template +struct SchemaValidationContext { + typedef Schema SchemaType; + typedef ISchemaStateFactory SchemaValidatorFactoryType; + typedef typename SchemaType::ValueType ValueType; + typedef typename ValueType::Ch Ch; + + enum PatternValidatorType { + kPatternValidatorOnly, + kPatternValidatorWithProperty, + kPatternValidatorWithAdditionalProperty + }; + + SchemaValidationContext(SchemaValidatorFactoryType& f, const SchemaType* s) : + factory(f), + schema(s), + valueSchema(), + invalidKeyword(), + hasher(), + arrayElementHashCodes(), + validators(), + validatorCount(), + patternPropertiesValidators(), + patternPropertiesValidatorCount(), + patternPropertiesSchemas(), + patternPropertiesSchemaCount(), + valuePatternValidatorType(kPatternValidatorOnly), + objectDependencies(), + inArray(false), + valueUniqueness(false), + arrayUniqueness(false) + { + } + + ~SchemaValidationContext() { + if (hasher) + factory.DestroryHasher(hasher); + if (validators) { + for (SizeType i = 0; i < validatorCount; i++) + factory.DestroySchemaValidator(validators[i]); + factory.FreeState(validators); + } + if (patternPropertiesValidators) { + for (SizeType i = 0; i < patternPropertiesValidatorCount; i++) + factory.DestroySchemaValidator(patternPropertiesValidators[i]); + factory.FreeState(patternPropertiesValidators); + } + if (patternPropertiesSchemas) + factory.FreeState(patternPropertiesSchemas); + if (objectDependencies) + factory.FreeState(objectDependencies); + } + + SchemaValidatorFactoryType& factory; + const SchemaType* schema; + const SchemaType* valueSchema; + const Ch* invalidKeyword; + void* hasher; // Only validator access + void* arrayElementHashCodes; // Only validator access this + ISchemaValidator** validators; + SizeType validatorCount; + ISchemaValidator** patternPropertiesValidators; + SizeType patternPropertiesValidatorCount; + const SchemaType** patternPropertiesSchemas; + SizeType patternPropertiesSchemaCount; + PatternValidatorType valuePatternValidatorType; + PatternValidatorType objectPatternValidatorType; + SizeType objectRequiredCount; + SizeType arrayElementIndex; + bool* objectDependencies; + bool inArray; + bool valueUniqueness; + bool arrayUniqueness; +}; + +/////////////////////////////////////////////////////////////////////////////// +// Schema + +template +class Schema { +public: + typedef typename SchemaDocumentType::ValueType ValueType; + typedef typename SchemaDocumentType::AllocatorType AllocatorType; + typedef typename SchemaDocumentType::PointerType PointerType; + typedef typename ValueType::EncodingType EncodingType; + typedef typename EncodingType::Ch Ch; + typedef SchemaValidationContext Context; + typedef Schema SchemaType; + typedef GenericValue SValue; + friend class GenericSchemaDocument; + + Schema(SchemaDocumentType* schemaDocument, const PointerType& p, const ValueType& value, const ValueType& document, AllocatorType* allocator) : + allocator_(allocator), + enum_(), + enumCount_(), + not_(), + type_((1 << kTotalSchemaType) - 1), // typeless + validatorCount_(), + properties_(), + additionalPropertiesSchema_(), + patternProperties_(), + patternPropertyCount_(), + propertyCount_(), + requiredCount_(), + minProperties_(), + maxProperties_(SizeType(~0)), + additionalProperties_(true), + hasDependencies_(), + hasSchemaDependencies_(), + additionalItemsSchema_(), + itemsList_(), + itemsTuple_(), + itemsTupleCount_(), + minItems_(), + maxItems_(SizeType(~0)), + additionalItems_(true), + uniqueItems_(false), + pattern_(), + minLength_(0), + maxLength_(~SizeType(0)), + exclusiveMinimum_(false), + exclusiveMaximum_(false) + { + typedef typename SchemaDocumentType::ValueType ValueType; + typedef typename ValueType::ConstValueIterator ConstValueIterator; + typedef typename ValueType::ConstMemberIterator ConstMemberIterator; + + if (!value.IsObject()) + return; + + if (const ValueType* v = GetMember(value, GetTypeString())) { + type_ = 0; + if (v->IsString()) + AddType(*v); + else if (v->IsArray()) + for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr) + AddType(*itr); + } + + if (const ValueType* v = GetMember(value, GetEnumString())) + if (v->IsArray() && v->Size() > 0) { + enum_ = static_cast(allocator_->Malloc(sizeof(uint64_t) * v->Size())); + for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr) { + typedef Hasher > EnumHasherType; + char buffer[256 + 24]; + MemoryPoolAllocator<> hasherAllocator(buffer, sizeof(buffer)); + EnumHasherType h(&hasherAllocator, 256); + itr->Accept(h); + enum_[enumCount_++] = h.GetHashCode(); + } + } + + AssignIfExist(allOf_, *schemaDocument, p, value, GetAllOfString(), document); + AssignIfExist(anyOf_, *schemaDocument, p, value, GetAnyOfString(), document); + AssignIfExist(oneOf_, *schemaDocument, p, value, GetOneOfString(), document); + + if (const ValueType* v = GetMember(value, GetNotString())) { + schemaDocument->CreateSchema(¬_, p.Append(GetNotString(), allocator_), *v, document); + notValidatorIndex_ = validatorCount_; + validatorCount_++; + } + + // Object + + const ValueType* properties = GetMember(value, GetPropertiesString()); + const ValueType* required = GetMember(value, GetRequiredString()); + const ValueType* dependencies = GetMember(value, GetDependenciesString()); + { + // Gather properties from properties/required/dependencies + SValue allProperties(kArrayType); + + if (properties && properties->IsObject()) + for (ConstMemberIterator itr = properties->MemberBegin(); itr != properties->MemberEnd(); ++itr) + AddUniqueElement(allProperties, itr->name); + + if (required && required->IsArray()) + for (ConstValueIterator itr = required->Begin(); itr != required->End(); ++itr) + if (itr->IsString()) + AddUniqueElement(allProperties, *itr); + + if (dependencies && dependencies->IsObject()) + for (ConstMemberIterator itr = dependencies->MemberBegin(); itr != dependencies->MemberEnd(); ++itr) { + AddUniqueElement(allProperties, itr->name); + if (itr->value.IsArray()) + for (ConstValueIterator i = itr->value.Begin(); i != itr->value.End(); ++i) + if (i->IsString()) + AddUniqueElement(allProperties, *i); + } + + if (allProperties.Size() > 0) { + propertyCount_ = allProperties.Size(); + properties_ = static_cast(allocator_->Malloc(sizeof(Property) * propertyCount_)); + for (SizeType i = 0; i < propertyCount_; i++) { + new (&properties_[i]) Property(); + properties_[i].name = allProperties[i]; + properties_[i].schema = GetTypeless(); + } + } + } + + if (properties && properties->IsObject()) { + PointerType q = p.Append(GetPropertiesString(), allocator_); + for (ConstMemberIterator itr = properties->MemberBegin(); itr != properties->MemberEnd(); ++itr) { + SizeType index; + if (FindPropertyIndex(itr->name, &index)) + schemaDocument->CreateSchema(&properties_[index].schema, q.Append(itr->name, allocator_), itr->value, document); + } + } + + if (const ValueType* v = GetMember(value, GetPatternPropertiesString())) { + PointerType q = p.Append(GetPatternPropertiesString(), allocator_); + patternProperties_ = static_cast(allocator_->Malloc(sizeof(PatternProperty) * v->MemberCount())); + patternPropertyCount_ = 0; + + for (ConstMemberIterator itr = v->MemberBegin(); itr != v->MemberEnd(); ++itr) { + new (&patternProperties_[patternPropertyCount_]) PatternProperty(); + patternProperties_[patternPropertyCount_].pattern = CreatePattern(itr->name); + schemaDocument->CreateSchema(&patternProperties_[patternPropertyCount_].schema, q.Append(itr->name, allocator_), itr->value, document); + patternPropertyCount_++; + } + } + + if (required && required->IsArray()) + for (ConstValueIterator itr = required->Begin(); itr != required->End(); ++itr) + if (itr->IsString()) { + SizeType index; + if (FindPropertyIndex(*itr, &index)) { + properties_[index].required = true; + requiredCount_++; + } + } + + if (dependencies && dependencies->IsObject()) { + PointerType q = p.Append(GetDependenciesString(), allocator_); + hasDependencies_ = true; + for (ConstMemberIterator itr = dependencies->MemberBegin(); itr != dependencies->MemberEnd(); ++itr) { + SizeType sourceIndex; + if (FindPropertyIndex(itr->name, &sourceIndex)) { + if (itr->value.IsArray()) { + properties_[sourceIndex].dependencies = static_cast(allocator_->Malloc(sizeof(bool) * propertyCount_)); + std::memset(properties_[sourceIndex].dependencies, 0, sizeof(bool)* propertyCount_); + for (ConstValueIterator targetItr = itr->value.Begin(); targetItr != itr->value.End(); ++targetItr) { + SizeType targetIndex; + if (FindPropertyIndex(*targetItr, &targetIndex)) + properties_[sourceIndex].dependencies[targetIndex] = true; + } + } + else if (itr->value.IsObject()) { + hasSchemaDependencies_ = true; + schemaDocument->CreateSchema(&properties_[sourceIndex].dependenciesSchema, q.Append(itr->name, allocator_), itr->value, document); + properties_[sourceIndex].dependenciesValidatorIndex = validatorCount_; + validatorCount_++; + } + } + } + } + + if (const ValueType* v = GetMember(value, GetAdditionalPropertiesString())) { + if (v->IsBool()) + additionalProperties_ = v->GetBool(); + else if (v->IsObject()) + schemaDocument->CreateSchema(&additionalPropertiesSchema_, p.Append(GetAdditionalPropertiesString(), allocator_), *v, document); + } + + AssignIfExist(minProperties_, value, GetMinPropertiesString()); + AssignIfExist(maxProperties_, value, GetMaxPropertiesString()); + + // Array + if (const ValueType* v = GetMember(value, GetItemsString())) { + PointerType q = p.Append(GetItemsString(), allocator_); + if (v->IsObject()) // List validation + schemaDocument->CreateSchema(&itemsList_, q, *v, document); + else if (v->IsArray()) { // Tuple validation + itemsTuple_ = static_cast(allocator_->Malloc(sizeof(const Schema*) * v->Size())); + SizeType index = 0; + for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr, index++) + schemaDocument->CreateSchema(&itemsTuple_[itemsTupleCount_++], q.Append(index, allocator_), *itr, document); + } + } + + AssignIfExist(minItems_, value, GetMinItemsString()); + AssignIfExist(maxItems_, value, GetMaxItemsString()); + + if (const ValueType* v = GetMember(value, GetAdditionalItemsString())) { + if (v->IsBool()) + additionalItems_ = v->GetBool(); + else if (v->IsObject()) + schemaDocument->CreateSchema(&additionalItemsSchema_, p.Append(GetAdditionalItemsString(), allocator_), *v, document); + } + + AssignIfExist(uniqueItems_, value, GetUniqueItemsString()); + + // String + AssignIfExist(minLength_, value, GetMinLengthString()); + AssignIfExist(maxLength_, value, GetMaxLengthString()); + + if (const ValueType* v = GetMember(value, GetPatternString())) + pattern_ = CreatePattern(*v); + + // Number + if (const ValueType* v = GetMember(value, GetMinimumString())) + if (v->IsNumber()) + minimum_.CopyFrom(*v, *allocator_); + + if (const ValueType* v = GetMember(value, GetMaximumString())) + if (v->IsNumber()) + maximum_.CopyFrom(*v, *allocator_); + + AssignIfExist(exclusiveMinimum_, value, GetExclusiveMinimumString()); + AssignIfExist(exclusiveMaximum_, value, GetExclusiveMaximumString()); + + if (const ValueType* v = GetMember(value, GetMultipleOfString())) + if (v->IsNumber() && v->GetDouble() > 0.0) + multipleOf_.CopyFrom(*v, *allocator_); + } + + ~Schema() { + allocator_->Free(enum_); + if (properties_) { + for (SizeType i = 0; i < propertyCount_; i++) + properties_[i].~Property(); + AllocatorType::Free(properties_); + } + if (patternProperties_) { + for (SizeType i = 0; i < patternPropertyCount_; i++) + patternProperties_[i].~PatternProperty(); + AllocatorType::Free(patternProperties_); + } + AllocatorType::Free(itemsTuple_); +#if RAPIDJSON_SCHEMA_HAS_REGEX + if (pattern_) { + pattern_->~RegexType(); + allocator_->Free(pattern_); + } +#endif + } + + bool BeginValue(Context& context) const { + if (context.inArray) { + if (uniqueItems_) + context.valueUniqueness = true; + + if (itemsList_) + context.valueSchema = itemsList_; + else if (itemsTuple_) { + if (context.arrayElementIndex < itemsTupleCount_) + context.valueSchema = itemsTuple_[context.arrayElementIndex]; + else if (additionalItemsSchema_) + context.valueSchema = additionalItemsSchema_; + else if (additionalItems_) + context.valueSchema = GetTypeless(); + else + RAPIDJSON_INVALID_KEYWORD_RETURN(GetItemsString()); + } + else + context.valueSchema = GetTypeless(); + + context.arrayElementIndex++; + } + return true; + } + + RAPIDJSON_FORCEINLINE bool EndValue(Context& context) const { + if (context.patternPropertiesValidatorCount > 0) { + bool otherValid = false; + SizeType count = context.patternPropertiesValidatorCount; + if (context.objectPatternValidatorType != Context::kPatternValidatorOnly) + otherValid = context.patternPropertiesValidators[--count]->IsValid(); + + bool patternValid = true; + for (SizeType i = 0; i < count; i++) + if (!context.patternPropertiesValidators[i]->IsValid()) { + patternValid = false; + break; + } + + if (context.objectPatternValidatorType == Context::kPatternValidatorOnly) { + if (!patternValid) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternPropertiesString()); + } + else if (context.objectPatternValidatorType == Context::kPatternValidatorWithProperty) { + if (!patternValid || !otherValid) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternPropertiesString()); + } + else if (!patternValid && !otherValid) // kPatternValidatorWithAdditionalProperty) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternPropertiesString()); + } + + if (enum_) { + const uint64_t h = context.factory.GetHashCode(context.hasher); + for (SizeType i = 0; i < enumCount_; i++) + if (enum_[i] == h) + goto foundEnum; + RAPIDJSON_INVALID_KEYWORD_RETURN(GetEnumString()); + foundEnum:; + } + + if (allOf_.schemas) + for (SizeType i = allOf_.begin; i < allOf_.begin + allOf_.count; i++) + if (!context.validators[i]->IsValid()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetAllOfString()); + + if (anyOf_.schemas) { + for (SizeType i = anyOf_.begin; i < anyOf_.begin + anyOf_.count; i++) + if (context.validators[i]->IsValid()) + goto foundAny; + RAPIDJSON_INVALID_KEYWORD_RETURN(GetAnyOfString()); + foundAny:; + } + + if (oneOf_.schemas) { + bool oneValid = false; + for (SizeType i = oneOf_.begin; i < oneOf_.begin + oneOf_.count; i++) + if (context.validators[i]->IsValid()) { + if (oneValid) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetOneOfString()); + else + oneValid = true; + } + if (!oneValid) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetOneOfString()); + } + + if (not_ && context.validators[notValidatorIndex_]->IsValid()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetNotString()); + + return true; + } + + bool Null(Context& context) const { + if (!(type_ & (1 << kNullSchemaType))) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + return CreateParallelValidator(context); + } + + bool Bool(Context& context, bool) const { + if (!(type_ & (1 << kBooleanSchemaType))) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + return CreateParallelValidator(context); + } + + bool Int(Context& context, int i) const { + if (!CheckInt(context, i)) + return false; + return CreateParallelValidator(context); + } + + bool Uint(Context& context, unsigned u) const { + if (!CheckUint(context, u)) + return false; + return CreateParallelValidator(context); + } + + bool Int64(Context& context, int64_t i) const { + if (!CheckInt(context, i)) + return false; + return CreateParallelValidator(context); + } + + bool Uint64(Context& context, uint64_t u) const { + if (!CheckUint(context, u)) + return false; + return CreateParallelValidator(context); + } + + bool Double(Context& context, double d) const { + if (!(type_ & (1 << kNumberSchemaType))) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + + if (!minimum_.IsNull() && !CheckDoubleMinimum(context, d)) + return false; + + if (!maximum_.IsNull() && !CheckDoubleMaximum(context, d)) + return false; + + if (!multipleOf_.IsNull() && !CheckDoubleMultipleOf(context, d)) + return false; + + return CreateParallelValidator(context); + } + + bool String(Context& context, const Ch* str, SizeType length, bool) const { + if (!(type_ & (1 << kStringSchemaType))) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + + if (minLength_ != 0 || maxLength_ != SizeType(~0)) { + SizeType count; + if (internal::CountStringCodePoint(str, length, &count)) { + if (count < minLength_) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinLengthString()); + if (count > maxLength_) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaxLengthString()); + } + } + + if (pattern_ && !IsPatternMatch(pattern_, str, length)) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternString()); + + return CreateParallelValidator(context); + } + + bool StartObject(Context& context) const { + if (!(type_ & (1 << kObjectSchemaType))) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + + context.objectRequiredCount = 0; + if (hasDependencies_) { + context.objectDependencies = static_cast(context.factory.MallocState(sizeof(bool) * propertyCount_)); + std::memset(context.objectDependencies, 0, sizeof(bool) * propertyCount_); + } + + if (patternProperties_) { // pre-allocate schema array + SizeType count = patternPropertyCount_ + 1; // extra for valuePatternValidatorType + context.patternPropertiesSchemas = static_cast(context.factory.MallocState(sizeof(const SchemaType*) * count)); + context.patternPropertiesSchemaCount = 0; + std::memset(context.patternPropertiesSchemas, 0, sizeof(SchemaType*) * count); + } + + return CreateParallelValidator(context); + } + + bool Key(Context& context, const Ch* str, SizeType len, bool) const { + if (patternProperties_) { + context.patternPropertiesSchemaCount = 0; + for (SizeType i = 0; i < patternPropertyCount_; i++) + if (patternProperties_[i].pattern && IsPatternMatch(patternProperties_[i].pattern, str, len)) + context.patternPropertiesSchemas[context.patternPropertiesSchemaCount++] = patternProperties_[i].schema; + } + + SizeType index; + if (FindPropertyIndex(ValueType(str, len).Move(), &index)) { + if (context.patternPropertiesSchemaCount > 0) { + context.patternPropertiesSchemas[context.patternPropertiesSchemaCount++] = properties_[index].schema; + context.valueSchema = GetTypeless(); + context.valuePatternValidatorType = Context::kPatternValidatorWithProperty; + } + else + context.valueSchema = properties_[index].schema; + + if (properties_[index].required) + context.objectRequiredCount++; + + if (hasDependencies_) + context.objectDependencies[index] = true; + + return true; + } + + if (additionalPropertiesSchema_) { + if (additionalPropertiesSchema_ && context.patternPropertiesSchemaCount > 0) { + context.patternPropertiesSchemas[context.patternPropertiesSchemaCount++] = additionalPropertiesSchema_; + context.valueSchema = GetTypeless(); + context.valuePatternValidatorType = Context::kPatternValidatorWithAdditionalProperty; + } + else + context.valueSchema = additionalPropertiesSchema_; + return true; + } + else if (additionalProperties_) { + context.valueSchema = GetTypeless(); + return true; + } + + if (context.patternPropertiesSchemaCount == 0) // patternProperties are not additional properties + RAPIDJSON_INVALID_KEYWORD_RETURN(GetAdditionalPropertiesString()); + + return true; + } + + bool EndObject(Context& context, SizeType memberCount) const { + if (context.objectRequiredCount != requiredCount_) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetRequiredString()); + + if (memberCount < minProperties_) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinPropertiesString()); + + if (memberCount > maxProperties_) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaxPropertiesString()); + + if (hasDependencies_) { + for (SizeType sourceIndex = 0; sourceIndex < propertyCount_; sourceIndex++) + if (context.objectDependencies[sourceIndex]) { + if (properties_[sourceIndex].dependencies) { + for (SizeType targetIndex = 0; targetIndex < propertyCount_; targetIndex++) + if (properties_[sourceIndex].dependencies[targetIndex] && !context.objectDependencies[targetIndex]) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetDependenciesString()); + } + else if (properties_[sourceIndex].dependenciesSchema) + if (!context.validators[properties_[sourceIndex].dependenciesValidatorIndex]->IsValid()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetDependenciesString()); + } + } + + return true; + } + + bool StartArray(Context& context) const { + if (!(type_ & (1 << kArraySchemaType))) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + + context.arrayElementIndex = 0; + context.inArray = true; + + return CreateParallelValidator(context); + } + + bool EndArray(Context& context, SizeType elementCount) const { + context.inArray = false; + + if (elementCount < minItems_) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinItemsString()); + + if (elementCount > maxItems_) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaxItemsString()); + + return true; + } + + // Generate functions for string literal according to Ch +#define RAPIDJSON_STRING_(name, ...) \ + static const ValueType& Get##name##String() {\ + static const Ch s[] = { __VA_ARGS__, '\0' };\ + static const ValueType v(s, sizeof(s) / sizeof(Ch) - 1);\ + return v;\ + } + + RAPIDJSON_STRING_(Null, 'n', 'u', 'l', 'l') + RAPIDJSON_STRING_(Boolean, 'b', 'o', 'o', 'l', 'e', 'a', 'n') + RAPIDJSON_STRING_(Object, 'o', 'b', 'j', 'e', 'c', 't') + RAPIDJSON_STRING_(Array, 'a', 'r', 'r', 'a', 'y') + RAPIDJSON_STRING_(String, 's', 't', 'r', 'i', 'n', 'g') + RAPIDJSON_STRING_(Number, 'n', 'u', 'm', 'b', 'e', 'r') + RAPIDJSON_STRING_(Integer, 'i', 'n', 't', 'e', 'g', 'e', 'r') + RAPIDJSON_STRING_(Type, 't', 'y', 'p', 'e') + RAPIDJSON_STRING_(Enum, 'e', 'n', 'u', 'm') + RAPIDJSON_STRING_(AllOf, 'a', 'l', 'l', 'O', 'f') + RAPIDJSON_STRING_(AnyOf, 'a', 'n', 'y', 'O', 'f') + RAPIDJSON_STRING_(OneOf, 'o', 'n', 'e', 'O', 'f') + RAPIDJSON_STRING_(Not, 'n', 'o', 't') + RAPIDJSON_STRING_(Properties, 'p', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(Required, 'r', 'e', 'q', 'u', 'i', 'r', 'e', 'd') + RAPIDJSON_STRING_(Dependencies, 'd', 'e', 'p', 'e', 'n', 'd', 'e', 'n', 'c', 'i', 'e', 's') + RAPIDJSON_STRING_(PatternProperties, 'p', 'a', 't', 't', 'e', 'r', 'n', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(AdditionalProperties, 'a', 'd', 'd', 'i', 't', 'i', 'o', 'n', 'a', 'l', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(MinProperties, 'm', 'i', 'n', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(MaxProperties, 'm', 'a', 'x', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(Items, 'i', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(MinItems, 'm', 'i', 'n', 'I', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(MaxItems, 'm', 'a', 'x', 'I', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(AdditionalItems, 'a', 'd', 'd', 'i', 't', 'i', 'o', 'n', 'a', 'l', 'I', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(UniqueItems, 'u', 'n', 'i', 'q', 'u', 'e', 'I', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(MinLength, 'm', 'i', 'n', 'L', 'e', 'n', 'g', 't', 'h') + RAPIDJSON_STRING_(MaxLength, 'm', 'a', 'x', 'L', 'e', 'n', 'g', 't', 'h') + RAPIDJSON_STRING_(Pattern, 'p', 'a', 't', 't', 'e', 'r', 'n') + RAPIDJSON_STRING_(Minimum, 'm', 'i', 'n', 'i', 'm', 'u', 'm') + RAPIDJSON_STRING_(Maximum, 'm', 'a', 'x', 'i', 'm', 'u', 'm') + RAPIDJSON_STRING_(ExclusiveMinimum, 'e', 'x', 'c', 'l', 'u', 's', 'i', 'v', 'e', 'M', 'i', 'n', 'i', 'm', 'u', 'm') + RAPIDJSON_STRING_(ExclusiveMaximum, 'e', 'x', 'c', 'l', 'u', 's', 'i', 'v', 'e', 'M', 'a', 'x', 'i', 'm', 'u', 'm') + RAPIDJSON_STRING_(MultipleOf, 'm', 'u', 'l', 't', 'i', 'p', 'l', 'e', 'O', 'f') + +#undef RAPIDJSON_STRING_ + +private: + enum SchemaValueType { + kNullSchemaType, + kBooleanSchemaType, + kObjectSchemaType, + kArraySchemaType, + kStringSchemaType, + kNumberSchemaType, + kIntegerSchemaType, + kTotalSchemaType + }; + +#if RAPIDJSON_SCHEMA_USE_INTERNALREGEX + typedef internal::GenericRegex RegexType; +#elif RAPIDJSON_SCHEMA_USE_STDREGEX + typedef std::basic_regex RegexType; +#else + typedef char RegexType; +#endif + + struct SchemaArray { + SchemaArray() : schemas(), count() {} + ~SchemaArray() { AllocatorType::Free(schemas); } + const SchemaType** schemas; + SizeType begin; // begin index of context.validators + SizeType count; + }; + + static const SchemaType* GetTypeless() { + static SchemaType typeless(0, PointerType(), ValueType(kObjectType).Move(), ValueType(kObjectType).Move(), 0); + return &typeless; + } + + template + void AddUniqueElement(V1& a, const V2& v) { + for (typename V1::ConstValueIterator itr = a.Begin(); itr != a.End(); ++itr) + if (*itr == v) + return; + V1 c(v, *allocator_); + a.PushBack(c, *allocator_); + } + + static const ValueType* GetMember(const ValueType& value, const ValueType& name) { + typename ValueType::ConstMemberIterator itr = value.FindMember(name); + return itr != value.MemberEnd() ? &(itr->value) : 0; + } + + static void AssignIfExist(bool& out, const ValueType& value, const ValueType& name) { + if (const ValueType* v = GetMember(value, name)) + if (v->IsBool()) + out = v->GetBool(); + } + + static void AssignIfExist(SizeType& out, const ValueType& value, const ValueType& name) { + if (const ValueType* v = GetMember(value, name)) + if (v->IsUint64() && v->GetUint64() <= SizeType(~0)) + out = static_cast(v->GetUint64()); + } + + void AssignIfExist(SchemaArray& out, SchemaDocumentType& schemaDocument, const PointerType& p, const ValueType& value, const ValueType& name, const ValueType& document) { + if (const ValueType* v = GetMember(value, name)) { + if (v->IsArray() && v->Size() > 0) { + PointerType q = p.Append(name, allocator_); + out.count = v->Size(); + out.schemas = static_cast(allocator_->Malloc(out.count * sizeof(const Schema*))); + memset(out.schemas, 0, sizeof(Schema*)* out.count); + for (SizeType i = 0; i < out.count; i++) + schemaDocument.CreateSchema(&out.schemas[i], q.Append(i, allocator_), (*v)[i], document); + out.begin = validatorCount_; + validatorCount_ += out.count; + } + } + } + +#if RAPIDJSON_SCHEMA_USE_INTERNALREGEX + template + RegexType* CreatePattern(const ValueType& value) { + if (value.IsString()) { + RegexType* r = new (allocator_->Malloc(sizeof(RegexType))) RegexType(value.GetString()); + if (!r->IsValid()) { + r->~RegexType(); + r = 0; + } + return r; + } + return 0; + } + + static bool IsPatternMatch(const RegexType* pattern, const Ch *str, SizeType) { + return pattern->Search(str); + } +#elif RAPIDJSON_SCHEMA_USE_STDREGEX + template + RegexType* CreatePattern(const ValueType& value) { + if (value.IsString()) + try { + return new (allocator_->Malloc(sizeof(RegexType))) RegexType(value.GetString(), std::size_t(value.GetStringLength()), std::regex_constants::ECMAScript); + } + catch (const std::regex_error&) { + } + return 0; + } + + static bool IsPatternMatch(const RegexType* pattern, const Ch *str, SizeType length) { + std::match_results r; + return std::regex_search(str, str + length, r, *pattern); + } +#else + template + RegexType* CreatePattern(const ValueType&) { return 0; } + + static bool IsPatternMatch(const RegexType*, const Ch *, SizeType) { return true; } +#endif // RAPIDJSON_SCHEMA_USE_STDREGEX + + void AddType(const ValueType& type) { + if (type == GetNullString() ) type_ |= 1 << kNullSchemaType; + else if (type == GetBooleanString()) type_ |= 1 << kBooleanSchemaType; + else if (type == GetObjectString() ) type_ |= 1 << kObjectSchemaType; + else if (type == GetArrayString() ) type_ |= 1 << kArraySchemaType; + else if (type == GetStringString() ) type_ |= 1 << kStringSchemaType; + else if (type == GetIntegerString()) type_ |= 1 << kIntegerSchemaType; + else if (type == GetNumberString() ) type_ |= (1 << kNumberSchemaType) | (1 << kIntegerSchemaType); + } + + bool CreateParallelValidator(Context& context) const { + if (enum_ || context.arrayUniqueness) + context.hasher = context.factory.CreateHasher(); + + if (validatorCount_) { + RAPIDJSON_ASSERT(context.validators == 0); + context.validators = static_cast(context.factory.MallocState(sizeof(ISchemaValidator*) * validatorCount_)); + context.validatorCount = validatorCount_; + + if (allOf_.schemas) + CreateSchemaValidators(context, allOf_); + + if (anyOf_.schemas) + CreateSchemaValidators(context, anyOf_); + + if (oneOf_.schemas) + CreateSchemaValidators(context, oneOf_); + + if (not_) + context.validators[notValidatorIndex_] = context.factory.CreateSchemaValidator(*not_); + + if (hasSchemaDependencies_) { + for (SizeType i = 0; i < propertyCount_; i++) + if (properties_[i].dependenciesSchema) + context.validators[properties_[i].dependenciesValidatorIndex] = context.factory.CreateSchemaValidator(*properties_[i].dependenciesSchema); + } + } + + return true; + } + + void CreateSchemaValidators(Context& context, const SchemaArray& schemas) const { + for (SizeType i = 0; i < schemas.count; i++) + context.validators[schemas.begin + i] = context.factory.CreateSchemaValidator(*schemas.schemas[i]); + } + + // O(n) + bool FindPropertyIndex(const ValueType& name, SizeType* outIndex) const { + SizeType len = name.GetStringLength(); + const Ch* str = name.GetString(); + for (SizeType index = 0; index < propertyCount_; index++) + if (properties_[index].name.GetStringLength() == len && + (std::memcmp(properties_[index].name.GetString(), str, sizeof(Ch) * len) == 0)) + { + *outIndex = index; + return true; + } + return false; + } + + bool CheckInt(Context& context, int64_t i) const { + if (!(type_ & ((1 << kIntegerSchemaType) | (1 << kNumberSchemaType)))) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + + if (!minimum_.IsNull()) { + if (minimum_.IsInt64()) { + if (exclusiveMinimum_ ? i <= minimum_.GetInt64() : i < minimum_.GetInt64()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); + } + else if (!CheckDoubleMinimum(context, static_cast(i))) + return false; + } + + if (!maximum_.IsNull()) { + if (maximum_.IsInt64()) { + if (exclusiveMaximum_ ? i >= maximum_.GetInt64() : i > maximum_.GetInt64()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); + } + else if (!CheckDoubleMaximum(context, static_cast(i))) + return false; + } + + if (!multipleOf_.IsNull()) { + if (multipleOf_.IsUint64()) { + if (static_cast(i >= 0 ? i : -i) % multipleOf_.GetUint64() != 0) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMultipleOfString()); + } + else if (!CheckDoubleMultipleOf(context, static_cast(i))) + return false; + } + + return true; + } + + bool CheckUint(Context& context, uint64_t i) const { + if (!(type_ & ((1 << kIntegerSchemaType) | (1 << kNumberSchemaType)))) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + + if (!minimum_.IsNull()) { + if (minimum_.IsUint64()) { + if (exclusiveMinimum_ ? i <= minimum_.GetUint64() : i < minimum_.GetUint64()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); + } + else if (!CheckDoubleMinimum(context, static_cast(i))) + return false; + } + + if (!maximum_.IsNull()) { + if (maximum_.IsUint64()) { + if (exclusiveMaximum_ ? i >= maximum_.GetUint64() : i > maximum_.GetUint64()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); + } + else if (!CheckDoubleMaximum(context, static_cast(i))) + return false; + } + + if (!multipleOf_.IsNull()) { + if (multipleOf_.IsUint64()) { + if (i % multipleOf_.GetUint64() != 0) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMultipleOfString()); + } + else if (!CheckDoubleMultipleOf(context, static_cast(i))) + return false; + } + + return true; + } + + bool CheckDoubleMinimum(Context& context, double d) const { + if (exclusiveMinimum_ ? d <= minimum_.GetDouble() : d < minimum_.GetDouble()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); + return true; + } + + bool CheckDoubleMaximum(Context& context, double d) const { + if (exclusiveMaximum_ ? d >= maximum_.GetDouble() : d > maximum_.GetDouble()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); + return true; + } + + bool CheckDoubleMultipleOf(Context& context, double d) const { + double a = std::abs(d), b = std::abs(multipleOf_.GetDouble()); + double q = std::floor(a / b); + double r = a - q * b; + if (r > 0.0) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMultipleOfString()); + return true; + } + + struct Property { + Property() : schema(), dependenciesSchema(), dependenciesValidatorIndex(), dependencies(), required(false) {} + ~Property() { AllocatorType::Free(dependencies); } + SValue name; + const SchemaType* schema; + const SchemaType* dependenciesSchema; + SizeType dependenciesValidatorIndex; + bool* dependencies; + bool required; + }; + + struct PatternProperty { + PatternProperty() : schema(), pattern() {} + ~PatternProperty() { + if (pattern) { + pattern->~RegexType(); + AllocatorType::Free(pattern); + } + } + const SchemaType* schema; + RegexType* pattern; + }; + + AllocatorType* allocator_; + uint64_t* enum_; + SizeType enumCount_; + SchemaArray allOf_; + SchemaArray anyOf_; + SchemaArray oneOf_; + const SchemaType* not_; + unsigned type_; // bitmask of kSchemaType + SizeType validatorCount_; + SizeType notValidatorIndex_; + + Property* properties_; + const SchemaType* additionalPropertiesSchema_; + PatternProperty* patternProperties_; + SizeType patternPropertyCount_; + SizeType propertyCount_; + SizeType requiredCount_; + SizeType minProperties_; + SizeType maxProperties_; + bool additionalProperties_; + bool hasDependencies_; + bool hasSchemaDependencies_; + + const SchemaType* additionalItemsSchema_; + const SchemaType* itemsList_; + const SchemaType** itemsTuple_; + SizeType itemsTupleCount_; + SizeType minItems_; + SizeType maxItems_; + bool additionalItems_; + bool uniqueItems_; + + RegexType* pattern_; + SizeType minLength_; + SizeType maxLength_; + + SValue minimum_; + SValue maximum_; + SValue multipleOf_; + bool exclusiveMinimum_; + bool exclusiveMaximum_; +}; + +template +struct TokenHelper { + RAPIDJSON_FORCEINLINE static void AppendIndexToken(Stack& documentStack, SizeType index) { + *documentStack.template Push() = '/'; + char buffer[21]; + size_t length = static_cast((sizeof(SizeType) == 4 ? u32toa(index, buffer) : u64toa(index, buffer)) - buffer); + for (size_t i = 0; i < length; i++) + *documentStack.template Push() = buffer[i]; + } +}; + +// Partial specialized version for char to prevent buffer copying. +template +struct TokenHelper { + RAPIDJSON_FORCEINLINE static void AppendIndexToken(Stack& documentStack, SizeType index) { + if (sizeof(SizeType) == 4) { + char *buffer = documentStack.template Push(1 + 10); // '/' + uint + *buffer++ = '/'; + const char* end = internal::u32toa(index, buffer); + documentStack.template Pop(static_cast(10 - (end - buffer))); + } + else { + char *buffer = documentStack.template Push(1 + 20); // '/' + uint64 + *buffer++ = '/'; + const char* end = internal::u64toa(index, buffer); + documentStack.template Pop(static_cast(20 - (end - buffer))); + } + } +}; + +} // namespace internal + +/////////////////////////////////////////////////////////////////////////////// +// IGenericRemoteSchemaDocumentProvider + +template +class IGenericRemoteSchemaDocumentProvider { +public: + typedef typename SchemaDocumentType::Ch Ch; + + virtual ~IGenericRemoteSchemaDocumentProvider() {} + virtual const SchemaDocumentType* GetRemoteDocument(const Ch* uri, SizeType length) = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// +// GenericSchemaDocument + +//! JSON schema document. +/*! + A JSON schema document is a compiled version of a JSON schema. + It is basically a tree of internal::Schema. + + \note This is an immutable class (i.e. its instance cannot be modified after construction). + \tparam ValueT Type of JSON value (e.g. \c Value ), which also determine the encoding. + \tparam Allocator Allocator type for allocating memory of this document. +*/ +template +class GenericSchemaDocument { +public: + typedef ValueT ValueType; + typedef IGenericRemoteSchemaDocumentProvider IRemoteSchemaDocumentProviderType; + typedef Allocator AllocatorType; + typedef typename ValueType::EncodingType EncodingType; + typedef typename EncodingType::Ch Ch; + typedef internal::Schema SchemaType; + typedef GenericPointer PointerType; + friend class internal::Schema; + template + friend class GenericSchemaValidator; + + //! Constructor. + /*! + Compile a JSON document into schema document. + + \param document A JSON document as source. + \param remoteProvider An optional remote schema document provider for resolving remote reference. Can be null. + \param allocator An optional allocator instance for allocating memory. Can be null. + */ + GenericSchemaDocument(const ValueType& document, IRemoteSchemaDocumentProviderType* remoteProvider = 0, Allocator* allocator = 0) : + remoteProvider_(remoteProvider), + allocator_(allocator), + ownAllocator_(), + root_(), + schemaMap_(allocator, kInitialSchemaMapSize), + schemaRef_(allocator, kInitialSchemaRefSize) + { + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator()); + + // Generate root schema, it will call CreateSchema() to create sub-schemas, + // And call AddRefSchema() if there are $ref. + CreateSchemaRecursive(&root_, PointerType(), document, document); + + // Resolve $ref + while (!schemaRef_.Empty()) { + SchemaRefEntry* refEntry = schemaRef_.template Pop(1); + if (const SchemaType* s = GetSchema(refEntry->target)) { + if (refEntry->schema) + *refEntry->schema = s; + + // Create entry in map if not exist + if (!GetSchema(refEntry->source)) { + new (schemaMap_.template Push()) SchemaEntry(refEntry->source, const_cast(s), false, allocator_); + } + } + refEntry->~SchemaRefEntry(); + } + + RAPIDJSON_ASSERT(root_ != 0); + + schemaRef_.ShrinkToFit(); // Deallocate all memory for ref + } + + //! Destructor + ~GenericSchemaDocument() { + while (!schemaMap_.Empty()) + schemaMap_.template Pop(1)->~SchemaEntry(); + + RAPIDJSON_DELETE(ownAllocator_); + } + + //! Get the root schema. + const SchemaType& GetRoot() const { return *root_; } + +private: + struct SchemaRefEntry { + SchemaRefEntry(const PointerType& s, const PointerType& t, const SchemaType** outSchema, Allocator *allocator) : source(s, allocator), target(t, allocator), schema(outSchema) {} + PointerType source; + PointerType target; + const SchemaType** schema; + }; + + struct SchemaEntry { + SchemaEntry(const PointerType& p, SchemaType* s, bool o, Allocator* allocator) : pointer(p, allocator), schema(s), owned(o) {} + ~SchemaEntry() { + if (owned) { + schema->~SchemaType(); + Allocator::Free(schema); + } + } + PointerType pointer; + SchemaType* schema; + bool owned; + }; + + void CreateSchemaRecursive(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document) { + if (schema) + *schema = SchemaType::GetTypeless(); + + if (v.GetType() == kObjectType) { + const SchemaType* s = GetSchema(pointer); + if (!s) + CreateSchema(schema, pointer, v, document); + else if (schema) + *schema = s; + + for (typename ValueType::ConstMemberIterator itr = v.MemberBegin(); itr != v.MemberEnd(); ++itr) + CreateSchemaRecursive(0, pointer.Append(itr->name, allocator_), itr->value, document); + } + else if (v.GetType() == kArrayType) + for (SizeType i = 0; i < v.Size(); i++) + CreateSchemaRecursive(0, pointer.Append(i, allocator_), v[i], document); + } + + void CreateSchema(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document) { + RAPIDJSON_ASSERT(pointer.IsValid()); + if (v.IsObject()) { + if (!HandleRefSchema(pointer, schema, v, document)) { + SchemaType* s = new (allocator_->Malloc(sizeof(SchemaType))) SchemaType(this, pointer, v, document, allocator_); + new (schemaMap_.template Push()) SchemaEntry(pointer, s, true, allocator_); + if (schema) + *schema = s; + } + } + } + + bool HandleRefSchema(const PointerType& source, const SchemaType** schema, const ValueType& v, const ValueType& document) { + static const Ch kRefString[] = { '$', 'r', 'e', 'f', '\0' }; + static const ValueType kRefValue(kRefString, 4); + + typename ValueType::ConstMemberIterator itr = v.FindMember(kRefValue); + if (itr == v.MemberEnd()) + return false; + + if (itr->value.IsString()) { + SizeType len = itr->value.GetStringLength(); + if (len > 0) { + const Ch* s = itr->value.GetString(); + SizeType i = 0; + while (i < len && s[i] != '#') // Find the first # + i++; + + if (i > 0) { // Remote reference, resolve immediately + if (remoteProvider_) { + if (const GenericSchemaDocument* remoteDocument = remoteProvider_->GetRemoteDocument(s, i - 1)) { + PointerType pointer(&s[i], len - i, allocator_); + if (pointer.IsValid()) { + if (const SchemaType* sc = remoteDocument->GetSchema(pointer)) { + if (schema) + *schema = sc; + return true; + } + } + } + } + } + else if (s[i] == '#') { // Local reference, defer resolution + PointerType pointer(&s[i], len - i, allocator_); + if (pointer.IsValid()) { + if (const ValueType* nv = pointer.Get(document)) + if (HandleRefSchema(source, schema, *nv, document)) + return true; + + new (schemaRef_.template Push()) SchemaRefEntry(source, pointer, schema, allocator_); + return true; + } + } + } + } + return false; + } + + const SchemaType* GetSchema(const PointerType& pointer) const { + for (const SchemaEntry* target = schemaMap_.template Bottom(); target != schemaMap_.template End(); ++target) + if (pointer == target->pointer) + return target->schema; + return 0; + } + + PointerType GetPointer(const SchemaType* schema) const { + for (const SchemaEntry* target = schemaMap_.template Bottom(); target != schemaMap_.template End(); ++target) + if (schema == target->schema) + return target->pointer; + return PointerType(); + } + + static const size_t kInitialSchemaMapSize = 64; + static const size_t kInitialSchemaRefSize = 64; + + IRemoteSchemaDocumentProviderType* remoteProvider_; + Allocator *allocator_; + Allocator *ownAllocator_; + const SchemaType* root_; //!< Root schema. + internal::Stack schemaMap_; // Stores created Pointer -> Schemas + internal::Stack schemaRef_; // Stores Pointer from $ref and schema which holds the $ref +}; + +//! GenericSchemaDocument using Value type. +typedef GenericSchemaDocument SchemaDocument; +//! IGenericRemoteSchemaDocumentProvider using SchemaDocument. +typedef IGenericRemoteSchemaDocumentProvider IRemoteSchemaDocumentProvider; + +/////////////////////////////////////////////////////////////////////////////// +// GenericSchemaValidator + +//! JSON Schema Validator. +/*! + A SAX style JSON schema validator. + It uses a \c GenericSchemaDocument to validate SAX events. + It delegates the incoming SAX events to an output handler. + The default output handler does nothing. + It can be reused multiple times by calling \c Reset(). + + \tparam SchemaDocumentType Type of schema document. + \tparam OutputHandler Type of output handler. Default handler does nothing. + \tparam StateAllocator Allocator for storing the internal validation states. +*/ +template < + typename SchemaDocumentType, + typename OutputHandler = BaseReaderHandler, + typename StateAllocator = CrtAllocator> +class GenericSchemaValidator : + public internal::ISchemaStateFactory, + public internal::ISchemaValidator +{ +public: + typedef typename SchemaDocumentType::SchemaType SchemaType; + typedef typename SchemaDocumentType::PointerType PointerType; + typedef typename SchemaType::EncodingType EncodingType; + typedef typename EncodingType::Ch Ch; + + //! Constructor without output handler. + /*! + \param schemaDocument The schema document to conform to. + \param allocator Optional allocator for storing internal validation states. + \param schemaStackCapacity Optional initial capacity of schema path stack. + \param documentStackCapacity Optional initial capacity of document path stack. + */ + GenericSchemaValidator( + const SchemaDocumentType& schemaDocument, + StateAllocator* allocator = 0, + size_t schemaStackCapacity = kDefaultSchemaStackCapacity, + size_t documentStackCapacity = kDefaultDocumentStackCapacity) + : + schemaDocument_(&schemaDocument), + root_(schemaDocument.GetRoot()), + outputHandler_(GetNullHandler()), + stateAllocator_(allocator), + ownStateAllocator_(0), + schemaStack_(allocator, schemaStackCapacity), + documentStack_(allocator, documentStackCapacity), + valid_(true) +#if RAPIDJSON_SCHEMA_VERBOSE + , depth_(0) +#endif + { + } + + //! Constructor with output handler. + /*! + \param schemaDocument The schema document to conform to. + \param allocator Optional allocator for storing internal validation states. + \param schemaStackCapacity Optional initial capacity of schema path stack. + \param documentStackCapacity Optional initial capacity of document path stack. + */ + GenericSchemaValidator( + const SchemaDocumentType& schemaDocument, + OutputHandler& outputHandler, + StateAllocator* allocator = 0, + size_t schemaStackCapacity = kDefaultSchemaStackCapacity, + size_t documentStackCapacity = kDefaultDocumentStackCapacity) + : + schemaDocument_(&schemaDocument), + root_(schemaDocument.GetRoot()), + outputHandler_(outputHandler), + stateAllocator_(allocator), + ownStateAllocator_(0), + schemaStack_(allocator, schemaStackCapacity), + documentStack_(allocator, documentStackCapacity), + valid_(true) +#if RAPIDJSON_SCHEMA_VERBOSE + , depth_(0) +#endif + { + } + + //! Destructor. + ~GenericSchemaValidator() { + Reset(); + RAPIDJSON_DELETE(ownStateAllocator_); + } + + //! Reset the internal states. + void Reset() { + while (!schemaStack_.Empty()) + PopSchema(); + documentStack_.Clear(); + valid_ = true; + } + + //! Checks whether the current state is valid. + // Implementation of ISchemaValidator + virtual bool IsValid() const { return valid_; } + + //! Gets the JSON pointer pointed to the invalid schema. + PointerType GetInvalidSchemaPointer() const { + return schemaStack_.Empty() ? PointerType() : schemaDocument_->GetPointer(&CurrentSchema()); + } + + //! Gets the keyword of invalid schema. + const Ch* GetInvalidSchemaKeyword() const { + return schemaStack_.Empty() ? 0 : CurrentContext().invalidKeyword; + } + + //! Gets the JSON pointer pointed to the invalid value. + PointerType GetInvalidDocumentPointer() const { + return documentStack_.Empty() ? PointerType() : PointerType(documentStack_.template Bottom(), documentStack_.GetSize() / sizeof(Ch)); + } + +#if RAPIDJSON_SCHEMA_VERBOSE +#define RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_() \ +RAPIDJSON_MULTILINEMACRO_BEGIN\ + *documentStack_.template Push() = '\0';\ + documentStack_.template Pop(1);\ + internal::PrintInvalidDocument(documentStack_.template Bottom());\ +RAPIDJSON_MULTILINEMACRO_END +#else +#define RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_() +#endif + +#define RAPIDJSON_SCHEMA_HANDLE_BEGIN_(method, arg1)\ + if (!valid_) return false; \ + if (!BeginValue() || !CurrentSchema().method arg1) {\ + RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_();\ + return valid_ = false;\ + } + +#define RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(method, arg2)\ + for (Context* context = schemaStack_.template Bottom(); context != schemaStack_.template End(); context++) {\ + if (context->hasher)\ + static_cast(context->hasher)->method arg2;\ + if (context->validators)\ + for (SizeType i_ = 0; i_ < context->validatorCount; i_++)\ + static_cast(context->validators[i_])->method arg2;\ + if (context->patternPropertiesValidators)\ + for (SizeType i_ = 0; i_ < context->patternPropertiesValidatorCount; i_++)\ + static_cast(context->patternPropertiesValidators[i_])->method arg2;\ + } + +#define RAPIDJSON_SCHEMA_HANDLE_END_(method, arg2)\ + return valid_ = EndValue() && outputHandler_.method arg2 + +#define RAPIDJSON_SCHEMA_HANDLE_VALUE_(method, arg1, arg2) \ + RAPIDJSON_SCHEMA_HANDLE_BEGIN_ (method, arg1);\ + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(method, arg2);\ + RAPIDJSON_SCHEMA_HANDLE_END_ (method, arg2) + + bool Null() { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Null, (CurrentContext() ), ( )); } + bool Bool(bool b) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Bool, (CurrentContext(), b), (b)); } + bool Int(int i) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Int, (CurrentContext(), i), (i)); } + bool Uint(unsigned u) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Uint, (CurrentContext(), u), (u)); } + bool Int64(int64_t i) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Int64, (CurrentContext(), i), (i)); } + bool Uint64(uint64_t u) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Uint64, (CurrentContext(), u), (u)); } + bool Double(double d) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Double, (CurrentContext(), d), (d)); } + bool String(const Ch* str, SizeType length, bool copy) + { RAPIDJSON_SCHEMA_HANDLE_VALUE_(String, (CurrentContext(), str, length, copy), (str, length, copy)); } + + bool StartObject() { + RAPIDJSON_SCHEMA_HANDLE_BEGIN_(StartObject, (CurrentContext())); + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(StartObject, ()); + return valid_ = outputHandler_.StartObject(); + } + + bool Key(const Ch* str, SizeType len, bool copy) { + if (!valid_) return false; + AppendToken(str, len); + if (!CurrentSchema().Key(CurrentContext(), str, len, copy)) return valid_ = false; + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(Key, (str, len, copy)); + return valid_ = outputHandler_.Key(str, len, copy); + } + + bool EndObject(SizeType memberCount) { + if (!valid_) return false; + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(EndObject, (memberCount)); + if (!CurrentSchema().EndObject(CurrentContext(), memberCount)) return valid_ = false; + RAPIDJSON_SCHEMA_HANDLE_END_(EndObject, (memberCount)); + } + + bool StartArray() { + RAPIDJSON_SCHEMA_HANDLE_BEGIN_(StartArray, (CurrentContext())); + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(StartArray, ()); + return valid_ = outputHandler_.StartArray(); + } + + bool EndArray(SizeType elementCount) { + if (!valid_) return false; + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(EndArray, (elementCount)); + if (!CurrentSchema().EndArray(CurrentContext(), elementCount)) return valid_ = false; + RAPIDJSON_SCHEMA_HANDLE_END_(EndArray, (elementCount)); + } + +#undef RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_ +#undef RAPIDJSON_SCHEMA_HANDLE_BEGIN_ +#undef RAPIDJSON_SCHEMA_HANDLE_PARALLEL_ +#undef RAPIDJSON_SCHEMA_HANDLE_VALUE_ + + // Implementation of ISchemaStateFactory + virtual ISchemaValidator* CreateSchemaValidator(const SchemaType& root) { + return new (GetStateAllocator().Malloc(sizeof(GenericSchemaValidator))) GenericSchemaValidator(*schemaDocument_, root, +#if RAPIDJSON_SCHEMA_VERBOSE + depth_ + 1, +#endif + &GetStateAllocator()); + } + + virtual void DestroySchemaValidator(ISchemaValidator* validator) { + GenericSchemaValidator* v = static_cast(validator); + v->~GenericSchemaValidator(); + StateAllocator::Free(v); + } + + virtual void* CreateHasher() { + return new (GetStateAllocator().Malloc(sizeof(HasherType))) HasherType(&GetStateAllocator()); + } + + virtual uint64_t GetHashCode(void* hasher) { + return static_cast(hasher)->GetHashCode(); + } + + virtual void DestroryHasher(void* hasher) { + HasherType* h = static_cast(hasher); + h->~HasherType(); + StateAllocator::Free(h); + } + + virtual void* MallocState(size_t size) { + return GetStateAllocator().Malloc(size); + } + + virtual void* ReallocState(void* originalPtr, size_t originalSize, size_t newSize) { + return GetStateAllocator().Realloc(originalPtr, originalSize, newSize); + } + + virtual void FreeState(void* p) { + return StateAllocator::Free(p); + } + +private: + typedef typename SchemaType::Context Context; + typedef GenericValue, StateAllocator> HashCodeArray; + typedef internal::Hasher HasherType; + + GenericSchemaValidator( + const SchemaDocumentType& schemaDocument, + const SchemaType& root, +#if RAPIDJSON_SCHEMA_VERBOSE + unsigned depth, +#endif + StateAllocator* allocator = 0, + size_t schemaStackCapacity = kDefaultSchemaStackCapacity, + size_t documentStackCapacity = kDefaultDocumentStackCapacity) + : + schemaDocument_(&schemaDocument), + root_(root), + outputHandler_(GetNullHandler()), + stateAllocator_(allocator), + ownStateAllocator_(0), + schemaStack_(allocator, schemaStackCapacity), + documentStack_(allocator, documentStackCapacity), + valid_(true) +#if RAPIDJSON_SCHEMA_VERBOSE + , depth_(depth) +#endif + { + } + + StateAllocator& GetStateAllocator() { + if (!stateAllocator_) + stateAllocator_ = ownStateAllocator_ = RAPIDJSON_NEW(StateAllocator()); + return *stateAllocator_; + } + + bool BeginValue() { + if (schemaStack_.Empty()) + PushSchema(root_); + else { + if (CurrentContext().inArray) + internal::TokenHelper, Ch>::AppendIndexToken(documentStack_, CurrentContext().arrayElementIndex); + + if (!CurrentSchema().BeginValue(CurrentContext())) + return false; + + SizeType count = CurrentContext().patternPropertiesSchemaCount; + const SchemaType** sa = CurrentContext().patternPropertiesSchemas; + typename Context::PatternValidatorType patternValidatorType = CurrentContext().valuePatternValidatorType; + bool valueUniqueness = CurrentContext().valueUniqueness; + if (CurrentContext().valueSchema) + PushSchema(*CurrentContext().valueSchema); + + if (count > 0) { + CurrentContext().objectPatternValidatorType = patternValidatorType; + ISchemaValidator**& va = CurrentContext().patternPropertiesValidators; + SizeType& validatorCount = CurrentContext().patternPropertiesValidatorCount; + va = static_cast(MallocState(sizeof(ISchemaValidator*) * count)); + for (SizeType i = 0; i < count; i++) + va[validatorCount++] = CreateSchemaValidator(*sa[i]); + } + + CurrentContext().arrayUniqueness = valueUniqueness; + } + return true; + } + + bool EndValue() { + if (!CurrentSchema().EndValue(CurrentContext())) + return false; + +#if RAPIDJSON_SCHEMA_VERBOSE + GenericStringBuffer sb; + schemaDocument_->GetPointer(&CurrentSchema()).Stringify(sb); + + *documentStack_.template Push() = '\0'; + documentStack_.template Pop(1); + internal::PrintValidatorPointers(depth_, sb.GetString(), documentStack_.template Bottom()); +#endif + + uint64_t h = CurrentContext().arrayUniqueness ? static_cast(CurrentContext().hasher)->GetHashCode() : 0; + + PopSchema(); + + if (!schemaStack_.Empty()) { + Context& context = CurrentContext(); + if (context.valueUniqueness) { + HashCodeArray* a = static_cast(context.arrayElementHashCodes); + if (!a) + CurrentContext().arrayElementHashCodes = a = new (GetStateAllocator().Malloc(sizeof(HashCodeArray))) HashCodeArray(kArrayType); + for (typename HashCodeArray::ConstValueIterator itr = a->Begin(); itr != a->End(); ++itr) + if (itr->GetUint64() == h) + RAPIDJSON_INVALID_KEYWORD_RETURN(SchemaType::GetUniqueItemsString()); + a->PushBack(h, GetStateAllocator()); + } + } + + // Remove the last token of document pointer + while (!documentStack_.Empty() && *documentStack_.template Pop(1) != '/') + ; + + return true; + } + + void AppendToken(const Ch* str, SizeType len) { + documentStack_.template Reserve(1 + len * 2); // worst case all characters are escaped as two characters + *documentStack_.template PushUnsafe() = '/'; + for (SizeType i = 0; i < len; i++) { + if (str[i] == '~') { + *documentStack_.template PushUnsafe() = '~'; + *documentStack_.template PushUnsafe() = '0'; + } + else if (str[i] == '/') { + *documentStack_.template PushUnsafe() = '~'; + *documentStack_.template PushUnsafe() = '1'; + } + else + *documentStack_.template PushUnsafe() = str[i]; + } + } + + RAPIDJSON_FORCEINLINE void PushSchema(const SchemaType& schema) { new (schemaStack_.template Push()) Context(*this, &schema); } + + RAPIDJSON_FORCEINLINE void PopSchema() { + Context* c = schemaStack_.template Pop(1); + if (HashCodeArray* a = static_cast(c->arrayElementHashCodes)) { + a->~HashCodeArray(); + StateAllocator::Free(a); + } + c->~Context(); + } + + const SchemaType& CurrentSchema() const { return *schemaStack_.template Top()->schema; } + Context& CurrentContext() { return *schemaStack_.template Top(); } + const Context& CurrentContext() const { return *schemaStack_.template Top(); } + + static OutputHandler& GetNullHandler() { + static OutputHandler nullHandler; + return nullHandler; + } + + static const size_t kDefaultSchemaStackCapacity = 1024; + static const size_t kDefaultDocumentStackCapacity = 256; + const SchemaDocumentType* schemaDocument_; + const SchemaType& root_; + OutputHandler& outputHandler_; + StateAllocator* stateAllocator_; + StateAllocator* ownStateAllocator_; + internal::Stack schemaStack_; //!< stack to store the current path of schema (BaseSchemaType *) + internal::Stack documentStack_; //!< stack to store the current path of validating document (Ch) + bool valid_; +#if RAPIDJSON_SCHEMA_VERBOSE + unsigned depth_; +#endif +}; + +typedef GenericSchemaValidator SchemaValidator; + +/////////////////////////////////////////////////////////////////////////////// +// SchemaValidatingReader + +//! A helper class for parsing with validation. +/*! + This helper class is a functor, designed as a parameter of \ref GenericDocument::Populate(). + + \tparam parseFlags Combination of \ref ParseFlag. + \tparam InputStream Type of input stream, implementing Stream concept. + \tparam SourceEncoding Encoding of the input stream. + \tparam SchemaDocumentType Type of schema document. + \tparam StackAllocator Allocator type for stack. +*/ +template < + unsigned parseFlags, + typename InputStream, + typename SourceEncoding, + typename SchemaDocumentType = SchemaDocument, + typename StackAllocator = CrtAllocator> +class SchemaValidatingReader { +public: + typedef typename SchemaDocumentType::PointerType PointerType; + typedef typename InputStream::Ch Ch; + + //! Constructor + /*! + \param is Input stream. + \param sd Schema document. + */ + SchemaValidatingReader(InputStream& is, const SchemaDocumentType& sd) : is_(is), sd_(sd), invalidSchemaKeyword_(), isValid_(true) {} + + template + bool operator()(Handler& handler) { + GenericReader reader; + GenericSchemaValidator validator(sd_, handler); + parseResult_ = reader.template Parse(is_, validator); + + if ((isValid_ = validator.IsValid())) { + invalidSchemaPointer_ = PointerType(); + invalidSchemaKeyword_ = 0; + invalidDocumentPointer_ = PointerType(); + } + else { + invalidSchemaPointer_ = validator.GetInvalidSchemaPointer(); + invalidSchemaKeyword_ = validator.GetInvalidSchemaKeyword(); + invalidDocumentPointer_ = validator.GetInvalidDocumentPointer(); + } + + return parseResult_; + } + + const ParseResult& GetParseResult() const { return parseResult_; } + bool IsValid() const { return isValid_; } + const PointerType& GetInvalidSchemaPointer() const { return invalidSchemaPointer_; } + const Ch* GetInvalidSchemaKeyword() const { return invalidSchemaKeyword_; } + const PointerType& GetInvalidDocumentPointer() const { return invalidDocumentPointer_; } + +private: + InputStream& is_; + const SchemaDocumentType& sd_; + + ParseResult parseResult_; + PointerType invalidSchemaPointer_; + const Ch* invalidSchemaKeyword_; + PointerType invalidDocumentPointer_; + bool isValid_; +}; + +RAPIDJSON_NAMESPACE_END + +#if defined(__GNUC__) +RAPIDJSON_DIAG_POP +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_SCHEMA_H_ diff --git a/test/perftest/CMakeLists.txt b/test/perftest/CMakeLists.txt index 4121bf9..c88cf70 100644 --- a/test/perftest/CMakeLists.txt +++ b/test/perftest/CMakeLists.txt @@ -2,7 +2,8 @@ set(PERFTEST_SOURCES misctest.cpp perftest.cpp platformtest.cpp - rapidjsontest.cpp) + rapidjsontest.cpp + schematest.cpp) add_executable(perftest ${PERFTEST_SOURCES}) target_link_libraries(perftest ${TEST_LIBRARIES}) diff --git a/test/perftest/schematest.cpp b/test/perftest/schematest.cpp new file mode 100644 index 0000000..dc27e44 --- /dev/null +++ b/test/perftest/schematest.cpp @@ -0,0 +1,213 @@ +#include "perftest.h" + +#if TEST_RAPIDJSON + +#include "rapidjson/schema.h" +#include +#include +#include + +#define ARRAY_SIZE(a) sizeof(a) / sizeof(a[0]) + +using namespace rapidjson; + +template +static char* ReadFile(const char* filename, Allocator& allocator) { + const char *paths[] = { + "", + "bin/", + "../bin/", + "../../bin/", + "../../../bin/" + }; + char buffer[1024]; + FILE *fp = 0; + for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); i++) { + sprintf(buffer, "%s%s", paths[i], filename); + fp = fopen(buffer, "rb"); + if (fp) + break; + } + + if (!fp) + return 0; + + fseek(fp, 0, SEEK_END); + size_t length = static_cast(ftell(fp)); + fseek(fp, 0, SEEK_SET); + char* json = reinterpret_cast(allocator.Malloc(length + 1)); + size_t readLength = fread(json, 1, length, fp); + json[readLength] = '\0'; + fclose(fp); + return json; +} + +class Schema : public PerfTest { +public: + Schema() {} + + virtual void SetUp() { + PerfTest::SetUp(); + + const char* filenames[] = { + "additionalItems.json", + "additionalProperties.json", + "allOf.json", + "anyOf.json", + "default.json", + "definitions.json", + "dependencies.json", + "enum.json", + "items.json", + "maximum.json", + "maxItems.json", + "maxLength.json", + "maxProperties.json", + "minimum.json", + "minItems.json", + "minLength.json", + "minProperties.json", + "multipleOf.json", + "not.json", + "oneOf.json", + "pattern.json", + "patternProperties.json", + "properties.json", + "ref.json", + "refRemote.json", + "required.json", + "type.json", + "uniqueItems.json" + }; + + char jsonBuffer[65536]; + MemoryPoolAllocator<> jsonAllocator(jsonBuffer, sizeof(jsonBuffer)); + + for (size_t i = 0; i < ARRAY_SIZE(filenames); i++) { + char filename[FILENAME_MAX]; + sprintf(filename, "jsonschema/tests/draft4/%s", filenames[i]); + char* json = ReadFile(filename, jsonAllocator); + if (!json) { + printf("json test suite file %s not found", filename); + return; + } + + Document d; + d.Parse(json); + if (d.HasParseError()) { + printf("json test suite file %s has parse error", filename); + return; + } + + for (Value::ConstValueIterator schemaItr = d.Begin(); schemaItr != d.End(); ++schemaItr) { + if (IsExcludeTestSuite((*schemaItr)["description"].GetString())) + continue; + + TestSuite* ts = new TestSuite; + ts->schema = new SchemaDocument((*schemaItr)["schema"]); + + const Value& tests = (*schemaItr)["tests"]; + for (Value::ConstValueIterator testItr = tests.Begin(); testItr != tests.End(); ++testItr) { + if (IsExcludeTest((*testItr)["description"].GetString())) + continue; + + Document* d2 = new Document; + d2->CopyFrom((*testItr)["data"], d2->GetAllocator()); + ts->tests.push_back(d2); + } + testSuites.push_back(ts); + } + } + } + + virtual void TearDown() { + PerfTest::TearDown(); + for (TestSuiteList::const_iterator itr = testSuites.begin(); itr != testSuites.end(); ++itr) + delete *itr; + testSuites.clear(); + } + +private: + // Using the same exclusion in https://github.com/json-schema/JSON-Schema-Test-Suite + static bool IsExcludeTestSuite(const std::string& description) { + const char* excludeTestSuites[] = { + //lost failing these tests + "remote ref", + "remote ref, containing refs itself", + "fragment within remote ref", + "ref within remote ref", + "change resolution scope", + // these below were added to get jsck in the benchmarks) + "uniqueItems validation", + "valid definition", + "invalid definition" + }; + + for (size_t i = 0; i < ARRAY_SIZE(excludeTestSuites); i++) + if (excludeTestSuites[i] == description) + return true; + return false; + } + + // Using the same exclusion in https://github.com/json-schema/JSON-Schema-Test-Suite + static bool IsExcludeTest(const std::string& description) { + const char* excludeTests[] = { + //lots of validators fail these + "invalid definition, invalid definition schema", + "maxLength validation, two supplementary Unicode code points is long enough", + "minLength validation, one supplementary Unicode code point is not long enough", + //this is to get tv4 in the benchmarks + "heterogeneous enum validation, something else is invalid" + }; + + for (size_t i = 0; i < ARRAY_SIZE(excludeTests); i++) + if (excludeTests[i] == description) + return true; + return false; + } + + Schema(const Schema&); + Schema& operator=(const Schema&); + +protected: + typedef std::vector DocumentList; + + struct TestSuite { + TestSuite() : schema() {} + ~TestSuite() { + delete schema; + for (DocumentList::iterator itr = tests.begin(); itr != tests.end(); ++itr) + delete *itr; + } + SchemaDocument* schema; + DocumentList tests; + }; + + typedef std::vector TestSuiteList; + TestSuiteList testSuites; +}; + +TEST_F(Schema, TestSuite) { + char validatorBuffer[65536]; + MemoryPoolAllocator<> validatorAllocator(validatorBuffer, sizeof(validatorBuffer)); + + int testCount = 0; + clock_t start = clock(); + for (int i = 0; i < 10000; i++) { + for (TestSuiteList::const_iterator itr = testSuites.begin(); itr != testSuites.end(); ++itr) { + const TestSuite& ts = **itr; + GenericSchemaValidator >, MemoryPoolAllocator<> > validator(*ts.schema, &validatorAllocator); + for (DocumentList::const_iterator testItr = ts.tests.begin(); testItr != ts.tests.end(); ++testItr) { + validator.Reset(); + (*testItr)->Accept(validator); + testCount++; + } + validatorAllocator.Clear(); + } + } + clock_t end = clock(); + double duration = double(end - start) / CLOCKS_PER_SEC; + printf("%d tests in %f s -> %f tests per sec\n", testCount, duration, testCount / duration); +} + +#endif diff --git a/test/unittest/CMakeLists.txt b/test/unittest/CMakeLists.txt index 35b800f..21a2dfb 100644 --- a/test/unittest/CMakeLists.txt +++ b/test/unittest/CMakeLists.txt @@ -11,7 +11,10 @@ set(UNITTEST_SOURCES pointertest.cpp prettywritertest.cpp readertest.cpp + regextest.cpp + schematest.cpp simdtest.cpp + strfunctest.cpp stringbuffertest.cpp strtodtest.cpp unittest.cpp diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp index eee9ff7..2f399ad 100644 --- a/test/unittest/pointertest.cpp +++ b/test/unittest/pointertest.cpp @@ -32,6 +32,12 @@ static const char kJson[] = "{\n" " \"m~n\" : 8\n" "}"; +TEST(Pointer, DefaultConstructor) { + Pointer p; + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(0u, p.GetTokenCount()); +} + TEST(Pointer, Parse) { { Pointer p(""); diff --git a/test/unittest/regextest.cpp b/test/unittest/regextest.cpp new file mode 100644 index 0000000..65105fa --- /dev/null +++ b/test/unittest/regextest.cpp @@ -0,0 +1,533 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "unittest.h" +#include "rapidjson/internal/regex.h" + +using namespace rapidjson::internal; + +TEST(Regex, Concatenation) { + Regex re("abc"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("abc")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("a")); + EXPECT_FALSE(re.Match("b")); + EXPECT_FALSE(re.Match("ab")); + EXPECT_FALSE(re.Match("abcd")); +} + +TEST(Regex, Alternation1) { + Regex re("abab|abbb"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("abab")); + EXPECT_TRUE(re.Match("abbb")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("ab")); + EXPECT_FALSE(re.Match("ababa")); + EXPECT_FALSE(re.Match("abb")); + EXPECT_FALSE(re.Match("abbbb")); +} + +TEST(Regex, Alternation2) { + Regex re("a|b|c"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("a")); + EXPECT_TRUE(re.Match("b")); + EXPECT_TRUE(re.Match("c")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("aa")); + EXPECT_FALSE(re.Match("ab")); +} + +TEST(Regex, Parenthesis1) { + Regex re("(ab)c"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("abc")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("a")); + EXPECT_FALSE(re.Match("b")); + EXPECT_FALSE(re.Match("ab")); + EXPECT_FALSE(re.Match("abcd")); +} + +TEST(Regex, Parenthesis2) { + Regex re("a(bc)"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("abc")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("a")); + EXPECT_FALSE(re.Match("b")); + EXPECT_FALSE(re.Match("ab")); + EXPECT_FALSE(re.Match("abcd")); +} + +TEST(Regex, Parenthesis3) { + Regex re("(a|b)(c|d)"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("ac")); + EXPECT_TRUE(re.Match("ad")); + EXPECT_TRUE(re.Match("bc")); + EXPECT_TRUE(re.Match("bd")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("ab")); + EXPECT_FALSE(re.Match("cd")); +} + +TEST(Regex, ZeroOrOne1) { + Regex re("a?"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("")); + EXPECT_TRUE(re.Match("a")); + EXPECT_FALSE(re.Match("aa")); +} + +TEST(Regex, ZeroOrOne2) { + Regex re("a?b"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("b")); + EXPECT_TRUE(re.Match("ab")); + EXPECT_FALSE(re.Match("a")); + EXPECT_FALSE(re.Match("aa")); + EXPECT_FALSE(re.Match("bb")); + EXPECT_FALSE(re.Match("ba")); +} + +TEST(Regex, ZeroOrOne3) { + Regex re("ab?"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("a")); + EXPECT_TRUE(re.Match("ab")); + EXPECT_FALSE(re.Match("b")); + EXPECT_FALSE(re.Match("aa")); + EXPECT_FALSE(re.Match("bb")); + EXPECT_FALSE(re.Match("ba")); +} + +TEST(Regex, ZeroOrOne4) { + Regex re("a?b?"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("")); + EXPECT_TRUE(re.Match("a")); + EXPECT_TRUE(re.Match("b")); + EXPECT_TRUE(re.Match("ab")); + EXPECT_FALSE(re.Match("aa")); + EXPECT_FALSE(re.Match("bb")); + EXPECT_FALSE(re.Match("ba")); + EXPECT_FALSE(re.Match("abc")); +} + +TEST(Regex, ZeroOrOne5) { + Regex re("a(ab)?b"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("ab")); + EXPECT_TRUE(re.Match("aabb")); + EXPECT_FALSE(re.Match("aab")); + EXPECT_FALSE(re.Match("abb")); +} + +TEST(Regex, ZeroOrMore1) { + Regex re("a*"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("")); + EXPECT_TRUE(re.Match("a")); + EXPECT_TRUE(re.Match("aa")); + EXPECT_FALSE(re.Match("b")); + EXPECT_FALSE(re.Match("ab")); +} + +TEST(Regex, ZeroOrMore2) { + Regex re("a*b"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("b")); + EXPECT_TRUE(re.Match("ab")); + EXPECT_TRUE(re.Match("aab")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("bb")); +} + +TEST(Regex, ZeroOrMore3) { + Regex re("a*b*"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("")); + EXPECT_TRUE(re.Match("a")); + EXPECT_TRUE(re.Match("aa")); + EXPECT_TRUE(re.Match("b")); + EXPECT_TRUE(re.Match("bb")); + EXPECT_TRUE(re.Match("ab")); + EXPECT_TRUE(re.Match("aabb")); + EXPECT_FALSE(re.Match("ba")); +} + +TEST(Regex, ZeroOrMore4) { + Regex re("a(ab)*b"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("ab")); + EXPECT_TRUE(re.Match("aabb")); + EXPECT_TRUE(re.Match("aababb")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("aa")); +} + +TEST(Regex, OneOrMore1) { + Regex re("a+"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("a")); + EXPECT_TRUE(re.Match("aa")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("b")); + EXPECT_FALSE(re.Match("ab")); +} + +TEST(Regex, OneOrMore2) { + Regex re("a+b"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("ab")); + EXPECT_TRUE(re.Match("aab")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("b")); +} + +TEST(Regex, OneOrMore3) { + Regex re("a+b+"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("ab")); + EXPECT_TRUE(re.Match("aab")); + EXPECT_TRUE(re.Match("abb")); + EXPECT_TRUE(re.Match("aabb")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("b")); + EXPECT_FALSE(re.Match("ba")); +} + +TEST(Regex, OneOrMore4) { + Regex re("a(ab)+b"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("aabb")); + EXPECT_TRUE(re.Match("aababb")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("ab")); +} + +TEST(Regex, QuantifierExact1) { + Regex re("ab{3}c"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("abbbc")); + EXPECT_FALSE(re.Match("ac")); + EXPECT_FALSE(re.Match("abc")); + EXPECT_FALSE(re.Match("abbc")); + EXPECT_FALSE(re.Match("abbbbc")); +} + +TEST(Regex, QuantifierExact2) { + Regex re("a(bc){3}d"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("abcbcbcd")); + EXPECT_FALSE(re.Match("ad")); + EXPECT_FALSE(re.Match("abcd")); + EXPECT_FALSE(re.Match("abcbcd")); + EXPECT_FALSE(re.Match("abcbcbcbcd")); +} + +TEST(Regex, QuantifierExact3) { + Regex re("a(b|c){3}d"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("abbbd")); + EXPECT_TRUE(re.Match("acccd")); + EXPECT_TRUE(re.Match("abcbd")); + EXPECT_FALSE(re.Match("ad")); + EXPECT_FALSE(re.Match("abbd")); + EXPECT_FALSE(re.Match("accccd")); + EXPECT_FALSE(re.Match("abbbbd")); +} + +TEST(Regex, QuantifierMin1) { + Regex re("ab{3,}c"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("abbbc")); + EXPECT_TRUE(re.Match("abbbbc")); + EXPECT_TRUE(re.Match("abbbbbc")); + EXPECT_FALSE(re.Match("ac")); + EXPECT_FALSE(re.Match("abc")); + EXPECT_FALSE(re.Match("abbc")); +} + +TEST(Regex, QuantifierMin2) { + Regex re("a(bc){3,}d"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("abcbcbcd")); + EXPECT_TRUE(re.Match("abcbcbcbcd")); + EXPECT_FALSE(re.Match("ad")); + EXPECT_FALSE(re.Match("abcd")); + EXPECT_FALSE(re.Match("abcbcd")); +} + +TEST(Regex, QuantifierMin3) { + Regex re("a(b|c){3,}d"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("abbbd")); + EXPECT_TRUE(re.Match("acccd")); + EXPECT_TRUE(re.Match("abcbd")); + EXPECT_TRUE(re.Match("accccd")); + EXPECT_TRUE(re.Match("abbbbd")); + EXPECT_FALSE(re.Match("ad")); + EXPECT_FALSE(re.Match("abbd")); +} + +TEST(Regex, QuantifierMinMax1) { + Regex re("ab{3,5}c"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("abbbc")); + EXPECT_TRUE(re.Match("abbbbc")); + EXPECT_TRUE(re.Match("abbbbbc")); + EXPECT_FALSE(re.Match("ac")); + EXPECT_FALSE(re.Match("abc")); + EXPECT_FALSE(re.Match("abbc")); + EXPECT_FALSE(re.Match("abbbbbbc")); +} + +TEST(Regex, QuantifierMinMax2) { + Regex re("a(bc){3,5}d"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("abcbcbcd")); + EXPECT_TRUE(re.Match("abcbcbcbcd")); + EXPECT_TRUE(re.Match("abcbcbcbcbcd")); + EXPECT_FALSE(re.Match("ad")); + EXPECT_FALSE(re.Match("abcd")); + EXPECT_FALSE(re.Match("abcbcd")); + EXPECT_FALSE(re.Match("abcbcbcbcbcbcd")); +} + +TEST(Regex, QuantifierMinMax3) { + Regex re("a(b|c){3,5}d"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("abbbd")); + EXPECT_TRUE(re.Match("acccd")); + EXPECT_TRUE(re.Match("abcbd")); + EXPECT_TRUE(re.Match("accccd")); + EXPECT_TRUE(re.Match("abbbbd")); + EXPECT_TRUE(re.Match("acccccd")); + EXPECT_TRUE(re.Match("abbbbbd")); + EXPECT_FALSE(re.Match("ad")); + EXPECT_FALSE(re.Match("abbd")); + EXPECT_FALSE(re.Match("accccccd")); + EXPECT_FALSE(re.Match("abbbbbbd")); +} + +#define EURO "\xE2\x82\xAC" // "\xE2\x82\xAC" is UTF-8 sequence of Euro sign U+20AC + +TEST(Regex, Unicode) { + Regex re("a" EURO "+b"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("a" EURO "b")); + EXPECT_TRUE(re.Match("a" EURO EURO "b")); + EXPECT_FALSE(re.Match("a?b")); + EXPECT_FALSE(re.Match("a" EURO "\xAC" "b")); // unaware of UTF-8 will match +} + +TEST(Regex, AnyCharacter) { + Regex re("."); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("a")); + EXPECT_TRUE(re.Match("b")); + EXPECT_TRUE(re.Match(EURO)); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("aa")); +} + +TEST(Regex, CharacterRange1) { + Regex re("[abc]"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("a")); + EXPECT_TRUE(re.Match("b")); + EXPECT_TRUE(re.Match("c")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("`")); + EXPECT_FALSE(re.Match("d")); + EXPECT_FALSE(re.Match("aa")); +} + +TEST(Regex, CharacterRange2) { + Regex re("[^abc]"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("`")); + EXPECT_TRUE(re.Match("d")); + EXPECT_FALSE(re.Match("a")); + EXPECT_FALSE(re.Match("b")); + EXPECT_FALSE(re.Match("c")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("aa")); +} + +TEST(Regex, CharacterRange3) { + Regex re("[a-c]"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("a")); + EXPECT_TRUE(re.Match("b")); + EXPECT_TRUE(re.Match("c")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("`")); + EXPECT_FALSE(re.Match("d")); + EXPECT_FALSE(re.Match("aa")); +} + +TEST(Regex, CharacterRange4) { + Regex re("[^a-c]"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("`")); + EXPECT_TRUE(re.Match("d")); + EXPECT_FALSE(re.Match("a")); + EXPECT_FALSE(re.Match("b")); + EXPECT_FALSE(re.Match("c")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("aa")); +} + +TEST(Regex, CharacterRange5) { + Regex re("[-]"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("-")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("a")); +} + +TEST(Regex, CharacterRange6) { + Regex re("[a-]"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("a")); + EXPECT_TRUE(re.Match("-")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("`")); + EXPECT_FALSE(re.Match("b")); +} + +TEST(Regex, CharacterRange7) { + Regex re("[-a]"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("a")); + EXPECT_TRUE(re.Match("-")); + EXPECT_FALSE(re.Match("")); + EXPECT_FALSE(re.Match("`")); + EXPECT_FALSE(re.Match("b")); +} + +TEST(Regex, CharacterRange8) { + Regex re("[a-zA-Z0-9]*"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("Milo")); + EXPECT_TRUE(re.Match("MT19937")); + EXPECT_TRUE(re.Match("43")); + EXPECT_FALSE(re.Match("a_b")); + EXPECT_FALSE(re.Match("!")); +} + +TEST(Regex, Search) { + Regex re("abc"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Search("abc")); + EXPECT_TRUE(re.Search("_abc")); + EXPECT_TRUE(re.Search("abc_")); + EXPECT_TRUE(re.Search("_abc_")); + EXPECT_TRUE(re.Search("__abc__")); + EXPECT_TRUE(re.Search("abcabc")); + EXPECT_FALSE(re.Search("a")); + EXPECT_FALSE(re.Search("ab")); + EXPECT_FALSE(re.Search("bc")); + EXPECT_FALSE(re.Search("cba")); +} + +TEST(Regex, Search_BeginAnchor) { + Regex re("^abc"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Search("abc")); + EXPECT_TRUE(re.Search("abc_")); + EXPECT_TRUE(re.Search("abcabc")); + EXPECT_FALSE(re.Search("_abc")); + EXPECT_FALSE(re.Search("_abc_")); + EXPECT_FALSE(re.Search("a")); + EXPECT_FALSE(re.Search("ab")); + EXPECT_FALSE(re.Search("bc")); + EXPECT_FALSE(re.Search("cba")); +} + +TEST(Regex, Search_EndAnchor) { + Regex re("abc$"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Search("abc")); + EXPECT_TRUE(re.Search("_abc")); + EXPECT_TRUE(re.Search("abcabc")); + EXPECT_FALSE(re.Search("abc_")); + EXPECT_FALSE(re.Search("_abc_")); + EXPECT_FALSE(re.Search("a")); + EXPECT_FALSE(re.Search("ab")); + EXPECT_FALSE(re.Search("bc")); + EXPECT_FALSE(re.Search("cba")); +} + +TEST(Regex, Search_BothAnchor) { + Regex re("^abc$"); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Search("abc")); + EXPECT_FALSE(re.Search("")); + EXPECT_FALSE(re.Search("a")); + EXPECT_FALSE(re.Search("b")); + EXPECT_FALSE(re.Search("ab")); + EXPECT_FALSE(re.Search("abcd")); +} + +TEST(Regex, Escape) { + const char* s = "\\^\\$\\|\\(\\)\\?\\*\\+\\.\\[\\]\\{\\}\\\\\\f\\n\\r\\t\\v[\\b][\\[][\\]]"; + Regex re(s); + ASSERT_TRUE(re.IsValid()); + EXPECT_TRUE(re.Match("^$|()?*+.[]{}\\\x0C\n\r\t\x0B\b[]")); + EXPECT_FALSE(re.Match(s)); // Not escaping +} + +TEST(Regex, Invalid) { +#define TEST_INVALID(s) \ + {\ + Regex re(s);\ + EXPECT_FALSE(re.IsValid());\ + } + + TEST_INVALID("a|"); + TEST_INVALID("()"); + TEST_INVALID(")"); + TEST_INVALID("(a))"); + TEST_INVALID("(a|)"); + TEST_INVALID("(a||b)"); + TEST_INVALID("(|b)"); + TEST_INVALID("?"); + TEST_INVALID("*"); + TEST_INVALID("+"); + TEST_INVALID("{"); + TEST_INVALID("{}"); + TEST_INVALID("a{a}"); + TEST_INVALID("a{0}"); + TEST_INVALID("a{-1}"); + TEST_INVALID("a{}"); + TEST_INVALID("a{0,}"); + TEST_INVALID("a{,0}"); + TEST_INVALID("a{1,0}"); + TEST_INVALID("a{-1,0}"); + TEST_INVALID("a{-1,1}"); + TEST_INVALID("[]"); + TEST_INVALID("[^]"); + TEST_INVALID("[\\a]"); + TEST_INVALID("\\a"); + +#undef TEST_INVALID +} + +#undef EURO diff --git a/test/unittest/schematest.cpp b/test/unittest/schematest.cpp new file mode 100644 index 0000000..623c65a --- /dev/null +++ b/test/unittest/schematest.cpp @@ -0,0 +1,1157 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "unittest.h" +#include "rapidjson/schema.h" +#include "rapidjson/stringbuffer.h" +#include "rapidjson/writer.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(variadic-macros) +#endif + +using namespace rapidjson; + +#define TEST_HASHER(json1, json2, expected) \ +{\ + Document d1, d2;\ + d1.Parse(json1);\ + ASSERT_FALSE(d1.HasParseError());\ + d2.Parse(json2);\ + ASSERT_FALSE(d2.HasParseError());\ + internal::Hasher h1, h2;\ + d1.Accept(h1);\ + d2.Accept(h2);\ + ASSERT_TRUE(h1.IsValid());\ + ASSERT_TRUE(h2.IsValid());\ + /*printf("%s: 0x%016llx\n%s: 0x%016llx\n\n", json1, h1.GetHashCode(), json2, h2.GetHashCode());*/\ + EXPECT_TRUE(expected == (h1.GetHashCode() == h2.GetHashCode()));\ +} + +TEST(SchemaValidator, Hasher) { + TEST_HASHER("null", "null", true); + + TEST_HASHER("true", "true", true); + TEST_HASHER("false", "false", true); + TEST_HASHER("true", "false", false); + TEST_HASHER("false", "true", false); + TEST_HASHER("true", "null", false); + TEST_HASHER("false", "null", false); + + TEST_HASHER("1", "1", true); + TEST_HASHER("1.5", "1.5", true); + TEST_HASHER("1", "1.0", true); + TEST_HASHER("1", "-1", false); + TEST_HASHER("0.0", "-0.0", false); + TEST_HASHER("1", "true", false); + TEST_HASHER("0", "false", false); + TEST_HASHER("0", "null", false); + + TEST_HASHER("\"\"", "\"\"", true); + TEST_HASHER("\"\"", "\"\\u0000\"", false); + TEST_HASHER("\"Hello\"", "\"Hello\"", true); + TEST_HASHER("\"Hello\"", "\"World\"", false); + TEST_HASHER("\"Hello\"", "null", false); + TEST_HASHER("\"Hello\\u0000\"", "\"Hello\"", false); + TEST_HASHER("\"\"", "null", false); + TEST_HASHER("\"\"", "true", false); + TEST_HASHER("\"\"", "false", false); + + TEST_HASHER("[]", "[ ]", true); + TEST_HASHER("[1, true, false]", "[1, true, false]", true); + TEST_HASHER("[1, true, false]", "[1, true]", false); + TEST_HASHER("[1, 2]", "[2, 1]", false); + TEST_HASHER("[[1], 2]", "[[1, 2]]", false); + TEST_HASHER("[1, 2]", "[1, [2]]", false); + TEST_HASHER("[]", "null", false); + TEST_HASHER("[]", "true", false); + TEST_HASHER("[]", "false", false); + TEST_HASHER("[]", "0", false); + TEST_HASHER("[]", "0.0", false); + TEST_HASHER("[]", "\"\"", false); + + TEST_HASHER("{}", "{ }", true); + TEST_HASHER("{\"a\":1}", "{\"a\":1}", true); + TEST_HASHER("{\"a\":1}", "{\"b\":1}", false); + TEST_HASHER("{\"a\":1}", "{\"a\":2}", false); + TEST_HASHER("{\"a\":1, \"b\":2}", "{\"b\":2, \"a\":1}", true); // Member order insensitive + TEST_HASHER("{}", "null", false); + TEST_HASHER("{}", "false", false); + TEST_HASHER("{}", "true", false); + TEST_HASHER("{}", "0", false); + TEST_HASHER("{}", "0.0", false); + TEST_HASHER("{}", "\"\"", false); +} + +// Test cases following http://spacetelescope.github.io/understanding-json-schema + +#define VALIDATE(schema, json, expected) \ +{\ + SchemaValidator validator(schema);\ + Document d;\ + /*printf("\n%s\n", json);*/\ + d.Parse(json);\ + EXPECT_FALSE(d.HasParseError());\ + EXPECT_TRUE(expected == d.Accept(validator));\ + EXPECT_TRUE(expected == validator.IsValid());\ + if (expected && !validator.IsValid()) {\ + StringBuffer sb;\ + validator.GetInvalidSchemaPointer().StringifyUriFragment(sb);\ + printf("Invalid schema: %s\n", sb.GetString());\ + printf("Invalid keyword: %s\n", validator.GetInvalidSchemaKeyword());\ + sb.Clear();\ + validator.GetInvalidDocumentPointer().StringifyUriFragment(sb);\ + printf("Invalid document: %s\n", sb.GetString());\ + }\ +} + +#define INVALIDATE(schema, json, invalidSchemaPointer, invalidSchemaKeyword, invalidDocumentPointer) \ +{\ + SchemaValidator validator(schema);\ + Document d;\ + /*printf("\n%s\n", json);*/\ + d.Parse(json);\ + EXPECT_FALSE(d.HasParseError());\ + EXPECT_FALSE(d.Accept(validator));\ + EXPECT_FALSE(validator.IsValid());\ + if (validator.GetInvalidSchemaPointer() != Pointer(invalidSchemaPointer)) {\ + StringBuffer sb;\ + validator.GetInvalidSchemaPointer().Stringify(sb);\ + printf("GetInvalidSchemaPointer() Expected: %s Actual: %s\n", invalidSchemaPointer, sb.GetString());\ + ADD_FAILURE();\ + }\ + ASSERT_TRUE(validator.GetInvalidSchemaKeyword() != 0);\ + if (strcmp(validator.GetInvalidSchemaKeyword(), invalidSchemaKeyword) != 0) {\ + printf("GetInvalidSchemaKeyword() Expected: %s Actual %s\n", invalidSchemaKeyword, validator.GetInvalidSchemaKeyword());\ + ADD_FAILURE();\ + }\ + if (validator.GetInvalidDocumentPointer() != Pointer(invalidDocumentPointer)) {\ + StringBuffer sb;\ + validator.GetInvalidDocumentPointer().Stringify(sb);\ + printf("GetInvalidDocumentPointer() Expected: %s Actual: %s\n", invalidDocumentPointer, sb.GetString());\ + ADD_FAILURE();\ + }\ +} + +TEST(SchemaValidator, Typeless) { + Document sd; + sd.Parse("{}"); + SchemaDocument s(sd); + + VALIDATE(s, "42", true); + VALIDATE(s, "\"I'm a string\"", true); + VALIDATE(s, "{ \"an\": [ \"arbitrarily\", \"nested\" ], \"data\": \"structure\" }", true); +} + +TEST(SchemaValidator, MultiType) { + Document sd; + sd.Parse("{ \"type\": [\"number\", \"string\"] }"); + SchemaDocument s(sd); + + VALIDATE(s, "42", true); + VALIDATE(s, "\"Life, the universe, and everything\"", true); + INVALIDATE(s, "[\"Life\", \"the universe\", \"and everything\"]", "", "type", ""); +} + +TEST(SchemaValidator, Enum_Typed) { + Document sd; + sd.Parse("{ \"type\": \"string\", \"enum\" : [\"red\", \"amber\", \"green\"] }"); + SchemaDocument s(sd); + + VALIDATE(s, "\"red\"", true); + INVALIDATE(s, "\"blue\"", "", "enum", ""); +} + +TEST(SchemaValidator, Enum_Typless) { + Document sd; + sd.Parse("{ \"enum\": [\"red\", \"amber\", \"green\", null, 42] }"); + SchemaDocument s(sd); + + VALIDATE(s, "\"red\"", true); + VALIDATE(s, "null", true); + VALIDATE(s, "42", true); + INVALIDATE(s, "0", "", "enum", ""); +} + +TEST(SchemaValidator, Enum_InvalidType) { + Document sd; + sd.Parse("{ \"type\": \"string\", \"enum\": [\"red\", \"amber\", \"green\", null] }"); + SchemaDocument s(sd); + + VALIDATE(s, "\"red\"", true); + INVALIDATE(s, "null", "", "type", ""); +} + +TEST(SchemaValidator, AllOf) { + { + Document sd; + sd.Parse("{\"allOf\": [{ \"type\": \"string\" }, { \"type\": \"string\", \"maxLength\": 5 }]}"); + SchemaDocument s(sd); + + VALIDATE(s, "\"ok\"", true); + INVALIDATE(s, "\"too long\"", "", "allOf", ""); + } + { + Document sd; + sd.Parse("{\"allOf\": [{ \"type\": \"string\" }, { \"type\": \"number\" } ] }"); + SchemaDocument s(sd); + + VALIDATE(s, "\"No way\"", false); + INVALIDATE(s, "-1", "", "allOf", ""); + } +} + +TEST(SchemaValidator, AnyOf) { + Document sd; + sd.Parse("{\"anyOf\": [{ \"type\": \"string\" }, { \"type\": \"number\" } ] }"); + SchemaDocument s(sd); + + VALIDATE(s, "\"Yes\"", true); + VALIDATE(s, "42", true); + INVALIDATE(s, "{ \"Not a\": \"string or number\" }", "", "anyOf", ""); +} + +TEST(SchemaValidator, OneOf) { + Document sd; + sd.Parse("{\"oneOf\": [{ \"type\": \"number\", \"multipleOf\": 5 }, { \"type\": \"number\", \"multipleOf\": 3 } ] }"); + SchemaDocument s(sd); + + VALIDATE(s, "10", true); + VALIDATE(s, "9", true); + INVALIDATE(s, "2", "", "oneOf", ""); + INVALIDATE(s, "15", "", "oneOf", ""); +} + +TEST(SchemaValidator, Not) { + Document sd; + sd.Parse("{\"not\":{ \"type\": \"string\"}}"); + SchemaDocument s(sd); + + VALIDATE(s, "42", true); + VALIDATE(s, "{ \"key\": \"value\" }", true); + INVALIDATE(s, "\"I am a string\"", "", "not", ""); +} + +TEST(SchemaValidator, Ref) { + Document sd; + sd.Parse( + "{" + " \"$schema\": \"http://json-schema.org/draft-04/schema#\"," + "" + " \"definitions\": {" + " \"address\": {" + " \"type\": \"object\"," + " \"properties\": {" + " \"street_address\": { \"type\": \"string\" }," + " \"city\": { \"type\": \"string\" }," + " \"state\": { \"type\": \"string\" }" + " }," + " \"required\": [\"street_address\", \"city\", \"state\"]" + " }" + " }," + " \"type\": \"object\"," + " \"properties\": {" + " \"billing_address\": { \"$ref\": \"#/definitions/address\" }," + " \"shipping_address\": { \"$ref\": \"#/definitions/address\" }" + " }" + "}"); + SchemaDocument s(sd); + + VALIDATE(s, "{\"shipping_address\": {\"street_address\": \"1600 Pennsylvania Avenue NW\", \"city\": \"Washington\", \"state\": \"DC\"}, \"billing_address\": {\"street_address\": \"1st Street SE\", \"city\": \"Washington\", \"state\": \"DC\"} }", true); +} + +TEST(SchemaValidator, Ref_AllOf) { + Document sd; + sd.Parse( + "{" + " \"$schema\": \"http://json-schema.org/draft-04/schema#\"," + "" + " \"definitions\": {" + " \"address\": {" + " \"type\": \"object\"," + " \"properties\": {" + " \"street_address\": { \"type\": \"string\" }," + " \"city\": { \"type\": \"string\" }," + " \"state\": { \"type\": \"string\" }" + " }," + " \"required\": [\"street_address\", \"city\", \"state\"]" + " }" + " }," + " \"type\": \"object\"," + " \"properties\": {" + " \"billing_address\": { \"$ref\": \"#/definitions/address\" }," + " \"shipping_address\": {" + " \"allOf\": [" + " { \"$ref\": \"#/definitions/address\" }," + " { \"properties\":" + " { \"type\": { \"enum\": [ \"residential\", \"business\" ] } }," + " \"required\": [\"type\"]" + " }" + " ]" + " }" + " }" + "}"); + SchemaDocument s(sd); + + INVALIDATE(s, "{\"shipping_address\": {\"street_address\": \"1600 Pennsylvania Avenue NW\", \"city\": \"Washington\", \"state\": \"DC\"} }", "/properties/shipping_address", "allOf", "/shipping_address"); + VALIDATE(s, "{\"shipping_address\": {\"street_address\": \"1600 Pennsylvania Avenue NW\", \"city\": \"Washington\", \"state\": \"DC\", \"type\": \"business\"} }", true); +} + +TEST(SchemaValidator, String) { + Document sd; + sd.Parse("{\"type\":\"string\"}"); + SchemaDocument s(sd); + + VALIDATE(s, "\"I'm a string\"", true); + INVALIDATE(s, "42", "", "type", ""); +} + +TEST(SchemaValidator, String_LengthRange) { + Document sd; + sd.Parse("{\"type\":\"string\",\"minLength\":2,\"maxLength\":3}"); + SchemaDocument s(sd); + + INVALIDATE(s, "\"A\"", "", "minLength", ""); + VALIDATE(s, "\"AB\"", true); + VALIDATE(s, "\"ABC\"", true); + INVALIDATE(s, "\"ABCD\"", "", "maxLength", ""); +} + +#if RAPIDJSON_SCHEMA_HAS_REGEX +TEST(SchemaValidator, String_Pattern) { + Document sd; + sd.Parse("{\"type\":\"string\",\"pattern\":\"^(\\\\([0-9]{3}\\\\))?[0-9]{3}-[0-9]{4}$\"}"); + SchemaDocument s(sd); + + VALIDATE(s, "\"555-1212\"", true); + VALIDATE(s, "\"(888)555-1212\"", true); + INVALIDATE(s, "\"(888)555-1212 ext. 532\"", "", "pattern", ""); + INVALIDATE(s, "\"(800)FLOWERS\"", "", "pattern", ""); +} +#endif + +TEST(SchemaValidator, Integer) { + Document sd; + sd.Parse("{\"type\":\"integer\"}"); + SchemaDocument s(sd); + + VALIDATE(s, "42", true); + VALIDATE(s, "-1", true); + INVALIDATE(s, "3.1415926", "", "type", ""); + INVALIDATE(s, "\"42\"", "", "type", ""); +} + +TEST(SchemaValidator, Integer_Range) { + Document sd; + sd.Parse("{\"type\":\"integer\",\"minimum\":0,\"maximum\":100,\"exclusiveMaximum\":true}"); + SchemaDocument s(sd); + + INVALIDATE(s, "-1", "", "minimum", ""); + VALIDATE(s, "0", true); + VALIDATE(s, "10", true); + VALIDATE(s, "99", true); + INVALIDATE(s, "100", "", "maximum", ""); + INVALIDATE(s, "101", "", "maximum", ""); +} + +TEST(SchemaValidator, Integer_Range64Boundary) { + Document sd; + sd.Parse("{\"type\":\"integer\",\"minimum\":-9223372036854775807,\"maximum\":18446744073709551614}"); + SchemaDocument s(sd); + + INVALIDATE(s, "-9223372036854775808", "", "minimum", ""); + VALIDATE(s, "-9223372036854775807", true); + VALIDATE(s, "18446744073709551614", true); + INVALIDATE(s, "18446744073709551615", "", "maximum", ""); +} + +TEST(SchemaValidator, Integer_Range64BoundaryExclusive) { + Document sd; + sd.Parse("{\"type\":\"integer\",\"minimum\":-9223372036854775808,\"maximum\":18446744073709551615,\"exclusiveMinimum\":true,\"exclusiveMaximum\":true}"); + SchemaDocument s(sd); + + INVALIDATE(s, "-9223372036854775808", "", "minimum", ""); + VALIDATE(s, "-9223372036854775807", true); + VALIDATE(s, "18446744073709551614", true); + INVALIDATE(s, "18446744073709551615", "", "maximum", ""); +} + +TEST(SchemaValidator, Integer_MultipleOf) { + Document sd; + sd.Parse("{\"type\":\"integer\",\"multipleOf\":10}"); + SchemaDocument s(sd); + + VALIDATE(s, "0", true); + VALIDATE(s, "10", true); + VALIDATE(s, "-10", true); + VALIDATE(s, "20", true); + INVALIDATE(s, "23", "", "multipleOf", ""); + INVALIDATE(s, "-23", "", "multipleOf", ""); +} + +TEST(SchemaValidator, Integer_MultipleOf64Boundary) { + Document sd; + sd.Parse("{\"type\":\"integer\",\"multipleOf\":18446744073709551615}"); + SchemaDocument s(sd); + + VALIDATE(s, "0", true); + VALIDATE(s, "18446744073709551615", true); + INVALIDATE(s, "18446744073709551614", "", "multipleOf", ""); +} + +TEST(SchemaValidator, Number_Range) { + Document sd; + sd.Parse("{\"type\":\"number\",\"minimum\":0,\"maximum\":100,\"exclusiveMaximum\":true}"); + SchemaDocument s(sd); + + INVALIDATE(s, "-1", "", "minimum", ""); + VALIDATE(s, "0", true); + VALIDATE(s, "10", true); + VALIDATE(s, "99", true); + INVALIDATE(s, "100", "", "maximum", ""); + INVALIDATE(s, "101", "", "maximum", ""); +} + +TEST(SchemaValidator, Number_MultipleOf) { + Document sd; + sd.Parse("{\"type\":\"number\",\"multipleOf\":10.0}"); + SchemaDocument s(sd); + + VALIDATE(s, "0", true); + VALIDATE(s, "10", true); + VALIDATE(s, "-10", true); + VALIDATE(s, "20", true); + INVALIDATE(s, "23", "", "multipleOf", ""); +} + +TEST(SchemaValidator, Number_MultipleOfOne) { + Document sd; + sd.Parse("{\"type\":\"number\",\"multipleOf\":1}"); + SchemaDocument s(sd); + + VALIDATE(s, "42", true); + VALIDATE(s, "42.0", true); + INVALIDATE(s, "3.1415926", "", "multipleOf", ""); +} + +TEST(SchemaValidator, Object) { + Document sd; + sd.Parse("{\"type\":\"object\"}"); + SchemaDocument s(sd); + + VALIDATE(s, "{\"key\":\"value\",\"another_key\":\"another_value\"}", true); + VALIDATE(s, "{\"Sun\":1.9891e30,\"Jupiter\":1.8986e27,\"Saturn\":5.6846e26,\"Neptune\":10.243e25,\"Uranus\":8.6810e25,\"Earth\":5.9736e24,\"Venus\":4.8685e24,\"Mars\":6.4185e23,\"Mercury\":3.3022e23,\"Moon\":7.349e22,\"Pluto\":1.25e22}", true); + INVALIDATE(s, "[\"An\", \"array\", \"not\", \"an\", \"object\"]", "", "type", ""); + INVALIDATE(s, "\"Not an object\"", "", "type", ""); +} + +TEST(SchemaValidator, Object_Properties) { + Document sd; + sd.Parse( + "{" + " \"type\": \"object\"," + " \"properties\" : {" + " \"number\": { \"type\": \"number\" }," + " \"street_name\" : { \"type\": \"string\" }," + " \"street_type\" : { \"type\": \"string\", \"enum\" : [\"Street\", \"Avenue\", \"Boulevard\"] }" + " }" + "}"); + + SchemaDocument s(sd); + + VALIDATE(s, "{ \"number\": 1600, \"street_name\": \"Pennsylvania\", \"street_type\": \"Avenue\" }", true); + INVALIDATE(s, "{ \"number\": \"1600\", \"street_name\": \"Pennsylvania\", \"street_type\": \"Avenue\" }", "/properties/number", "type", "/number"); + VALIDATE(s, "{ \"number\": 1600, \"street_name\": \"Pennsylvania\" }", true); + VALIDATE(s, "{}", true); + VALIDATE(s, "{ \"number\": 1600, \"street_name\": \"Pennsylvania\", \"street_type\": \"Avenue\", \"direction\": \"NW\" }", true); +} + +TEST(SchemaValidator, Object_AdditionalPropertiesBoolean) { + Document sd; + sd.Parse( + "{" + " \"type\": \"object\"," + " \"properties\" : {" + " \"number\": { \"type\": \"number\" }," + " \"street_name\" : { \"type\": \"string\" }," + " \"street_type\" : { \"type\": \"string\"," + " \"enum\" : [\"Street\", \"Avenue\", \"Boulevard\"]" + " }" + " }," + " \"additionalProperties\": false" + "}"); + + SchemaDocument s(sd); + + VALIDATE(s, "{ \"number\": 1600, \"street_name\": \"Pennsylvania\", \"street_type\": \"Avenue\" }", true); + INVALIDATE(s, "{ \"number\": 1600, \"street_name\": \"Pennsylvania\", \"street_type\": \"Avenue\", \"direction\": \"NW\" }", "", "additionalProperties", "/direction"); +} + +TEST(SchemaValidator, Object_AdditionalPropertiesObject) { + Document sd; + sd.Parse( + "{" + " \"type\": \"object\"," + " \"properties\" : {" + " \"number\": { \"type\": \"number\" }," + " \"street_name\" : { \"type\": \"string\" }," + " \"street_type\" : { \"type\": \"string\"," + " \"enum\" : [\"Street\", \"Avenue\", \"Boulevard\"]" + " }" + " }," + " \"additionalProperties\": { \"type\": \"string\" }" + "}"); + SchemaDocument s(sd); + + VALIDATE(s, "{ \"number\": 1600, \"street_name\": \"Pennsylvania\", \"street_type\": \"Avenue\" }", true); + VALIDATE(s, "{ \"number\": 1600, \"street_name\": \"Pennsylvania\", \"street_type\": \"Avenue\", \"direction\": \"NW\" }", true); + INVALIDATE(s, "{ \"number\": 1600, \"street_name\": \"Pennsylvania\", \"street_type\": \"Avenue\", \"office_number\": 201 }", "/additionalProperties", "type", "/office_number"); +} + +TEST(SchemaValidator, Object_Required) { + Document sd; + sd.Parse( + "{" + " \"type\": \"object\"," + " \"properties\" : {" + " \"name\": { \"type\": \"string\" }," + " \"email\" : { \"type\": \"string\" }," + " \"address\" : { \"type\": \"string\" }," + " \"telephone\" : { \"type\": \"string\" }" + " }," + " \"required\":[\"name\", \"email\"]" + "}"); + SchemaDocument s(sd); + + VALIDATE(s, "{ \"name\": \"William Shakespeare\", \"email\" : \"bill@stratford-upon-avon.co.uk\" }", true); + VALIDATE(s, "{ \"name\": \"William Shakespeare\", \"email\" : \"bill@stratford-upon-avon.co.uk\", \"address\" : \"Henley Street, Stratford-upon-Avon, Warwickshire, England\", \"authorship\" : \"in question\"}", true); + INVALIDATE(s, "{ \"name\": \"William Shakespeare\", \"address\" : \"Henley Street, Stratford-upon-Avon, Warwickshire, England\" }", "", "required", ""); +} + + +TEST(SchemaValidator, Object_PropertiesRange) { + Document sd; + sd.Parse("{\"type\":\"object\", \"minProperties\":2, \"maxProperties\":3}"); + SchemaDocument s(sd); + + INVALIDATE(s, "{}", "", "minProperties", ""); + INVALIDATE(s, "{\"a\":0}", "", "minProperties", ""); + VALIDATE(s, "{\"a\":0,\"b\":1}", true); + VALIDATE(s, "{\"a\":0,\"b\":1,\"c\":2}", true); + INVALIDATE(s, "{\"a\":0,\"b\":1,\"c\":2,\"d\":3}", "", "maxProperties", ""); +} + +TEST(SchemaValidator, Object_PropertyDependencies) { + Document sd; + sd.Parse( + "{" + " \"type\": \"object\"," + " \"properties\": {" + " \"name\": { \"type\": \"string\" }," + " \"credit_card\": { \"type\": \"number\" }," + " \"billing_address\": { \"type\": \"string\" }" + " }," + " \"required\": [\"name\"]," + " \"dependencies\": {" + " \"credit_card\": [\"billing_address\"]" + " }" + "}"); + SchemaDocument s(sd); + + VALIDATE(s, "{ \"name\": \"John Doe\", \"credit_card\": 5555555555555555, \"billing_address\": \"555 Debtor's Lane\" }", true); + INVALIDATE(s, "{ \"name\": \"John Doe\", \"credit_card\": 5555555555555555 }", "", "dependencies", ""); + VALIDATE(s, "{ \"name\": \"John Doe\"}", true); + VALIDATE(s, "{ \"name\": \"John Doe\", \"billing_address\": \"555 Debtor's Lane\" }", true); +} + +TEST(SchemaValidator, Object_SchemaDependencies) { + Document sd; + sd.Parse( + "{" + " \"type\": \"object\"," + " \"properties\" : {" + " \"name\": { \"type\": \"string\" }," + " \"credit_card\" : { \"type\": \"number\" }" + " }," + " \"required\" : [\"name\"]," + " \"dependencies\" : {" + " \"credit_card\": {" + " \"properties\": {" + " \"billing_address\": { \"type\": \"string\" }" + " }," + " \"required\" : [\"billing_address\"]" + " }" + " }" + "}"); + SchemaDocument s(sd); + + VALIDATE(s, "{\"name\": \"John Doe\", \"credit_card\" : 5555555555555555,\"billing_address\" : \"555 Debtor's Lane\"}", true); + INVALIDATE(s, "{\"name\": \"John Doe\", \"credit_card\" : 5555555555555555 }", "", "dependencies", ""); + VALIDATE(s, "{\"name\": \"John Doe\", \"billing_address\" : \"555 Debtor's Lane\"}", true); +} + +#if RAPIDJSON_SCHEMA_HAS_REGEX +TEST(SchemaValidator, Object_PatternProperties) { + Document sd; + sd.Parse( + "{" + " \"type\": \"object\"," + " \"patternProperties\": {" + " \"^S_\": { \"type\": \"string\" }," + " \"^I_\": { \"type\": \"integer\" }" + " }" + "}"); + SchemaDocument s(sd); + + VALIDATE(s, "{ \"S_25\": \"This is a string\" }", true); + VALIDATE(s, "{ \"I_0\": 42 }", true); + INVALIDATE(s, "{ \"S_0\": 42 }", "", "patternProperties", "/S_0"); + INVALIDATE(s, "{ \"I_42\": \"This is a string\" }", "", "patternProperties", "/I_42"); + VALIDATE(s, "{ \"keyword\": \"value\" }", true); +} + +TEST(SchemaValidator, Object_PatternProperties_AdditionalProperties) { + Document sd; + sd.Parse( + "{" + " \"type\": \"object\"," + " \"properties\": {" + " \"builtin\": { \"type\": \"number\" }" + " }," + " \"patternProperties\": {" + " \"^S_\": { \"type\": \"string\" }," + " \"^I_\": { \"type\": \"integer\" }" + " }," + " \"additionalProperties\": { \"type\": \"string\" }" + "}"); + SchemaDocument s(sd); + + VALIDATE(s, "{ \"builtin\": 42 }", true); + VALIDATE(s, "{ \"keyword\": \"value\" }", true); + INVALIDATE(s, "{ \"keyword\": 42 }", "/additionalProperties", "type", "/keyword"); +} +#endif + +TEST(SchemaValidator, Array) { + Document sd; + sd.Parse("{\"type\":\"array\"}"); + SchemaDocument s(sd); + + VALIDATE(s, "[1, 2, 3, 4, 5]", true); + VALIDATE(s, "[3, \"different\", { \"types\" : \"of values\" }]", true); + INVALIDATE(s, "{\"Not\": \"an array\"}", "", "type", ""); +} + +TEST(SchemaValidator, Array_ItemsList) { + Document sd; + sd.Parse( + "{" + " \"type\": \"array\"," + " \"items\" : {" + " \"type\": \"number\"" + " }" + "}"); + SchemaDocument s(sd); + + VALIDATE(s, "[1, 2, 3, 4, 5]", true); + INVALIDATE(s, "[1, 2, \"3\", 4, 5]", "/items", "type", "/2"); + VALIDATE(s, "[]", true); +} + +TEST(SchemaValidator, Array_ItemsTuple) { + Document sd; + sd.Parse( + "{" + " \"type\": \"array\"," + " \"items\": [" + " {" + " \"type\": \"number\"" + " }," + " {" + " \"type\": \"string\"" + " }," + " {" + " \"type\": \"string\"," + " \"enum\": [\"Street\", \"Avenue\", \"Boulevard\"]" + " }," + " {" + " \"type\": \"string\"," + " \"enum\": [\"NW\", \"NE\", \"SW\", \"SE\"]" + " }" + " ]" + "}"); + SchemaDocument s(sd); + + VALIDATE(s, "[1600, \"Pennsylvania\", \"Avenue\", \"NW\"]", true); + INVALIDATE(s, "[24, \"Sussex\", \"Drive\"]", "/items/2", "enum", "/2"); + INVALIDATE(s, "[\"Palais de l'Elysee\"]", "/items/0", "type", "/0"); + VALIDATE(s, "[10, \"Downing\", \"Street\"]", true); + VALIDATE(s, "[1600, \"Pennsylvania\", \"Avenue\", \"NW\", \"Washington\"]", true); +} + +TEST(SchemaValidator, Array_AdditionalItmes) { + Document sd; + sd.Parse( + "{" + " \"type\": \"array\"," + " \"items\": [" + " {" + " \"type\": \"number\"" + " }," + " {" + " \"type\": \"string\"" + " }," + " {" + " \"type\": \"string\"," + " \"enum\": [\"Street\", \"Avenue\", \"Boulevard\"]" + " }," + " {" + " \"type\": \"string\"," + " \"enum\": [\"NW\", \"NE\", \"SW\", \"SE\"]" + " }" + " ]," + " \"additionalItems\": false" + "}"); + SchemaDocument s(sd); + + VALIDATE(s, "[1600, \"Pennsylvania\", \"Avenue\", \"NW\"]", true); + VALIDATE(s, "[1600, \"Pennsylvania\", \"Avenue\"]", true); + INVALIDATE(s, "[1600, \"Pennsylvania\", \"Avenue\", \"NW\", \"Washington\"]", "", "items", "/4"); +} + +TEST(SchemaValidator, Array_ItemsRange) { + Document sd; + sd.Parse("{\"type\": \"array\",\"minItems\": 2,\"maxItems\" : 3}"); + SchemaDocument s(sd); + + INVALIDATE(s, "[]", "", "minItems", ""); + INVALIDATE(s, "[1]", "", "minItems", ""); + VALIDATE(s, "[1, 2]", true); + VALIDATE(s, "[1, 2, 3]", true); + INVALIDATE(s, "[1, 2, 3, 4]", "", "maxItems", ""); +} + +TEST(SchemaValidator, Array_UniqueItems) { + Document sd; + sd.Parse("{\"type\": \"array\", \"uniqueItems\": true}"); + SchemaDocument s(sd); + + VALIDATE(s, "[1, 2, 3, 4, 5]", true); + INVALIDATE(s, "[1, 2, 3, 3, 4]", "", "uniqueItems", "/3"); + VALIDATE(s, "[]", true); +} + +TEST(SchemaValidator, Boolean) { + Document sd; + sd.Parse("{\"type\":\"boolean\"}"); + SchemaDocument s(sd); + + VALIDATE(s, "true", true); + VALIDATE(s, "false", true); + INVALIDATE(s, "\"true\"", "", "type", ""); + INVALIDATE(s, "0", "", "type", ""); +} + +TEST(SchemaValidator, Null) { + Document sd; + sd.Parse("{\"type\":\"null\"}"); + SchemaDocument s(sd); + + VALIDATE(s, "null", true); + INVALIDATE(s, "false", "", "type", ""); + INVALIDATE(s, "0", "", "type", ""); + INVALIDATE(s, "\"\"", "", "type", ""); +} + +// Additional tests + +TEST(SchemaValidator, ObjectInArray) { + Document sd; + sd.Parse("{\"type\":\"array\", \"items\": { \"type\":\"string\" }}"); + SchemaDocument s(sd); + + VALIDATE(s, "[\"a\"]", true); + INVALIDATE(s, "[1]", "/items", "type", "/0"); + INVALIDATE(s, "[{}]", "/items", "type", "/0"); +} + +TEST(SchemaValidator, MultiTypeInObject) { + Document sd; + sd.Parse( + "{" + " \"type\":\"object\"," + " \"properties\": {" + " \"tel\" : {" + " \"type\":[\"integer\", \"string\"]" + " }" + " }" + "}"); + SchemaDocument s(sd); + + VALIDATE(s, "{ \"tel\": 999 }", true); + VALIDATE(s, "{ \"tel\": \"123-456\" }", true); + INVALIDATE(s, "{ \"tel\": true }", "/properties/tel", "type", "/tel"); +} + +TEST(SchemaValidator, MultiTypeWithObject) { + Document sd; + sd.Parse( + "{" + " \"type\": [\"object\",\"string\"]," + " \"properties\": {" + " \"tel\" : {" + " \"type\": \"integer\"" + " }" + " }" + "}"); + SchemaDocument s(sd); + + VALIDATE(s, "\"Hello\"", true); + VALIDATE(s, "{ \"tel\": 999 }", true); + INVALIDATE(s, "{ \"tel\": \"fail\" }", "/properties/tel", "type", "/tel"); +} + +TEST(SchemaValidator, AllOf_Nested) { + Document sd; + sd.Parse( + "{" + " \"allOf\": [" + " { \"type\": \"string\", \"minLength\": 2 }," + " { \"type\": \"string\", \"maxLength\": 5 }," + " { \"allOf\": [ { \"enum\" : [\"ok\", \"okay\", \"OK\", \"o\"] }, { \"enum\" : [\"ok\", \"OK\", \"o\"]} ] }" + " ]" + "}"); + SchemaDocument s(sd); + + VALIDATE(s, "\"ok\"", true); + VALIDATE(s, "\"OK\"", true); + INVALIDATE(s, "\"okay\"", "", "allOf", ""); + INVALIDATE(s, "\"o\"", "", "allOf", ""); + INVALIDATE(s, "\"n\"", "", "allOf", ""); + INVALIDATE(s, "\"too long\"", "", "allOf", ""); + INVALIDATE(s, "123", "", "allOf", ""); +} + +template +static char* ReadFile(const char* filename, Allocator& allocator) { + const char *paths[] = { + "", + "bin/", + "../bin/", + "../../bin/", + "../../../bin/" + }; + char buffer[1024]; + FILE *fp = 0; + for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); i++) { + sprintf(buffer, "%s%s", paths[i], filename); + fp = fopen(buffer, "rb"); + if (fp) + break; + } + + if (!fp) + return 0; + + fseek(fp, 0, SEEK_END); + size_t length = static_cast(ftell(fp)); + fseek(fp, 0, SEEK_SET); + char* json = reinterpret_cast(allocator.Malloc(length + 1)); + size_t readLength = fread(json, 1, length, fp); + json[readLength] = '\0'; + fclose(fp); + return json; +} + +TEST(SchemaValidator, ValidateMetaSchema) { + CrtAllocator allocator; + char* json = ReadFile("draft-04/schema", allocator); + Document d; + d.Parse(json); + ASSERT_FALSE(d.HasParseError()); + SchemaDocument sd(d); + SchemaValidator validator(sd); + if (!d.Accept(validator)) { + StringBuffer sb; + validator.GetInvalidSchemaPointer().StringifyUriFragment(sb); + printf("Invalid schema: %s\n", sb.GetString()); + printf("Invalid keyword: %s\n", validator.GetInvalidSchemaKeyword()); + sb.Clear(); + validator.GetInvalidDocumentPointer().StringifyUriFragment(sb); + printf("Invalid document: %s\n", sb.GetString()); + ADD_FAILURE(); + } + CrtAllocator::Free(json); +} + +TEST(SchemaValidator, ValidateMetaSchema_UTF16) { + typedef GenericDocument > D; + typedef GenericSchemaDocument SD; + typedef GenericSchemaValidator SV; + + CrtAllocator allocator; + char* json = ReadFile("draft-04/schema", allocator); + + D d; + StringStream ss(json); + d.ParseStream<0, UTF8<> >(ss); + ASSERT_FALSE(d.HasParseError()); + SD sd(d); + SV validator(sd); + if (!d.Accept(validator)) { + GenericStringBuffer > sb; + validator.GetInvalidSchemaPointer().StringifyUriFragment(sb); + wprintf(L"Invalid schema: %ls\n", sb.GetString()); + wprintf(L"Invalid keyword: %ls\n", validator.GetInvalidSchemaKeyword()); + sb.Clear(); + validator.GetInvalidDocumentPointer().StringifyUriFragment(sb); + wprintf(L"Invalid document: %ls\n", sb.GetString()); + ADD_FAILURE(); + } + CrtAllocator::Free(json); +} + +template +class RemoteSchemaDocumentProvider : public IGenericRemoteSchemaDocumentProvider { +public: + RemoteSchemaDocumentProvider() : + documentAllocator_(documentBuffer_, sizeof(documentBuffer_)), + schemaAllocator_(schemaBuffer_, sizeof(schemaBuffer_)) + { + const char* filenames[kCount] = { + "jsonschema/remotes/integer.json", + "jsonschema/remotes/subSchemas.json", + "jsonschema/remotes/folder/folderInteger.json", + "draft-04/schema" + }; + + for (size_t i = 0; i < kCount; i++) { + sd_[i] = 0; + + char jsonBuffer[8192]; + MemoryPoolAllocator<> jsonAllocator(jsonBuffer, sizeof(jsonBuffer)); + char* json = ReadFile(filenames[i], jsonAllocator); + if (!json) { + printf("json remote file %s not found", filenames[i]); + ADD_FAILURE(); + } + else { + char stackBuffer[4096]; + MemoryPoolAllocator<> stackAllocator(stackBuffer, sizeof(stackBuffer)); + DocumentType d(&documentAllocator_, 1024, &stackAllocator); + d.Parse(json); + sd_[i] = new SchemaDocumentType(d, 0, &schemaAllocator_); + MemoryPoolAllocator<>::Free(json); + } + }; + } + + ~RemoteSchemaDocumentProvider() { + for (size_t i = 0; i < kCount; i++) + delete sd_[i]; + } + + virtual const SchemaDocumentType* GetRemoteDocument(const char* uri, SizeType length) { + const char* uris[kCount] = { + "http://localhost:1234/integer.json", + "http://localhost:1234/subSchemas.json", + "http://localhost:1234/folder/folderInteger.json", + "http://json-schema.org/draft-04/schema" + }; + + for (size_t i = 0; i < kCount; i++) + if (strncmp(uri, uris[i], length) == 0) + return sd_[i]; + return 0; + } + +private: + typedef GenericDocument, MemoryPoolAllocator<> > DocumentType; + + RemoteSchemaDocumentProvider(const RemoteSchemaDocumentProvider&); + RemoteSchemaDocumentProvider& operator=(const RemoteSchemaDocumentProvider&); + + static const size_t kCount = 4; + SchemaDocumentType* sd_[kCount]; + typename DocumentType::AllocatorType documentAllocator_; + typename SchemaDocumentType::AllocatorType schemaAllocator_; + char documentBuffer_[16384]; + char schemaBuffer_[128 * 1024]; +}; + +TEST(SchemaValidator, TestSuite) { + const char* filenames[] = { + "additionalItems.json", + "additionalProperties.json", + "allOf.json", + "anyOf.json", + "default.json", + "definitions.json", + "dependencies.json", + "enum.json", + "items.json", + "maximum.json", + "maxItems.json", + "maxLength.json", + "maxProperties.json", + "minimum.json", + "minItems.json", + "minLength.json", + "minProperties.json", + "multipleOf.json", + "not.json", + "oneOf.json", + "pattern.json", + "patternProperties.json", + "properties.json", + "ref.json", + "refRemote.json", + "required.json", + "type.json", + "uniqueItems.json" + }; + + const char* onlyRunDescription = 0; + //const char* onlyRunDescription = "a string is a string"; + + unsigned testCount = 0; + unsigned passCount = 0; + + typedef GenericSchemaDocument > SchemaDocumentType; + RemoteSchemaDocumentProvider provider; + + char jsonBuffer[65536]; + char documentBuffer[65536]; + char documentStackBuffer[65536]; + char schemaBuffer[65536]; + char validatorBuffer[65536]; + MemoryPoolAllocator<> jsonAllocator(jsonBuffer, sizeof(jsonBuffer)); + MemoryPoolAllocator<> documentAllocator(documentBuffer, sizeof(documentBuffer)); + MemoryPoolAllocator<> documentStackAllocator(documentStackBuffer, sizeof(documentStackBuffer)); + MemoryPoolAllocator<> schemaAllocator(schemaBuffer, sizeof(schemaBuffer)); + MemoryPoolAllocator<> validatorAllocator(validatorBuffer, sizeof(validatorBuffer)); + + for (size_t i = 0; i < sizeof(filenames) / sizeof(filenames[0]); i++) { + char filename[FILENAME_MAX]; + sprintf(filename, "jsonschema/tests/draft4/%s", filenames[i]); + char* json = ReadFile(filename, jsonAllocator); + if (!json) { + printf("json test suite file %s not found", filename); + ADD_FAILURE(); + } + else { + GenericDocument, MemoryPoolAllocator<>, MemoryPoolAllocator<> > d(&documentAllocator, 1024, &documentStackAllocator); + d.Parse(json); + if (d.HasParseError()) { + printf("json test suite file %s has parse error", filename); + ADD_FAILURE(); + } + else { + for (Value::ConstValueIterator schemaItr = d.Begin(); schemaItr != d.End(); ++schemaItr) { + { + SchemaDocumentType schema((*schemaItr)["schema"], &provider, &schemaAllocator); + GenericSchemaValidator >, MemoryPoolAllocator<> > validator(schema, &validatorAllocator); + const char* description1 = (*schemaItr)["description"].GetString(); + const Value& tests = (*schemaItr)["tests"]; + for (Value::ConstValueIterator testItr = tests.Begin(); testItr != tests.End(); ++testItr) { + const char* description2 = (*testItr)["description"].GetString(); + if (!onlyRunDescription || strcmp(description2, onlyRunDescription) == 0) { + const Value& data = (*testItr)["data"]; + bool expected = (*testItr)["valid"].GetBool(); + testCount++; + validator.Reset(); + bool actual = data.Accept(validator); + if (expected != actual) + printf("Fail: %30s \"%s\" \"%s\"\n", filename, description1, description2); + else + passCount++; + } + } + //printf("%zu %zu %zu\n", documentAllocator.Size(), schemaAllocator.Size(), validatorAllocator.Size()); + } + schemaAllocator.Clear(); + validatorAllocator.Clear(); + } + } + } + documentAllocator.Clear(); + MemoryPoolAllocator<>::Free(json); + jsonAllocator.Clear(); + } + printf("%d / %d passed (%2d%%)\n", passCount, testCount, passCount * 100 / testCount); + // if (passCount != testCount) + // ADD_FAILURE(); +} + +TEST(SchemaValidatingReader, Simple) { + Document sd; + sd.Parse("{ \"type\": \"string\", \"enum\" : [\"red\", \"amber\", \"green\"] }"); + SchemaDocument s(sd); + + Document d; + StringStream ss("\"red\""); + SchemaValidatingReader > reader(ss, s); + d.Populate(reader); + EXPECT_TRUE(reader.GetParseResult()); + EXPECT_TRUE(reader.IsValid()); + EXPECT_TRUE(d.IsString()); + EXPECT_STREQ("red", d.GetString()); +} + +TEST(SchemaValidatingReader, Invalid) { + Document sd; + sd.Parse("{\"type\":\"string\",\"minLength\":2,\"maxLength\":3}"); + SchemaDocument s(sd); + + Document d; + StringStream ss("\"ABCD\""); + SchemaValidatingReader > reader(ss, s); + d.Populate(reader); + EXPECT_FALSE(reader.GetParseResult()); + EXPECT_FALSE(reader.IsValid()); + EXPECT_EQ(kParseErrorTermination, reader.GetParseResult().Code()); + EXPECT_STREQ("maxLength", reader.GetInvalidSchemaKeyword()); + EXPECT_TRUE(reader.GetInvalidSchemaPointer() == SchemaDocument::PointerType("")); + EXPECT_TRUE(reader.GetInvalidDocumentPointer() == SchemaDocument::PointerType("")); + EXPECT_TRUE(d.IsNull()); +} + +TEST(SchemaValidatingWriter, Simple) { + Document sd; + sd.Parse("{\"type\":\"string\",\"minLength\":2,\"maxLength\":3}"); + SchemaDocument s(sd); + + Document d; + StringBuffer sb; + Writer writer(sb); + GenericSchemaValidator > validator(s, writer); + + d.Parse("\"red\""); + EXPECT_TRUE(d.Accept(validator)); + EXPECT_TRUE(validator.IsValid()); + EXPECT_STREQ("\"red\"", sb.GetString()); + + sb.Clear(); + validator.Reset(); + d.Parse("\"ABCD\""); + EXPECT_FALSE(d.Accept(validator)); + EXPECT_FALSE(validator.IsValid()); + EXPECT_TRUE(validator.GetInvalidSchemaPointer() == SchemaDocument::PointerType("")); + EXPECT_TRUE(validator.GetInvalidDocumentPointer() == SchemaDocument::PointerType("")); +} + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif diff --git a/test/unittest/strfunctest.cpp b/test/unittest/strfunctest.cpp new file mode 100644 index 0000000..186755c --- /dev/null +++ b/test/unittest/strfunctest.cpp @@ -0,0 +1,31 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "unittest.h" + +#include "rapidjson/internal/strfunc.h" + +using namespace rapidjson; +using namespace rapidjson::internal; + +TEST(StrFunc, CountStringCodePoint) { + SizeType count; + EXPECT_TRUE(CountStringCodePoint >("", 0, &count)); + EXPECT_EQ(0u, count); + EXPECT_TRUE(CountStringCodePoint >("Hello", 5, &count)); + EXPECT_EQ(5u, count); + EXPECT_TRUE(CountStringCodePoint >("\xC2\xA2\xE2\x82\xAC\xF0\x9D\x84\x9E", 9, &count)); // cents euro G-clef + EXPECT_EQ(3u, count); + EXPECT_FALSE(CountStringCodePoint >("\xC2\xA2\xE2\x82\xAC\xF0\x9D\x84\x9E\x80", 10, &count)); +}