diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000000..49cd38d87d --- /dev/null +++ b/.clang-format @@ -0,0 +1,17 @@ +--- +BasedOnStyle: LLVM +BraceWrapping: + AfterClass: true + AfterControlStatement: false + AfterEnum: true + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + BeforeCatch: false + BeforeElse: false + IndentBraces: false +BreakBeforeBraces: Custom +IndentWidth: 4 +AllowShortFunctionsOnASingleLine: Empty diff --git a/.gitignore b/.gitignore index c4a612d3d3..67a1212c94 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ doc/html .idea cmake-build-* CMakeFiles/ +.vs diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 6d2f8b864a..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,74 +0,0 @@ -language: cpp -sudo: required -dist: trusty - -matrix: - include: - - os: linux - compiler: gcc - addons: - apt: - sources: ['ubuntu-toolchain-r-test'] - packages: ['g++-5', 'qtbase5-dev', 'libboost1.55-dev', 'libssl-dev', 'libcurl4-openssl-dev'] - env: - - CXX_COMPILER=g++-5 - - C_COMPILER=gcc-5 - - - os: linux - compiler: clang - addons: - apt: - sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.6'] - packages: ['clang-3.6', 'qtbase5-dev', 'libboost1.55-dev', 'libssl-dev', 'libcurl4-openssl-dev'] - env: - - CXX_COMPILER=clang++-3.6 - - C_COMPILER=clang-3.6 - - - os: linux - compiler: clang - addons: - apt: - sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.7'] - packages: ['clang-3.7', 'qtbase5-dev', 'libboost1.55-dev', 'libssl-dev', 'libcurl4-openssl-dev'] - env: - - CXX_COMPILER=clang++-3.7 - - C_COMPILER=clang-3.7 - - - os: linux - compiler: clang - addons: - apt: - sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.8'] - packages: ['clang-3.8', 'libc++-dev', 'libc++abi-dev', 'qtbase5-dev', 'libboost1.55-dev', 'libssl-dev', 'libcurl4-openssl-dev'] - env: - - CXX_COMPILER=clang++-3.8 - - C_COMPILER=clang-3.8 - - CMAKE_CXX_FLAGS="-stdlib=libc++" - - CMAKE_EXE_LINKER_FLAGS="-lc++" - -before_install: - - pushd ~ - - wget https://github.com/pocoproject/poco/archive/poco-1.7.8p2-release.tar.gz - - tar -xf poco-1.7.8p2-release.tar.gz - - cd poco-poco-1.7.8p2-release - - mkdir cmake_build - - cd cmake_build - - export POCO_OPTS="-DENABLE_CRYPTO=off -DENABLE_DATA=off -DENABLE_DATA_MYSQL=off -DENABLE_DATA_ODBC=off -DENABLE_DATA_SQLITE=off" - - export POCO_OPTS="$POCO_OPTS -DENABLE_MONGODB=off -DENABLE_NET=off -DENABLE_NETSSL=off -DENABLE_PAGECOMPILER=off" - - export POCO_OPTS="$POCO_OPTS -DENABLE_PAGECOMPILER_FILE2PAGE=off -DENABLE_PDF=off -DENABLE_UTIL=off -DENABLE_XML=off -DENABLE_ZIP=off" - - cmake -D CMAKE_CXX_COMPILER=`which ${CXX_COMPILER}` -DCMAKE_CXX_FLAGS="$CMAKE_CXX_FLAGS" -DCMAKE_EXE_LINKER_FLAGS="$CMAKE_EXE_LINKER_FLAGS" $POCO_OPTS .. - - sudo make -j 4 install - - wget -O curlpp-0.8.1.tar.gz https://github.com/jpbarrette/curlpp/archive/v0.8.1.tar.gz - - tar -xf curlpp-0.8.1.tar.gz - - cd curlpp-0.8.1 - - mkdir cmake_build - - cd cmake_build - - cmake -D CMAKE_CXX_COMPILER=`which ${CXX_COMPILER}` -DCMAKE_CXX_FLAGS="$CMAKE_CXX_FLAGS" -DCMAKE_EXE_LINKER_FLAGS="$CMAKE_EXE_LINKER_FLAGS" .. - - sudo make -j 4 install - - popd - -script: - - mkdir build && cd build - - cmake -D CMAKE_CXX_FLAGS="$CMAKE_CXX_FLAGS" -D CMAKE_EXE_LINKER_FLAGS="$CMAKE_EXE_LINKER_FLAGS" -D CMAKE_CXX_COMPILER="$CXX_COMPILER" -D CMAKE_C_COMPILER="$C_COMPILER" .. - - VERBOSE=1 make - - ./test_suite diff --git a/Authors b/Authors index 82432bd620..6a4887abc0 100644 --- a/Authors +++ b/Authors @@ -38,4 +38,7 @@ Jordan Bayles (jophba), jophba@chromium.org JsonCpp owner Matt Young (matty0ung), - Adapter for Boost.JSON parser library + Adapter for Boost.JSON parser library + +Pras Velagapudi (psigen), + Adapter for yaml-cpp parser library diff --git a/CMakeLists.txt b/CMakeLists.txt index e2fb0aca04..9b63b680d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,10 @@ -cmake_minimum_required(VERSION 3.1) +cmake_minimum_required(VERSION 3.1.2) project(valijson) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake") -option(valijson_INSTALL_HEADERS "Install valijson headers." FALSE) option(valijson_BUILD_EXAMPLES "Build valijson examples." FALSE) -option(valijson_BUILD_TESTS "Build valijson test suite." TRUE) +option(valijson_BUILD_TESTS "Build valijson test suite." FALSE) option(valijson_EXCLUDE_BOOST "Exclude Boost when building test suite." FALSE) option(valijson_USE_EXCEPTIONS "Use exceptions in valijson and included libs." TRUE) @@ -35,21 +34,32 @@ target_include_directories(valijson INTERFACE $ $) -if(valijson_INSTALL_HEADERS) - install(DIRECTORY include/ DESTINATION include) +if(valijson_USE_EXCEPTIONS) + target_compile_definitions(valijson INTERFACE -DVALIJSON_USE_EXCEPTIONS=1) endif() +install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + +install(TARGETS valijson + EXPORT valijsonConfig + DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +install(EXPORT valijsonConfig + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/valijson" +) + if(NOT valijson_BUILD_TESTS AND NOT valijson_BUILD_EXAMPLES) return() endif() if(valijson_USE_EXCEPTIONS) - add_definitions(-DVALIJSON_USE_EXCEPTIONS=1) + add_compile_definitions(VALIJSON_USE_EXCEPTIONS=1) else() - add_definitions(-D_HAS_EXCEPTIONS=0) - add_definitions(-DBOOST_NO_EXCEPTIONS) - add_definitions(-DJSON_USE_EXCEPTION=0) - add_definitions(-DVALIJSON_USE_EXCEPTIONS=0) + add_compile_definitions(_HAS_EXCEPTIONS=0) + add_compile_definitions(BOOST_NO_EXCEPTIONS) + add_compile_definitions(JSON_USE_EXCEPTION=0) + add_compile_definitions(VALIJSON_USE_EXCEPTIONS=0) endif() find_package(Poco OPTIONAL_COMPONENTS JSON) @@ -57,13 +67,13 @@ find_package(Qt5Core) # jsoncpp library add_library(jsoncpp - thirdparty/jsoncpp-1.9.4/src/lib_json/json_reader.cpp - thirdparty/jsoncpp-1.9.4/src/lib_json/json_value.cpp - thirdparty/jsoncpp-1.9.4/src/lib_json/json_writer.cpp + thirdparty/jsoncpp-1.9.5/src/lib_json/json_reader.cpp + thirdparty/jsoncpp-1.9.5/src/lib_json/json_value.cpp + thirdparty/jsoncpp-1.9.5/src/lib_json/json_writer.cpp ) -target_include_directories(jsoncpp SYSTEM PRIVATE thirdparty/jsoncpp-1.9.4/include) -set_target_properties(jsoncpp PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/thirdparty/jsoncpp-1.9.4) +target_include_directories(jsoncpp SYSTEM PRIVATE thirdparty/jsoncpp-1.9.5/include) +set_target_properties(jsoncpp PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/thirdparty/jsoncpp-1.9.5) add_library(json11 thirdparty/json11-ec4e452/json11.cpp @@ -72,15 +82,23 @@ add_library(json11 target_include_directories(json11 SYSTEM PRIVATE thirdparty/json11-ec4e452) set_target_properties(json11 PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/thirdparty/json11-ec4e452) +# yaml-cpp library +file(GLOB yamlcpp_SOURCES "thirdparty/yaml-cpp-0.7.0/src/*.cpp") +add_library(yamlcpp ${yamlcpp_SOURCES}) + +target_include_directories(yamlcpp SYSTEM PRIVATE thirdparty/yamlcpp-0.7.0/include) +set_target_properties(yamlcpp PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/thirdparty/yamlcpp-0.7.0) + # Not all of these are required for examples build it doesn't hurt to include them include_directories(include SYSTEM thirdparty/gtest-1.11.0/include thirdparty/json11-ec4e452 - thirdparty/jsoncpp-1.9.4/include + thirdparty/jsoncpp-1.9.5/include thirdparty/rapidjson-48fbd8c/include thirdparty/picojson-1.3.0 thirdparty/nlohmann-json-3.1.2 - ) + thirdparty/yaml-cpp-0.7.0/include +) if(valijson_BUILD_TESTS) if(NOT valijson_EXCLUDE_BOOST) @@ -95,8 +113,8 @@ if(valijson_BUILD_TESTS) set(TEST_SOURCES tests/test_adapter_comparison.cpp - tests/test_fetch_urn_document_callback.cpp tests/test_fetch_absolute_uri_document_callback.cpp + tests/test_fetch_urn_document_callback.cpp tests/test_json_pointer.cpp tests/test_json11_adapter.cpp tests/test_jsoncpp_adapter.cpp @@ -106,14 +124,30 @@ if(valijson_BUILD_TESTS) tests/test_poly_constraint.cpp tests/test_validation_errors.cpp tests/test_validator.cpp + tests/test_yaml_cpp_adapter.cpp ) - set(TEST_LIBS gtest gtest_main jsoncpp json11) + set(TEST_LIBS gtest gtest_main jsoncpp json11 yamlcpp) if(Boost_FOUND) include_directories(${Boost_INCLUDE_DIRS}) - list(APPEND TEST_SOURCES tests/test_boost_json_adapter.cpp) + + # Property Trees have been in Boost since 1.41.0, so we just assume they're present list(APPEND TEST_SOURCES tests/test_property_tree_adapter.cpp) + + # Boost.JSON was introduced in Boost 1.75.0, so we should check for its presence before + # including the unit tests for boost_json_adapter + include(CheckIncludeFileCXX) + set (CMAKE_REQUIRED_INCLUDES ${Boost_INCLUDE_DIRS}) + check_include_file_cxx("boost/json.hpp" BOOST_JSON_HPP_FOUND) + if(${BOOST_JSON_HPP_FOUND}) + list(APPEND TEST_SOURCES tests/test_boost_json_adapter.cpp) + else() + message(WARNING + "boost/json.hpp not found; tests for boost_json_adapter will not be built. " + "If you have recently upgraded Boost to 1.75.0 or later, you may need to clear " + "your CMake cache for the header to be found.") + endif() endif() if(Poco_FOUND) @@ -146,15 +180,22 @@ if(valijson_BUILD_TESTS) set_target_properties(test_suite PROPERTIES COMPILE_FLAGS " -pedantic -Werror -Wshadow -Wunused") endif() + if (MSVC) + target_compile_options(test_suite PRIVATE "/bigobj") + endif() + # Definition for using picojson set_target_properties(test_suite PROPERTIES COMPILE_DEFINITIONS "PICOJSON_USE_INT64") if(Boost_FOUND) - add_definitions(-DBOOST_ALL_DYN_LINK) + add_compile_definitions(BOOST_ALL_DYN_LINK) set(Boost_USE_STATIC_LIBS OFF) set(Boost_USE_MULTITHREADED ON) set(Boost_USE_STATIC_RUNTIME OFF) - target_compile_definitions(test_suite PRIVATE "VALIJSON_BUILD_BOOST_ADAPTERS") + target_compile_definitions(test_suite PRIVATE "VALIJSON_BUILD_BOOST_PROPERTY_TREE_ADAPTER") + if(${BOOST_JSON_HPP_FOUND}) + target_compile_definitions(test_suite PRIVATE "VALIJSON_BUILD_BOOST_JSON_ADAPTER") + endif() endif() if(Poco_FOUND) @@ -200,6 +241,10 @@ if(valijson_BUILD_EXAMPLES) examples/remote_resolution_local_file.cpp ) + add_executable(valijson_nlohmann_bundled_test + examples/valijson_nlohmann_bundled_test.cpp + ) + if(curlpp_FOUND) include_directories(${curlpp_INCLUDE_DIR}) diff --git a/README.md b/README.md index 60bebb79c5..229760bc65 100644 --- a/README.md +++ b/README.md @@ -1,131 +1,156 @@ -# Valijson [![Build Status](https://travis-ci.org/tristanpenman/valijson.svg?branch=master)](https://travis-ci.org/tristanpenman/valijson) # +# Valijson -## Overview ## - -Valijson is a header-only [JSON Schema](http://json-schema.org/) Validation library for C++11. +Valijson is a header-only [JSON Schema](http://json-schema.org/) validation library for C++11. Valijson provides a simple validation API that allows you to load JSON Schemas, and validate documents loaded by one of several supported parser libraries. -## Project Goals ## +## Project Goals The goal of this project is to support validation of all constraints available in JSON Schema v7, while being competitive with the performance of a hand-written schema validator. -## Usage ## +## Usage The following code snippets show how you might implement a simple validator using RapidJson as the underlying JSON Parser. Include the necessary headers: ```cpp - #include - #include - #include - #include - #include +#include +#include +#include +#include +#include ``` These are the classes that we'll be using: ```cpp - using valijson::Schema; - using valijson::SchemaParser; - using valijson::Validator; - using valijson::adapters::RapidJsonAdapter; +using valijson::Schema; +using valijson::SchemaParser; +using valijson::Validator; +using valijson::adapters::RapidJsonAdapter; ``` We are going to use RapidJSON to load the schema and the target document: ```cpp - // Load JSON document using RapidJSON with Valijson helper function - rapidjson::Document mySchemaDoc; - if (!valijson::utils::loadDocument("mySchema.json", mySchemaDoc)) { - throw std::runtime_error("Failed to load schema document"); - } - - // Parse JSON schema content using valijson - Schema mySchema; - SchemaParser parser; - RapidJsonAdapter mySchemaAdapter(mySchemaDoc); - parser.populateSchema(mySchemaAdapter, mySchema); +// Load JSON document using RapidJSON with Valijson helper function +rapidjson::Document mySchemaDoc; +if (!valijson::utils::loadDocument("mySchema.json", mySchemaDoc)) { + throw std::runtime_error("Failed to load schema document"); +} + +// Parse JSON schema content using valijson +Schema mySchema; +SchemaParser parser; +RapidJsonAdapter mySchemaAdapter(mySchemaDoc); +parser.populateSchema(mySchemaAdapter, mySchema); ``` Load a document to validate: ```cpp - rapidjson::Document myTargetDoc; - if (!valijson::utils::loadDocument("myTarget.json", myTargetDoc)) { - throw std::runtime_error("Failed to load target document"); - } +rapidjson::Document myTargetDoc; +if (!valijson::utils::loadDocument("myTarget.json", myTargetDoc)) { + throw std::runtime_error("Failed to load target document"); +} ``` Validate a document: ```cpp - Validator validator; - RapidJsonAdapter myTargetAdapter(myTargetDoc); - if (!validator.validate(mySchema, myTargetAdapter, NULL)) { - throw std::runtime_error("Validation failed."); - } +Validator validator; +RapidJsonAdapter myTargetAdapter(myTargetDoc); +if (!validator.validate(mySchema, myTargetAdapter, NULL)) { + throw std::runtime_error("Validation failed."); +} ``` Note that Valijson's `SchemaParser` and `Validator` classes expect you to pass in a `RapidJsonAdapter` rather than a `rapidjson::Document`. This is due to the fact that `SchemaParser` and `Validator` are template classes that can be used with any of the JSON parsers supported by Valijson. -## Memory Management ## +### Exceptions + +By default, Valijson classes will not throw exceptions (e.g. when failing to parse a schema). To enable exceptions for these cases, `VALIJSON_USE_EXCEPTIONS` must be defined. +However note that `VALIJSON_USE_EXCEPTIONS` is defined as interface compile definition of the cmake target, and the definition populates all the targets linking Valijson with cmake. + +### Strong vs Weak Types + +Valijson has a notion of strong and weak typing. By default, strong typing is used. For example, the following will create a validator that uses strong typing: + +```cpp +Validator validator; +``` + +This validator will not attempt to cast between types to satisfy a schema. So the string `"23"` will not be parsed as a number. + +Alternatively, weak typing can be used: + +```cpp +Validator validator(Validator::kWeakTypes); +``` + +This will create a validator that will attempt to cast values to satisfy a schema. The original motivation for this was to support the Boost Property Tree library, which can parse JSON, but stores values as strings. + +## Memory Management Valijson has been designed to safely manage, and eventually free, the memory that is allocated while parsing a schema or validating a document. When working with an externally loaded schema (i.e. one that is populated using the `SchemaParser` class) you can rely on RAII semantics. Things get more interesting when you build a schema using custom code, as illustrated in the following snippet. This code demonstrates how you would create a schema to verify that the value of a 'description' property (if present) is always a string: ```cpp - { - // Root schema object that manages memory allocated for - // constraints or sub-schemas - Schema schema; +{ + // Root schema object that manages memory allocated for + // constraints or sub-schemas + Schema schema; - // Allocating memory for a sub-schema returns a const pointer - // which allows inspection but not mutation. This memory will be - // freed only when the root schema goes out of scope - const Subschema *subschema = schema.createSubschema(); + // Allocating memory for a sub-schema returns a const pointer + // which allows inspection but not mutation. This memory will be + // freed only when the root schema goes out of scope + const Subschema *subschema = schema.createSubschema(); - { // Limited scope, for example purposes + { // Limited scope, for example purposes - // Construct a constraint on the stack - TypeConstraint typeConstraint; - typeConstraint.addNamedType(TypeConstraint::kString); + // Construct a constraint on the stack + TypeConstraint typeConstraint; + typeConstraint.addNamedType(TypeConstraint::kString); - // Constraints are added to a sub-schema via the root schema, - // which will make a copy of the constraint - schema.addConstraintToSubschema(typeConstraint, subschema); + // Constraints are added to a sub-schema via the root schema, + // which will make a copy of the constraint + schema.addConstraintToSubschema(typeConstraint, subschema); - // Constraint on the stack goes out of scope, but the copy - // held by the root schema continues to exist - } + // Constraint on the stack goes out of scope, but the copy + // held by the root schema continues to exist + } - // Include subschema in properties constraint - PropertiesConstraint propertiesConstraint; - propertiesConstraint.addPropertySubschema("description", subschema); + // Include subschema in properties constraint + PropertiesConstraint propertiesConstraint; + propertiesConstraint.addPropertySubschema("description", subschema); - // Add the properties constraint - schema.addConstraint(propertiesConstraint); + // Add the properties constraint + schema.addConstraint(propertiesConstraint); - // Root schema goes out of scope and all allocated memory is freed - } + // Root schema goes out of scope and all allocated memory is freed +} ``` -## JSON References ## +## JSON References The library includes support for local JSON References. Remote JSON References are supported only when the appropriate callback functions are provided. Valijson's JSON Reference implementation requires that two callback functions are required. The first is expected to return a pointer to a newly fetched document. Valijson takes ownership of this pointer. The second callback function is used to release ownership of that pointer back to the application. Typically, this would immediately free the memory that was allocated for the document. -## Test Suite ## +## Test Suite Valijson's' test suite currently contains several hand-crafted tests and uses the standard [JSON Schema Test Suite](https://github.com/json-schema/JSON-Schema-Test-Suite) to test support for parts of the JSON Schema feature set that have been implemented. -### cmake ### +### cmake The examples and test suite can be built using cmake: ```bash - # Build examples and test suite - mkdir build - cd build - cmake .. - make - - # Run test suite (from build directory) - ./test_suite +# Build examples and test suite +mkdir build +cd build +cmake .. -Dvalijson_BUILD_TESTS=ON -Dvalijson_BUILD_EXAMPLES=ON +make + +# Run test suite (from build directory) +./test_suite ``` -#### How to add this library to your cmake target #### +## How to add this library to your cmake target + +Valijson can be integrated either as git submodule or with find_package(). + +### Valijson as git submodule Download this repository into your project @@ -143,27 +168,55 @@ git submodule add https://github.com/tristanpenman/valijson third-party/valijson Before the target add the module subdirectory in your CMakeLists.txt ```cmake -set(valijson_BUILD_TESTS OFF) +set(valijson_BUILD_TESTS OFF CACHE BOOL "don't build valijson tests") add_subdirectory(third-party/valijson) add_executable(your-executable ...) target_link_libraries(your-executable ValiJSON::valijson) ``` +### Install Valijson and import it + +It is possible to install headers by running cmake's install command from the build tree. Once Valijson is installed, use it from other CMake projects using `find_package(Valijson)` in your CMakeLists.txt. +```bash +# Install Valijson +git clone --depth=1 git@github.com:tristanpenman/valijson.git +cd valijson +mkdir build +cd build +cmake .. +cmake --install . +``` +```cmake +# Import installed valijson and link it to your executable +find_package(valijson REQUIRED) +add_executable(executable main.cpp) +target_link_libraries(executable valijson) +``` + +## Bundled Headers -### Xcode ### +An alternative way to include Valijson in your project is to generate a bundled header file, containing support for just one parser/adapter. -An Xcode project has also been provided, in the 'xcode' directory. Note that in order to run the test suite, you may need to configure the working directory for the 'test\_suite' scheme. It is recommended that you use the 'xcode' directory as the working directory. +You can generate a header file using the `bundle.sh` script: -The Xcode project has been configured so that /usr/local/include is in the include path, and /usr/local/lib is in the library path. These are the locations that homebrew installed Boost on my test system. + ./bundle.sh nlohmann_json > valijson_nlohmann_bundled.hpp -## Examples ## +This can then be used in your project with a single `#include`: + + #include "valijson_nlohmann_bundled.hpp" + +An example can be found in [examples/valijson_nlohmann_bundled_test.cpp](examples/valijson_nlohmann_bundled_test.cpp). + +Note: the bundled version of Valijson always embeds a compatibility header in place of `std::optional`. + +## Examples Building the Valijson Test Suite, using the instructions above, will also compile two example applications: `custom_schema` and `external_schema`. `custom_schema` shows how you can hard-code a schema definition into an application, while `external_schema` builds on the example code above to show you how to validate and document and report on any validation errors. -## JSON Schema Support ## +## JSON Schema Support Valijson supports most of the constraints defined in [Draft 7](https://json-schema.org/draft-07/json-schema-release-notes.html) @@ -178,30 +231,38 @@ Support for JSON References is in development. It is mostly working, however som An example application based on Qt is also included under [inspector](./inspector). It can be used to experiment with JSON Schemas and target documents. JSON Inspector is a self-contained CMake project, so it must be built separately: ```bash - cd inspector - mkdir build - cd build - cmake .. - make +cd inspector +mkdir build +cd build +cmake .. +make ``` Schemas and target documents can be loaded from file or entered manually. Content is parsed dynamically, so you get rapid feedback. Here is a screenshot of JSON Inspector in action: -![JSON Inspector in action](./doc/inspector/screenshot.png) +![JSON Inspector in action](./doc/screenshots/inspector.png) + +## Live Demo -## Documentation ## +A web-based demo can be found [here](https://letmaik.github.io/valijson-wasm), courtesy of [Maik Riechert](https://github.com/letmaik). + +This demo uses Emscripten to compile Valijson and Nlohmann JSON (JSON for Modern C++) to WebAssembly. The source code can be found [here](https://github.com/letmaik/valijson-wasm) and is available under the MIT license. + +![WebAssembly Demo](doc/screenshots/wasm.png) + +## Documentation Doxygen documentation can be built by running 'doxygen' from the project root directory. Generated documentation will be placed in 'doc/html'. Other relevant documentation such as schemas and specifications have been included in the 'doc' directory. -## Dependencies ## +## Dependencies -Valijson requires a compiler with C++11 support. +Valijson requires a compiler with full C++11 support. Please note that versions of GCC prior to 4.9.0 had incomplete `` support, so `pattern` constraints may not work. If using GCC, it is recommended that you use GCC 5.0 or later. When building the test suite, Boost 1.54, Qt 5 and Poco are optional dependencies. -## Supported Parsers ## +## Supported Parsers Valijson supports JSON documents loaded using various JSON parser libraries. It has been tested against the following versions of these libraries: @@ -217,30 +278,32 @@ Valijson supports JSON documents loaded using various JSON parser libraries. It Other versions of these libraries may work, but have not been tested. In particular, versions of jsoncpp going back to 0.5.0 should also work correctly. -## Package Managers ## +When compiling with older versions of Boost (< 1.76.0) you may see compiler warnings from the `boost::property_tree` headers. This has been addressed in version 1.76.0 of Boost. + +## Package Managers If you are using [vcpkg](https://github.com/Microsoft/vcpkg) on your project for external dependencies, then you can use the [valijson](https://github.com/microsoft/vcpkg/tree/master/ports/valijson) package. Please see the vcpkg project for any issues regarding the packaging. You can also use [conan](https://conan.io/) as a package manager to handle [valijson](https://conan.io/center/valijson/0.3/) package. Please see the [conan recipe](https://github.com/conan-io/conan-center-index/tree/master/recipes/valijson) for any issues regarding the packaging via conan. -## Test Suite Requirements ## +## Test Suite Requirements Supported versions of these libraries have been included in the 'thirdparty' directory so as to support Valijson's examples and test suite. The exceptions to this are boost, Poco and Qt5, which due to their size must be installed to a location that CMake can find. -## Known Issues ## +## Known Issues When using PicoJSON, it may be necessary to include the `picojson.h` before other headers to ensure that the appropriate macros have been enabled. When building Valijson using CMake on macOS, with Qt 5 installed via Homebrew, you may need to set `CMAKE_PREFIX_PATH` so that CMake can find your Qt installation, e.g: ```bash - mkdir build - cd build - cmake .. -DCMAKE_PREFIX_PATH=$(brew --prefix qt5) - make +mkdir build +cd build +cmake .. -DCMAKE_PREFIX_PATH=$(brew --prefix qt5) +make ``` -## License ## +## License Valijson is licensed under the Simplified BSD License. diff --git a/bundle.sh b/bundle.sh new file mode 100755 index 0000000000..bd9bd489ae --- /dev/null +++ b/bundle.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash + +# +# Exit codes: +# +# 0 - success +# 64 - usage +# 65 - invalid adapter +# + +set -euo pipefail + +adapter_path=include/valijson/adapters +utils_path=include/valijson/utils + +# find all available adapters +pushd "${adapter_path}" > /dev/null +adapters=($(ls *.hpp)) +popd > /dev/null + +# remove _adapter.hpp suffix +adapters=("${adapters[@]/_adapter.hpp/}") + +usage() { + echo 'Generates a single header file for a particular Valijson adapter' + echo + echo 'This makes it easier to embed Valijson in smaller projects, where integrating a' + echo 'third-party dependency is inconvenient or undesirable.' + echo + echo 'Output is written to STDOUT.' + echo + echo 'Usage:' + echo + echo ' ./bundle.sh ' + echo + echo 'Example usage:' + echo + echo ' ./bundle.sh nlohmann_json > valijson_nlohmann_bundled.hpp' + echo + echo 'Available adapters:' + echo + for adapter in "${adapters[@]}"; do + echo " - ${adapter}" + done + echo + exit 64 +} + +if [ $# -ne 1 ]; then + usage +fi + +adapter_header= +for adapter in "${adapters[@]}"; do + if [ "${adapter}" == "$1" ]; then + adapter_header="${adapter_path}/${adapter}_adapter.hpp" + break + fi +done + +if [ -z "${adapter_header}" ]; then + echo "Error: Adapter name is not valid." + exit 65 +fi + +common_headers=( + include/valijson/exceptions.hpp + include/compat/optional.hpp + include/valijson/internal/optional_bundled.hpp + include/valijson/internal/adapter.hpp + include/valijson/internal/basic_adapter.hpp + include/valijson/internal/custom_allocator.hpp + include/valijson/internal/debug.hpp + include/valijson/internal/frozen_value.hpp + include/valijson/internal/json_pointer.hpp + include/valijson/internal/json_reference.hpp + include/valijson/internal/uri.hpp + include/valijson/utils/file_utils.hpp + include/valijson/utils/utf8_utils.hpp + include/valijson/constraints/constraint.hpp + include/valijson/subschema.hpp + include/valijson/schema.hpp + include/valijson/constraints/constraint_visitor.hpp + include/valijson/constraints/basic_constraint.hpp + include/valijson/constraints/concrete_constraints.hpp + include/valijson/constraint_builder.hpp + include/valijson/schema_parser.hpp + include/valijson/adapters/std_string_adapter.hpp + include/valijson/validation_results.hpp + include/valijson/validation_visitor.hpp + include/valijson/validator.hpp) + +# remove internal #includes +grep --no-filename -v "include +#include +#include +#include +#include +#include +#include + +using namespace std::string_literals; + +namespace json = boost::json; + +const auto schemaJson = R"({ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Product", + "description": "A product from Acme's catalog", + "type": "object", + "properties": { + "id": { + "description": "The unique identifier for a product", + "type": "integer" + }, + "name": { + "description": "Name of the product", + "type": "string" + }, + "price": { + "type": "number", + "minimum": 0 + }, + "tags": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1, + "uniqueItems": true + } + }, + "required": ["id", "name", "price" ] +})"s; + +const auto targetJson = R"({ + "id": 123, + "name": "Test" +})"s; + +int main() +{ + json::error_code ec; + auto schemaDoc = json::parse(schemaJson, ec); + if (ec) { + std::cerr << "Error parsing schema json: " << ec.message() << std::endl; + return 1; + } + + auto obj = schemaDoc.as_object(); + auto iter = obj.find("$schema"); + if (iter == obj.cend()) { + std::cerr << "Error finding key $schema" << std::endl; + return 2; + } + + iter = obj.find("$ref"); + if (iter != obj.cend()) { + std::cerr << "Invalid iterator for non-existent key $ref" << std::endl; + return 3; + } + + valijson::Schema schema; + valijson::SchemaParser schemaParser; + + // Won't compile because the necessary constructor has been deleted + // valijson::adapters::BoostJsonAdapter schemaAdapter(obj); + + valijson::adapters::BoostJsonAdapter schemaAdapter(schemaDoc); + std::cerr << "Populating schema..." << std::endl; + schemaParser.populateSchema(schemaAdapter, schema); + + auto targetDoc = json::parse(targetJson, ec); + if (ec) { + std::cerr << "Error parsing target json: " << ec.message() << std::endl; + return 1; + } + + valijson::Validator validator; + valijson::ValidationResults results; + valijson::adapters::BoostJsonAdapter targetAdapter(targetDoc); + if (validator.validate(schema, targetAdapter, &results)) { + std::cerr << "Validation succeeded." << std::endl; + return 0; + } + + std::cerr << "Validation failed." << std::endl; + valijson::ValidationResults::Error error; + unsigned int errorNum = 1; + while (results.popError(error)) { + std::cerr << "Error #" << errorNum << std::endl; + std::cerr << " "; + for (const std::string &contextElement : error.context) { + std::cerr << contextElement << " "; + } + std::cerr << std::endl; + std::cerr << " - " << error.description << std::endl; + ++errorNum; + } + + return 1; +} \ No newline at end of file diff --git a/examples/external_schema.cpp b/examples/external_schema.cpp index d18a529021..82d33da442 100644 --- a/examples/external_schema.cpp +++ b/examples/external_schema.cpp @@ -57,7 +57,7 @@ int main(int argc, char *argv[]) } // Perform validation - Validator validator(Validator::kWeakTypes); + Validator validator(Validator::kStrongTypes); ValidationResults results; RapidJsonAdapter targetDocumentAdapter(targetDocument); if (!validator.validate(schema, targetDocumentAdapter, &results)) { diff --git a/examples/valijson_nlohmann_bundled.hpp b/examples/valijson_nlohmann_bundled.hpp new file mode 100644 index 0000000000..05ae5c06e4 --- /dev/null +++ b/examples/valijson_nlohmann_bundled.hpp @@ -0,0 +1,10584 @@ +#pragma once + +#include +#include + +namespace valijson { +#if defined(_MSC_VER) && _MSC_VER == 1800 +#define VALIJSON_NORETURN __declspec(noreturn) +#else +#define VALIJSON_NORETURN [[noreturn]] +#endif + +#if VALIJSON_USE_EXCEPTIONS +#include + +VALIJSON_NORETURN inline void throwRuntimeError(const std::string& msg) { + throw std::runtime_error(msg); +} + +VALIJSON_NORETURN inline void throwLogicError(const std::string& msg) { + throw std::logic_error(msg); +} +#else +VALIJSON_NORETURN inline void throwRuntimeError(const std::string& msg) { + std::cerr << msg << std::endl; + abort(); +} +VALIJSON_NORETURN inline void throwLogicError(const std::string& msg) { + std::cerr << msg << std::endl; + abort(); +} + +#endif + +VALIJSON_NORETURN inline void throwNotSupported() { + throwRuntimeError("Not supported"); +} + +} // namespace valijson +// Copyright (C) 2011 - 2012 Andrzej Krzemienski. +// +// Use, modification, and distribution is subject to the Boost Software +// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// The idea and interface is based on Boost.Optional library +// authored by Fernando Luis Cacciola Carballal + +# ifndef ___OPTIONAL_HPP___ +# define ___OPTIONAL_HPP___ + +# include +# include +# include +# include +# include +# include +# include + +# define TR2_OPTIONAL_REQUIRES(...) typename enable_if<__VA_ARGS__::value, bool>::type = false + +# if defined __GNUC__ // NOTE: GNUC is also defined for Clang +# if (__GNUC__ == 4) && (__GNUC_MINOR__ >= 8) +# define TR2_OPTIONAL_GCC_4_8_AND_HIGHER___ +# elif (__GNUC__ > 4) +# define TR2_OPTIONAL_GCC_4_8_AND_HIGHER___ +# endif +# +# if (__GNUC__ == 4) && (__GNUC_MINOR__ >= 7) +# define TR2_OPTIONAL_GCC_4_7_AND_HIGHER___ +# elif (__GNUC__ > 4) +# define TR2_OPTIONAL_GCC_4_7_AND_HIGHER___ +# endif +# +# if (__GNUC__ == 4) && (__GNUC_MINOR__ == 8) && (__GNUC_PATCHLEVEL__ >= 1) +# define TR2_OPTIONAL_GCC_4_8_1_AND_HIGHER___ +# elif (__GNUC__ == 4) && (__GNUC_MINOR__ >= 9) +# define TR2_OPTIONAL_GCC_4_8_1_AND_HIGHER___ +# elif (__GNUC__ > 4) +# define TR2_OPTIONAL_GCC_4_8_1_AND_HIGHER___ +# endif +# endif +# +# if defined __clang_major__ +# if (__clang_major__ == 3 && __clang_minor__ >= 5) +# define TR2_OPTIONAL_CLANG_3_5_AND_HIGHTER_ +# elif (__clang_major__ > 3) +# define TR2_OPTIONAL_CLANG_3_5_AND_HIGHTER_ +# endif +# if defined TR2_OPTIONAL_CLANG_3_5_AND_HIGHTER_ +# define TR2_OPTIONAL_CLANG_3_4_2_AND_HIGHER_ +# elif (__clang_major__ == 3 && __clang_minor__ == 4 && __clang_patchlevel__ >= 2) +# define TR2_OPTIONAL_CLANG_3_4_2_AND_HIGHER_ +# endif +# endif +# +# if defined _MSC_VER +# if (_MSC_VER >= 1900) +# define TR2_OPTIONAL_MSVC_2015_AND_HIGHER___ +# endif +# endif + +# if defined __clang__ +# if (__clang_major__ > 2) || (__clang_major__ == 2) && (__clang_minor__ >= 9) +# define OPTIONAL_HAS_THIS_RVALUE_REFS 1 +# else +# define OPTIONAL_HAS_THIS_RVALUE_REFS 0 +# endif +# elif defined TR2_OPTIONAL_GCC_4_8_1_AND_HIGHER___ +# define OPTIONAL_HAS_THIS_RVALUE_REFS 1 +# elif defined TR2_OPTIONAL_MSVC_2015_AND_HIGHER___ +# define OPTIONAL_HAS_THIS_RVALUE_REFS 1 +# else +# define OPTIONAL_HAS_THIS_RVALUE_REFS 0 +# endif + + +# if defined TR2_OPTIONAL_GCC_4_8_1_AND_HIGHER___ +# define OPTIONAL_HAS_CONSTEXPR_INIT_LIST 1 +# define OPTIONAL_CONSTEXPR_INIT_LIST constexpr +# else +# define OPTIONAL_HAS_CONSTEXPR_INIT_LIST 0 +# define OPTIONAL_CONSTEXPR_INIT_LIST +# endif + +# if defined TR2_OPTIONAL_CLANG_3_5_AND_HIGHTER_ && (defined __cplusplus) && (__cplusplus != 201103L) +# define OPTIONAL_HAS_MOVE_ACCESSORS 1 +# else +# define OPTIONAL_HAS_MOVE_ACCESSORS 0 +# endif + +# // In C++11 constexpr implies const, so we need to make non-const members also non-constexpr +# if (defined __cplusplus) && (__cplusplus == 201103L) +# define OPTIONAL_MUTABLE_CONSTEXPR +# else +# define OPTIONAL_MUTABLE_CONSTEXPR constexpr +# endif + +namespace std{ + + namespace experimental{ + + // BEGIN workaround for missing is_trivially_destructible +# if defined TR2_OPTIONAL_GCC_4_8_AND_HIGHER___ + // leave it: it is already there +# elif defined TR2_OPTIONAL_CLANG_3_4_2_AND_HIGHER_ + // leave it: it is already there +# elif defined TR2_OPTIONAL_MSVC_2015_AND_HIGHER___ + // leave it: it is already there +# elif defined TR2_OPTIONAL_DISABLE_EMULATION_OF_TYPE_TRAITS + // leave it: the user doesn't want it +# else + template + using is_trivially_destructible = std::has_trivial_destructor; +# endif + // END workaround for missing is_trivially_destructible + +# if (defined TR2_OPTIONAL_GCC_4_7_AND_HIGHER___) + // leave it; our metafunctions are already defined. +# elif defined TR2_OPTIONAL_CLANG_3_4_2_AND_HIGHER_ + // leave it; our metafunctions are already defined. +# elif defined TR2_OPTIONAL_MSVC_2015_AND_HIGHER___ + // leave it: it is already there +# elif defined TR2_OPTIONAL_DISABLE_EMULATION_OF_TYPE_TRAITS + // leave it: the user doesn't want it +# else + + + // workaround for missing traits in GCC and CLANG + template + struct is_nothrow_move_constructible + { + constexpr static bool value = std::is_nothrow_constructible::value; + }; + + + template + struct is_assignable + { + template + constexpr static bool has_assign(...) { return false; } + + template () = std::declval(), true)) > + // the comma operator is necessary for the cases where operator= returns void + constexpr static bool has_assign(bool) { return true; } + + constexpr static bool value = has_assign(true); + }; + + + template + struct is_nothrow_move_assignable + { + template + struct has_nothrow_move_assign { + constexpr static bool value = false; + }; + + template + struct has_nothrow_move_assign { + constexpr static bool value = noexcept( std::declval() = std::declval() ); + }; + + constexpr static bool value = has_nothrow_move_assign::value>::value; + }; + // end workaround + + +# endif + + + + // 20.5.4, optional for object types + template class optional; + + // 20.5.5, optional for lvalue reference types + template class optional; + + + // workaround: std utility functions aren't constexpr yet + template inline constexpr T&& constexpr_forward(typename std::remove_reference::type& t) noexcept + { + return static_cast(t); + } + + template inline constexpr T&& constexpr_forward(typename std::remove_reference::type&& t) noexcept + { + static_assert(!std::is_lvalue_reference::value, "!!"); + return static_cast(t); + } + + template inline constexpr typename std::remove_reference::type&& constexpr_move(T&& t) noexcept + { + return static_cast::type&&>(t); + } + + +#if defined NDEBUG +# define TR2_OPTIONAL_ASSERTED_EXPRESSION(CHECK, EXPR) (EXPR) +#else +# define TR2_OPTIONAL_ASSERTED_EXPRESSION(CHECK, EXPR) ((CHECK) ? (EXPR) : ([]{assert(!#CHECK);}(), (EXPR))) +#endif + + + namespace detail_ + { + + // static_addressof: a constexpr version of addressof + template + struct has_overloaded_addressof + { + template + constexpr static bool has_overload(...) { return false; } + + template ().operator&()) > + constexpr static bool has_overload(bool) { return true; } + + constexpr static bool value = has_overload(true); + }; + + template )> + constexpr T* static_addressof(T& ref) + { + return &ref; + } + + template )> + T* static_addressof(T& ref) + { + return std::addressof(ref); + } + + + // the call to convert(b) has return type A and converts b to type A iff b decltype(b) is implicitly convertible to A + template + constexpr U convert(U v) { return v; } + + } // namespace detail + + + constexpr struct trivial_init_t{} trivial_init{}; + + + // 20.5.6, In-place construction + constexpr struct in_place_t{} in_place{}; + + + // 20.5.7, Disengaged state indicator + struct nullopt_t + { + struct init{}; + constexpr explicit nullopt_t(init){} + }; + constexpr nullopt_t nullopt{nullopt_t::init()}; + + + // 20.5.8, class bad_optional_access + class bad_optional_access : public logic_error { + public: + explicit bad_optional_access(const string& what_arg) : logic_error{what_arg} {} + explicit bad_optional_access(const char* what_arg) : logic_error{what_arg} {} + }; + + + template + union storage_t + { + unsigned char dummy_; + T value_; + + constexpr storage_t( trivial_init_t ) noexcept : dummy_() {}; + + template + constexpr storage_t( Args&&... args ) : value_(constexpr_forward(args)...) {} + + ~storage_t(){} + }; + + + template + union constexpr_storage_t + { + unsigned char dummy_; + T value_; + + constexpr constexpr_storage_t( trivial_init_t ) noexcept : dummy_() {}; + + template + constexpr constexpr_storage_t( Args&&... args ) : value_(constexpr_forward(args)...) {} + + ~constexpr_storage_t() = default; + }; + + + template + struct optional_base + { + bool init_; + storage_t storage_; + + constexpr optional_base() noexcept : init_(false), storage_(trivial_init) {}; + + explicit constexpr optional_base(const T& v) : init_(true), storage_(v) {} + + explicit constexpr optional_base(T&& v) : init_(true), storage_(constexpr_move(v)) {} + + template explicit optional_base(in_place_t, Args&&... args) + : init_(true), storage_(constexpr_forward(args)...) {} + + template >)> + explicit optional_base(in_place_t, std::initializer_list il, Args&&... args) + : init_(true), storage_(il, std::forward(args)...) {} + + ~optional_base() { if (init_) storage_.value_.T::~T(); } + }; + + + template + struct constexpr_optional_base + { + bool init_; + constexpr_storage_t storage_; + + constexpr constexpr_optional_base() noexcept : init_(false), storage_(trivial_init) {}; + + explicit constexpr constexpr_optional_base(const T& v) : init_(true), storage_(v) {} + + explicit constexpr constexpr_optional_base(T&& v) : init_(true), storage_(constexpr_move(v)) {} + + template explicit constexpr constexpr_optional_base(in_place_t, Args&&... args) + : init_(true), storage_(constexpr_forward(args)...) {} + + template >)> + OPTIONAL_CONSTEXPR_INIT_LIST explicit constexpr_optional_base(in_place_t, std::initializer_list il, Args&&... args) + : init_(true), storage_(il, std::forward(args)...) {} + + ~constexpr_optional_base() = default; + }; + + template + using OptionalBase = typename std::conditional< + is_trivially_destructible::value, + constexpr_optional_base::type>, + optional_base::type> + >::type; + + + + template + class optional : private OptionalBase + { + static_assert( !std::is_same::type, nullopt_t>::value, "bad T" ); + static_assert( !std::is_same::type, in_place_t>::value, "bad T" ); + + + constexpr bool initialized() const noexcept { return OptionalBase::init_; } + typename std::remove_const::type* dataptr() { return std::addressof(OptionalBase::storage_.value_); } + constexpr const T* dataptr() const { return detail_::static_addressof(OptionalBase::storage_.value_); } + +# if OPTIONAL_HAS_THIS_RVALUE_REFS == 1 + constexpr const T& contained_val() const& { return OptionalBase::storage_.value_; } +# if OPTIONAL_HAS_MOVE_ACCESSORS == 1 + OPTIONAL_MUTABLE_CONSTEXPR T&& contained_val() && { return std::move(OptionalBase::storage_.value_); } + OPTIONAL_MUTABLE_CONSTEXPR T& contained_val() & { return OptionalBase::storage_.value_; } +# else + T& contained_val() & { return OptionalBase::storage_.value_; } + T&& contained_val() && { return std::move(OptionalBase::storage_.value_); } +# endif +# else + constexpr const T& contained_val() const { return OptionalBase::storage_.value_; } + T& contained_val() { return OptionalBase::storage_.value_; } +# endif + + void clear() noexcept { + if (initialized()) dataptr()->T::~T(); + OptionalBase::init_ = false; + } + + template + void initialize(Args&&... args) noexcept(noexcept(T(std::forward(args)...))) + { + assert(!OptionalBase::init_); + ::new (static_cast(dataptr())) T(std::forward(args)...); + OptionalBase::init_ = true; + } + + template + void initialize(std::initializer_list il, Args&&... args) noexcept(noexcept(T(il, std::forward(args)...))) + { + assert(!OptionalBase::init_); + ::new (static_cast(dataptr())) T(il, std::forward(args)...); + OptionalBase::init_ = true; + } + + public: + typedef T value_type; + + // 20.5.5.1, constructors + constexpr optional() noexcept : OptionalBase() {}; + constexpr optional(nullopt_t) noexcept : OptionalBase() {}; + + optional(const optional& rhs) + : OptionalBase() + { + if (rhs.initialized()) { + ::new (static_cast(dataptr())) T(*rhs); + OptionalBase::init_ = true; + } + } + + optional(optional&& rhs) noexcept(is_nothrow_move_constructible::value) + : OptionalBase() + { + if (rhs.initialized()) { + ::new (static_cast(dataptr())) T(std::move(*rhs)); + OptionalBase::init_ = true; + } + } + + constexpr optional(const T& v) : OptionalBase(v) {} + + constexpr optional(T&& v) : OptionalBase(constexpr_move(v)) {} + + template + explicit constexpr optional(in_place_t, Args&&... args) + : OptionalBase(in_place_t{}, constexpr_forward(args)...) {} + + template >)> + OPTIONAL_CONSTEXPR_INIT_LIST explicit optional(in_place_t, std::initializer_list il, Args&&... args) + : OptionalBase(in_place_t{}, il, constexpr_forward(args)...) {} + + // 20.5.4.2, Destructor + ~optional() = default; + + // 20.5.4.3, assignment + optional& operator=(nullopt_t) noexcept + { + clear(); + return *this; + } + + optional& operator=(const optional& rhs) + { + if (initialized() == true && rhs.initialized() == false) clear(); + else if (initialized() == false && rhs.initialized() == true) initialize(*rhs); + else if (initialized() == true && rhs.initialized() == true) contained_val() = *rhs; + return *this; + } + + optional& operator=(optional&& rhs) + noexcept(is_nothrow_move_assignable::value && is_nothrow_move_constructible::value) + { + if (initialized() == true && rhs.initialized() == false) clear(); + else if (initialized() == false && rhs.initialized() == true) initialize(std::move(*rhs)); + else if (initialized() == true && rhs.initialized() == true) contained_val() = std::move(*rhs); + return *this; + } + + template + auto operator=(U&& v) + -> typename enable_if + < + is_same::type, T>::value, + optional& + >::type + { + if (initialized()) { contained_val() = std::forward(v); } + else { initialize(std::forward(v)); } + return *this; + } + + + template + void emplace(Args&&... args) + { + clear(); + initialize(std::forward(args)...); + } + + template + void emplace(initializer_list il, Args&&... args) + { + clear(); + initialize(il, std::forward(args)...); + } + + // 20.5.4.4, Swap + void swap(optional& rhs) noexcept(is_nothrow_move_constructible::value && noexcept(swap(declval(), declval()))) + { + if (initialized() == true && rhs.initialized() == false) { rhs.initialize(std::move(**this)); clear(); } + else if (initialized() == false && rhs.initialized() == true) { initialize(std::move(*rhs)); rhs.clear(); } + else if (initialized() == true && rhs.initialized() == true) { using std::swap; swap(**this, *rhs); } + } + + // 20.5.4.5, Observers + + explicit constexpr operator bool() const noexcept { return initialized(); } + + constexpr T const* operator ->() const { + return TR2_OPTIONAL_ASSERTED_EXPRESSION(initialized(), dataptr()); + } + +# if OPTIONAL_HAS_MOVE_ACCESSORS == 1 + + OPTIONAL_MUTABLE_CONSTEXPR T* operator ->() { + assert (initialized()); + return dataptr(); + } + + constexpr T const& operator *() const& { + return TR2_OPTIONAL_ASSERTED_EXPRESSION(initialized(), contained_val()); + } + + OPTIONAL_MUTABLE_CONSTEXPR T& operator *() & { + assert (initialized()); + return contained_val(); + } + + OPTIONAL_MUTABLE_CONSTEXPR T&& operator *() && { + assert (initialized()); + return constexpr_move(contained_val()); + } + + constexpr T const& value() const& { + return initialized() ? contained_val() : (valijson::throwRuntimeError("bad optional access"), contained_val()); + } + + OPTIONAL_MUTABLE_CONSTEXPR T& value() & { + return initialized() ? contained_val() : (valijson::throwRuntimeError("bad optional access"), contained_val()); + } + + OPTIONAL_MUTABLE_CONSTEXPR T&& value() && { + if (!initialized()) valijson::throwRuntimeError("bad optional access"); + return std::move(contained_val()); + } + +# else + + T* operator ->() { + assert (initialized()); + return dataptr(); + } + + constexpr T const& operator *() const { + return TR2_OPTIONAL_ASSERTED_EXPRESSION(initialized(), contained_val()); + } + + T& operator *() { + assert (initialized()); + return contained_val(); + } + + constexpr T const& value() const { + return initialized() ? contained_val() : (valijson::throwRuntimeError("bad optional access"), contained_val()); + } + + T& value() { + return initialized() ? contained_val() : (valijson::throwRuntimeError("bad optional access"), contained_val()); + } + +# endif + +# if OPTIONAL_HAS_THIS_RVALUE_REFS == 1 + + template + constexpr T value_or(V&& v) const& + { + return *this ? **this : detail_::convert(constexpr_forward(v)); + } + +# if OPTIONAL_HAS_MOVE_ACCESSORS == 1 + + template + OPTIONAL_MUTABLE_CONSTEXPR T value_or(V&& v) && + { + return *this ? constexpr_move(const_cast&>(*this).contained_val()) : detail_::convert(constexpr_forward(v)); + } + +# else + + template + T value_or(V&& v) && + { + return *this ? constexpr_move(const_cast&>(*this).contained_val()) : detail_::convert(constexpr_forward(v)); + } + +# endif + +# else + + template + constexpr T value_or(V&& v) const + { + return *this ? **this : detail_::convert(constexpr_forward(v)); + } + +# endif + + }; + + + template + class optional + { + static_assert( !std::is_same::value, "bad T" ); + static_assert( !std::is_same::value, "bad T" ); + T* ref; + + public: + + // 20.5.5.1, construction/destruction + constexpr optional() noexcept : ref(nullptr) {} + + constexpr optional(nullopt_t) noexcept : ref(nullptr) {} + + constexpr optional(T& v) noexcept : ref(detail_::static_addressof(v)) {} + + optional(T&&) = delete; + + constexpr optional(const optional& rhs) noexcept : ref(rhs.ref) {} + + explicit constexpr optional(in_place_t, T& v) noexcept : ref(detail_::static_addressof(v)) {} + + explicit optional(in_place_t, T&&) = delete; + + ~optional() = default; + + // 20.5.5.2, mutation + optional& operator=(nullopt_t) noexcept { + ref = nullptr; + return *this; + } + + // optional& operator=(const optional& rhs) noexcept { + // ref = rhs.ref; + // return *this; + // } + + // optional& operator=(optional&& rhs) noexcept { + // ref = rhs.ref; + // return *this; + // } + + template + auto operator=(U&& rhs) noexcept + -> typename enable_if + < + is_same::type, optional>::value, + optional& + >::type + { + ref = rhs.ref; + return *this; + } + + template + auto operator=(U&& rhs) noexcept + -> typename enable_if + < + !is_same::type, optional>::value, + optional& + >::type + = delete; + + void emplace(T& v) noexcept { + ref = detail_::static_addressof(v); + } + + void emplace(T&&) = delete; + + + void swap(optional& rhs) noexcept + { + std::swap(ref, rhs.ref); + } + + // 20.5.5.3, observers + constexpr T* operator->() const { + return TR2_OPTIONAL_ASSERTED_EXPRESSION(ref, ref); + } + + constexpr T& operator*() const { + return TR2_OPTIONAL_ASSERTED_EXPRESSION(ref, *ref); + } + + constexpr T& value() const { + return ref ? *ref : (valijson::throwRuntimeError("bad optional access"), *ref); + } + + explicit constexpr operator bool() const noexcept { + return ref != nullptr; + } + + template + constexpr typename decay::type value_or(V&& v) const + { + return *this ? **this : detail_::convert::type>(constexpr_forward(v)); + } + }; + + + template + class optional + { + static_assert( sizeof(T) == 0, "optional rvalue references disallowed" ); + }; + + + // 20.5.8, Relational operators + template constexpr bool operator==(const optional& x, const optional& y) + { + return bool(x) != bool(y) ? false : bool(x) == false ? true : *x == *y; + } + + template constexpr bool operator!=(const optional& x, const optional& y) + { + return !(x == y); + } + + template constexpr bool operator<(const optional& x, const optional& y) + { + return (!y) ? false : (!x) ? true : *x < *y; + } + + template constexpr bool operator>(const optional& x, const optional& y) + { + return (y < x); + } + + template constexpr bool operator<=(const optional& x, const optional& y) + { + return !(y < x); + } + + template constexpr bool operator>=(const optional& x, const optional& y) + { + return !(x < y); + } + + + // 20.5.9, Comparison with nullopt + template constexpr bool operator==(const optional& x, nullopt_t) noexcept + { + return (!x); + } + + template constexpr bool operator==(nullopt_t, const optional& x) noexcept + { + return (!x); + } + + template constexpr bool operator!=(const optional& x, nullopt_t) noexcept + { + return bool(x); + } + + template constexpr bool operator!=(nullopt_t, const optional& x) noexcept + { + return bool(x); + } + + template constexpr bool operator<(const optional&, nullopt_t) noexcept + { + return false; + } + + template constexpr bool operator<(nullopt_t, const optional& x) noexcept + { + return bool(x); + } + + template constexpr bool operator<=(const optional& x, nullopt_t) noexcept + { + return (!x); + } + + template constexpr bool operator<=(nullopt_t, const optional&) noexcept + { + return true; + } + + template constexpr bool operator>(const optional& x, nullopt_t) noexcept + { + return bool(x); + } + + template constexpr bool operator>(nullopt_t, const optional&) noexcept + { + return false; + } + + template constexpr bool operator>=(const optional&, nullopt_t) noexcept + { + return true; + } + + template constexpr bool operator>=(nullopt_t, const optional& x) noexcept + { + return (!x); + } + + + + // 20.5.10, Comparison with T + template constexpr bool operator==(const optional& x, const T& v) + { + return bool(x) ? *x == v : false; + } + + template constexpr bool operator==(const T& v, const optional& x) + { + return bool(x) ? v == *x : false; + } + + template constexpr bool operator!=(const optional& x, const T& v) + { + return bool(x) ? *x != v : true; + } + + template constexpr bool operator!=(const T& v, const optional& x) + { + return bool(x) ? v != *x : true; + } + + template constexpr bool operator<(const optional& x, const T& v) + { + return bool(x) ? *x < v : true; + } + + template constexpr bool operator>(const T& v, const optional& x) + { + return bool(x) ? v > *x : true; + } + + template constexpr bool operator>(const optional& x, const T& v) + { + return bool(x) ? *x > v : false; + } + + template constexpr bool operator<(const T& v, const optional& x) + { + return bool(x) ? v < *x : false; + } + + template constexpr bool operator>=(const optional& x, const T& v) + { + return bool(x) ? *x >= v : false; + } + + template constexpr bool operator<=(const T& v, const optional& x) + { + return bool(x) ? v <= *x : false; + } + + template constexpr bool operator<=(const optional& x, const T& v) + { + return bool(x) ? *x <= v : true; + } + + template constexpr bool operator>=(const T& v, const optional& x) + { + return bool(x) ? v >= *x : true; + } + + + // Comparison of optional with T + template constexpr bool operator==(const optional& x, const T& v) + { + return bool(x) ? *x == v : false; + } + + template constexpr bool operator==(const T& v, const optional& x) + { + return bool(x) ? v == *x : false; + } + + template constexpr bool operator!=(const optional& x, const T& v) + { + return bool(x) ? *x != v : true; + } + + template constexpr bool operator!=(const T& v, const optional& x) + { + return bool(x) ? v != *x : true; + } + + template constexpr bool operator<(const optional& x, const T& v) + { + return bool(x) ? *x < v : true; + } + + template constexpr bool operator>(const T& v, const optional& x) + { + return bool(x) ? v > *x : true; + } + + template constexpr bool operator>(const optional& x, const T& v) + { + return bool(x) ? *x > v : false; + } + + template constexpr bool operator<(const T& v, const optional& x) + { + return bool(x) ? v < *x : false; + } + + template constexpr bool operator>=(const optional& x, const T& v) + { + return bool(x) ? *x >= v : false; + } + + template constexpr bool operator<=(const T& v, const optional& x) + { + return bool(x) ? v <= *x : false; + } + + template constexpr bool operator<=(const optional& x, const T& v) + { + return bool(x) ? *x <= v : true; + } + + template constexpr bool operator>=(const T& v, const optional& x) + { + return bool(x) ? v >= *x : true; + } + + // Comparison of optional with T + template constexpr bool operator==(const optional& x, const T& v) + { + return bool(x) ? *x == v : false; + } + + template constexpr bool operator==(const T& v, const optional& x) + { + return bool(x) ? v == *x : false; + } + + template constexpr bool operator!=(const optional& x, const T& v) + { + return bool(x) ? *x != v : true; + } + + template constexpr bool operator!=(const T& v, const optional& x) + { + return bool(x) ? v != *x : true; + } + + template constexpr bool operator<(const optional& x, const T& v) + { + return bool(x) ? *x < v : true; + } + + template constexpr bool operator>(const T& v, const optional& x) + { + return bool(x) ? v > *x : true; + } + + template constexpr bool operator>(const optional& x, const T& v) + { + return bool(x) ? *x > v : false; + } + + template constexpr bool operator<(const T& v, const optional& x) + { + return bool(x) ? v < *x : false; + } + + template constexpr bool operator>=(const optional& x, const T& v) + { + return bool(x) ? *x >= v : false; + } + + template constexpr bool operator<=(const T& v, const optional& x) + { + return bool(x) ? v <= *x : false; + } + + template constexpr bool operator<=(const optional& x, const T& v) + { + return bool(x) ? *x <= v : true; + } + + template constexpr bool operator>=(const T& v, const optional& x) + { + return bool(x) ? v >= *x : true; + } + + + // 20.5.12, Specialized algorithms + template + void swap(optional& x, optional& y) noexcept(noexcept(x.swap(y))) + { + x.swap(y); + } + + + template + constexpr optional::type> make_optional(T&& v) + { + return optional::type>(constexpr_forward(v)); + } + + template + constexpr optional make_optional(reference_wrapper v) + { + return optional(v.get()); + } + + + } // namespace experimental +} // namespace std + +namespace std +{ + template + struct hash> + { + typedef typename hash::result_type result_type; + typedef std::experimental::optional argument_type; + + constexpr result_type operator()(argument_type const& arg) const { + return arg ? std::hash{}(*arg) : result_type{}; + } + }; + + template + struct hash> + { + typedef typename hash::result_type result_type; + typedef std::experimental::optional argument_type; + + constexpr result_type operator()(argument_type const& arg) const { + return arg ? std::hash{}(*arg) : result_type{}; + } + }; +} + +# undef TR2_OPTIONAL_REQUIRES +# undef TR2_OPTIONAL_ASSERTED_EXPRESSION + +# endif //___OPTIONAL_HPP___ +#pragma once + +namespace opt = std::experimental; +#pragma once + +#include + +namespace valijson { +namespace adapters { + +class FrozenValue; + +/** + * @brief An interface that encapsulates access to the JSON values provided + * by a JSON parser implementation. + * + * This interface allows JSON processing code to be parser-agnostic. It provides + * functions to access the plain old datatypes (PODs) that are described in the + * JSON specification, and callback-based access to the contents of arrays and + * objects. + * + * The interface also defines a set of functions that allow for type-casting and + * type-comparison based on value rather than on type. + */ +class Adapter +{ +public: + + /// Typedef for callback function supplied to applyToArray. + typedef std::function + ArrayValueCallback; + + /// Typedef for callback function supplied to applyToObject. + typedef std::function + ObjectMemberCallback; + + /** + * @brief Virtual destructor defined to ensure deletion via base-class + * pointers is safe. + */ + virtual ~Adapter() = default; + + /** + * @brief Apply a callback function to each value in an array. + * + * The callback function is invoked for each element in the array, until + * it has been applied to all values, or it returns false. + * + * @param fn Callback function to invoke + * + * @returns true if Adapter contains an array and all values are equal, + * false otherwise. + */ + virtual bool applyToArray(ArrayValueCallback fn) const = 0; + + /** + * @brief Apply a callback function to each member in an object. + * + * The callback function shall be invoked for each member in the object, + * until it has been applied to all values, or it returns false. + * + * @param fn Callback function to invoke + * + * @returns true if Adapter contains an object, and callback function + * returns true for each member in the object, false otherwise. + */ + virtual bool applyToObject(ObjectMemberCallback fn) const = 0; + + /** + * @brief Return the boolean representation of the contained value. + * + * This function shall return a boolean value if the Adapter contains either + * an actual boolean value, or one of the strings 'true' or 'false'. + * The string comparison is case sensitive. + * + * An exception shall be thrown if the value cannot be cast to a boolean. + * + * @returns Boolean representation of contained value. + */ + virtual bool asBool() const = 0; + + /** + * @brief Retrieve the boolean representation of the contained value. + * + * This function shall retrieve a boolean value if the Adapter contains + * either an actual boolean value, or one of the strings 'true' or 'false'. + * The string comparison is case sensitive. + * + * The retrieved value is returned via reference. + * + * @param result reference to a bool to set with retrieved value. + * + * @returns true if the value could be retrieved, false otherwise + */ + virtual bool asBool(bool &result) const = 0; + + /** + * @brief Return the double representation of the contained value. + * + * This function shall return a double value if the Adapter contains either + * an actual double, an integer, or a string that contains a valid + * representation of a numeric value (according to the C++ Std Library). + * + * An exception shall be thrown if the value cannot be cast to a double. + * + * @returns Double representation of contained value. + */ + virtual double asDouble() const = 0; + + /** + * @brief Retrieve the double representation of the contained value. + * + * This function shall retrieve a double value if the Adapter contains either + * an actual double, an integer, or a string that contains a valid + * representation of a numeric value (according to the C++ Std Library). + * + * The retrieved value is returned via reference. + * + * @param result reference to a double to set with retrieved value. + * + * @returns true if the value could be retrieved, false otherwise + */ + virtual bool asDouble(double &result) const = 0; + + /** + * @brief Return the int64_t representation of the contained value. + * + * This function shall return an int64_t value if the Adapter contains either + * an actual integer, or a string that contains a valid representation of an + * integer value (according to the C++ Std Library). + * + * An exception shall be thrown if the value cannot be cast to an int64_t. + * + * @returns int64_t representation of contained value. + */ + virtual int64_t asInteger() const = 0; + + /** + * @brief Retrieve the int64_t representation of the contained value. + * + * This function shall retrieve an int64_t value if the Adapter contains + * either an actual integer, or a string that contains a valid + * representation of an integer value (according to the C++ Std Library). + * + * The retrieved value is returned via reference. + * + * @param result reference to a int64_t to set with retrieved value. + * + * @returns true if the value could be retrieved, false otherwise + */ + virtual bool asInteger(int64_t &result) const = 0; + + /** + * @brief Return the string representation of the contained value. + * + * This function shall return a string value if the Adapter contains either + * an actual string, a literal value of another POD type, an empty array, + * an empty object, or null. + * + * An exception shall be thrown if the value cannot be cast to a string. + * + * @returns string representation of contained value. + */ + virtual std::string asString() const = 0; + + /** + * @brief Retrieve the string representation of the contained value. + * + * This function shall retrieve a string value if the Adapter contains either + * an actual string, a literal value of another POD type, an empty array, + * an empty object, or null. + * + * The retrieved value is returned via reference. + * + * @param result reference to a string to set with retrieved value. + * + * @returns true if the value could be retrieved, false otherwise + */ + virtual bool asString(std::string &result) const = 0; + + /** + * @brief Compare the value held by this Adapter instance with the value + * held by another Adapter instance. + * + * @param other the other adapter instance + * @param strict flag to use strict type comparison + * + * @returns true if values are equal, false otherwise + */ + virtual bool equalTo(const Adapter &other, bool strict) const = 0; + + /** + * @brief Create a new FrozenValue instance that is equivalent to the + * value contained by the Adapter. + * + * @returns pointer to a new FrozenValue instance, belonging to the caller. + */ + virtual FrozenValue* freeze() const = 0; + + /** + * @brief Return the number of elements in the array. + * + * Throws an exception if the value is not an array. + * + * @return number of elements if value is an array + */ + virtual size_t getArraySize() const = 0; + + /** + * @brief Retrieve the number of elements in the array. + * + * This function shall return true or false to indicate whether or not the + * result value was set. If the contained value is not an array, the + * result value shall not be set. This applies even if the value could be + * cast to an empty array. The calling code is expected to handles those + * cases manually. + * + * @param result reference to size_t variable to set with result. + * + * @return true if value retrieved successfully, false otherwise. + */ + virtual bool getArraySize(size_t &result) const = 0; + + /** + * @brief Return the contained boolean value. + * + * This function shall throw an exception if the contained value is not a + * boolean. + * + * @returns contained boolean value. + */ + virtual bool getBool() const = 0; + + /** + * @brief Retrieve the contained boolean value. + * + * This function shall retrieve the boolean value contained by this Adapter, + * and store it in the result variable that was passed by reference. + * + * @param result reference to boolean variable to set with result. + * + * @returns true if the value was retrieved, false otherwise. + */ + virtual bool getBool(bool &result) const = 0; + + /** + * @brief Return the contained double value. + * + * This function shall throw an exception if the contained value is not a + * double. + * + * @returns contained double value. + */ + virtual double getDouble() const = 0; + + /** + * @brief Retrieve the contained double value. + * + * This function shall retrieve the double value contained by this Adapter, + * and store it in the result variable that was passed by reference. + * + * @param result reference to double variable to set with result. + * + * @returns true if the value was retrieved, false otherwise. + */ + virtual bool getDouble(double &result) const = 0; + + /** + * @brief Return the contained integer value. + * + * This function shall throw an exception if the contained value is not a + * integer. + * + * @returns contained integer value. + */ + virtual int64_t getInteger() const = 0; + + /** + * @brief Retrieve the contained integer value. + * + * This function shall retrieve the integer value contained by this Adapter, + * and store it in the result variable that was passed by reference. + * + * @param result reference to integer variable to set with result. + * + * @returns true if the value was retrieved, false otherwise. + */ + virtual bool getInteger(int64_t &result) const = 0; + + /** + * @brief Return the contained numeric value as a double. + * + * This function shall throw an exception if the contained value is not a + * integer or a double. + * + * @returns contained double or integral value. + */ + virtual double getNumber() const = 0; + + /** + * @brief Retrieve the contained numeric value as a double. + * + * This function shall retrieve the double or integral value contained by + * this Adapter, and store it in the result variable that was passed by + * reference. + * + * @param result reference to double variable to set with result. + * + * @returns true if the value was retrieved, false otherwise. + */ + virtual bool getNumber(double &result) const = 0; + + /** + * @brief Return the number of members in the object. + * + * Throws an exception if the value is not an object. + * + * @return number of members if value is an object + */ + virtual size_t getObjectSize() const = 0; + + /** + * @brief Retrieve the number of members in the object. + * + * This function shall return true or false to indicate whether or not the + * result value was set. If the contained value is not an object, the + * result value shall not be set. This applies even if the value could be + * cast to an empty object. The calling code is expected to handles those + * cases manually. + * + * @param result reference to size_t variable to set with result. + * + * @return true if value retrieved successfully, false otherwise. + */ + virtual bool getObjectSize(size_t &result) const = 0; + + /** + * @brief Return the contained string value. + * + * This function shall throw an exception if the contained value is not a + * string - even if the value could be cast to a string. The asString() + * function should be used when casting is allowed. + * + * @returns string contained by this Adapter + */ + virtual std::string getString() const = 0; + + /** + * @brief Retrieve the contained string value. + * + * This function shall retrieve the string value contained by this Adapter, + * and store it in result variable that is passed by reference. + * + * @param result reference to string to set with result + * + * @returns true if string was retrieved, false otherwise + */ + virtual bool getString(std::string &result) const = 0; + + /** + * @brief Returns whether or not this Adapter supports strict types. + * + * This function shall return true if the Adapter implementation supports + * strict types, or false if the Adapter fails to store any part of the + * type information supported by the Adapter interface. + * + * For example, the PropertyTreeAdapter implementation stores POD values as + * strings, effectively discarding any other type information. If you were + * to call isDouble() on a double stored by this Adapter, the result would + * be false. The maybeDouble(), asDouble() and various related functions + * are provided to perform type checking based on value rather than on type. + * + * The BasicAdapter template class provides implementations for the type- + * casting functions so that Adapter implementations are semantically + * equivalent in their type-casting behaviour. + * + * @returns true if Adapter supports strict types, false otherwise + */ + virtual bool hasStrictTypes() const = 0; + + /// Returns true if the contained value is definitely an array. + virtual bool isArray() const = 0; + + /// Returns true if the contained value is definitely a boolean. + virtual bool isBool() const = 0; + + /// Returns true if the contained value is definitely a double. + virtual bool isDouble() const = 0; + + /// Returns true if the contained value is definitely an integer. + virtual bool isInteger() const = 0; + + /// Returns true if the contained value is definitely a null. + virtual bool isNull() const = 0; + + /// Returns true if the contained value is either a double or an integer. + virtual bool isNumber() const = 0; + + /// Returns true if the contained value is definitely an object. + virtual bool isObject() const = 0; + + /// Returns true if the contained value is definitely a string. + virtual bool isString() const = 0; + + /** + * @brief Returns true if the contained value can be cast to an array. + * + * @returns true if the contained value is an array, an empty string, or an + * empty object. + */ + virtual bool maybeArray() const = 0; + + /** + * @brief Returns true if the contained value can be cast to a boolean. + * + * @returns true if the contained value is a boolean, or one of the strings + * 'true' or 'false'. Note that numeric values are not to be cast + * to boolean values. + */ + virtual bool maybeBool() const = 0; + + /** + * @brief Returns true if the contained value can be cast to a double. + * + * @returns true if the contained value is a double, an integer, or a string + * containing a double or integral value. + */ + virtual bool maybeDouble() const = 0; + + /** + * @brief Returns true if the contained value can be cast to an integer. + * + * @returns true if the contained value is an integer, or a string + * containing an integral value. + */ + virtual bool maybeInteger() const = 0; + + /** + * @brief Returns true if the contained value can be cast to a null. + * + * @returns true if the contained value is null or an empty string. + */ + virtual bool maybeNull() const = 0; + + /** + * @brief Returns true if the contained value can be cast to an object. + * + * @returns true if the contained value is an object, an empty array or + * an empty string. + */ + virtual bool maybeObject() const = 0; + + /** + * @brief Returns true if the contained value can be cast to a string. + * + * @returns true if the contained value is a non-null POD type, an empty + * array, or an empty object. + */ + virtual bool maybeString() const = 0; +}; + +/** + * @brief Template struct that should be specialised for each concrete Adapter + * class. + * + * @deprecated This is a bit of a hack, and I'd like to remove it. + */ +template +struct AdapterTraits +{ + +}; + +} // namespace adapters +} // namespace valijson +#pragma once + +#include +#include + + +namespace valijson { +namespace adapters { + +/** + * @brief A helper for the array and object member iterators. + * + * See http://www.stlsoft.org/doc-1.9/group__group____pattern____dereference__proxy.html + * for motivation + * + * @tparam Value Name of the value type + */ +template +struct DerefProxy +{ + explicit DerefProxy(const Value& x) + : m_ref(x) { } + + Value* operator->() + { + return std::addressof(m_ref); + } + + explicit operator Value*() + { + return std::addressof(m_ref); + } + +private: + Value m_ref; +}; + +/** + * @brief Template class that implements the expected semantics of an Adapter. + * + * Implementing all of the type-casting functionality for each Adapter is error + * prone and tedious, so this template class aims to minimise the duplication + * of code between various Adapter implementations. This template doesn't quite + * succeed in removing all duplication, but it has greatly simplified the + * implementation of a new Adapter by encapsulating the type-casting semantics + * and a lot of the trivial functionality associated with the Adapter interface. + * + * By inheriting from this template class, Adapter implementations will inherit + * the exception throwing behaviour that is expected by other parts of the + * Valijson library. + * + * @tparam AdapterType Self-referential name of the Adapter being + * specialised. + * @tparam ArrayType Name of the type that will be returned by the + * getArray() function. Instances of this type should + * provide begin(), end() and size() functions so + * that it is possible to iterate over the values in + * the array. + * @tparam ObjectMemberType Name of the type exposed when iterating over the + * contents of an object returned by getObject(). + * @tparam ObjectType Name of the type that will be returned by the + * getObject() function. Instances of this type + * should provide begin(), end(), find() and size() + * functions so that it is possible to iterate over + * the members of the object. + * @tparam ValueType Name of the type that provides a consistent + * interface to a JSON value for a parser. For + * example, this type should provide the getDouble() + * and isDouble() functions. But it does not need to + * know how to cast values from one type to another - + * that functionality is provided by this template + * class. + */ +template< + typename AdapterType, + typename ArrayType, + typename ObjectMemberType, + typename ObjectType, + typename ValueType> +class BasicAdapter: public Adapter +{ +protected: + + /** + * @brief Functor for comparing two arrays. + * + * This functor is used to compare the elements in an array of the type + * ArrayType with individual values provided as generic Adapter objects. + * Comparison is performed by the () operator. + * + * The functor works by maintaining an iterator for the current position + * in an array. Each time the () operator is called, the value at this + * position is compared with the value passed as an argument to (). + * Immediately after the comparison, the iterator will be incremented. + * + * This functor is designed to be passed to the applyToArray() function + * of an Adapter object. + */ + class ArrayComparisonFunctor + { + public: + + /** + * @brief Construct an ArrayComparisonFunctor for an array. + * + * @param array Array to compare values against + * @param strict Flag to use strict type comparison + */ + ArrayComparisonFunctor(const ArrayType &array, bool strict) + : m_itr(array.begin()), + m_end(array.end()), + m_strict(strict) { } + + /** + * @brief Compare a value against the current element in the array. + * + * @param adapter Value to be compared with current element + * + * @returns true if values are equal, false otherwise. + */ + bool operator()(const Adapter &adapter) + { + if (m_itr == m_end) { + return false; + } + + return AdapterType(*m_itr++).equalTo(adapter, m_strict); + } + + private: + + /// Iterator for current element in the array + typename ArrayType::const_iterator m_itr; + + /// Iterator for one-past the last element of the array + typename ArrayType::const_iterator m_end; + + /// Flag to use strict type comparison + const bool m_strict; + }; + + /** + * @brief Functor for comparing two objects + * + * This functor is used to compare the members of an object of the type + * ObjectType with key-value pairs belonging to another object. + * + * The functor works by maintaining a reference to an object provided via + * the constructor. When time the () operator is called with a key-value + * pair as arguments, the function will attempt to find the key in the + * base object. If found, the associated value will be compared with the + * value provided to the () operator. + * + * This functor is designed to be passed to the applyToObject() function + * of an Adapter object. + */ + class ObjectComparisonFunctor + { + public: + + /** + * @brief Construct a new ObjectComparisonFunctor for an object. + * + * @param object object to use as comparison baseline + * @param strict flag to use strict type-checking + */ + ObjectComparisonFunctor(const ObjectType &object, bool strict) + : m_object(object), + m_strict(strict) { } + + /** + * @brief Find a key in the object and compare its value. + * + * @param key Key to find + * @param value Value to be compared against + * + * @returns true if key is found and values are equal, false otherwise. + */ + bool operator()(const std::string &key, const Adapter &value) + { + const typename ObjectType::const_iterator itr = m_object.find(key); + if (itr == m_object.end()) { + return false; + } + + return (*itr).second.equalTo(value, m_strict); + } + + private: + + /// Object to be used as a comparison baseline + const ObjectType &m_object; + + /// Flag to use strict type-checking + bool m_strict; + }; + + +public: + + /// Alias for ArrayType template parameter + typedef ArrayType Array; + + /// Alias for ObjectMemberType template parameter + typedef ObjectMemberType ObjectMember; + + /// Alias for ObjectType template parameter + typedef ObjectType Object; + + /** + * @brief Construct an Adapter using the default value. + * + * This constructor relies on the default constructor of the ValueType + * class provided as a template argument. + */ + BasicAdapter() = default; + + /** + * @brief Construct an Adapter using a specified ValueType object. + * + * This constructor relies on the copy constructor of the ValueType + * class provided as template argument. + */ + explicit BasicAdapter(const ValueType &value) + : m_value(value) { } + + bool applyToArray(ArrayValueCallback fn) const override + { + if (!maybeArray()) { + return false; + } + + // Due to the fact that the only way a value can be 'maybe an array' is + // if it is an empty string or empty object, we only need to go to + // effort of constructing an ArrayType instance if the value is + // definitely an array. + if (m_value.isArray()) { + const opt::optional array = m_value.getArrayOptional(); + for (const AdapterType element : *array) { + if (!fn(element)) { + return false; + } + } + } + + return true; + } + + bool applyToObject(ObjectMemberCallback fn) const override + { + if (!maybeObject()) { + return false; + } + + if (m_value.isObject()) { + const opt::optional object = m_value.getObjectOptional(); + for (const ObjectMemberType member : *object) { + if (!fn(member.first, AdapterType(member.second))) { + return false; + } + } + } + + return true; + } + + /** + * @brief Return an ArrayType instance containing an array representation + * of the value held by this Adapter. + * + * This is a convenience function that is not actually declared in the + * Adapter interface, but allows for useful techniques such as procedural + * iteration over the elements in an array. The ArrayType instance that is + * returned by this function is compatible with the BOOST_FOREACH macro. + * + * If the contained value is either an empty object, or an empty string, + * then this function will cast the value to an empty array. + * + * @returns ArrayType instance containing an array representation of the + * value held by this Adapter. + */ + ArrayType asArray() const + { + if (m_value.isArray()) { + return *m_value.getArrayOptional(); + } else if (m_value.isObject()) { + size_t objectSize; + if (m_value.getObjectSize(objectSize) && objectSize == 0) { + return ArrayType(); + } + } else if (m_value.isString()) { + std::string stringValue; + if (m_value.getString(stringValue) && stringValue.empty()) { + return ArrayType(); + } + } + + throwRuntimeError("JSON value cannot be cast to an array."); + } + + bool asBool() const override + { + bool result; + if (asBool(result)) { + return result; + } + + throwRuntimeError("JSON value cannot be cast to a boolean."); + } + + bool asBool(bool &result) const override + { + if (m_value.isBool()) { + return m_value.getBool(result); + } else if (m_value.isString()) { + std::string s; + if (m_value.getString(s)) { + if (s == "true") { + result = true; + return true; + } else if (s == "false") { + result = false; + return true; + } + } + } + + return false; + } + + double asDouble() const override + { + double result; + if (asDouble(result)) { + return result; + } + + throwRuntimeError("JSON value cannot be cast to a double."); + } + + bool asDouble(double &result) const override + { + if (m_value.isDouble()) { + return m_value.getDouble(result); + } else if (m_value.isInteger()) { + int64_t i; + if (m_value.getInteger(i)) { + result = double(i); + return true; + } + } else if (m_value.isString()) { + std::string s; + if (m_value.getString(s)) { + const char *b = s.c_str(); + char *e = nullptr; + double x = strtod(b, &e); + if (e == b || e != b + s.length()) { + return false; + } + result = x; + return true; + } + } + + return false; + } + + int64_t asInteger() const override + { + int64_t result; + if (asInteger(result)) { + return result; + } + + throwRuntimeError("JSON value cannot be cast as an integer."); + } + + bool asInteger(int64_t &result) const override + { + if (m_value.isInteger()) { + return m_value.getInteger(result); + } else if (m_value.isString()) { + std::string s; + if (m_value.getString(s)) { + std::istringstream i(s); + int64_t x; + char c; + if (!(!(i >> x) || i.get(c))) { + result = x; + return true; + } + } + } + + return false; + } + + /** + * @brief Return an ObjectType instance containing an array representation + * of the value held by this Adapter. + * + * This is a convenience function that is not actually declared in the + * Adapter interface, but allows for useful techniques such as procedural + * iteration over the members of the object. The ObjectType instance that is + * returned by this function is compatible with the BOOST_FOREACH macro. + * + * @returns ObjectType instance containing an object representation of the + * value held by this Adapter. + */ + ObjectType asObject() const + { + if (m_value.isObject()) { + return *m_value.getObjectOptional(); + } else if (m_value.isArray()) { + size_t arraySize; + if (m_value.getArraySize(arraySize) && arraySize == 0) { + return ObjectType(); + } + } else if (m_value.isString()) { + std::string stringValue; + if (m_value.getString(stringValue) && stringValue.empty()) { + return ObjectType(); + } + } + + throwRuntimeError("JSON value cannot be cast to an object."); + } + + std::string asString() const override + { + std::string result; + if (asString(result)) { + return result; + } + + throwRuntimeError("JSON value cannot be cast to a string."); + } + + bool asString(std::string &result) const override + { + if (m_value.isString()) { + return m_value.getString(result); + } else if (m_value.isNull()) { + result.clear(); + return true; + } else if (m_value.isArray()) { + size_t arraySize; + if (m_value.getArraySize(arraySize) && arraySize == 0) { + result.clear(); + return true; + } + } else if (m_value.isObject()) { + size_t objectSize; + if (m_value.getObjectSize(objectSize) && objectSize == 0) { + result.clear(); + return true; + } + } else if (m_value.isBool()) { + bool boolValue; + if (m_value.getBool(boolValue)) { + result = boolValue ? "true" : "false"; + return true; + } + } else if (m_value.isInteger()) { + int64_t integerValue; + if (m_value.getInteger(integerValue)) { + result = std::to_string(integerValue); + return true; + } + } else if (m_value.isDouble()) { + double doubleValue; + if (m_value.getDouble(doubleValue)) { + result = std::to_string(doubleValue); + return true; + } + } + + return false; + } + + bool equalTo(const Adapter &other, bool strict) const override + { + if (isNull() || (!strict && maybeNull())) { + return other.isNull() || (!strict && other.maybeNull()); + } else if (isBool() || (!strict && maybeBool())) { + return (other.isBool() || (!strict && other.maybeBool())) && other.asBool() == asBool(); + } else if (isNumber() && strict) { + return other.isNumber() && other.getNumber() == getNumber(); + } else if (!strict && maybeDouble()) { + return (other.maybeDouble() && other.asDouble() == asDouble()); + } else if (!strict && maybeInteger()) { + return (other.maybeInteger() && other.asInteger() == asInteger()); + } else if (isString() || (!strict && maybeString())) { + return (other.isString() || (!strict && other.maybeString())) && + other.asString() == asString(); + } else if (isArray()) { + if (other.isArray() && getArraySize() == other.getArraySize()) { + const opt::optional array = m_value.getArrayOptional(); + if (array) { + ArrayComparisonFunctor fn(*array, strict); + return other.applyToArray(fn); + } + } else if (!strict && other.maybeArray() && getArraySize() == 0) { + return true; + } + } else if (isObject()) { + if (other.isObject() && other.getObjectSize() == getObjectSize()) { + const opt::optional object = m_value.getObjectOptional(); + if (object) { + ObjectComparisonFunctor fn(*object, strict); + return other.applyToObject(fn); + } + } else if (!strict && other.maybeObject() && getObjectSize() == 0) { + return true; + } + } + + return false; + } + + /** + * @brief Return an ArrayType instance representing the array contained + * by this Adapter instance. + * + * This is a convenience function that is not actually declared in the + * Adapter interface, but allows for useful techniques such as procedural + * iteration over the elements in an array. The ArrayType instance that is + * returned by this function is compatible with the BOOST_FOREACH macro. + * + * If the contained is not an array, this function will throw an exception. + * + * @returns ArrayType instance containing an array representation of the + * value held by this Adapter. + */ + ArrayType getArray() const + { + opt::optional arrayValue = m_value.getArrayOptional(); + if (arrayValue) { + return *arrayValue; + } + + throwRuntimeError("JSON value is not an array."); + } + + size_t getArraySize() const override + { + size_t result; + if (m_value.getArraySize(result)) { + return result; + } + + throwRuntimeError("JSON value is not an array."); + } + + bool getArraySize(size_t &result) const override + { + return m_value.getArraySize(result); + } + + bool getBool() const override + { + bool result; + if (getBool(result)) { + return result; + } + + throwRuntimeError("JSON value is not a boolean."); + } + + bool getBool(bool &result) const override + { + return m_value.getBool(result); + } + + double getDouble() const override + { + double result; + if (getDouble(result)) { + return result; + } + + throwRuntimeError("JSON value is not a double."); + } + + bool getDouble(double &result) const override + { + return m_value.getDouble(result); + } + + int64_t getInteger() const override + { + int64_t result; + if (getInteger(result)) { + return result; + } + + throwRuntimeError("JSON value is not an integer."); + } + + bool getInteger(int64_t &result) const override + { + return m_value.getInteger(result); + } + + double getNumber() const override + { + double result; + if (getNumber(result)) { + return result; + } + + throwRuntimeError("JSON value is not a number."); + } + + bool getNumber(double &result) const override + { + if (isDouble()) { + return getDouble(result); + } else if (isInteger()) { + int64_t integerResult; + if (getInteger(integerResult)) { + result = static_cast(integerResult); + return true; + } + } + + return false; + } + + /** + * @brief Return an ObjectType instance representing the object contained + * by this Adapter instance. + * + * This is a convenience function that is not actually declared in the + * Adapter interface, but allows for useful techniques such as procedural + * iteration over the members of an object. The ObjectType instance that is + * returned by this function is compatible with the BOOST_FOREACH macro. + * + * If the contained is not an object, this function will throw an exception. + * + * @returns ObjectType instance containing an array representation of the + * value held by this Adapter. + */ + ObjectType getObject() const + { + opt::optional objectValue = m_value.getObjectOptional(); + if (objectValue) { + return *objectValue; + } + + throwRuntimeError("JSON value is not an object."); + } + + size_t getObjectSize() const override + { + size_t result; + if (getObjectSize(result)) { + return result; + } + + throwRuntimeError("JSON value is not an object."); + } + + bool getObjectSize(size_t &result) const override + { + return m_value.getObjectSize(result); + } + + std::string getString() const override + { + std::string result; + if (getString(result)) { + return result; + } + + throwRuntimeError("JSON value is not a string."); + } + + bool getString(std::string &result) const override + { + return m_value.getString(result); + } + + FrozenValue * freeze() const override + { + return m_value.freeze(); + } + + bool hasStrictTypes() const override + { + return ValueType::hasStrictTypes(); + } + + bool isArray() const override + { + return m_value.isArray(); + } + + bool isBool() const override + { + return m_value.isBool(); + } + + bool isDouble() const override + { + return m_value.isDouble(); + } + + bool isInteger() const override + { + return m_value.isInteger(); + } + + bool isNull() const override + { + return m_value.isNull(); + } + + bool isNumber() const override + { + return m_value.isInteger() || m_value.isDouble(); + } + + bool isObject() const override + { + return m_value.isObject(); + } + + bool isString() const override + { + return m_value.isString(); + } + + bool maybeArray() const override + { + if (m_value.isArray()) { + return true; + } else if (m_value.isObject()) { + size_t objectSize; + if (m_value.getObjectSize(objectSize) && objectSize == 0) { + return true; + } + } + + return false; + } + + bool maybeBool() const override + { + if (m_value.isBool()) { + return true; + } else if (maybeString()) { + std::string stringValue; + if (m_value.getString(stringValue)) { + if (stringValue == "true" || stringValue == "false") { + return true; + } + } + } + + return false; + } + + bool maybeDouble() const override + { + if (m_value.isNumber()) { + return true; + } else if (maybeString()) { + std::string s; + if (m_value.getString(s)) { + const char *b = s.c_str(); + char *e = nullptr; + strtod(b, &e); + return e != b && e == b + s.length(); + } + } + + return false; + } + + bool maybeInteger() const override + { + if (m_value.isInteger()) { + return true; + } else if (maybeString()) { + std::string s; + if (m_value.getString(s)) { + std::istringstream i(s); + int64_t x; + char c; + if (!(i >> x) || i.get(c)) { + return false; + } + return true; + } + } + + return false; + } + + bool maybeNull() const override + { + if (m_value.isNull()) { + return true; + } else if (maybeString()) { + std::string stringValue; + if (m_value.getString(stringValue)) { + if (stringValue.empty()) { + return true; + } + } + } + + return false; + } + + bool maybeObject() const override + { + if (m_value.isObject()) { + return true; + } else if (maybeArray()) { + size_t arraySize; + if (m_value.getArraySize(arraySize) && arraySize == 0) { + return true; + } + } + + return false; + } + + bool maybeString() const override + { + if (m_value.isString() || m_value.isBool() || m_value.isInteger() || m_value.isDouble()) { + return true; + } else if (m_value.isObject()) { + size_t objectSize; + if (m_value.getObjectSize(objectSize) && objectSize == 0) { + return true; + } + } else if (m_value.isArray()) { + size_t arraySize; + if (m_value.getArraySize(arraySize) && arraySize == 0) { + return true; + } + } + + return false; + } + +private: + + const ValueType m_value; +}; + +} // namespace adapters +} // namespace valijson +#pragma once + +namespace valijson { +namespace internal { + +template +class CustomAllocator +{ +public: + /// Typedef for custom new-/malloc-like function + typedef void * (*CustomAlloc)(size_t size); + + /// Typedef for custom free-like function + typedef void (*CustomFree)(void *); + + // Standard allocator typedefs + typedef T value_type; + typedef T* pointer; + typedef const T* const_pointer; + typedef T& reference; + typedef const T& const_reference; + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + + template + struct rebind + { + typedef CustomAllocator other; + }; + + CustomAllocator() + : m_allocFn(::operator new), + m_freeFn(::operator delete) { } + + CustomAllocator(CustomAlloc allocFn, CustomFree freeFn) + : m_allocFn(allocFn), + m_freeFn(freeFn) { } + + CustomAllocator(const CustomAllocator &other) + : m_allocFn(other.m_allocFn), + m_freeFn(other.m_freeFn) { } + + template + CustomAllocator(CustomAllocator const &other) + : m_allocFn(other.m_allocFn), + m_freeFn(other.m_freeFn) { } + + CustomAllocator & operator=(const CustomAllocator &other) + { + m_allocFn = other.m_allocFn; + m_freeFn = other.m_freeFn; + + return *this; + } + + pointer address(reference r) + { + return &r; + } + + const_pointer address(const_reference r) + { + return &r; + } + + pointer allocate(size_type cnt, const void * = nullptr) + { + return reinterpret_cast(m_allocFn(cnt * sizeof(T))); + } + + void deallocate(pointer p, size_type) + { + m_freeFn(p); + } + + size_type max_size() const + { + return std::numeric_limits::max() / sizeof(T); + } + + void construct(pointer p, const T& t) + { + new(p) T(t); + } + + void destroy(pointer p) + { + p->~T(); + } + + bool operator==(const CustomAllocator &other) const + { + return other.m_allocFn == m_allocFn && other.m_freeFn == m_freeFn; + } + + bool operator!=(const CustomAllocator &other) const + { + return !operator==(other); + } + + CustomAlloc m_allocFn; + + CustomFree m_freeFn; +}; + +} // end namespace internal +} // end namespace valijson +#pragma once + +#include + +namespace valijson { +namespace internal { + +template +std::string nodeTypeAsString(const AdapterType &node) { + if (node.isArray()) { + return "array"; + } else if (node.isObject()) { + return "object"; + } else if (node.isString()) { + return "string"; + } else if (node.isNull()) { + return "null"; + } else if (node.isInteger()) { + return "integer"; + } else if (node.isDouble()) { + return "double"; + } else if (node.isBool()) { + return "bool"; + } + + return "unknown"; +} + +} // end namespace internal +} // end namespace valijson +#pragma once + + +namespace valijson { +namespace adapters { + +/** + * @brief An interface that provides minimal access to a stored JSON value. + * + * The main reason that this interface exists is to support the 'enum' + * constraint. Each Adapter type is expected to provide an implementation of + * this interface. That class should be able to maintain its own copy of a + * JSON value, independent of the original document. + * + * This interface currently provides just the clone and equalTo functions, but + * could be expanded to include other functions declared in the Adapter + * interface. + * + * @todo it would be nice to better integrate this with the Adapter interface + */ +class FrozenValue +{ +public: + + /** + * @brief Virtual destructor defined to ensure deletion via base-class + * pointers is safe. + */ + virtual ~FrozenValue() { } + + /** + * @brief Clone the stored value and return a pointer to a new FrozenValue + * object containing the value. + */ + virtual FrozenValue *clone() const = 0; + + /** + * @brief Return true if the stored value is equal to the value contained + * by an Adapter instance. + * + * @param adapter Adapter to compare value against + * @param strict Flag to use strict type comparison + * + * @returns true if values are equal, false otherwise + */ + virtual bool equalTo(const Adapter &adapter, bool strict) const = 0; + +}; + +} // namespace adapters +} // namespace valijson +#pragma once + +#include +#include +#include +#include +#include +#include + + +#ifdef _MSC_VER +#pragma warning( push ) +#pragma warning( disable : 4702 ) +#endif + +namespace valijson { +namespace internal { +namespace json_pointer { + +/** + * @brief Replace all occurrences of `search` with `replace`. Modifies `subject` in place. + * + * @param subject string to operate on + * @param search string to search + * @param replace replacement string + */ +inline void replaceAllInPlace(std::string& subject, const char* search, + const char* replace) +{ + size_t pos = 0; + + while((pos = subject.find(search, pos)) != std::string::npos) { + subject.replace(pos, strlen(search), replace); + pos += strlen(replace); + } +} + +/** + * @brief Return the char value corresponding to a 2-digit hexadecimal string + * + * @throws std::runtime_error for strings that are not exactly two characters + * in length and for strings that contain non-hexadecimal characters + * + * @return decoded char value corresponding to the hexadecimal string + */ +inline char decodePercentEncodedChar(const std::string &digits) +{ + if (digits.length() != 2) { + throwRuntimeError("Failed to decode %-encoded character '" + + digits + "' due to unexpected number of characters; " + "expected two characters"); + } + + errno = 0; + const char *begin = digits.c_str(); + char *end = nullptr; + const unsigned long value = strtoul(begin, &end, 16); + if (end != begin && *end != '\0') { + throwRuntimeError("Failed to decode %-encoded character '" + + digits + "'"); + } + + return char(value); +} + +/** + * @brief Extract and transform the token between two iterators + * + * This function is responsible for extracting a JSON Reference token from + * between two iterators, and performing any necessary transformations, before + * returning the resulting string. Its main purpose is to replace the escaped + * character sequences defined in the RFC-6901 (JSON Pointer), and to decode + * %-encoded character sequences defined in RFC-3986 (URI). + * + * The encoding used in RFC-3986 should be familiar to many developers, but + * the escaped character sequences used in JSON Pointers may be less so. From + * the JSON Pointer specification (RFC 6901, April 2013): + * + * Evaluation of each reference token begins by decoding any escaped + * character sequence. This is performed by first transforming any + * occurrence of the sequence '~1' to '/', and then transforming any + * occurrence of the sequence '~0' to '~'. By performing the + * substitutions in this order, an implementation avoids the error of + * turning '~01' first into '~1' and then into '/', which would be + * incorrect (the string '~01' correctly becomes '~1' after + * transformation). + * + * @param begin iterator pointing to beginning of a token + * @param end iterator pointing to one character past the end of the token + * + * @return string with escaped character sequences replaced + * + */ +inline std::string extractReferenceToken(std::string::const_iterator begin, + std::string::const_iterator end) +{ + std::string token(begin, end); + + // Replace JSON Pointer-specific escaped character sequences + replaceAllInPlace(token, "~1", "/"); + replaceAllInPlace(token, "~0", "~"); + + // Replace %-encoded character sequences with their actual characters + for (size_t n = token.find('%'); n != std::string::npos; + n = token.find('%', n + 1)) { + +#if VALIJSON_USE_EXCEPTIONS + try { +#endif + const char c = decodePercentEncodedChar(token.substr(n + 1, 2)); + token.replace(n, 3, &c, 1); +#if VALIJSON_USE_EXCEPTIONS + } catch (const std::runtime_error &e) { + throwRuntimeError( + std::string(e.what()) + "; in token: " + token); + } +#endif + } + + return token; +} + +/** + * @brief Recursively locate the value referenced by a JSON Pointer + * + * This function takes both a string reference and an iterator to the beginning + * of the substring that is being resolved. This iterator is expected to point + * to the beginning of a reference token, whose length will be determined by + * searching for the next delimiter ('/' or '\0'). A reference token must be + * at least one character in length to be considered valid. + * + * Once the next reference token has been identified, it will be used either as + * an array index or as an the name an object member. The validity of a + * reference token depends on the type of the node currently being traversed, + * and the applicability of the token to that node. For example, an array can + * only be dereferenced by a non-negative integral index. + * + * Once the next node has been identified, the length of the remaining portion + * of the JSON Pointer will be used to determine whether recursion should + * terminate. + * + * @param node current node in recursive evaluation of JSON Pointer + * @param jsonPointer string containing complete JSON Pointer + * @param jsonPointerItr string iterator pointing the beginning of the next + * reference token + * + * @return an instance of AdapterType that wraps the dereferenced node + */ +template +inline AdapterType resolveJsonPointer( + const AdapterType &node, + const std::string &jsonPointer, + const std::string::const_iterator jsonPointerItr) +{ + // TODO: This function will probably need to implement support for + // fetching documents referenced by JSON Pointers, similar to the + // populateSchema function. + + const std::string::const_iterator jsonPointerEnd = jsonPointer.end(); + + // Terminate recursion if all reference tokens have been consumed + if (jsonPointerItr == jsonPointerEnd) { + return node; + } + + // Reference tokens must begin with a leading slash + if (*jsonPointerItr != '/') { + throwRuntimeError("Expected reference token to begin with " + "leading slash; remaining tokens: " + + std::string(jsonPointerItr, jsonPointerEnd)); + } + + // Find iterator that points to next slash or newline character; this is + // one character past the end of the current reference token + std::string::const_iterator jsonPointerNext = + std::find(jsonPointerItr + 1, jsonPointerEnd, '/'); + + // Extract the next reference token + const std::string referenceToken = extractReferenceToken( + jsonPointerItr + 1, jsonPointerNext); + + // Empty reference tokens should be ignored + if (referenceToken.empty()) { + return resolveJsonPointer(node, jsonPointer, jsonPointerNext); + + } else if (node.isArray()) { + if (referenceToken == "-") { + throwRuntimeError("Hyphens cannot be used as array indices " + "since the requested array element does not yet exist"); + } + +#if VALIJSON_USE_EXCEPTIONS + try { +#endif + // Fragment must be non-negative integer + const uint64_t index = std::stoul(referenceToken); + typedef typename AdapterType::Array Array; + const Array arr = node.asArray(); + typename Array::const_iterator itr = arr.begin(); + const uint64_t arrSize = arr.size(); + + if (arrSize == 0 || index > arrSize - 1) { + throwRuntimeError("Expected reference token to identify " + "an element in the current array, but array index is " + "out of bounds; actual token: " + referenceToken); + } + + if (index > static_cast(std::numeric_limits::max())) { + throwRuntimeError("Array index out of bounds; hard " + "limit is " + std::to_string( + std::numeric_limits::max())); + } + + itr.advance(static_cast(index)); + + // Recursively process the remaining tokens + return resolveJsonPointer(*itr, jsonPointer, jsonPointerNext); + +#if VALIJSON_USE_EXCEPTIONS + } catch (std::invalid_argument &) { + throwRuntimeError("Expected reference token to contain a " + "non-negative integer to identify an element in the " + "current array; actual token: " + referenceToken); + } +#endif + } else if (node.maybeObject()) { + // Fragment must identify a member of the candidate object + typedef typename AdapterType::Object Object; + + const Object object = node.asObject(); + typename Object::const_iterator itr = object.find( + referenceToken); + if (itr == object.end()) { + throwRuntimeError("Expected reference token to identify an " + "element in the current object; " + "actual token: " + referenceToken); + abort(); + } + + // Recursively process the remaining tokens + return resolveJsonPointer(itr->second, jsonPointer, jsonPointerNext); + } + + throwRuntimeError("Expected end of JSON Pointer, but at least " + "one reference token has not been processed; remaining tokens: " + + std::string(jsonPointerNext, jsonPointerEnd)); + abort(); +} + +/** + * @brief Return the JSON Value referenced by a JSON Pointer + * + * @param rootNode node to use as root for JSON Pointer resolution + * @param jsonPointer string containing JSON Pointer + * + * @return an instance AdapterType in the specified document + */ +template +inline AdapterType resolveJsonPointer( + const AdapterType &rootNode, + const std::string &jsonPointer) +{ + return resolveJsonPointer(rootNode, jsonPointer, jsonPointer.begin()); +} + +} // namespace json_pointer +} // namespace internal +} // namespace valijson + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif +#pragma once + +#include +#include + + +namespace valijson { +namespace internal { +namespace json_reference { + +/** + * @brief Extract URI from JSON Reference relative to the current schema + * + * @param jsonRef JSON Reference to extract from + * @param schema Schema that JSON Reference URI is relative to + * + * @return Optional string containing URI + */ +inline opt::optional getJsonReferenceUri( + const std::string &jsonRef) +{ + const size_t ptrPos = jsonRef.find('#'); + if (ptrPos == 0) { + // The JSON Reference does not contain a URI, but might contain a + // JSON Pointer that refers to the current document + return opt::optional(); + } else if (ptrPos != std::string::npos) { + // The JSON Reference contains a URI and possibly a JSON Pointer + return jsonRef.substr(0, ptrPos); + } + + // The entire JSON Reference should be treated as a URI + return jsonRef; +} + +/** + * @brief Extract JSON Pointer portion of a JSON Reference + * + * @param jsonRef JSON Reference to extract from + * + * @return Optional string containing JSON Pointer + */ +inline opt::optional getJsonReferencePointer( + const std::string &jsonRef) +{ + // Attempt to extract JSON Pointer if '#' character is present. Note + // that a valid pointer would contain at least a leading forward + // slash character. + const size_t ptrPos = jsonRef.find('#'); + if (ptrPos != std::string::npos) { + return jsonRef.substr(ptrPos + 1); + } + + return opt::optional(); +} + +} // namespace json_reference +} // namespace internal +} // namespace valijson +#pragma once + +#include +#include + +namespace valijson { +namespace internal { +namespace uri { + +/** + * @brief Placeholder function to check whether a URI is absolute + * + * This function just checks for '://' + */ +inline bool isUriAbsolute(const std::string &documentUri) +{ + static const char * placeholderMarker = "://"; + + return documentUri.find(placeholderMarker) != std::string::npos; +} + +/** + * @brief Placeholder function to check whether a URI is a URN + * + * This function validates that the URI matches the RFC 8141 spec + */ +inline bool isUrn(const std::string &documentUri) { + static const std::regex pattern( + "^((urn)|(URN)):(?!urn:)([a-zA-Z0-9][a-zA-Z0-9-]{1,31})(:[-a-zA-Z0-9\\\\._~%!$&'()\\/*+,;=]+)+(\\?[-a-zA-Z0-9\\\\._~%!$&'()\\/*+,;:=]+){0,1}(#[-a-zA-Z0-9\\\\._~%!$&'()\\/*+,;:=]+){0,1}$"); + + return std::regex_match(documentUri, pattern); +} + +/** + * Placeholder function to resolve a relative URI within a given scope + */ +inline std::string resolveRelativeUri( + const std::string &resolutionScope, + const std::string &relativeUri) +{ + return resolutionScope + relativeUri; +} + +} // namespace uri +} // namespace internal +} // namespace valijson +#pragma once + +#include +#include + +namespace valijson { +namespace utils { + +/** + * Load a file into a string + * + * @param path path to the file to be loaded + * @param dest string into which file should be loaded + * + * @return true if loaded, false otherwise + */ +inline bool loadFile(const std::string &path, std::string &dest) +{ + // Open file for reading + std::ifstream file(path.c_str()); + if (!file.is_open()) { + return false; + } + + // Allocate space for file contents + file.seekg(0, std::ios::end); + const std::streamoff offset = file.tellg(); + if (offset < 0 || offset > std::numeric_limits::max()) { + return false; + } + + dest.clear(); + dest.reserve(static_cast(offset)); + + // Assign file contents to destination string + file.seekg(0, std::ios::beg); + dest.assign(std::istreambuf_iterator(file), + std::istreambuf_iterator()); + + return true; +} + +} // namespace utils +} // namespace valijson +#pragma once + +#include +#include +#include + + +/* + Basic UTF-8 manipulation routines, adapted from code that was released into + the public domain by Jeff Bezanson. +*/ + +namespace valijson { +namespace utils { + +static const uint32_t offsetsFromUTF8[6] = { + 0x00000000UL, 0x00003080UL, 0x000E2080UL, + 0x03C82080UL, 0xFA082080UL, 0x82082080UL +}; + +/* is c the start of a utf8 sequence? */ +inline bool isutf(char c) { + return ((c & 0xC0) != 0x80); +} + +/* reads the next utf-8 sequence out of a string, updating an index */ +inline uint64_t u8_nextchar(const char *s, uint64_t *i) +{ + uint64_t ch = 0; + int sz = 0; + + do { + ch <<= 6; + ch += static_cast(s[(*i)++]); + sz++; + } while (s[*i] && !isutf(s[*i])); + ch -= offsetsFromUTF8[sz-1]; + + return ch; +} + +/* number of characters */ +inline uint64_t u8_strlen(const char *s) +{ + constexpr auto maxLength = std::numeric_limits::max(); + uint64_t count = 0; + uint64_t i = 0; + + while (s[i] != 0 && u8_nextchar(s, &i) != 0) { + if (i == maxLength) { + throwRuntimeError( + "String exceeded maximum size of " + + std::to_string(maxLength) + " bytes."); + } + count++; + } + + return count; +} + +} // namespace utils +} // namespace valijson +#pragma once + +#include +#include + +namespace valijson { +namespace constraints { + +class ConstraintVisitor; + +/** + * @brief Interface that must be implemented by concrete constraint types. + * + * @todo Consider using something like the boost::cloneable concept here. + */ +struct Constraint +{ + /// Typedef for custom new-/malloc-like function + typedef void * (*CustomAlloc)(size_t size); + + /// Typedef for custom free-like function + typedef void (*CustomFree)(void *); + + /// Deleter type to be used with std::unique_ptr / std::shared_ptr + /// @tparam T Const or non-const type (same as the one used in unique_ptr/shared_ptr) + template + struct CustomDeleter + { + CustomDeleter(CustomFree freeFn) + : m_freeFn(freeFn) { } + + void operator()(T *ptr) const + { + auto *nonconst = const_cast::type *>(ptr); + nonconst->~T(); + m_freeFn(nonconst); + } + + private: + CustomFree m_freeFn; + }; + + /// Exclusive-ownership pointer to automatically handle deallocation + typedef std::unique_ptr> OwningPointer; + + /** + * @brief Virtual destructor. + */ + virtual ~Constraint() = default; + + /** + * @brief Perform an action on the constraint using the visitor pattern. + * + * Note that Constraints cannot be modified by visitors. + * + * @param visitor Reference to a ConstraintVisitor object. + * + * @returns the boolean value returned by one of the visitor's visit + * functions. + */ + virtual bool accept(ConstraintVisitor &visitor) const = 0; + + /** + * @brief Make a copy of a constraint. + * + * Note that this should be a deep copy of the constraint. + * + * @returns an owning-pointer to the new constraint. + */ + virtual OwningPointer clone(CustomAlloc, CustomFree) const = 0; + +}; + +} // namespace constraints +} // namespace valijson +#pragma once + +#include +#include +#include + + +namespace valijson { + +/** + * Represents a sub-schema within a JSON Schema + * + * While all JSON Schemas have at least one sub-schema, the root, some will + * have additional sub-schemas that are defined as part of constraints that are + * included in the schema. For example, a 'oneOf' constraint maintains a set of + * references to one or more nested sub-schemas. As per the definition of a + * oneOf constraint, a document is valid within that constraint if it validates + * against one of the nested sub-schemas. + */ +class Subschema +{ +public: + + /// Typedef for custom new-/malloc-like function + typedef void * (*CustomAlloc)(size_t size); + + /// Typedef for custom free-like function + typedef void (*CustomFree)(void *); + + /// Typedef the Constraint class into the local namespace for convenience + typedef constraints::Constraint Constraint; + + /// Typedef for a function that can be applied to each of the Constraint + /// instances owned by a Schema. + typedef std::function ApplyFunction; + + // Disable copy construction + Subschema(const Subschema &) = delete; + + // Disable copy assignment + Subschema & operator=(const Subschema &) = delete; + + /** + * @brief Construct a new Subschema object + */ + Subschema() + : m_allocFn(::operator new) + , m_freeFn(::operator delete) + , m_alwaysInvalid(false) { } + + /** + * @brief Construct a new Subschema using custom memory management + * functions + * + * @param allocFn malloc- or new-like function to allocate memory + * within Schema, such as for Subschema instances + * @param freeFn free-like function to free memory allocated with + * the `customAlloc` function + */ + Subschema(CustomAlloc allocFn, CustomFree freeFn) + : m_allocFn(allocFn) + , m_freeFn(freeFn) + , m_alwaysInvalid(false) + { + // explicitly initialise optionals. See: https://github.com/tristanpenman/valijson/issues/124 + m_description = opt::nullopt; + m_id = opt::nullopt; + m_title = opt::nullopt; + } + + /** + * @brief Clean up and free all memory managed by the Subschema + */ + virtual ~Subschema() + { +#if VALIJSON_USE_EXCEPTIONS + try { +#endif + m_constraints.clear(); +#if VALIJSON_USE_EXCEPTIONS + } catch (const std::exception &e) { + fprintf(stderr, "Caught an exception in Subschema destructor: %s", + e.what()); + } +#endif + } + + /** + * @brief Add a constraint to this sub-schema + * + * The constraint will be copied before being added to the list of + * constraints for this Subschema. Note that constraints will be copied + * only as deep as references to other Subschemas - e.g. copies of + * constraints that refer to sub-schemas, will continue to refer to the + * same Subschema instances. + * + * @param constraint Reference to the constraint to copy + */ + void addConstraint(const Constraint &constraint) + { + // the vector allocation might throw but the constraint memory will be taken care of anyways + m_constraints.push_back(constraint.clone(m_allocFn, m_freeFn)); + } + + /** + * @brief Invoke a function on each child Constraint + * + * This function will apply the callback function to each constraint in + * the Subschema, even if one of the invocations returns \c false. However, + * if one or more invocations of the callback function return \c false, + * this function will also return \c false. + * + * @returns \c true if all invocations of the callback function are + * successful, \c false otherwise + */ + bool apply(ApplyFunction &applyFunction) const + { + bool allTrue = true; + for (auto &&constraint : m_constraints) { + allTrue = applyFunction(*constraint) && allTrue; + } + + return allTrue; + } + + /** + * @brief Invoke a function on each child Constraint + * + * This is a stricter version of the apply() function that will return + * immediately if any of the invocations of the callback function return + * \c false. + * + * @returns \c true if all invocations of the callback function are + * successful, \c false otherwise + */ + bool applyStrict(ApplyFunction &applyFunction) const + { + for (auto &&constraint : m_constraints) { + if (!applyFunction(*constraint)) { + return false; + } + } + + return true; + } + + bool getAlwaysInvalid() const + { + return m_alwaysInvalid; + } + + /** + * @brief Get the description associated with this sub-schema + * + * @throws std::runtime_error if a description has not been set + * + * @returns string containing sub-schema description + */ + std::string getDescription() const + { + if (m_description) { + return *m_description; + } + + throwRuntimeError("Schema does not have a description"); + } + + /** + * @brief Get the ID associated with this sub-schema + * + * @throws std::runtime_error if an ID has not been set + * + * @returns string containing sub-schema ID + */ + std::string getId() const + { + if (m_id) { + return *m_id; + } + + throwRuntimeError("Schema does not have an ID"); + } + + /** + * @brief Get the title associated with this sub-schema + * + * @throws std::runtime_error if a title has not been set + * + * @returns string containing sub-schema title + */ + std::string getTitle() const + { + if (m_title) { + return *m_title; + } + + throwRuntimeError("Schema does not have a title"); + } + + /** + * @brief Check whether this sub-schema has a description + * + * @return boolean value + */ + bool hasDescription() const + { + return static_cast(m_description); + } + + /** + * @brief Check whether this sub-schema has an ID + * + * @return boolean value + */ + bool hasId() const + { + return static_cast(m_id); + } + + /** + * @brief Check whether this sub-schema has a title + * + * @return boolean value + */ + bool hasTitle() const + { + return static_cast(m_title); + } + + void setAlwaysInvalid(bool value) + { + m_alwaysInvalid = value; + } + + /** + * @brief Set the description for this sub-schema + * + * The description will not be used for validation, but may be used as part + * of the user interface for interacting with schemas and sub-schemas. As + * an example, it may be used as part of the validation error descriptions + * that are produced by the Validator and ValidationVisitor classes. + * + * @param description new description + */ + void setDescription(const std::string &description) + { + m_description = description; + } + + void setId(const std::string &id) + { + m_id = id; + } + + /** + * @brief Set the title for this sub-schema + * + * The title will not be used for validation, but may be used as part + * of the user interface for interacting with schemas and sub-schema. As an + * example, it may be used as part of the validation error descriptions + * that are produced by the Validator and ValidationVisitor classes. + * + * @param title new title + */ + void setTitle(const std::string &title) + { + m_title = title; + } + +protected: + + CustomAlloc m_allocFn; + + CustomFree m_freeFn; + +private: + + bool m_alwaysInvalid; + + /// List of pointers to constraints that apply to this schema. + std::vector m_constraints; + + /// Schema description (optional) + opt::optional m_description; + + /// Id to apply when resolving the schema URI + opt::optional m_id; + + /// Title string associated with the schema (optional) + opt::optional m_title; +}; + +} // namespace valijson +#pragma once + +#include +#include + + +namespace valijson { + +/** + * Represents the root of a JSON Schema + * + * The root is distinct from other sub-schemas because it is the canonical + * starting point for validation of a document against a given a JSON Schema. + */ +class Schema: public Subschema +{ +public: + /** + * @brief Construct a new Schema instance with no constraints + */ + Schema() + : sharedEmptySubschema(newSubschema()) { } + + /** + * @brief Construct a new Schema using custom memory management + * functions + * + * @param allocFn malloc- or new-like function to allocate memory + * within Schema, such as for Subschema instances + * @param freeFn free-like function to free memory allocated with + * the `customAlloc` function + */ + Schema(CustomAlloc allocFn, CustomFree freeFn) + : Subschema(allocFn, freeFn), + sharedEmptySubschema(newSubschema()) { } + + // Disable copy construction + Schema(const Schema &) = delete; + + // Disable copy assignment + Schema & operator=(const Schema &) = delete; + + /** + * @brief Clean up and free all memory managed by the Schema + * + * Note that any Subschema pointers created and returned by this Schema + * should be considered invalid. + */ + ~Schema() override + { + sharedEmptySubschema->~Subschema(); + m_freeFn(const_cast(sharedEmptySubschema)); + sharedEmptySubschema = nullptr; + +#if VALIJSON_USE_EXCEPTIONS + try { +#endif + for (auto subschema : subschemaSet) { + subschema->~Subschema(); + m_freeFn(subschema); + } +#if VALIJSON_USE_EXCEPTIONS + } catch (const std::exception &e) { + fprintf(stderr, "Caught an exception while destroying Schema: %s", + e.what()); + } +#endif + } + + /** + * @brief Copy a constraint to a specific sub-schema + * + * @param constraint reference to a constraint that will be copied into + * the sub-schema + * @param subschema pointer to the sub-schema that will own the copied + * constraint + * + * @throws std::runtime_error if the sub-schema is not owned by this Schema + * instance + */ + void addConstraintToSubschema(const Constraint &constraint, + const Subschema *subschema) + { + // TODO: Check heirarchy for subschemas that do not belong... + + mutableSubschema(subschema)->addConstraint(constraint); + } + + /** + * @brief Create a new Subschema instance that is owned by this Schema + * + * @returns const pointer to the new Subschema instance + */ + const Subschema * createSubschema() + { + Subschema *subschema = newSubschema(); + +#if VALIJSON_USE_EXCEPTIONS + try { +#endif + if (!subschemaSet.insert(subschema).second) { + throwRuntimeError( + "Failed to store pointer for new sub-schema"); + } +#if VALIJSON_USE_EXCEPTIONS + } catch (...) { + subschema->~Subschema(); + m_freeFn(subschema); + throw; + } +#endif + return subschema; + } + + /** + * @brief Return a pointer to the shared empty schema + */ + const Subschema * emptySubschema() const + { + return sharedEmptySubschema; + } + + /** + * @brief Get a pointer to the root sub-schema of this Schema instance + */ + const Subschema * root() const + { + return this; + } + + void setAlwaysInvalid(const Subschema *subschema, bool value) + { + mutableSubschema(subschema)->setAlwaysInvalid(value); + } + + /** + * @brief Update the description for one of the sub-schemas owned by this + * Schema instance + * + * @param subschema sub-schema to update + * @param description new description + */ + void setSubschemaDescription(const Subschema *subschema, + const std::string &description) + { + mutableSubschema(subschema)->setDescription(description); + } + + /** + * @brief Update the ID for one of the sub-schemas owned by this Schema + * instance + * + * @param subschema sub-schema to update + * @param id new ID + */ + void setSubschemaId(const Subschema *subschema, const std::string &id) + { + mutableSubschema(subschema)->setId(id); + } + + /** + * @brief Update the title for one of the sub-schemas owned by this Schema + * instance + * + * @param subschema sub-schema to update + * @param title new title + */ + void setSubschemaTitle(const Subschema *subschema, const std::string &title) + { + mutableSubschema(subschema)->setTitle(title); + } + +private: + + Subschema *newSubschema() + { + void *ptr = m_allocFn(sizeof(Subschema)); + if (!ptr) { + throwRuntimeError( + "Failed to allocate memory for shared empty sub-schema"); + } + +#if VALIJSON_USE_EXCEPTIONS + try { +#endif + return new (ptr) Subschema(); +#if VALIJSON_USE_EXCEPTIONS + } catch (...) { + m_freeFn(ptr); + throw; + } +#endif + } + + Subschema * mutableSubschema(const Subschema *subschema) + { + if (subschema == this) { + return this; + } + + if (subschema == sharedEmptySubschema) { + throwRuntimeError( + "Cannot modify the shared empty sub-schema"); + } + + auto *noConst = const_cast(subschema); + if (subschemaSet.find(noConst) == subschemaSet.end()) { + throwRuntimeError( + "Subschema pointer is not owned by this Schema instance"); + } + + return noConst; + } + + /// Set of Subschema instances owned by this schema + std::set subschemaSet; + + /// Empty schema that can be reused by multiple constraints + const Subschema *sharedEmptySubschema; +}; + +} // namespace valijson +#pragma once + +namespace valijson { +namespace constraints { + +class AllOfConstraint; +class AnyOfConstraint; +class ConditionalConstraint; +class ConstConstraint; +class ContainsConstraint; +class DependenciesConstraint; +class EnumConstraint; +class LinearItemsConstraint; +class MaxItemsConstraint; +class MaximumConstraint; +class MaxLengthConstraint; +class MaxPropertiesConstraint; +class MinItemsConstraint; +class MinimumConstraint; +class MinLengthConstraint; +class MinPropertiesConstraint; +class MultipleOfDoubleConstraint; +class MultipleOfIntConstraint; +class NotConstraint; +class OneOfConstraint; +class PatternConstraint; +class PolyConstraint; +class PropertiesConstraint; +class PropertyNamesConstraint; +class RequiredConstraint; +class SingularItemsConstraint; +class TypeConstraint; +class UniqueItemsConstraint; + +/// Interface to allow usage of the visitor pattern with Constraints +class ConstraintVisitor +{ +protected: + virtual ~ConstraintVisitor() = default; + + // Shorten type names for derived classes outside of this namespace + typedef constraints::AllOfConstraint AllOfConstraint; + typedef constraints::AnyOfConstraint AnyOfConstraint; + typedef constraints::ConditionalConstraint ConditionalConstraint; + typedef constraints::ConstConstraint ConstConstraint; + typedef constraints::ContainsConstraint ContainsConstraint; + typedef constraints::DependenciesConstraint DependenciesConstraint; + typedef constraints::EnumConstraint EnumConstraint; + typedef constraints::LinearItemsConstraint LinearItemsConstraint; + typedef constraints::MaximumConstraint MaximumConstraint; + typedef constraints::MaxItemsConstraint MaxItemsConstraint; + typedef constraints::MaxLengthConstraint MaxLengthConstraint; + typedef constraints::MaxPropertiesConstraint MaxPropertiesConstraint; + typedef constraints::MinimumConstraint MinimumConstraint; + typedef constraints::MinItemsConstraint MinItemsConstraint; + typedef constraints::MinLengthConstraint MinLengthConstraint; + typedef constraints::MinPropertiesConstraint MinPropertiesConstraint; + typedef constraints::MultipleOfDoubleConstraint MultipleOfDoubleConstraint; + typedef constraints::MultipleOfIntConstraint MultipleOfIntConstraint; + typedef constraints::NotConstraint NotConstraint; + typedef constraints::OneOfConstraint OneOfConstraint; + typedef constraints::PatternConstraint PatternConstraint; + typedef constraints::PolyConstraint PolyConstraint; + typedef constraints::PropertiesConstraint PropertiesConstraint; + typedef constraints::PropertyNamesConstraint PropertyNamesConstraint; + typedef constraints::RequiredConstraint RequiredConstraint; + typedef constraints::SingularItemsConstraint SingularItemsConstraint; + typedef constraints::TypeConstraint TypeConstraint; + typedef constraints::UniqueItemsConstraint UniqueItemsConstraint; + +public: + + virtual bool visit(const AllOfConstraint &) = 0; + virtual bool visit(const AnyOfConstraint &) = 0; + virtual bool visit(const ConditionalConstraint &) = 0; + virtual bool visit(const ConstConstraint &) = 0; + virtual bool visit(const ContainsConstraint &) = 0; + virtual bool visit(const DependenciesConstraint &) = 0; + virtual bool visit(const EnumConstraint &) = 0; + virtual bool visit(const LinearItemsConstraint &) = 0; + virtual bool visit(const MaximumConstraint &) = 0; + virtual bool visit(const MaxItemsConstraint &) = 0; + virtual bool visit(const MaxLengthConstraint &) = 0; + virtual bool visit(const MaxPropertiesConstraint &) = 0; + virtual bool visit(const MinimumConstraint &) = 0; + virtual bool visit(const MinItemsConstraint &) = 0; + virtual bool visit(const MinLengthConstraint &) = 0; + virtual bool visit(const MinPropertiesConstraint &) = 0; + virtual bool visit(const MultipleOfDoubleConstraint &) = 0; + virtual bool visit(const MultipleOfIntConstraint &) = 0; + virtual bool visit(const NotConstraint &) = 0; + virtual bool visit(const OneOfConstraint &) = 0; + virtual bool visit(const PatternConstraint &) = 0; + virtual bool visit(const PolyConstraint &) = 0; + virtual bool visit(const PropertiesConstraint &) = 0; + virtual bool visit(const PropertyNamesConstraint &) = 0; + virtual bool visit(const RequiredConstraint &) = 0; + virtual bool visit(const SingularItemsConstraint &) = 0; + virtual bool visit(const TypeConstraint &) = 0; + virtual bool visit(const UniqueItemsConstraint &) = 0; +}; + +} // namespace constraints +} // namespace valijson +#pragma once + + +namespace valijson { +namespace constraints { + +/** + * @brief Template class that implements the accept() and clone() functions of the Constraint interface. + * + * @tparam ConstraintType name of the concrete constraint type, which must provide a copy constructor. + */ +template +struct BasicConstraint: Constraint +{ + typedef internal::CustomAllocator Allocator; + + typedef std::basic_string, internal::CustomAllocator> String; + + BasicConstraint() + : m_allocator() { } + + BasicConstraint(Allocator::CustomAlloc allocFn, Allocator::CustomFree freeFn) + : m_allocator(allocFn, freeFn) { } + + BasicConstraint(const BasicConstraint &other) + : m_allocator(other.m_allocator) { } + + ~BasicConstraint() override = default; + + bool accept(ConstraintVisitor &visitor) const override + { + return visitor.visit(*static_cast(this)); + } + + OwningPointer clone(CustomAlloc allocFn, CustomFree freeFn) const override + { + // smart pointer to automatically free raw memory on exception + typedef std::unique_ptr RawOwningPointer; + auto ptr = RawOwningPointer(static_cast(allocFn(sizeof(ConstraintType))), freeFn); + if (!ptr) { + throwRuntimeError("Failed to allocate memory for cloned constraint"); + } + + // constructor might throw but the memory will be taken care of anyways + (void)new (ptr.get()) ConstraintType(*static_cast(this)); + + // implicitly convert to smart pointer that will also destroy object instance + return ptr; + } + +protected: + + Allocator m_allocator; +}; + +} // namespace constraints +} // namespace valijson +/** + * @file + * + * @brief Class definitions to support JSON Schema constraints + * + * This file contains class definitions for all of the constraints required to + * support JSON Schema. These classes all inherit from the BasicConstraint + * template class, which implements the common parts of the Constraint + * interface. + * + * @see BasicConstraint + * @see Constraint + */ + +#pragma once + +#include +#include +#include +#include +#include + + +#ifdef _MSC_VER +#pragma warning( push ) +#pragma warning( disable : 4702 ) +#endif + +namespace valijson { + +class ValidationResults; + +namespace constraints { + +/** + * @brief Represents an 'allOf' constraint. + * + * An allOf constraint provides a collection of sub-schemas that a value must + * validate against. If a value fails to validate against any of these sub- + * schemas, then validation fails. + */ +class AllOfConstraint: public BasicConstraint +{ +public: + AllOfConstraint() + : m_subschemas(Allocator::rebind::other(m_allocator)) { } + + AllOfConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + m_subschemas(Allocator::rebind::other(m_allocator)) { } + + void addSubschema(const Subschema *subschema) + { + m_subschemas.push_back(subschema); + } + + template + void applyToSubschemas(const FunctorType &fn) const + { + unsigned int index = 0; + for (const Subschema *subschema : m_subschemas) { + if (!fn(index, subschema)) { + return; + } + + index++; + } + } + +private: + typedef std::vector> Subschemas; + + /// Collection of sub-schemas, all of which must be satisfied + Subschemas m_subschemas; +}; + +/** + * @brief Represents an 'anyOf' constraint + * + * An anyOf constraint provides a collection of sub-schemas that a value can + * validate against. If a value validates against one of these sub-schemas, + * then the validation passes. + */ +class AnyOfConstraint: public BasicConstraint +{ +public: + AnyOfConstraint() + : m_subschemas(Allocator::rebind::other(m_allocator)) { } + + AnyOfConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + m_subschemas(Allocator::rebind::other(m_allocator)) { } + + void addSubschema(const Subschema *subschema) + { + m_subschemas.push_back(subschema); + } + + template + void applyToSubschemas(const FunctorType &fn) const + { + unsigned int index = 0; + for (const Subschema *subschema : m_subschemas) { + if (!fn(index, subschema)) { + return; + } + + index++; + } + } + +private: + typedef std::vector> Subschemas; + + /// Collection of sub-schemas, at least one of which must be satisfied + Subschemas m_subschemas; +}; + +/** + * @brief Represents a combination 'if', 'then' and 'else' constraints + * + * The schema provided by an 'if' constraint is used as the expression for a conditional. When the + * target validates against that schema, the 'then' subschema will be also be tested. Otherwise, + * the 'else' subschema will be tested. + */ +class ConditionalConstraint: public BasicConstraint +{ +public: + ConditionalConstraint() + : m_ifSubschema(nullptr), + m_thenSubschema(nullptr), + m_elseSubschema(nullptr) { } + + ConditionalConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + m_ifSubschema(nullptr), + m_thenSubschema(nullptr), + m_elseSubschema(nullptr) { } + + const Subschema * getIfSubschema() const + { + return m_ifSubschema; + } + + const Subschema * getThenSubschema() const + { + return m_thenSubschema; + } + + const Subschema * getElseSubschema() const + { + return m_elseSubschema; + } + + void setIfSubschema(const Subschema *subschema) + { + m_ifSubschema = subschema; + } + + void setThenSubschema(const Subschema *subschema) + { + m_thenSubschema = subschema; + } + + void setElseSubschema(const Subschema *subschema) + { + m_elseSubschema = subschema; + } + +private: + const Subschema *m_ifSubschema; + const Subschema *m_thenSubschema; + const Subschema *m_elseSubschema; +}; + +class ConstConstraint: public BasicConstraint +{ +public: + ConstConstraint() + : m_value(nullptr) { } + + ConstConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + m_value(nullptr) { } + + ConstConstraint(const ConstConstraint &other) + : BasicConstraint(other), + m_value(other.m_value->clone()) { } + + adapters::FrozenValue * getValue() const + { + return m_value.get(); + } + + void setValue(const adapters::Adapter &value) + { + m_value = std::unique_ptr(value.freeze()); + } + +private: + std::unique_ptr m_value; +}; + +/** + * @brief Represents a 'contains' constraint + * + * A 'contains' constraint specifies a schema that must be satisfied by at least one + * of the values in an array. + */ +class ContainsConstraint: public BasicConstraint +{ +public: + ContainsConstraint() + : m_subschema(nullptr) { } + + ContainsConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + m_subschema(nullptr) { } + + const Subschema * getSubschema() const + { + return m_subschema; + } + + void setSubschema(const Subschema *subschema) + { + m_subschema = subschema; + } + +private: + const Subschema *m_subschema; +}; + +/** + * @brief Represents a 'dependencies' constraint. + * + * A dependency constraint ensures that a given property is valid only if the + * properties that it depends on are present. + */ +class DependenciesConstraint: public BasicConstraint +{ +public: + DependenciesConstraint() + : m_propertyDependencies(std::less(), m_allocator), + m_schemaDependencies(std::less(), m_allocator) + { } + + DependenciesConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + m_propertyDependencies(std::less(), m_allocator), + m_schemaDependencies(std::less(), m_allocator) + { } + + template + DependenciesConstraint & addPropertyDependency( + const StringType &propertyName, + const StringType &dependencyName) + { + const String key(propertyName.c_str(), m_allocator); + auto itr = m_propertyDependencies.find(key); + if (itr == m_propertyDependencies.end()) { + itr = m_propertyDependencies.insert(PropertyDependencies::value_type( + key, PropertySet(std::less(), m_allocator))).first; + } + + itr->second.insert(String(dependencyName.c_str(), m_allocator)); + + return *this; + } + + template + DependenciesConstraint & addPropertyDependencies( + const StringType &propertyName, + const ContainerType &dependencyNames) + { + const String key(propertyName.c_str(), m_allocator); + auto itr = m_propertyDependencies.find(key); + if (itr == m_propertyDependencies.end()) { + itr = m_propertyDependencies.insert(PropertyDependencies::value_type( + key, PropertySet(std::less(), m_allocator))).first; + } + + typedef typename ContainerType::value_type ValueType; + for (const ValueType &dependencyName : dependencyNames) { + itr->second.insert(String(dependencyName.c_str(), m_allocator)); + } + + return *this; + } + + template + DependenciesConstraint & addSchemaDependency(const StringType &propertyName, const Subschema *schemaDependency) + { + if (m_schemaDependencies.insert(SchemaDependencies::value_type( + String(propertyName.c_str(), m_allocator), + schemaDependency)).second) { + return *this; + } + + throwRuntimeError("Dependencies constraint already contains a dependent " + "schema for the property '" + propertyName + "'"); + } + + template + void applyToPropertyDependencies(const FunctorType &fn) const + { + for (const PropertyDependencies::value_type &v : m_propertyDependencies) { + if (!fn(v.first, v.second)) { + return; + } + } + } + + template + void applyToSchemaDependencies(const FunctorType &fn) const + { + for (const SchemaDependencies::value_type &v : m_schemaDependencies) { + if (!fn(v.first, v.second)) { + return; + } + } + } + +private: + typedef std::set, internal::CustomAllocator> PropertySet; + + typedef std::map, + internal::CustomAllocator>> PropertyDependencies; + + typedef std::map, + internal::CustomAllocator>> SchemaDependencies; + + /// Mapping from property names to their property-based dependencies + PropertyDependencies m_propertyDependencies; + + /// Mapping from property names to their schema-based dependencies + SchemaDependencies m_schemaDependencies; +}; + +/** + * @brief Represents an 'enum' constraint + * + * An enum constraint provides a collection of permissible values for a JSON + * node. The node will only validate against this constraint if it matches one + * or more of the values in the collection. + */ +class EnumConstraint: public BasicConstraint +{ +public: + EnumConstraint() + : m_enumValues(Allocator::rebind::other(m_allocator)) { } + + EnumConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + m_enumValues(Allocator::rebind::other(m_allocator)) { } + + EnumConstraint(const EnumConstraint &other) + : BasicConstraint(other), + m_enumValues(Allocator::rebind::other(m_allocator)) + { +#if VALIJSON_USE_EXCEPTIONS + try { +#endif + // Clone individual enum values + for (const EnumValue *otherValue : other.m_enumValues) { + const EnumValue *value = otherValue->clone(); +#if VALIJSON_USE_EXCEPTIONS + try { +#endif + m_enumValues.push_back(value); +#if VALIJSON_USE_EXCEPTIONS + } catch (...) { + delete value; + value = nullptr; + throw; + } + } + } catch (...) { + // Delete values already added to constraint + for (const EnumValue *value : m_enumValues) { + delete value; + } + throw; +#endif + } + } + + ~EnumConstraint() override + { + for (const EnumValue *value : m_enumValues) { + delete value; + } + } + + void addValue(const adapters::Adapter &value) + { + // TODO: Freeze value using custom alloc/free functions + m_enumValues.push_back(value.freeze()); + } + + void addValue(const adapters::FrozenValue &value) + { + // TODO: Clone using custom alloc/free functions + m_enumValues.push_back(value.clone()); + } + + template + void applyToValues(const FunctorType &fn) const + { + for (const EnumValue *value : m_enumValues) { + if (!fn(*value)) { + return; + } + } + } + +private: + typedef adapters::FrozenValue EnumValue; + + typedef std::vector> EnumValues; + + EnumValues m_enumValues; +}; + +/** + * @brief Represents non-singular 'items' and 'additionalItems' constraints + * + * Unlike the SingularItemsConstraint class, this class represents an 'items' + * constraint that specifies an array of sub-schemas, which should be used to + * validate each item in an array, in sequence. It also represents an optional + * 'additionalItems' sub-schema that should be used when an array contains + * more values than there are sub-schemas in the 'items' constraint. + * + * The prefix 'Linear' comes from the fact that this class contains a list of + * sub-schemas that corresponding array items must be validated against, and + * this validation is performed linearly (i.e. in sequence). + */ +class LinearItemsConstraint: public BasicConstraint +{ +public: + LinearItemsConstraint() + : m_itemSubschemas(Allocator::rebind::other(m_allocator)), + m_additionalItemsSubschema(nullptr) { } + + LinearItemsConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + m_itemSubschemas(Allocator::rebind::other(m_allocator)), + m_additionalItemsSubschema(nullptr) { } + + void addItemSubschema(const Subschema *subschema) + { + m_itemSubschemas.push_back(subschema); + } + + template + void applyToItemSubschemas(const FunctorType &fn) const + { + unsigned int index = 0; + for (const Subschema *subschema : m_itemSubschemas) { + if (!fn(index, subschema)) { + return; + } + + index++; + } + } + + const Subschema * getAdditionalItemsSubschema() const + { + return m_additionalItemsSubschema; + } + + size_t getItemSubschemaCount() const + { + return m_itemSubschemas.size(); + } + + void setAdditionalItemsSubschema(const Subschema *subschema) + { + m_additionalItemsSubschema = subschema; + } + +private: + typedef std::vector> Subschemas; + + Subschemas m_itemSubschemas; + + const Subschema* m_additionalItemsSubschema; +}; + +/** + * @brief Represents 'maximum' and 'exclusiveMaximum' constraints + */ +class MaximumConstraint: public BasicConstraint +{ +public: + MaximumConstraint() + : m_maximum(std::numeric_limits::infinity()), + m_exclusiveMaximum(false) { } + + MaximumConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + m_maximum(std::numeric_limits::infinity()), + m_exclusiveMaximum(false) { } + + bool getExclusiveMaximum() const + { + return m_exclusiveMaximum; + } + + void setExclusiveMaximum(bool newExclusiveMaximum) + { + m_exclusiveMaximum = newExclusiveMaximum; + } + + double getMaximum() const + { + return m_maximum; + } + + void setMaximum(double newMaximum) + { + m_maximum = newMaximum; + } + +private: + double m_maximum; + bool m_exclusiveMaximum; +}; + +/** + * @brief Represents a 'maxItems' constraint + */ +class MaxItemsConstraint: public BasicConstraint +{ +public: + MaxItemsConstraint() + : m_maxItems(std::numeric_limits::max()) { } + + MaxItemsConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + m_maxItems(std::numeric_limits::max()) { } + + uint64_t getMaxItems() const + { + return m_maxItems; + } + + void setMaxItems(uint64_t newMaxItems) + { + m_maxItems = newMaxItems; + } + +private: + uint64_t m_maxItems; +}; + +/** + * @brief Represents a 'maxLength' constraint + */ +class MaxLengthConstraint: public BasicConstraint +{ +public: + MaxLengthConstraint() + : m_maxLength(std::numeric_limits::max()) { } + + MaxLengthConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + m_maxLength(std::numeric_limits::max()) { } + + uint64_t getMaxLength() const + { + return m_maxLength; + } + + void setMaxLength(uint64_t newMaxLength) + { + m_maxLength = newMaxLength; + } + +private: + uint64_t m_maxLength; +}; + +/** + * @brief Represents a 'maxProperties' constraint + */ +class MaxPropertiesConstraint: public BasicConstraint +{ +public: + MaxPropertiesConstraint() + : m_maxProperties(std::numeric_limits::max()) { } + + MaxPropertiesConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + m_maxProperties(std::numeric_limits::max()) { } + + uint64_t getMaxProperties() const + { + return m_maxProperties; + } + + void setMaxProperties(uint64_t newMaxProperties) + { + m_maxProperties = newMaxProperties; + } + +private: + uint64_t m_maxProperties; +}; + +/** + * @brief Represents 'minimum' and 'exclusiveMinimum' constraints + */ +class MinimumConstraint: public BasicConstraint +{ +public: + MinimumConstraint() + : m_minimum(-std::numeric_limits::infinity()), + m_exclusiveMinimum(false) { } + + MinimumConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + m_minimum(-std::numeric_limits::infinity()), + m_exclusiveMinimum(false) { } + + bool getExclusiveMinimum() const + { + return m_exclusiveMinimum; + } + + void setExclusiveMinimum(bool newExclusiveMinimum) + { + m_exclusiveMinimum = newExclusiveMinimum; + } + + double getMinimum() const + { + return m_minimum; + } + + void setMinimum(double newMinimum) + { + m_minimum = newMinimum; + } + +private: + double m_minimum; + bool m_exclusiveMinimum; +}; + +/** + * @brief Represents a 'minItems' constraint + */ +class MinItemsConstraint: public BasicConstraint +{ +public: + MinItemsConstraint() + : m_minItems(0) { } + + MinItemsConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + m_minItems(0) { } + + uint64_t getMinItems() const + { + return m_minItems; + } + + void setMinItems(uint64_t newMinItems) + { + m_minItems = newMinItems; + } + +private: + uint64_t m_minItems; +}; + +/** + * @brief Represents a 'minLength' constraint + */ +class MinLengthConstraint: public BasicConstraint +{ +public: + MinLengthConstraint() + : m_minLength(0) { } + + MinLengthConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + m_minLength(0) { } + + uint64_t getMinLength() const + { + return m_minLength; + } + + void setMinLength(uint64_t newMinLength) + { + m_minLength = newMinLength; + } + +private: + uint64_t m_minLength; +}; + +/** + * @brief Represents a 'minProperties' constraint + */ +class MinPropertiesConstraint: public BasicConstraint +{ +public: + MinPropertiesConstraint() + : m_minProperties(0) { } + + MinPropertiesConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + m_minProperties(0) { } + + uint64_t getMinProperties() const + { + return m_minProperties; + } + + void setMinProperties(uint64_t newMinProperties) + { + m_minProperties = newMinProperties; + } + +private: + uint64_t m_minProperties; +}; + +/** + * @brief Represents either 'multipleOf' or 'divisibleBy' constraints where + * the divisor is a floating point number + */ +class MultipleOfDoubleConstraint: + public BasicConstraint +{ +public: + MultipleOfDoubleConstraint() + : m_value(1.) { } + + MultipleOfDoubleConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + m_value(1.) { } + + double getDivisor() const + { + return m_value; + } + + void setDivisor(double newValue) + { + m_value = newValue; + } + +private: + double m_value; +}; + +/** + * @brief Represents either 'multipleOf' or 'divisibleBy' constraints where + * the divisor is of integer type + */ +class MultipleOfIntConstraint: + public BasicConstraint +{ +public: + MultipleOfIntConstraint() + : m_value(1) { } + + MultipleOfIntConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + m_value(1) { } + + int64_t getDivisor() const + { + return m_value; + } + + void setDivisor(int64_t newValue) + { + m_value = newValue; + } + +private: + int64_t m_value; +}; + +/** + * @brief Represents a 'not' constraint + */ +class NotConstraint: public BasicConstraint +{ +public: + NotConstraint() + : m_subschema(nullptr) { } + + NotConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + m_subschema(nullptr) { } + + const Subschema * getSubschema() const + { + return m_subschema; + } + + void setSubschema(const Subschema *subschema) + { + m_subschema = subschema; + } + +private: + const Subschema *m_subschema; +}; + +/** + * @brief Represents a 'oneOf' constraint. + */ +class OneOfConstraint: public BasicConstraint +{ +public: + OneOfConstraint() + : m_subschemas(Allocator::rebind::other(m_allocator)) { } + + OneOfConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + m_subschemas(Allocator::rebind::other(m_allocator)) { } + + void addSubschema(const Subschema *subschema) + { + m_subschemas.push_back(subschema); + } + + template + void applyToSubschemas(const FunctorType &fn) const + { + unsigned int index = 0; + for (const Subschema *subschema : m_subschemas) { + if (!fn(index, subschema)) { + return; + } + + index++; + } + } + +private: + typedef std::vector> Subschemas; + + /// Collection of sub-schemas, exactly one of which must be satisfied + Subschemas m_subschemas; +}; + +/** + * @brief Represents a 'pattern' constraint + */ +class PatternConstraint: public BasicConstraint +{ +public: + PatternConstraint() + : m_pattern(Allocator::rebind::other(m_allocator)) { } + + PatternConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + m_pattern(Allocator::rebind::other(m_allocator)) { } + + template + bool getPattern(std::basic_string, AllocatorType> &result) const + { + result.assign(m_pattern.c_str()); + return true; + } + + template + std::basic_string, AllocatorType> getPattern( + const AllocatorType &alloc = AllocatorType()) const + { + return std::basic_string, AllocatorType>(m_pattern.c_str(), alloc); + } + + template + void setPattern(const std::basic_string, AllocatorType> &pattern) + { + m_pattern.assign(pattern.c_str()); + } + +private: + String m_pattern; +}; + +class PolyConstraint : public Constraint +{ +public: + bool accept(ConstraintVisitor &visitor) const override + { + return visitor.visit(*static_cast(this)); + } + + OwningPointer clone(CustomAlloc allocFn, CustomFree freeFn) const override + { + // smart pointer to automatically free raw memory on exception + typedef std::unique_ptr RawOwningPointer; + auto ptr = RawOwningPointer(static_cast(allocFn(sizeOf())), freeFn); + if (!ptr) { + throwRuntimeError("Failed to allocate memory for cloned constraint"); + } + + // constructor might throw but the memory will be taken care of anyways + (void)cloneInto(ptr.get()); + + // implicitly convert to smart pointer that will also destroy object instance + return ptr; + } + + virtual bool validate(const adapters::Adapter &target, + const std::vector& context, + valijson::ValidationResults *results) const = 0; + +private: + virtual Constraint * cloneInto(void *) const = 0; + + virtual size_t sizeOf() const = 0; +}; + +/** + * @brief Represents a combination of 'properties', 'patternProperties' and + * 'additionalProperties' constraints + */ +class PropertiesConstraint: public BasicConstraint +{ +public: + PropertiesConstraint() + : m_properties(std::less(), m_allocator), + m_patternProperties(std::less(), m_allocator), + m_additionalProperties(nullptr) { } + + PropertiesConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + m_properties(std::less(), m_allocator), + m_patternProperties(std::less(), m_allocator), + m_additionalProperties(nullptr) { } + + bool addPatternPropertySubschema(const char *patternProperty, const Subschema *subschema) + { + return m_patternProperties.insert(PropertySchemaMap::value_type( + String(patternProperty, m_allocator), subschema)).second; + } + + template + bool addPatternPropertySubschema(const std::basic_string, AllocatorType> &patternProperty, + const Subschema *subschema) + { + return addPatternPropertySubschema(patternProperty.c_str(), subschema); + } + + bool addPropertySubschema(const char *propertyName, + const Subschema *subschema) + { + return m_properties.insert(PropertySchemaMap::value_type( + String(propertyName, m_allocator), subschema)).second; + } + + template + bool addPropertySubschema(const std::basic_string, AllocatorType> &propertyName, + const Subschema *subschema) + { + return addPropertySubschema(propertyName.c_str(), subschema); + } + + template + void applyToPatternProperties(const FunctorType &fn) const + { + typedef typename PropertySchemaMap::value_type ValueType; + for (const ValueType &value : m_patternProperties) { + if (!fn(value.first, value.second)) { + return; + } + } + } + + template + void applyToProperties(const FunctorType &fn) const + { + typedef typename PropertySchemaMap::value_type ValueType; + for (const ValueType &value : m_properties) { + if (!fn(value.first, value.second)) { + return; + } + } + } + + const Subschema * getAdditionalPropertiesSubschema() const + { + return m_additionalProperties; + } + + void setAdditionalPropertiesSubschema(const Subschema *subschema) + { + m_additionalProperties = subschema; + } + +private: + typedef std::map< + String, + const Subschema *, + std::less, + internal::CustomAllocator> + > PropertySchemaMap; + + PropertySchemaMap m_properties; + PropertySchemaMap m_patternProperties; + + const Subschema *m_additionalProperties; +}; + +class PropertyNamesConstraint: public BasicConstraint +{ +public: + PropertyNamesConstraint() + : m_subschema(nullptr) { } + + PropertyNamesConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + m_subschema(nullptr) { } + + const Subschema * getSubschema() const + { + return m_subschema; + } + + void setSubschema(const Subschema *subschema) + { + m_subschema = subschema; + } + +private: + const Subschema *m_subschema; +}; + +/** + * @brief Represents a 'required' constraint + */ +class RequiredConstraint: public BasicConstraint +{ +public: + RequiredConstraint() + : m_requiredProperties(std::less(), m_allocator) { } + + RequiredConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + m_requiredProperties(std::less(), m_allocator) { } + + bool addRequiredProperty(const char *propertyName) + { + return m_requiredProperties.insert(String(propertyName, + Allocator::rebind::other(m_allocator))).second; + } + + template + bool addRequiredProperty(const std::basic_string, AllocatorType> &propertyName) + { + return addRequiredProperty(propertyName.c_str()); + } + + template + void applyToRequiredProperties(const FunctorType &fn) const + { + for (const String &propertyName : m_requiredProperties) { + if (!fn(propertyName)) { + return; + } + } + } + +private: + typedef std::set, + internal::CustomAllocator> RequiredProperties; + + RequiredProperties m_requiredProperties; +}; + +/** + * @brief Represents an 'items' constraint that specifies one sub-schema + * + * A value is considered valid against this constraint if it is an array, and + * each item in the array validates against the sub-schema specified by this + * constraint. + * + * The prefix 'Singular' comes from the fact that array items must validate + * against exactly one sub-schema. + */ +class SingularItemsConstraint: public BasicConstraint +{ +public: + SingularItemsConstraint() + : m_itemsSubschema(nullptr) { } + + SingularItemsConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + m_itemsSubschema(nullptr) { } + + const Subschema * getItemsSubschema() const + { + return m_itemsSubschema; + } + + void setItemsSubschema(const Subschema *subschema) + { + m_itemsSubschema = subschema; + } + +private: + const Subschema *m_itemsSubschema; +}; + +/** + * @brief Represents a 'type' constraint. + */ +class TypeConstraint: public BasicConstraint +{ +public: + enum JsonType { + kAny, + kArray, + kBoolean, + kInteger, + kNull, + kNumber, + kObject, + kString + }; + + TypeConstraint() + : m_namedTypes(std::less(), m_allocator), + m_schemaTypes(Allocator::rebind::other(m_allocator)) { } + + TypeConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + m_namedTypes(std::less(), m_allocator), + m_schemaTypes(Allocator::rebind::other(m_allocator)) { } + + void addNamedType(JsonType type) + { + m_namedTypes.insert(type); + } + + void addSchemaType(const Subschema *subschema) + { + m_schemaTypes.push_back(subschema); + } + + template + void applyToNamedTypes(const FunctorType &fn) const + { + for (const JsonType namedType : m_namedTypes) { + if (!fn(namedType)) { + return; + } + } + } + + template + void applyToSchemaTypes(const FunctorType &fn) const + { + unsigned int index = 0; + for (const Subschema *subschema : m_schemaTypes) { + if (!fn(index, subschema)) { + return; + } + + index++; + } + } + + template + static JsonType jsonTypeFromString(const std::basic_string, AllocatorType> &typeName) + { + if (typeName.compare("any") == 0) { + return kAny; + } else if (typeName.compare("array") == 0) { + return kArray; + } else if (typeName.compare("boolean") == 0) { + return kBoolean; + } else if (typeName.compare("integer") == 0) { + return kInteger; + } else if (typeName.compare("null") == 0) { + return kNull; + } else if (typeName.compare("number") == 0) { + return kNumber; + } else if (typeName.compare("object") == 0) { + return kObject; + } else if (typeName.compare("string") == 0) { + return kString; + } + + throwRuntimeError("Unrecognised JSON type name '" + + std::string(typeName.c_str()) + "'"); + abort(); + } + +private: + typedef std::set, internal::CustomAllocator> NamedTypes; + + typedef std::vector::other> SchemaTypes; + + /// Set of named JSON types that serve as valid types + NamedTypes m_namedTypes; + + /// Set of sub-schemas that serve as valid types + SchemaTypes m_schemaTypes; +}; + +/** + * @brief Represents a 'uniqueItems' constraint + */ +class UniqueItemsConstraint: public BasicConstraint +{ +public: + UniqueItemsConstraint() = default; + + UniqueItemsConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn) { } +}; + +} // namespace constraints +} // namespace valijson + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif +#pragma once + +namespace valijson { + +namespace adapters { + class Adapter; +} + +namespace constraints { + struct Constraint; +} + +class ConstraintBuilder +{ +public: + virtual ~ConstraintBuilder() = default; + + virtual constraints::Constraint * make(const adapters::Adapter &) const = 0; +}; + +} // namespace valijson +#pragma once + +#include +#include +#include +#include +#include + + +namespace valijson { + +/** + * @brief Parser for populating a Schema based on a JSON Schema document. + * + * The SchemaParser class supports Drafts 3 and 4 of JSON Schema, however + * Draft 3 support should be considered deprecated. + * + * The functions provided by this class have been templated so that they can + * be used with different Adapter types. + */ +class SchemaParser +{ +public: + /// Supported versions of JSON Schema + enum Version { + kDraft3, ///< @deprecated JSON Schema v3 has been superseded by v4 + kDraft4, + kDraft7 + }; + + /** + * @brief Construct a new SchemaParser for a given version of JSON Schema + * + * @param version Version of JSON Schema that will be expected + */ + explicit SchemaParser(const Version version = kDraft7) + : m_version(version) { } + + /** + * @brief Release memory associated with custom ConstraintBuilders + */ + virtual ~SchemaParser() + { + for (const auto& entry : constraintBuilders) { + delete entry.second; + } + } + + /** + * @brief Struct to contain templated function type for fetching documents + */ + template + struct FunctionPtrs + { + typedef typename adapters::AdapterTraits::DocumentType DocumentType; + + /// Templated function pointer type for fetching remote documents + typedef std::function FetchDoc; + + /// Templated function pointer type for freeing fetched documents + typedef std::function FreeDoc; + }; + + /** + * @brief Add a custom contraint to this SchemaParser + + * @param key name that will be used to identify relevant constraints + * while parsing a schema document + * @param builder pointer to a subclass of ConstraintBuilder that can + * parse custom constraints found in a schema document, + * and return an appropriate instance of Constraint; this + * class guarantees that it will take ownership of this + * pointer - unless this function throws an exception + * + * @todo consider accepting a list of custom ConstraintBuilders in + * constructor, so that this class remains immutable after + * construction + * + * @todo Add additional checks for key conflicts, empty keys, and + * potential restrictions relating to case sensitivity + */ + void addConstraintBuilder(const std::string &key, const ConstraintBuilder *builder) + { + constraintBuilders.push_back(std::make_pair(key, builder)); + } + + /** + * @brief Populate a Schema object from JSON Schema document + * + * When processing Draft 3 schemas, the parentSubschema and ownName pointers + * should be set in contexts where a 'required' constraint would be valid. + * These are used to add a RequiredConstraint object to the Schema that + * contains the required property. + * + * @param node Reference to node to parse + * @param schema Reference to Schema to populate + * @param fetchDoc Function to fetch remote JSON documents (optional) + */ + template + void populateSchema( + const AdapterType &node, + Schema &schema, + typename FunctionPtrs::FetchDoc fetchDoc = nullptr , + typename FunctionPtrs::FreeDoc freeDoc = nullptr ) + { + if ((fetchDoc == nullptr ) ^ (freeDoc == nullptr)) { + throwRuntimeError("Remote document fetching can't be enabled without both fetch and free functions"); + } + + typename DocumentCache::Type docCache; + SchemaCache schemaCache; +#if VALIJSON_USE_EXCEPTIONS + try { +#endif + resolveThenPopulateSchema(schema, node, node, schema, opt::optional(), "", fetchDoc, nullptr, + nullptr, docCache, schemaCache); +#if VALIJSON_USE_EXCEPTIONS + } catch (...) { + freeDocumentCache(docCache, freeDoc); + throw; + } +#endif + + freeDocumentCache(docCache, freeDoc); + } + +private: + + typedef std::vector> + ConstraintBuilders; + + ConstraintBuilders constraintBuilders; + + template + struct DocumentCache + { + typedef typename adapters::AdapterTraits::DocumentType DocumentType; + + typedef std::map Type; + }; + + typedef std::map SchemaCache; + + /** + * @brief Free memory used by fetched documents + * + * If a custom 'free' function has not been provided, then the default + * delete operator will be used. + * + * @param docCache collection of fetched documents to free + * @param freeDoc optional custom free function + */ + template + void freeDocumentCache(const typename DocumentCache::Type + &docCache, typename FunctionPtrs::FreeDoc freeDoc) + { + typedef typename DocumentCache::Type DocCacheType; + + for (const typename DocCacheType::value_type &v : docCache) { + freeDoc(v.second); + } + } + + /** + * @brief Find the complete URI for a document, within a resolution scope + * + * This function captures five different cases that can occur when + * attempting to resolve a document URI within a particular resolution + * scope: + * + * (1) resolution scope not present, but URN or absolute document URI is + * => document URI as-is + * (2) resolution scope not present, and document URI is relative or absent + * => document URI, if present, otherwise no result + * (3) resolution scope is present, and document URI is a relative path + * => resolve document URI relative to resolution scope + * (4) resolution scope is present, and document URI is absolute + * => document URI as-is + * (5) resolution scope is present, but document URI is not + * => resolution scope as-is + * + * This function assumes that the resolution scope is absolute. + * + * When resolving a document URI relative to the resolution scope, the + * document URI should be used to replace the path, query and fragment + * portions of URI provided by the resolution scope. + */ + virtual opt::optional resolveDocumentUri( + const opt::optional& resolutionScope, + const opt::optional& documentUri) + { + if (resolutionScope) { + if (documentUri) { + if (internal::uri::isUriAbsolute(*documentUri) || internal::uri::isUrn(*documentUri)) { + // (4) resolution scope is present, and document URI is absolute + // => document URI as-is + return *documentUri; + } else { + // (3) resolution scope is present, and document URI is a relative path + // => resolve document URI relative to resolution scope + return internal::uri::resolveRelativeUri(*resolutionScope, *documentUri); + } + } else { + // (5) resolution scope is present, but document URI is not + // => resolution scope as-is + return *resolutionScope; + } + } else if (documentUri && internal::uri::isUriAbsolute(*documentUri)) { + // (1a) resolution scope not present, but absolute document URI is + // => document URI as-is + return *documentUri; + } else if (documentUri && internal::uri::isUrn(*documentUri)) { + // (1b) resolution scope not present, but URN is + // => document URI as-is + return *documentUri; + } else { + // (2) resolution scope not present, and document URI is relative or absent + // => document URI, if present, otherwise no result + // documentUri is already std::optional + return documentUri; + } + } + + /** + * @brief Extract a JSON Reference string from a node + * + * @param node node to extract the JSON Reference from + * @param result reference to string to set with the result + * + * @throws std::invalid_argument if node is an object containing a `$ref` + * property but with a value that cannot be interpreted as a string + * + * @return \c true if a JSON Reference was extracted; \c false otherwise + */ + template + bool extractJsonReference(const AdapterType &node, std::string &result) + { + if (!node.isObject()) { + return false; + } + + const typename AdapterType::Object o = node.getObject(); + const typename AdapterType::Object::const_iterator itr = o.find("$ref"); + if (itr == o.end()) { + return false; + } else if (!itr->second.getString(result)) { + throwRuntimeError("$ref property expected to contain string value."); + } + + return true; + } + + /** + * Sanitise an optional JSON Pointer, trimming trailing slashes + */ + static std::string sanitiseJsonPointer(const opt::optional& input) + { + if (input) { + // Trim trailing slash(es) + std::string sanitised = *input; + sanitised.erase(sanitised.find_last_not_of('/') + 1, + std::string::npos); + + return sanitised; + } + + // If the JSON Pointer is not set, assume that the URI points to + // the root of the document + return ""; + } + + /** + * @brief Search the schema cache for a schema matching a given key + * + * If the key is not present in the query cache, a nullptr will be + * returned, and the contents of the cache will remain unchanged. This is + * in contrast to the behaviour of the std::map [] operator, which would + * add the nullptr to the cache. + * + * @param schemaCache schema cache to query + * @param queryKey key to search for + * + * @return shared pointer to Schema if found, nullptr otherwise + */ + static const Subschema * querySchemaCache(SchemaCache &schemaCache, + const std::string &queryKey) + { + const SchemaCache::iterator itr = schemaCache.find(queryKey); + if (itr == schemaCache.end()) { + return nullptr; + } + + return itr->second; + } + + /** + * @brief Add entries to the schema cache for a given list of keys + * + * @param schemaCache schema cache to update + * @param keysToCreate list of keys to create entries for + * @param schema shared pointer to schema that keys will map to + * + * @throws std::logic_error if any of the keys are already present in the + * schema cache. This behaviour is intended to help detect incorrect + * usage of the schema cache during development, and is not expected + * to occur otherwise, even for malformed schemas. + */ + static void updateSchemaCache(SchemaCache &schemaCache, + const std::vector &keysToCreate, + const Subschema *schema) + { + for (const std::string &keyToCreate : keysToCreate) { + const SchemaCache::value_type value(keyToCreate, schema); + if (!schemaCache.insert(value).second) { + throwLogicError("Key '" + keyToCreate + "' already in schema cache."); + } + } + } + + /** + * @brief Recursive helper function for retrieving or creating schemas + * + * This function will be applied recursively until a concrete node is found. + * A concrete node is a node that contains actual schema constraints rather + * than a JSON Reference. + * + * This termination condition may be trigged by visiting the concrete node + * at the end of a series of $ref nodes, or by finding a schema for one of + * those $ref nodes in the schema cache. An entry will be added to the + * schema cache for each node visited on the path to the concrete node. + * + * @param rootSchema The Schema instance, and root subschema, through + * which other subschemas can be created and + * modified + * @param rootNode Reference to the node from which JSON References + * will be resolved when they refer to the current + * document + * @param node Reference to the node to parse + * @param currentScope URI for current resolution scope + * @param nodePath JSON Pointer representing path to current node + * @param fetchDoc Function to fetch remote JSON documents (optional) + * @param parentSchema Optional pointer to the parent schema, used to + * support required keyword in Draft 3 + * @param ownName Optional pointer to a node name, used to support + * the 'required' keyword in Draft 3 + * @param docCache Cache of resolved and fetched remote documents + * @param schemaCache Cache of populated schemas + * @param newCacheKeys A list of keys that should be added to the cache + * when recursion terminates + */ + template + const Subschema * makeOrReuseSchema( + Schema &rootSchema, + const AdapterType &rootNode, + const AdapterType &node, + const opt::optional currentScope, + const std::string &nodePath, + const typename FunctionPtrs::FetchDoc fetchDoc, + const Subschema *parentSubschema, + const std::string *ownName, + typename DocumentCache::Type &docCache, + SchemaCache &schemaCache, + std::vector &newCacheKeys) + { + std::string jsonRef; + + // Check for the first termination condition (found a non-$ref node) + if (!extractJsonReference(node, jsonRef)) { + + // Construct a key that we can use to search the schema cache for + // a schema corresponding to the current node + const std::string schemaCacheKey = currentScope ? (*currentScope + nodePath) : nodePath; + + // Retrieve an existing schema from the cache if possible + const Subschema *cachedPtr = querySchemaCache(schemaCache, schemaCacheKey); + + // Create a new schema otherwise + const Subschema *subschema = cachedPtr ? cachedPtr : rootSchema.createSubschema(); + + // Add cache entries for keys belonging to any $ref nodes that were + // visited before arriving at the current node + updateSchemaCache(schemaCache, newCacheKeys, subschema); + + // Schema cache did not contain a pre-existing schema corresponding + // to the current node, so the schema that was returned will need + // to be populated + if (!cachedPtr) { + populateSchema(rootSchema, rootNode, node, *subschema, + currentScope, nodePath, fetchDoc, parentSubschema, + ownName, docCache, schemaCache); + } + + return subschema; + } + + // Returns a document URI if the reference points somewhere + // other than the current document + const opt::optional documentUri = internal::json_reference::getJsonReferenceUri(jsonRef); + + // Extract JSON Pointer from JSON Reference, with any trailing + // slashes removed so that keys in the schema cache end + // consistently + const std::string actualJsonPointer = sanitiseJsonPointer( + internal::json_reference::getJsonReferencePointer(jsonRef)); + + // Determine the actual document URI based on the resolution + // scope. An absolute document URI will take precedence when + // present, otherwise we need to resolve the URI relative to + // the current resolution scope + const opt::optional actualDocumentUri = resolveDocumentUri(currentScope, documentUri); + + // Construct a key to search the schema cache for an existing schema + const std::string queryKey = actualDocumentUri ? (*actualDocumentUri + actualJsonPointer) : actualJsonPointer; + + // Check for the second termination condition (found a $ref node that + // already has an entry in the schema cache) + const Subschema *cachedPtr = querySchemaCache(schemaCache, queryKey); + if (cachedPtr) { + updateSchemaCache(schemaCache, newCacheKeys, cachedPtr); + return cachedPtr; + } + + if (actualDocumentUri && (!currentScope || *actualDocumentUri != *currentScope)) { + const typename FunctionPtrs::DocumentType *newDoc = nullptr; + + // Have we seen this document before? + typename DocumentCache::Type::iterator docCacheItr = + docCache.find(*actualDocumentUri); + if (docCacheItr == docCache.end()) { + // Resolve reference against remote document + if (!fetchDoc) { + throwRuntimeError("Fetching of remote JSON References not enabled."); + } + + // Returns a pointer to the remote document that was + // retrieved, or null if retrieval failed. This class + // will take ownership of the pointer, and call freeDoc + // when it is no longer needed. + newDoc = fetchDoc(*actualDocumentUri); + + // Can't proceed without the remote document + if (!newDoc) { + throwRuntimeError("Failed to fetch referenced schema document: " + *actualDocumentUri); + } + + typedef typename DocumentCache::Type::value_type + DocCacheValueType; + + docCache.insert(DocCacheValueType(*actualDocumentUri, newDoc)); + + } else { + newDoc = docCacheItr->second; + } + + const AdapterType newRootNode(*newDoc); + + // Find where we need to be in the document + const AdapterType &referencedAdapter = + internal::json_pointer::resolveJsonPointer(newRootNode, + actualJsonPointer); + + newCacheKeys.push_back(queryKey); + + // Populate the schema, starting from the referenced node, with + // nested JSON References resolved relative to the new root node + return makeOrReuseSchema(rootSchema, newRootNode, referencedAdapter, + currentScope, actualJsonPointer, fetchDoc, parentSubschema, + ownName, docCache, schemaCache, newCacheKeys); + + } + + // JSON References in nested schema will be resolved relative to the + // current document + const AdapterType &referencedAdapter = + internal::json_pointer::resolveJsonPointer( + rootNode, actualJsonPointer); + + newCacheKeys.push_back(queryKey); + + // Populate the schema, starting from the referenced node, with + // nested JSON References resolved relative to the new root node + return makeOrReuseSchema(rootSchema, rootNode, referencedAdapter, + currentScope, actualJsonPointer, fetchDoc, parentSubschema, + ownName, docCache, schemaCache, newCacheKeys); + } + + /** + * @brief Return pointer for the schema corresponding to a given node + * + * This function makes use of a schema cache, so that if the path to the + * current node is the same as one that has already been parsed and + * populated, a pointer to the existing Subschema will be returned. + * + * Should a series of $ref, or reference, nodes be resolved before reaching + * a concrete node, an entry will be added to the schema cache for each of + * the nodes in that path. + * + * @param rootSchema The Schema instance, and root subschema, through + * which other subschemas can be created and + * modified + * @param rootNode Reference to the node from which JSON References + * will be resolved when they refer to the current + * document + * @param node Reference to the node to parse + * @param currentScope URI for current resolution scope + * @param nodePath JSON Pointer representing path to current node + * @param fetchDoc Function to fetch remote JSON documents (optional) + * @param parentSchema Optional pointer to the parent schema, used to + * support required keyword in Draft 3 + * @param ownName Optional pointer to a node name, used to support + * the 'required' keyword in Draft 3 + * @param docCache Cache of resolved and fetched remote documents + * @param schemaCache Cache of populated schemas + */ + template + const Subschema * makeOrReuseSchema( + Schema &rootSchema, + const AdapterType &rootNode, + const AdapterType &node, + const opt::optional currentScope, + const std::string &nodePath, + const typename FunctionPtrs::FetchDoc fetchDoc, + const Subschema *parentSubschema, + const std::string *ownName, + typename DocumentCache::Type &docCache, + SchemaCache &schemaCache) + { + std::vector schemaCacheKeysToCreate; + + return makeOrReuseSchema(rootSchema, rootNode, node, currentScope, + nodePath, fetchDoc, parentSubschema, ownName, docCache, + schemaCache, schemaCacheKeysToCreate); + } + + /** + * @brief Populate a Schema object from JSON Schema document + * + * When processing Draft 3 schemas, the parentSubschema and ownName pointers + * should be set in contexts where a 'required' constraint would be valid. + * These are used to add a RequiredConstraint object to the Schema that + * contains the required property. + * + * @param rootSchema The Schema instance, and root subschema, through + * which other subschemas can be created and + * modified + * @param rootNode Reference to the node from which JSON References + * will be resolved when they refer to the current + * document + * @param node Reference to node to parse + * @param schema Reference to Schema to populate + * @param currentScope URI for current resolution scope + * @param nodePath JSON Pointer representing path to current node + * @param fetchDoc Optional function to fetch remote JSON documents + * @param parentSubschema Optional pointer to the parent schema, used to + * support required keyword in Draft 3 + * @param ownName Optional pointer to a node name, used to support + * the 'required' keyword in Draft 3 + * @param docCache Cache of resolved and fetched remote documents + * @param schemaCache Cache of populated schemas + */ + template + void populateSchema( + Schema &rootSchema, + const AdapterType &rootNode, + const AdapterType &node, + const Subschema &subschema, + const opt::optional& currentScope, + const std::string &nodePath, + const typename FunctionPtrs::FetchDoc fetchDoc, + const Subschema *parentSubschema, + const std::string *ownName, + typename DocumentCache::Type &docCache, + SchemaCache &schemaCache) + { + static_assert((std::is_convertible::value), + "SchemaParser::populateSchema must be invoked with an " + "appropriate Adapter implementation"); + + if (!node.isObject()) { + if (m_version == kDraft7 && node.maybeBool()) { + // Boolean schema + if (!node.asBool()) { + rootSchema.setAlwaysInvalid(&subschema, true); + } + return; + } else { + std::string s; + s += "Expected node at "; + s += nodePath; + if (m_version == kDraft7) { + s += " to contain schema object or boolean value; actual node type is: "; + } else { + s += " to contain schema object; actual node type is: "; + } + s += internal::nodeTypeAsString(node); + throwRuntimeError(s); + } + } + + const typename AdapterType::Object object = node.asObject(); + typename AdapterType::Object::const_iterator itr(object.end()); + + // Check for 'id' attribute and update current scope + opt::optional updatedScope; + if ((itr = object.find("id")) != object.end() && itr->second.maybeString()) { + const std::string id = itr->second.asString(); + rootSchema.setSubschemaId(&subschema, itr->second.asString()); + if (!currentScope || internal::uri::isUriAbsolute(id) || internal::uri::isUrn(id)) { + updatedScope = id; + } else { + updatedScope = internal::uri::resolveRelativeUri(*currentScope, id); + } + } else { + updatedScope = currentScope; + } + + if ((itr = object.find("allOf")) != object.end()) { + rootSchema.addConstraintToSubschema( + makeAllOfConstraint(rootSchema, rootNode, itr->second, + updatedScope, nodePath + "/allOf", fetchDoc, + docCache, schemaCache), + &subschema); + } + + if ((itr = object.find("anyOf")) != object.end()) { + rootSchema.addConstraintToSubschema( + makeAnyOfConstraint(rootSchema, rootNode, itr->second, + updatedScope, nodePath + "/anyOf", fetchDoc, + docCache, schemaCache), + &subschema); + } + + if ((itr = object.find("const")) != object.end()) { + rootSchema.addConstraintToSubschema(makeConstConstraint(itr->second), &subschema); + } + + if ((itr = object.find("contains")) != object.end()) { + rootSchema.addConstraintToSubschema( + makeContainsConstraint(rootSchema, rootNode, itr->second, + updatedScope, nodePath + "/contains", fetchDoc, + docCache, schemaCache), &subschema); + } + + if ((itr = object.find("dependencies")) != object.end()) { + rootSchema.addConstraintToSubschema( + makeDependenciesConstraint(rootSchema, rootNode, + itr->second, updatedScope, + nodePath + "/dependencies", fetchDoc, docCache, + schemaCache), + &subschema); + } + + if ((itr = object.find("description")) != object.end()) { + if (itr->second.maybeString()) { + rootSchema.setSubschemaDescription(&subschema, + itr->second.asString()); + } else { + throwRuntimeError( + "'description' attribute should have a string value"); + } + } + + if ((itr = object.find("divisibleBy")) != object.end()) { + if (m_version == kDraft3) { + if (itr->second.maybeInteger()) { + rootSchema.addConstraintToSubschema( + makeMultipleOfIntConstraint(itr->second), + &subschema); + } else if (itr->second.maybeDouble()) { + rootSchema.addConstraintToSubschema( + makeMultipleOfDoubleConstraint(itr->second), + &subschema); + } else { + throwRuntimeError("Expected an numeric value for " + " 'divisibleBy' constraint."); + } + } else { + throwRuntimeError( + "'divisibleBy' constraint not valid after draft 3"); + } + } + + if ((itr = object.find("enum")) != object.end()) { + rootSchema.addConstraintToSubschema(makeEnumConstraint(itr->second), &subschema); + } + + { + const typename AdapterType::Object::const_iterator itemsItr = + object.find("items"); + + if (object.end() != itemsItr) { + if (!itemsItr->second.isArray()) { + rootSchema.addConstraintToSubschema( + makeSingularItemsConstraint(rootSchema, rootNode, + itemsItr->second, updatedScope, + nodePath + "/items", fetchDoc, docCache, + schemaCache), + &subschema); + + } else { + const typename AdapterType::Object::const_iterator + additionalItemsItr = object.find("additionalItems"); + rootSchema.addConstraintToSubschema( + makeLinearItemsConstraint(rootSchema, rootNode, + itemsItr != object.end() ? &itemsItr->second : nullptr, + additionalItemsItr != object.end() ? &additionalItemsItr->second : nullptr, + updatedScope, nodePath + "/items", + nodePath + "/additionalItems", fetchDoc, + docCache, schemaCache), + &subschema); + } + } + } + + { + const typename AdapterType::Object::const_iterator ifItr = object.find("if"); + const typename AdapterType::Object::const_iterator thenItr = object.find("then"); + const typename AdapterType::Object::const_iterator elseItr = object.find("else"); + + if (object.end() != ifItr) { + if (m_version == kDraft7) { + rootSchema.addConstraintToSubschema( + makeConditionalConstraint(rootSchema, rootNode, + ifItr->second, + thenItr == object.end() ? nullptr : &thenItr->second, + elseItr == object.end() ? nullptr : &elseItr->second, + updatedScope, nodePath, fetchDoc, docCache, schemaCache), + &subschema); + } else { + throwRuntimeError("Not supported"); + } + } + } + + if (m_version == kDraft7) { + if ((itr = object.find("exclusiveMaximum")) != object.end()) { + rootSchema.addConstraintToSubschema( + makeMaximumConstraintExclusive(itr->second), + &subschema); + } + + if ((itr = object.find("maximum")) != object.end()) { + rootSchema.addConstraintToSubschema( + makeMaximumConstraint(itr->second, nullptr), + &subschema); + } + } else if ((itr = object.find("maximum")) != object.end()) { + typename AdapterType::Object::const_iterator exclusiveMaximumItr = + object.find("exclusiveMaximum"); + if (exclusiveMaximumItr == object.end()) { + rootSchema.addConstraintToSubschema( + makeMaximumConstraint(itr->second, nullptr), + &subschema); + } else { + rootSchema.addConstraintToSubschema( + makeMaximumConstraint(itr->second, &exclusiveMaximumItr->second), + &subschema); + } + } else if (object.find("exclusiveMaximum") != object.end()) { + throwRuntimeError("'exclusiveMaximum' constraint only valid if a 'maximum' " + "constraint is also present"); + } + + if ((itr = object.find("maxItems")) != object.end()) { + rootSchema.addConstraintToSubschema( + makeMaxItemsConstraint(itr->second), &subschema); + } + + if ((itr = object.find("maxLength")) != object.end()) { + rootSchema.addConstraintToSubschema( + makeMaxLengthConstraint(itr->second), &subschema); + } + + if ((itr = object.find("maxProperties")) != object.end()) { + rootSchema.addConstraintToSubschema( + makeMaxPropertiesConstraint(itr->second), &subschema); + } + + if (m_version == kDraft7) { + if ((itr = object.find("exclusiveMinimum")) != object.end()) { + rootSchema.addConstraintToSubschema( + makeMinimumConstraintExclusive(itr->second), &subschema); + } + + if ((itr = object.find("minimum")) != object.end()) { + rootSchema.addConstraintToSubschema( + makeMinimumConstraint(itr->second, nullptr), + &subschema); + } + } else if ((itr = object.find("minimum")) != object.end()) { + typename AdapterType::Object::const_iterator exclusiveMinimumItr = object.find("exclusiveMinimum"); + if (exclusiveMinimumItr == object.end()) { + rootSchema.addConstraintToSubschema( + makeMinimumConstraint(itr->second, nullptr), + &subschema); + } else { + rootSchema.addConstraintToSubschema( + makeMinimumConstraint(itr->second, &exclusiveMinimumItr->second), + &subschema); + } + } else if (object.find("exclusiveMinimum") != object.end()) { + throwRuntimeError("'exclusiveMinimum' constraint only valid if a 'minimum' " + "constraint is also present"); + } + + if ((itr = object.find("minItems")) != object.end()) { + rootSchema.addConstraintToSubschema( + makeMinItemsConstraint(itr->second), &subschema); + } + + if ((itr = object.find("minLength")) != object.end()) { + rootSchema.addConstraintToSubschema( + makeMinLengthConstraint(itr->second), &subschema); + } + + if ((itr = object.find("minProperties")) != object.end()) { + rootSchema.addConstraintToSubschema( + makeMinPropertiesConstraint(itr->second), &subschema); + } + + if ((itr = object.find("multipleOf")) != object.end()) { + if (m_version == kDraft3) { + throwRuntimeError("'multipleOf' constraint not available in draft 3"); + } else if (itr->second.maybeInteger()) { + rootSchema.addConstraintToSubschema( + makeMultipleOfIntConstraint(itr->second), + &subschema); + } else if (itr->second.maybeDouble()) { + rootSchema.addConstraintToSubschema( + makeMultipleOfDoubleConstraint(itr->second), + &subschema); + } else { + throwRuntimeError("Expected an numeric value for 'divisibleBy' constraint."); + } + } + + if ((itr = object.find("not")) != object.end()) { + rootSchema.addConstraintToSubschema( + makeNotConstraint(rootSchema, rootNode, itr->second, updatedScope, nodePath + "/not", fetchDoc, + docCache, schemaCache), + &subschema); + } + + if ((itr = object.find("oneOf")) != object.end()) { + rootSchema.addConstraintToSubschema( + makeOneOfConstraint(rootSchema, rootNode, itr->second, updatedScope, nodePath + "/oneOf", fetchDoc, + docCache, schemaCache), + &subschema); + } + + if ((itr = object.find("pattern")) != object.end()) { + rootSchema.addConstraintToSubschema( + makePatternConstraint(itr->second), &subschema); + } + + { + // Check for schema keywords that require the creation of a + // PropertiesConstraint instance. + const typename AdapterType::Object::const_iterator + propertiesItr = object.find("properties"), + patternPropertiesItr = object.find("patternProperties"), + additionalPropertiesItr = object.find("additionalProperties"); + if (object.end() != propertiesItr || + object.end() != patternPropertiesItr || + object.end() != additionalPropertiesItr) { + rootSchema.addConstraintToSubschema( + makePropertiesConstraint(rootSchema, rootNode, + propertiesItr != object.end() ? &propertiesItr->second : nullptr, + patternPropertiesItr != object.end() ? &patternPropertiesItr->second : nullptr, + additionalPropertiesItr != object.end() ? &additionalPropertiesItr->second : nullptr, + updatedScope, nodePath + "/properties", + nodePath + "/patternProperties", + nodePath + "/additionalProperties", + fetchDoc, &subschema, docCache, schemaCache), + &subschema); + } + } + + if ((itr = object.find("propertyNames")) != object.end()) { + if (m_version == kDraft7) { + rootSchema.addConstraintToSubschema( + makePropertyNamesConstraint(rootSchema, rootNode, itr->second, updatedScope, + nodePath, fetchDoc, docCache, schemaCache), + &subschema); + } else { + throwRuntimeError("Not supported"); + } + } + + if ((itr = object.find("required")) != object.end()) { + if (m_version == kDraft3) { + if (parentSubschema && ownName) { + opt::optional constraint = + makeRequiredConstraintForSelf(itr->second, *ownName); + if (constraint) { + rootSchema.addConstraintToSubschema(*constraint, parentSubschema); + } + } else { + throwRuntimeError("'required' constraint not valid here"); + } + } else { + rootSchema.addConstraintToSubschema(makeRequiredConstraint(itr->second), &subschema); + } + } + + if ((itr = object.find("title")) != object.end()) { + if (itr->second.maybeString()) { + rootSchema.setSubschemaTitle(&subschema, itr->second.asString()); + } else { + throwRuntimeError("'title' attribute should have a string value"); + } + } + + if ((itr = object.find("type")) != object.end()) { + rootSchema.addConstraintToSubschema( + makeTypeConstraint(rootSchema, rootNode, itr->second, updatedScope, nodePath + "/type", fetchDoc, + docCache, schemaCache), + &subschema); + } + + if ((itr = object.find("uniqueItems")) != object.end()) { + opt::optional constraint = makeUniqueItemsConstraint(itr->second); + if (constraint) { + rootSchema.addConstraintToSubschema(*constraint, &subschema); + } + } + + for (const auto & constraintBuilder : constraintBuilders) { + if ((itr = object.find(constraintBuilder.first)) != object.end()) { + constraints::Constraint *constraint = nullptr; +#if VALIJSON_USE_EXCEPTIONS + try { +#endif + constraint = constraintBuilder.second->make(itr->second); + rootSchema.addConstraintToSubschema(*constraint, &subschema); + delete constraint; +#if VALIJSON_USE_EXCEPTIONS + } catch (...) { + delete constraint; + throw; + } +#endif + } + } + } + + /** + * @brief Resolves a chain of JSON References before populating a schema + * + * This helper function is used directly by the publicly visible + * populateSchema function. It ensures that the node being parsed is a + * concrete node, and not a JSON Reference. This function will call itself + * recursively to resolve references until a concrete node is found. + * + * @param rootSchema The Schema instance, and root subschema, through + * which other subschemas can be created and modified + * @param rootNode Reference to the node from which JSON References + * will be resolved when they refer to the current + * document + * @param node Reference to node to parse + * @param subschema Reference to Schema to populate + * @param currentScope URI for current resolution scope + * @param nodePath JSON Pointer representing path to current node + * @param fetchDoc Function to fetch remote JSON documents (optional) + * @param parentSchema Optional pointer to the parent schema, used to + * support required keyword in Draft 3 + * @param ownName Optional pointer to a node name, used to support + * the 'required' keyword in Draft 3 + * @param docCache Cache of resolved and fetched remote documents + * @param schemaCache Cache of populated schemas + */ + template + void resolveThenPopulateSchema( + Schema &rootSchema, + const AdapterType &rootNode, + const AdapterType &node, + const Subschema &subschema, + const opt::optional currentScope, + const std::string &nodePath, + const typename FunctionPtrs::FetchDoc fetchDoc, + const Subschema *parentSchema, + const std::string *ownName, + typename DocumentCache::Type &docCache, + SchemaCache &schemaCache) + { + std::string jsonRef; + if (!extractJsonReference(node, jsonRef)) { + populateSchema(rootSchema, rootNode, node, subschema, currentScope, nodePath, fetchDoc, parentSchema, + ownName, docCache, schemaCache); + return; + } + + // Returns a document URI if the reference points somewhere + // other than the current document + const opt::optional documentUri = internal::json_reference::getJsonReferenceUri(jsonRef); + + // Extract JSON Pointer from JSON Reference + const std::string actualJsonPointer = sanitiseJsonPointer( + internal::json_reference::getJsonReferencePointer(jsonRef)); + + if (documentUri && (internal::uri::isUriAbsolute(*documentUri) || internal::uri::isUrn(*documentUri))) { + // Resolve reference against remote document + if (!fetchDoc) { + throwRuntimeError("Fetching of remote JSON References not enabled."); + } + + const typename DocumentCache::DocumentType *newDoc = fetchDoc(*documentUri); + + // Can't proceed without the remote document + if (!newDoc) { + throwRuntimeError("Failed to fetch referenced schema document: " + *documentUri); + } + + // Add to document cache + typedef typename DocumentCache::Type::value_type DocCacheValueType; + + docCache.insert(DocCacheValueType(*documentUri, newDoc)); + + const AdapterType newRootNode(*newDoc); + + const AdapterType &referencedAdapter = + internal::json_pointer::resolveJsonPointer(newRootNode, actualJsonPointer); + + // TODO: Need to detect degenerate circular references + resolveThenPopulateSchema(rootSchema, newRootNode, referencedAdapter, subschema, {}, actualJsonPointer, + fetchDoc, parentSchema, ownName, docCache, schemaCache); + + } else { + const AdapterType &referencedAdapter = + internal::json_pointer::resolveJsonPointer(rootNode, actualJsonPointer); + + // TODO: Need to detect degenerate circular references + resolveThenPopulateSchema(rootSchema, rootNode, referencedAdapter, subschema, {}, actualJsonPointer, + fetchDoc, parentSchema, ownName, docCache, schemaCache); + } + } + + /** + * @brief Make a new AllOfConstraint object + * + * @param rootSchema The Schema instance, and root subschema, through + * which other subschemas can be created and modified + * @param rootNode Reference to the node from which JSON References + * will be resolved when they refer to the current + * document; used for recursive parsing of schemas + * @param node JSON node containing an array of child schemas + * @param currentScope URI for current resolution scope + * @param nodePath JSON Pointer representing path to current node + * @param fetchDoc Function to fetch remote JSON documents (optional) + * @param docCache Cache of resolved and fetched remote documents + * @param schemaCache Cache of populated schemas + * + * @return pointer to a new AllOfConstraint object that belongs to the + * caller + */ + template + constraints::AllOfConstraint makeAllOfConstraint( + Schema &rootSchema, + const AdapterType &rootNode, + const AdapterType &node, + const opt::optional currentScope, + const std::string &nodePath, + const typename FunctionPtrs::FetchDoc fetchDoc, + typename DocumentCache::Type &docCache, + SchemaCache &schemaCache) + { + if (!node.maybeArray()) { + throwRuntimeError("Expected array value for 'allOf' constraint."); + } + + constraints::AllOfConstraint constraint; + + int index = 0; + for (const AdapterType schemaNode : node.asArray()) { + if (schemaNode.maybeObject() || (m_version == kDraft7 && schemaNode.isBool())) { + const std::string childPath = nodePath + "/" + std::to_string(index); + const Subschema *subschema = makeOrReuseSchema( + rootSchema, rootNode, schemaNode, currentScope, + childPath, fetchDoc, nullptr, nullptr, docCache, schemaCache); + constraint.addSubschema(subschema); + index++; + } else { + throwRuntimeError("Expected element to be a valid schema in 'allOf' constraint."); + } + } + + return constraint; + } + + /** + * @brief Make a new AnyOfConstraint object + * + * @param rootSchema The Schema instance, and root subschema, through + * which other subschemas can be created and modified + * @param rootNode Reference to the node from which JSON References + * will be resolved when they refer to the current + * document; used for recursive parsing of schemas + * @param node JSON node containing an array of child schemas + * @param currentScope URI for current resolution scope + * @param nodePath JSON Pointer representing path to current node + * @param fetchDoc Function to fetch remote JSON documents (optional) + * @param docCache Cache of resolved and fetched remote documents + * @param schemaCache Cache of populated schemas + * + * @return pointer to a new AnyOfConstraint object that belongs to the + * caller + */ + template + constraints::AnyOfConstraint makeAnyOfConstraint( + Schema &rootSchema, + const AdapterType &rootNode, + const AdapterType &node, + const opt::optional currentScope, + const std::string &nodePath, + const typename FunctionPtrs::FetchDoc fetchDoc, + typename DocumentCache::Type &docCache, + SchemaCache &schemaCache) + { + if (!node.maybeArray()) { + throwRuntimeError("Expected array value for 'anyOf' constraint."); + } + + constraints::AnyOfConstraint constraint; + + int index = 0; + for (const AdapterType schemaNode : node.asArray()) { + if (schemaNode.maybeObject() || (m_version == kDraft7 && schemaNode.isBool())) { + const std::string childPath = nodePath + "/" + std::to_string(index); + const Subschema *subschema = makeOrReuseSchema( + rootSchema, rootNode, schemaNode, currentScope, + childPath, fetchDoc, nullptr, nullptr, docCache, schemaCache); + constraint.addSubschema(subschema); + index++; + } else { + throwRuntimeError("Expected array element to be a valid schema in 'anyOf' constraint."); + } + } + + return constraint; + } + + /** + * @brief Make a new ConditionalConstraint object. + * + * @param rootSchema The Schema instance, and root subschema, + * through which other subschemas can be + * created and modified + * @param rootNode Reference to the node from which JSON + * References will be resolved when they refer + * to the current document; used for recursive + * parsing of schemas + * @param ifNode Schema that will be used to evaluate the + * conditional. + * @param thenNode Optional pointer to a JSON node containing + * a schema that will be used when the conditional + * evaluates to true. + * @param elseNode Optional pointer to a JSON node containing + * a schema that will be used when the conditional + * evaluates to false. + * @param currentScope URI for current resolution scope + * @param containsPath JSON Pointer representing the path to + * the 'contains' node + * @param fetchDoc Function to fetch remote JSON documents + * (optional) + * @param docCache Cache of resolved and fetched remote + * documents + * @param schemaCache Cache of populated schemas + * + * @return pointer to a new ContainsConstraint that belongs to the caller + */ + template + constraints::ConditionalConstraint makeConditionalConstraint( + Schema &rootSchema, + const AdapterType &rootNode, + const AdapterType &ifNode, + const AdapterType *thenNode, + const AdapterType *elseNode, + const opt::optional currentScope, + const std::string &nodePath, + const typename FunctionPtrs::FetchDoc fetchDoc, + typename DocumentCache::Type &docCache, + SchemaCache &schemaCache) + { + constraints::ConditionalConstraint constraint; + + const Subschema *ifSubschema = makeOrReuseSchema( + rootSchema, rootNode, ifNode, currentScope, + nodePath + "/if", fetchDoc, nullptr, nullptr, docCache, + schemaCache); + constraint.setIfSubschema(ifSubschema); + + if (thenNode) { + const Subschema *thenSubschema = makeOrReuseSchema( + rootSchema, rootNode, *thenNode, currentScope, nodePath + "/then", fetchDoc, nullptr, + nullptr, docCache, schemaCache); + constraint.setThenSubschema(thenSubschema); + } + + if (elseNode) { + const Subschema *elseSubschema = makeOrReuseSchema( + rootSchema, rootNode, *elseNode, currentScope, nodePath + "/else", fetchDoc, nullptr, + nullptr, docCache, schemaCache); + constraint.setElseSubschema(elseSubschema); + } + + return constraint; + } + + /** + * @brief Make a new ConstConstraint object. + * + * @param node JSON node containing an arbitrary value + * + * @return pointer to a new MinimumConstraint that belongs to the caller + */ + template + constraints::ConstConstraint makeConstConstraint(const AdapterType &node) + { + constraints::ConstConstraint constraint; + constraint.setValue(node); + return constraint; + } + + /** + * @brief Make a new ContainsConstraint object. + * + * @param rootSchema The Schema instance, and root subschema, + * through which other subschemas can be + * created and modified + * @param rootNode Reference to the node from which JSON + * References will be resolved when they refer + * to the current document; used for recursive + * parsing of schemas + * @param contains Optional pointer to a JSON node containing + * an object mapping property names to + * schemas. + * @param currentScope URI for current resolution scope + * @param containsPath JSON Pointer representing the path to + * the 'contains' node + * @param fetchDoc Function to fetch remote JSON documents + * (optional) + * @param docCache Cache of resolved and fetched remote + * documents + * @param schemaCache Cache of populated schemas + * + * @return pointer to a new ContainsConstraint that belongs to the caller + */ + template + constraints::ContainsConstraint makeContainsConstraint( + Schema &rootSchema, + const AdapterType &rootNode, + const AdapterType &contains, + const opt::optional currentScope, + const std::string &containsPath, + const typename FunctionPtrs::FetchDoc fetchDoc, + typename DocumentCache::Type &docCache, + SchemaCache &schemaCache) + { + constraints::ContainsConstraint constraint; + + if (contains.isObject() || (m_version == kDraft7 && contains.maybeBool())) { + const Subschema *subschema = makeOrReuseSchema( + rootSchema, rootNode, contains, currentScope, containsPath, + fetchDoc, nullptr, nullptr, docCache, schemaCache); + constraint.setSubschema(subschema); + + } else if (contains.maybeObject()) { + // If a loosely-typed Adapter type is being used, then we'll + // assume that an empty schema has been provided. + constraint.setSubschema(rootSchema.emptySubschema()); + + } else { + // All other formats will result in an exception being thrown. + throwRuntimeError("Expected valid schema for 'contains' constraint."); + } + + return constraint; + } + + /** + * @brief Make a new DependenciesConstraint object + * + * The dependencies for a property can be defined several ways. When parsing + * a Draft 4 schema, the following can be used: + * - an array that lists the name of each property that must be present + * if the dependent property is present + * - an object that specifies a schema which must be satisfied if the + * dependent property is present + * + * When parsing a Draft 3 schema, in addition to the formats above, the + * following format can be used: + * - a string that names a single property that must be present if the + * dependent property is presnet + * + * Multiple methods can be used in the same dependency constraint. + * + * If the format of any part of the the dependency node does not match one + * of these formats, an exception will be thrown. + * + * @param rootSchema The Schema instance, and root subschema, through + * which other subschemas can be created and modified + * @param rootNode Reference to the node from which JSON References + * will be resolved when they refer to the current + * document; used for recursive parsing of schemas + * @param node JSON node containing an object that defines a + * mapping of properties to their dependencies. + * @param currentScope URI for current resolution scope + * @param nodePath JSON Pointer representing path to current node + * @param fetchDoc Function to fetch remote JSON documents (optional) + * @param docCache Cache of resolved and fetched remote documents + * @param schemaCache Cache of populated schemas + * + * @return pointer to a new DependencyConstraint that belongs to the + * caller + */ + template + constraints::DependenciesConstraint makeDependenciesConstraint( + Schema &rootSchema, + const AdapterType &rootNode, + const AdapterType &node, + const opt::optional currentScope, + const std::string &nodePath, + const typename FunctionPtrs::FetchDoc fetchDoc, + typename DocumentCache::Type &docCache, + SchemaCache &schemaCache) + { + if (!node.maybeObject()) { + throwRuntimeError("Expected valid subschema for 'dependencies' constraint."); + } + + constraints::DependenciesConstraint dependenciesConstraint; + + // Process each of the dependency mappings defined by the object + for (const typename AdapterType::ObjectMember member : node.asObject()) { + + // First, we attempt to parse the value of the dependency mapping + // as an array of strings. If the Adapter type does not support + // strict types, then an empty string or empty object will be cast + // to an array, and the resulting dependency list will be empty. + // This is equivalent to using an empty object, but does mean that + // if the user provides an actual string then this error will not + // be detected. + if (member.second.maybeArray()) { + // Parse an array of dependency names + std::vector dependentPropertyNames; + for (const AdapterType dependencyName : member.second.asArray()) { + if (dependencyName.maybeString()) { + dependentPropertyNames.push_back(dependencyName.getString()); + } else { + throwRuntimeError("Expected string value in dependency list of property '" + + member.first + "' in 'dependencies' constraint."); + } + } + + dependenciesConstraint.addPropertyDependencies(member.first, + dependentPropertyNames); + + // If the value of dependency mapping could not be processed as an + // array, we'll try to process it as an object instead. Note that + // strict type comparison is used here, since we've already + // exercised the flexibility by loosely-typed Adapter types. If the + // value of the dependency mapping is an object, then we'll try to + // process it as a dependent schema. + } else if (member.second.isObject() || (m_version == kDraft7 && member.second.maybeBool())) { + // Parse dependent subschema + const Subschema *childSubschema = + makeOrReuseSchema(rootSchema, rootNode, + member.second, currentScope, nodePath, fetchDoc, + nullptr, nullptr, docCache, schemaCache); + dependenciesConstraint.addSchemaDependency(member.first, + childSubschema); + + // If we're supposed to be parsing a Draft3 schema, then the value + // of the dependency mapping can also be a string containing the + // name of a single dependency. + } else if (m_version == kDraft3 && member.second.isString()) { + dependenciesConstraint.addPropertyDependency(member.first, + member.second.getString()); + + // All other types result in an exception being thrown. + } else { + throwRuntimeError("Invalid dependencies definition."); + } + } + + return dependenciesConstraint; + } + + /** + * @brief Make a new EnumConstraint object. + * + * @param node JSON node containing an array of values permitted by the + * constraint. + * + * @return pointer to a new EnumConstraint that belongs to the caller + */ + template + constraints::EnumConstraint makeEnumConstraint( + const AdapterType &node) + { + // Make a copy of each value in the enum array + constraints::EnumConstraint constraint; + for (const AdapterType value : node.getArray()) { + constraint.addValue(value); + } + + /// @todo This will make another copy of the values while constructing + /// the EnumConstraint. Move semantics in C++11 should make it possible + /// to avoid these copies without complicating the implementation of the + /// EnumConstraint class. + return constraint; + } + + /** + * @brief Make a new ItemsConstraint object. + * + * @param rootSchema The Schema instance, and root subschema, + * through which other subschemas can be + * created and modified + * @param rootNode Reference to the node from which JSON + * References will be resolved when they refer + * to the current document; used for recursive + * parsing of schemas + * @param items Optional pointer to a JSON node containing + * an object mapping property names to + * schemas. + * @param additionalItems Optional pointer to a JSON node containing + * an additional properties schema or a + * boolean value. + * @param currentScope URI for current resolution scope + * @param itemsPath JSON Pointer representing the path to + * the 'items' node + * @param additionalItemsPath JSON Pointer representing the path to + * the 'additionalItems' node + * @param fetchDoc Function to fetch remote JSON documents + * (optional) + * @param docCache Cache of resolved and fetched remote + * documents + * @param schemaCache Cache of populated schemas + * + * @return pointer to a new ItemsConstraint that belongs to the caller + */ + template + constraints::LinearItemsConstraint makeLinearItemsConstraint( + Schema &rootSchema, + const AdapterType &rootNode, + const AdapterType *items, + const AdapterType *additionalItems, + const opt::optional currentScope, + const std::string &itemsPath, + const std::string &additionalItemsPath, + const typename FunctionPtrs::FetchDoc fetchDoc, + typename DocumentCache::Type &docCache, + SchemaCache &schemaCache) + { + constraints::LinearItemsConstraint constraint; + + // Construct a Schema object for the the additionalItems constraint, + // if the additionalItems property is present + if (additionalItems) { + if (additionalItems->maybeBool()) { + // If the value of the additionalItems property is a boolean + // and is set to true, then additional array items do not need + // to satisfy any constraints. + if (additionalItems->asBool()) { + constraint.setAdditionalItemsSubschema(rootSchema.emptySubschema()); + } + } else if (additionalItems->maybeObject()) { + // If the value of the additionalItems property is an object, + // then it should be parsed into a Schema object, which will be + // used to validate additional array items. + const Subschema *subschema = makeOrReuseSchema( + rootSchema, rootNode, *additionalItems, currentScope, + additionalItemsPath, fetchDoc, nullptr, nullptr, docCache, + schemaCache); + constraint.setAdditionalItemsSubschema(subschema); + } else { + // Any other format for the additionalItems property will result + // in an exception being thrown. + throwRuntimeError("Expected bool or object value for 'additionalItems'"); + } + } else { + // The default value for the additionalItems property is an empty + // object, which means that additional array items do not need to + // satisfy any constraints. + constraint.setAdditionalItemsSubschema(rootSchema.emptySubschema()); + } + + // Construct a Schema object for each item in the items array. + // If the items constraint is not provided, then array items + // will be validated against the additionalItems schema. + if (items) { + if (items->isArray()) { + // If the items constraint contains an array, then it should + // contain a list of child schemas which will be used to + // validate the values at the corresponding indexes in a target + // array. + int index = 0; + for (const AdapterType v : items->getArray()) { + const std::string childPath = itemsPath + "/" + + std::to_string(index); + const Subschema *subschema = makeOrReuseSchema( + rootSchema, rootNode, v, currentScope, childPath, + fetchDoc, nullptr, nullptr, docCache, schemaCache); + constraint.addItemSubschema(subschema); + index++; + } + } else { + throwRuntimeError("Expected array value for non-singular 'items' constraint."); + } + } + + return constraint; + } + + /** + * @brief Make a new ItemsConstraint object. + * + * @param rootSchema The Schema instance, and root subschema, + * through which other subschemas can be + * created and modified + * @param rootNode Reference to the node from which JSON + * References will be resolved when they refer + * to the current document; used for recursive + * parsing of schemas + * @param items Optional pointer to a JSON node containing + * an object mapping property names to + * schemas. + * @param additionalItems Optional pointer to a JSON node containing + * an additional properties schema or a + * boolean value. + * @param currentScope URI for current resolution scope + * @param itemsPath JSON Pointer representing the path to + * the 'items' node + * @param additionalItemsPath JSON Pointer representing the path to + * the 'additionalItems' node + * @param fetchDoc Function to fetch remote JSON documents + * (optional) + * @param docCache Cache of resolved and fetched remote + * documents + * @param schemaCache Cache of populated schemas + * + * @return pointer to a new ItemsConstraint that belongs to the caller + */ + template + constraints::SingularItemsConstraint makeSingularItemsConstraint( + Schema &rootSchema, + const AdapterType &rootNode, + const AdapterType &items, + const opt::optional currentScope, + const std::string &itemsPath, + const typename FunctionPtrs::FetchDoc fetchDoc, + typename DocumentCache::Type &docCache, + SchemaCache &schemaCache) + { + constraints::SingularItemsConstraint constraint; + + // Construct a Schema object for each item in the items array, if an + // array is provided, or a single Schema object, in an object value is + // provided. If the items constraint is not provided, then array items + // will be validated against the additionalItems schema. + if (items.isObject() || (m_version == kDraft7 && items.maybeBool())) { + // If the items constraint contains an object value, then it + // should contain a Schema that will be used to validate all + // items in a target array. Any schema defined by the + // additionalItems constraint will be ignored. + const Subschema *subschema = makeOrReuseSchema( + rootSchema, rootNode, items, currentScope, itemsPath, + fetchDoc, nullptr, nullptr, docCache, schemaCache); + constraint.setItemsSubschema(subschema); + + } else if (items.maybeObject()) { + // If a loosely-typed Adapter type is being used, then we'll + // assume that an empty schema has been provided. + constraint.setItemsSubschema(rootSchema.emptySubschema()); + + } else { + // All other formats will result in an exception being thrown. + throwRuntimeError("Expected valid schema for singular 'items' constraint."); + } + + return constraint; + } + + /** + * @brief Make a new MaximumConstraint object (draft 3 and 4). + * + * @param rootSchema The Schema instance, and root subschema, + * through which other subschemas can be + * created and modified + * @param rootNode Reference to the node from which JSON + * References will be resolved when they refer + * to the current document; used for recursive + * parsing of schemas + * @param node JSON node containing the maximum value. + * @param exclusiveMaximum Optional pointer to a JSON boolean value that + * indicates whether maximum value is excluded + * from the range of permitted values. + * + * @return pointer to a new MaximumConstraint that belongs to the caller + */ + template + constraints::MaximumConstraint makeMaximumConstraint( + const AdapterType &node, + const AdapterType *exclusiveMaximum) + { + if (!node.maybeDouble()) { + throwRuntimeError("Expected numeric value for maximum constraint."); + } + + constraints::MaximumConstraint constraint; + constraint.setMaximum(node.asDouble()); + + if (exclusiveMaximum) { + if (!exclusiveMaximum->maybeBool()) { + throwRuntimeError("Expected boolean value for exclusiveMaximum constraint."); + } + + constraint.setExclusiveMaximum(exclusiveMaximum->asBool()); + } + + return constraint; + } + + /** + * @brief Make a new MaximumConstraint object that is always exclusive (draft 7). + * + * @param node JSON node containing an integer, representing the maximum value. + * + * @param exclusive Optional pointer to a JSON boolean value that indicates whether the + * maximum value is excluded from the range of permitted values. + * + * @return pointer to a new Maximum that belongs to the caller + */ + template + constraints::MaximumConstraint makeMaximumConstraintExclusive(const AdapterType &node) + { + if (!node.maybeDouble()) { + throwRuntimeError("Expected numeric value for exclusiveMaximum constraint."); + } + + constraints::MaximumConstraint constraint; + constraint.setMaximum(node.asDouble()); + constraint.setExclusiveMaximum(true); + return constraint; + } + + /** + * @brief Make a new MaxItemsConstraint object. + * + * @param node JSON node containing an integer value representing the + * maximum number of items that may be contaned by an array. + * + * @return pointer to a new MaxItemsConstraint that belongs to the caller. + */ + template + constraints::MaxItemsConstraint makeMaxItemsConstraint( + const AdapterType &node) + { + if (node.maybeInteger()) { + const int64_t value = node.asInteger(); + if (value >= 0) { + constraints::MaxItemsConstraint constraint; + constraint.setMaxItems(value); + return constraint; + } + } + + throwRuntimeError("Expected non-negative integer value for 'maxItems' constraint."); + } + + /** + * @brief Make a new MaxLengthConstraint object. + * + * @param node JSON node containing an integer value representing the + * maximum length of a string. + * + * @return pointer to a new MaxLengthConstraint that belongs to the caller + */ + template + constraints::MaxLengthConstraint makeMaxLengthConstraint( + const AdapterType &node) + { + if (node.maybeInteger()) { + const int64_t value = node.asInteger(); + if (value >= 0) { + constraints::MaxLengthConstraint constraint; + constraint.setMaxLength(value); + return constraint; + } + } + + throwRuntimeError("Expected a non-negative integer value for 'maxLength' constraint."); + } + + /** + * @brief Make a new MaxPropertiesConstraint object. + * + * @param node JSON node containing an integer value representing the + * maximum number of properties that may be contained by an + * object. + * + * @return pointer to a new MaxPropertiesConstraint that belongs to the + * caller + */ + template + constraints::MaxPropertiesConstraint makeMaxPropertiesConstraint( + const AdapterType &node) + { + if (node.maybeInteger()) { + int64_t value = node.asInteger(); + if (value >= 0) { + constraints::MaxPropertiesConstraint constraint; + constraint.setMaxProperties(value); + return constraint; + } + } + + throwRuntimeError("Expected a non-negative integer for 'maxProperties' constraint."); + } + + /** + * @brief Make a new MinimumConstraint object (draft 3 and 4). + * + * @param node JSON node containing an integer, representing + * the minimum value. + * + * @param exclusiveMaximum Optional pointer to a JSON boolean value that + * indicates whether the minimum value is + * excluded from the range of permitted values. + * + * @return pointer to a new MinimumConstraint that belongs to the caller + */ + template + constraints::MinimumConstraint makeMinimumConstraint( + const AdapterType &node, + const AdapterType *exclusiveMinimum) + { + if (!node.maybeDouble()) { + throwRuntimeError("Expected numeric value for minimum constraint."); + } + + constraints::MinimumConstraint constraint; + constraint.setMinimum(node.asDouble()); + + if (exclusiveMinimum) { + if (!exclusiveMinimum->maybeBool()) { + throwRuntimeError("Expected boolean value for 'exclusiveMinimum' constraint."); + } + + constraint.setExclusiveMinimum(exclusiveMinimum->asBool()); + } + + return constraint; + } + + /** + * @brief Make a new MinimumConstraint object that is always exclusive (draft 7). + * + * @param node JSON node containing an integer, representing the minimum value. + * + * @param exclusive Optional pointer to a JSON boolean value that indicates whether the + * minimum value is excluded from the range of permitted values. + * + * @return pointer to a new MinimumConstraint that belongs to the caller + */ + template + constraints::MinimumConstraint makeMinimumConstraintExclusive(const AdapterType &node) + { + if (!node.maybeDouble()) { + throwRuntimeError("Expected numeric value for exclusiveMinimum constraint."); + } + + constraints::MinimumConstraint constraint; + constraint.setMinimum(node.asDouble()); + constraint.setExclusiveMinimum(true); + return constraint; + } + + /** + * @brief Make a new MinItemsConstraint object. + * + * @param node JSON node containing an integer value representing the + * minimum number of items that may be contained by an array. + * + * @return pointer to a new MinItemsConstraint that belongs to the caller + */ + template + constraints::MinItemsConstraint makeMinItemsConstraint(const AdapterType &node) + { + if (node.maybeInteger()) { + const int64_t value = node.asInteger(); + if (value >= 0) { + constraints::MinItemsConstraint constraint; + constraint.setMinItems(value); + return constraint; + } + } + + throwRuntimeError("Expected a non-negative integer value for 'minItems' constraint."); + } + + /** + * @brief Make a new MinLengthConstraint object. + * + * @param node JSON node containing an integer value representing the + * minimum length of a string. + * + * @return pointer to a new MinLengthConstraint that belongs to the caller + */ + template + constraints::MinLengthConstraint makeMinLengthConstraint(const AdapterType &node) + { + if (node.maybeInteger()) { + const int64_t value = node.asInteger(); + if (value >= 0) { + constraints::MinLengthConstraint constraint; + constraint.setMinLength(value); + return constraint; + } + } + + throwRuntimeError("Expected a non-negative integer value for 'minLength' constraint."); + } + + + /** + * @brief Make a new MaxPropertiesConstraint object. + * + * @param node JSON node containing an integer value representing the + * minimum number of properties that may be contained by an + * object. + * + * @return pointer to a new MinPropertiesConstraint that belongs to the + * caller + */ + template + constraints::MinPropertiesConstraint makeMinPropertiesConstraint(const AdapterType &node) + { + if (node.maybeInteger()) { + int64_t value = node.asInteger(); + if (value >= 0) { + constraints::MinPropertiesConstraint constraint; + constraint.setMinProperties(value); + return constraint; + } + } + + throwRuntimeError("Expected a non-negative integer for 'minProperties' constraint."); + } + + /** + * @brief Make a new MultipleOfDoubleConstraint object + * + * @param node JSON node containing an numeric value that a target value + * must divide by in order to satisfy this constraint + * + * @return a MultipleOfConstraint + */ + template + constraints::MultipleOfDoubleConstraint makeMultipleOfDoubleConstraint(const AdapterType &node) + { + constraints::MultipleOfDoubleConstraint constraint; + constraint.setDivisor(node.asDouble()); + return constraint; + } + + /** + * @brief Make a new MultipleOfIntConstraint object + * + * @param node JSON node containing a numeric value that a target value + * must divide by in order to satisfy this constraint + * + * @return a MultipleOfIntConstraint + */ + template + constraints::MultipleOfIntConstraint makeMultipleOfIntConstraint(const AdapterType &node) + { + constraints::MultipleOfIntConstraint constraint; + constraint.setDivisor(node.asInteger()); + return constraint; + } + + /** + * @brief Make a new NotConstraint object + * + * @param rootSchema The Schema instance, and root subschema, through + * which other subschemas can be created and modified + * @param rootNode Reference to the node from which JSON References + * will be resolved when they refer to the current + * document; used for recursive parsing of schemas + * @param node JSON node containing a schema + * @param currentScope URI for current resolution scope + * @param nodePath JSON Pointer representing path to current node + * @param fetchDoc Function to fetch remote JSON documents (optional) + * @param docCache Cache of resolved and fetched remote documents + * @param schemaCache Cache of populated schemas + * + * @return pointer to a new NotConstraint object that belongs to the caller + */ + template + constraints::NotConstraint makeNotConstraint( + Schema &rootSchema, + const AdapterType &rootNode, + const AdapterType &node, + const opt::optional currentScope, + const std::string &nodePath, + const typename FunctionPtrs::FetchDoc fetchDoc, + typename DocumentCache::Type &docCache, + SchemaCache &schemaCache) + { + if (node.maybeObject() || (m_version == kDraft7 && node.maybeBool())) { + const Subschema *subschema = makeOrReuseSchema( + rootSchema, rootNode, node, currentScope, nodePath, + fetchDoc, nullptr, nullptr, docCache, schemaCache); + constraints::NotConstraint constraint; + constraint.setSubschema(subschema); + return constraint; + } + + throwRuntimeError("Expected object value for 'not' constraint."); + } + + /** + * @brief Make a new OneOfConstraint object + * + * @param rootSchema The Schema instance, and root subschema, through + * which other subschemas can be created and modified + * @param rootNode Reference to the node from which JSON References + * will be resolved when they refer to the current + * document; used for recursive parsing of schemas + * @param node JSON node containing an array of child schemas + * @param currentScope URI for current resolution scope + * @param nodePath JSON Pointer representing path to current node + * @param fetchDoc Function to fetch remote JSON documents (optional) + * @param docCache Cache of resolved and fetched remote documents + * @param schemaCache Cache of populated schemas + * + * @return pointer to a new OneOfConstraint that belongs to the caller + */ + template + constraints::OneOfConstraint makeOneOfConstraint( + Schema &rootSchema, + const AdapterType &rootNode, + const AdapterType &node, + const opt::optional currentScope, + const std::string &nodePath, + const typename FunctionPtrs::FetchDoc fetchDoc, + typename DocumentCache::Type &docCache, + SchemaCache &schemaCache) + { + constraints::OneOfConstraint constraint; + + int index = 0; + for (const AdapterType schemaNode : node.getArray()) { + const std::string childPath = nodePath + "/" + std::to_string(index); + const Subschema *subschema = makeOrReuseSchema( + rootSchema, rootNode, schemaNode, currentScope, childPath, + fetchDoc, nullptr, nullptr, docCache, schemaCache); + constraint.addSubschema(subschema); + index++; + } + + return constraint; + } + + /** + * @brief Make a new PatternConstraint object. + * + * @param node JSON node containing a pattern string + * + * @return pointer to a new PatternConstraint object that belongs to the + * caller + */ + template + constraints::PatternConstraint makePatternConstraint( + const AdapterType &node) + { + constraints::PatternConstraint constraint; + constraint.setPattern(node.getString()); + return constraint; + } + + /** + * @brief Make a new Properties object. + * + * @param rootSchema The Schema instance, and root + * subschema, through which other + * subschemas can be created and modified + * @param rootNode Reference to the node from which JSON + * References will be resolved when they + * refer to the current document; used + * for recursive parsing of schemas + * @param properties Optional pointer to a JSON node + * containing an object mapping property + * names to schemas. + * @param patternProperties Optional pointer to a JSON node + * containing an object mapping pattern + * property names to schemas. + * @param additionalProperties Optional pointer to a JSON node + * containing an additional properties + * schema or a boolean value. + * @param currentScope URI for current resolution scope + * @param propertiesPath JSON Pointer representing the path to + * the 'properties' node + * @param patternPropertiesPath JSON Pointer representing the path to + * the 'patternProperties' node + * @param additionalPropertiesPath JSON Pointer representing the path to + * the 'additionalProperties' node + * @param fetchDoc Function to fetch remote JSON + * documents (optional) + * @param parentSubschema Optional pointer to the Schema of the + * parent object, needed to support the + * 'required' keyword in Draft 3 + * @param docCache Cache of resolved and fetched remote + * documents + * @param schemaCache Cache of populated schemas + * + * @return pointer to a new Properties that belongs to the caller + */ + template + constraints::PropertiesConstraint makePropertiesConstraint( + Schema &rootSchema, + const AdapterType &rootNode, + const AdapterType *properties, + const AdapterType *patternProperties, + const AdapterType *additionalProperties, + const opt::optional currentScope, + const std::string &propertiesPath, + const std::string &patternPropertiesPath, + const std::string &additionalPropertiesPath, + const typename FunctionPtrs::FetchDoc fetchDoc, + const Subschema *parentSubschema, + typename DocumentCache::Type &docCache, + SchemaCache &schemaCache) + { + typedef typename AdapterType::ObjectMember Member; + + constraints::PropertiesConstraint constraint; + + // Create subschemas for 'properties' constraint + if (properties) { + for (const Member m : properties->getObject()) { + const std::string &property = m.first; + const std::string childPath = propertiesPath + "/" + property; + const Subschema *subschema = makeOrReuseSchema( + rootSchema, rootNode, m.second, currentScope, childPath, + fetchDoc, parentSubschema, &property, docCache, + schemaCache); + constraint.addPropertySubschema(property, subschema); + } + } + + // Create subschemas for 'patternProperties' constraint + if (patternProperties) { + for (const Member m : patternProperties->getObject()) { + const std::string &pattern = m.first; + const std::string childPath = patternPropertiesPath + "/" + pattern; + const Subschema *subschema = makeOrReuseSchema( + rootSchema, rootNode, m.second, currentScope, childPath, + fetchDoc, parentSubschema, &pattern, docCache, + schemaCache); + constraint.addPatternPropertySubschema(pattern, subschema); + } + } + + // Create an additionalItems subschema if required + if (additionalProperties) { + // If additionalProperties has been set, check for a boolean value. + // Setting 'additionalProperties' to true allows the values of + // additional properties to take any form. Setting it false + // prohibits the use of additional properties. + // If additionalProperties is instead an object, it should be + // parsed as a schema. If additionalProperties has any other type, + // then the schema is not valid. + if (additionalProperties->isBool() || + additionalProperties->maybeBool()) { + // If it has a boolean value that is 'true', then an empty + // schema should be used. + if (additionalProperties->asBool()) { + constraint.setAdditionalPropertiesSubschema(rootSchema.emptySubschema()); + } + } else if (additionalProperties->isObject()) { + // If additionalProperties is an object, it should be used as + // a child schema. + const Subschema *subschema = makeOrReuseSchema( + rootSchema, rootNode, *additionalProperties, + currentScope, additionalPropertiesPath, fetchDoc, nullptr, + nullptr, docCache, schemaCache); + constraint.setAdditionalPropertiesSubschema(subschema); + } else { + // All other types are invalid + throwRuntimeError("Invalid type for 'additionalProperties' constraint."); + } + } else { + // If an additionalProperties constraint is not provided, then the + // default value is an empty schema. + constraint.setAdditionalPropertiesSubschema(rootSchema.emptySubschema()); + } + + return constraint; + } + + template + constraints::PropertyNamesConstraint makePropertyNamesConstraint( + Schema &rootSchema, + const AdapterType &rootNode, + const AdapterType ¤tNode, + const opt::optional currentScope, + const std::string &nodePath, + const typename FunctionPtrs::FetchDoc fetchDoc, + typename DocumentCache::Type &docCache, + SchemaCache &schemaCache) + { + const Subschema *subschema = makeOrReuseSchema(rootSchema, rootNode, currentNode, currentScope, + nodePath, fetchDoc, nullptr, nullptr, docCache, schemaCache); + constraints::PropertyNamesConstraint constraint; + constraint.setSubschema(subschema); + return constraint; + } + + /** + * @brief Make a new RequiredConstraint. + * + * This function is used to create new RequiredContraint objects for + * Draft 3 schemas. + * + * @param node Node containing a boolean value. + * @param name Name of the required attribute. + * + * @return pointer to a new RequiredConstraint object that belongs to the + * caller + */ + template + opt::optional + makeRequiredConstraintForSelf(const AdapterType &node, + const std::string &name) + { + if (!node.maybeBool()) { + throwRuntimeError("Expected boolean value for 'required' attribute."); + } + + if (node.asBool()) { + constraints::RequiredConstraint constraint; + constraint.addRequiredProperty(name); + return constraint; + } + + return opt::optional(); + } + + /** + * @brief Make a new RequiredConstraint. + * + * This function is used to create new RequiredContraint objects for + * Draft 4 schemas. + * + * @param node Node containing an array of strings. + * + * @return pointer to a new RequiredConstraint object that belongs to the + * caller + */ + template + constraints::RequiredConstraint makeRequiredConstraint( + const AdapterType &node) + { + constraints::RequiredConstraint constraint; + + for (const AdapterType v : node.getArray()) { + if (!v.maybeString()) { + throwRuntimeError("Expected required property name to be a string value"); + } + + constraint.addRequiredProperty(v.getString()); + } + + return constraint; + } + + /** + * @brief Make a new TypeConstraint object + * + * @param rootSchema The Schema instance, and root subschema, through + * which other subschemas can be created and modified + * @param rootNode Reference to the node from which JSON References + * will be resolved when they refer to the current + * document; used for recursive parsing of schemas + * @param node Node containing the name of a JSON type + * @param currentScope URI for current resolution scope + * @param nodePath JSON Pointer representing path to current node + * @param fetchDoc Function to fetch remote JSON documents (optional) + * @param docCache Cache of resolved and fetched remote documents + * @param schemaCache Cache of populated schemas + * + * @return pointer to a new TypeConstraint object. + */ + template + constraints::TypeConstraint makeTypeConstraint( + Schema &rootSchema, + const AdapterType &rootNode, + const AdapterType &node, + const opt::optional currentScope, + const std::string &nodePath, + const typename FunctionPtrs::FetchDoc fetchDoc, + typename DocumentCache::Type &docCache, + SchemaCache &schemaCache) + { + typedef constraints::TypeConstraint TypeConstraint; + + TypeConstraint constraint; + + if (node.maybeString()) { + const TypeConstraint::JsonType type = TypeConstraint::jsonTypeFromString(node.getString()); + if (type == TypeConstraint::kAny && m_version == kDraft4) { + throwRuntimeError("'any' type is not supported in version 4 schemas."); + } + + constraint.addNamedType(type); + + } else if (node.maybeArray()) { + int index = 0; + for (const AdapterType v : node.getArray()) { + if (v.maybeString()) { + const TypeConstraint::JsonType type = TypeConstraint::jsonTypeFromString(v.getString()); + if (type == TypeConstraint::kAny && m_version == kDraft4) { + throwRuntimeError("'any' type is not supported in version 4 schemas."); + } + + constraint.addNamedType(type); + + } else if (v.maybeObject() && m_version == kDraft3) { + const std::string childPath = nodePath + "/" + std::to_string(index); + const Subschema *subschema = makeOrReuseSchema(rootSchema, rootNode, v, currentScope, + childPath, fetchDoc, nullptr, nullptr, docCache, schemaCache); + constraint.addSchemaType(subschema); + + } else { + throwRuntimeError("Type name should be a string."); + } + + index++; + } + + } else if (node.maybeObject() && m_version == kDraft3) { + const Subschema *subschema = makeOrReuseSchema(rootSchema, rootNode, node, currentScope, + nodePath, fetchDoc, nullptr, nullptr, docCache, schemaCache); + constraint.addSchemaType(subschema); + + } else { + throwRuntimeError("Type name should be a string."); + } + + return constraint; + } + + /** + * @brief Make a new UniqueItemsConstraint object. + * + * @param node Node containing a boolean value. + * + * @return pointer to a new UniqueItemsConstraint object that belongs to + * the caller, or nullptr if the boolean value is false. + */ + template + opt::optional makeUniqueItemsConstraint(const AdapterType &node) + { + if (node.isBool() || node.maybeBool()) { + // If the boolean value is true, this function will return a pointer + // to a new UniqueItemsConstraint object. If it is value, then the + // constraint is redundant, so nullptr is returned instead. + if (node.asBool()) { + return constraints::UniqueItemsConstraint(); + } else { + return opt::optional(); + } + } + + throwRuntimeError("Expected boolean value for 'uniqueItems' constraint."); + } + +private: + + /// Version of JSON Schema that should be expected when parsing + Version m_version; +}; + +} // namespace valijson +/** + * @file + * + * @brief Adapter implementation that wraps a single std::string value + * + * This allows property names to be validated against a schema as though they are a generic JSON + * value, while allowing the rest of Valijson's API to expose property names as plain std::string + * values. + * + * This was added while implementing draft 7 support. This included support for a constraint + * called propertyNames, which can be used to ensure that the property names in an object + * validate against a subschema. + */ + +#pragma once + +#include + + +namespace valijson { +namespace adapters { + +class StdStringAdapter; +class StdStringArrayValueIterator; +class StdStringObjectMemberIterator; + +typedef std::pair StdStringObjectMember; + +class StdStringArray +{ +public: + typedef StdStringArrayValueIterator const_iterator; + typedef StdStringArrayValueIterator iterator; + + StdStringArray() = default; + + StdStringArrayValueIterator begin() const; + + StdStringArrayValueIterator end() const; + + static size_t size() + { + return 0; + } +}; + +class StdStringObject +{ +public: + typedef StdStringObjectMemberIterator const_iterator; + typedef StdStringObjectMemberIterator iterator; + + StdStringObject() = default; + + StdStringObjectMemberIterator begin() const; + + StdStringObjectMemberIterator end() const; + + StdStringObjectMemberIterator find(const std::string &propertyName) const; + + static size_t size() + { + return 0; + } +}; + +class StdStringFrozenValue: public FrozenValue +{ +public: + explicit StdStringFrozenValue(std::string source) + : value(std::move(source)) { } + + FrozenValue * clone() const override + { + return new StdStringFrozenValue(value); + } + + bool equalTo(const Adapter &other, bool strict) const override; + +private: + std::string value; +}; + +class StdStringAdapter: public Adapter +{ +public: + typedef StdStringArray Array; + typedef StdStringObject Object; + typedef StdStringObjectMember ObjectMember; + + explicit StdStringAdapter(const std::string &value) + : m_value(value) { } + + bool applyToArray(ArrayValueCallback) const override + { + return maybeArray(); + } + + bool applyToObject(ObjectMemberCallback) const override + { + return maybeObject(); + } + + StdStringArray asArray() const + { + if (maybeArray()) { + return {}; + } + + throwRuntimeError("String value cannot be cast to array"); + } + + bool asBool() const override + { + return true; + } + + bool asBool(bool &result) const override + { + result = true; + return true; + } + + double asDouble() const override + { + return 0; + } + + bool asDouble(double &result) const override + { + result = 0; + return true; + } + + int64_t asInteger() const override + { + return 0; + } + + bool asInteger(int64_t &result) const override + { + result = 0; + return true; + }; + + StdStringObject asObject() const + { + if (maybeObject()) { + return {}; + } + + throwRuntimeError("String value cannot be cast to object"); + } + + std::string asString() const override + { + return m_value; + } + + bool asString(std::string &result) const override + { + result = m_value; + return true; + } + + bool equalTo(const Adapter &other, bool strict) const override + { + if (strict && !other.isString()) { + return false; + } + + return m_value == other.asString(); + } + + FrozenValue* freeze() const override + { + return new StdStringFrozenValue(m_value); + } + + static StdStringArray getArray() + { + throwNotSupported(); + } + + size_t getArraySize() const override + { + throwNotSupported(); + } + + bool getArraySize(size_t &) const override + { + throwNotSupported(); + } + + bool getBool() const override + { + throwNotSupported(); + } + + bool getBool(bool &) const override + { + throwNotSupported(); + } + + double getDouble() const override + { + throwNotSupported(); + } + + bool getDouble(double &) const override + { + throwNotSupported(); + } + + int64_t getInteger() const override + { + throwNotSupported(); + } + + bool getInteger(int64_t &) const override + { + throwNotSupported(); + } + + double getNumber() const override + { + throwNotSupported(); + } + + bool getNumber(double &) const override + { + throwNotSupported(); + } + + size_t getObjectSize() const override + { + throwNotSupported(); + } + + bool getObjectSize(size_t &) const override + { + throwNotSupported(); + } + + std::string getString() const override + { + return m_value; + } + + bool getString(std::string &result) const override + { + result = m_value; + return true; + } + + bool hasStrictTypes() const override + { + return true; + } + + bool isArray() const override + { + return false; + } + + bool isBool() const override + { + return false; + } + + bool isDouble() const override + { + return false; + } + + bool isInteger() const override + { + return false; + } + + bool isNull() const override + { + return false; + } + + bool isNumber() const override + { + return false; + } + + bool isObject() const override + { + return false; + } + + bool isString() const override + { + return true; + } + + bool maybeArray() const override + { + return false; + } + + bool maybeBool() const override + { + return m_value == "true" || m_value == "false"; + } + + bool maybeDouble() const override + { + const char *b = m_value.c_str(); + char *e = nullptr; + strtod(b, &e); + return e != b && e == b + m_value.length(); + } + + bool maybeInteger() const override + { + std::istringstream i(m_value); + int64_t x; + char c; + if (!(i >> x) || i.get(c)) { + return false; + } + + return true; + } + + bool maybeNull() const override + { + return m_value.empty(); + } + + bool maybeObject() const override + { + return m_value.empty(); + } + + bool maybeString() const override + { + return true; + } + +private: + const std::string &m_value; +}; + +class StdStringArrayValueIterator +{ +public: + using iterator_category = std::bidirectional_iterator_tag; + using value_type = StdStringAdapter; + using difference_type = StdStringAdapter; + using pointer = StdStringAdapter*; + using reference = StdStringAdapter&; + + StdStringAdapter operator*() const + { + throwNotSupported(); + } + + DerefProxy operator->() const + { + throwNotSupported(); + } + + bool operator==(const StdStringArrayValueIterator &) const + { + return true; + } + + bool operator!=(const StdStringArrayValueIterator &) const + { + return false; + } + + const StdStringArrayValueIterator& operator++() + { + throwNotSupported(); + } + + StdStringArrayValueIterator operator++(int) + { + throwNotSupported(); + } + + const StdStringArrayValueIterator& operator--() + { + throwNotSupported(); + } + + void advance(std::ptrdiff_t) + { + throwNotSupported(); + } +}; + +inline StdStringArrayValueIterator StdStringArray::begin() const +{ + return {}; +} + +inline StdStringArrayValueIterator StdStringArray::end() const +{ + return {}; +} + +class StdStringObjectMemberIterator +{ +public: + using iterator_category = std::bidirectional_iterator_tag; + using value_type = StdStringObjectMember; + using difference_type = StdStringObjectMember; + using pointer = StdStringObjectMember*; + using reference = StdStringObjectMember&; + + StdStringObjectMember operator*() const + { + throwNotSupported(); + } + + DerefProxy operator->() const + { + throwNotSupported(); + } + + bool operator==(const StdStringObjectMemberIterator &) const + { + return true; + } + + bool operator!=(const StdStringObjectMemberIterator &) const + { + return false; + } + + const StdStringObjectMemberIterator& operator++() + { + throwNotSupported(); + } + + StdStringObjectMemberIterator operator++(int) + { + throwNotSupported(); + } + + const StdStringObjectMemberIterator& operator--() + { + throwNotSupported(); + } +}; + +inline StdStringObjectMemberIterator StdStringObject::begin() const +{ + return {}; +} + +inline StdStringObjectMemberIterator StdStringObject::end() const +{ + return {}; +} + +inline StdStringObjectMemberIterator StdStringObject::find(const std::string &) const +{ + return {}; +} + +template<> +struct AdapterTraits +{ + typedef std::string DocumentType; + + static std::string adapterName() + { + return "StdStringAdapter"; + } +}; + +inline bool StdStringFrozenValue::equalTo(const Adapter &other, bool strict) const +{ + return StdStringAdapter(value).equalTo(other, strict); +} + +} // namespace adapters +} // namespace valijson +#pragma once + +#include +#include +#include +#include + +namespace valijson { + +/** + * @brief Class that encapsulates the storage of validation errors. + * + * This class maintains an internal FIFO queue of errors that are reported + * during validation. Errors are pushed on to the back of an internal + * queue, and can retrieved by popping them from the front of the queue. + */ +class ValidationResults +{ +public: + + /** + * @brief Describes a validation error. + * + * This struct is used to pass around the context and description of a + * validation error. + */ + struct Error + { + /// Path to the node that failed validation. + std::vector context; + + /// A detailed description of the validation error. + std::string description; + }; + + /** + * @brief Return begin iterator for results in the queue. + */ + std::deque::const_iterator begin() const + { + return m_errors.begin(); + } + + /** + * @brief Return end iterator for results in the queue. + */ + std::deque::const_iterator end() const + { + return m_errors.end(); + } + + /** + * @brief Return the number of errors in the queue. + */ + size_t numErrors() const + { + return m_errors.size(); + } + + /** + * @brief Copy an Error and push it on to the back of the queue. + * + * @param error Reference to an Error object to be copied. + */ + void pushError(const Error &error) + { + m_errors.push_back(error); + } + + /** + * @brief Push an error onto the back of the queue. + * + * @param context Context of the validation error. + * @param description Description of the validation error. + */ + void + pushError(const std::vector &context, const std::string &description) + { + m_errors.push_back({context, description}); + } + + /** + * @brief Pop an error from the front of the queue. + * + * @param error Reference to an Error object to populate. + * + * @returns true if an Error was popped, false otherwise. + */ + bool + popError(Error &error) + { + if (m_errors.empty()) { + return false; + } + + error = m_errors.front(); + m_errors.pop_front(); + return true; + } + +private: + + /// FIFO queue of validation errors that have been reported + std::deque m_errors; +}; + +} // namespace valijson +#pragma once + +#include +#include +#include +#include + +#include + + +#ifdef _MSC_VER +#pragma warning( push ) +#pragma warning( disable : 4702 ) +#endif + +namespace valijson { + +class ValidationResults; + +/** + * @brief Implementation of the ConstraintVisitor interface that validates a + * target document + * + * @tparam AdapterType Adapter type for the target document. + */ +template +class ValidationVisitor: public constraints::ConstraintVisitor +{ +public: + + /** + * @brief Construct a new validator for a given target value and context. + * + * @param target Target value to be validated + * @param context Current context for validation error descriptions, + * only used if results is set. + * @param strictTypes Use strict type comparison + * @param results Optional pointer to ValidationResults object, for + * recording error descriptions. If this pointer is set + * to nullptr, validation errors will caused validation to + * stop immediately. + * @param regexesCache Cache of already created std::regex objects for pattern + * constraints. + */ + ValidationVisitor(const AdapterType &target, + std::vector context, + const bool strictTypes, + ValidationResults *results, + std::unordered_map& regexesCache) + : m_target(target), + m_context(std::move(context)), + m_results(results), + m_strictTypes(strictTypes), + m_regexesCache(regexesCache) { } + + /** + * @brief Validate the target against a schema. + * + * When a ValidationResults object has been set via the 'results' member + * variable, validation will proceed as long as no fatal errors occur, + * with error descriptions added to the ValidationResults object. + * + * If a pointer to a ValidationResults instance is not provided, validation + * will only continue for as long as the constraints are validated + * successfully. + * + * @param subschema Sub-schema that the target must validate against + * + * @return \c true if validation passes; \c false otherwise + */ + bool validateSchema(const Subschema &subschema) + { + if (subschema.getAlwaysInvalid()) { + return false; + } + + // Wrap the validationCallback() function below so that it will be + // passed a reference to a constraint (_1), and a reference to the + // visitor (*this). + Subschema::ApplyFunction fn(std::bind(validationCallback, std::placeholders::_1, std::ref(*this))); + + // Perform validation against each constraint defined in the schema + if (m_results == nullptr) { + // The applyStrict() function will return immediately if the + // callback function returns false + if (!subschema.applyStrict(fn)) { + return false; + } + } else { + // The apply() function will iterate over all constraints in the + // schema, even if the callback function returns false. Once + // iteration is complete, the apply() function will return true + // only if all invokations of the callback function returned true. + if (!subschema.apply(fn)) { + return false; + } + } + + return true; + } + + /** + * @brief Validate a value against an AllOfConstraint + * + * An allOf constraint provides a set of child schemas against which the + * target must be validated in order for the constraint to the satifisfied. + * + * When a ValidationResults object has been set via the 'results' member + * variable, validation will proceed as long as no fatal errors occur, + * with error descriptions added to the ValidationResults object. + * + * If a pointer to a ValidationResults instance is not provided, validation + * will only continue for as long as the child schemas are validated + * successfully. + * + * @param constraint Constraint that the target must validate against + * + * @return \c true if validation passes; \c false otherwise + */ + bool visit(const AllOfConstraint &constraint) override + { + bool validated = true; + constraint.applyToSubschemas( + ValidateSubschemas(m_target, m_context, true, false, *this, m_results, nullptr, &validated)); + + return validated; + } + + /** + * @brief Validate a value against an AnyOfConstraint + * + * An anyOf constraint provides a set of child schemas, any of which the + * target may be validated against in order for the constraint to the + * satifisfied. + * + * Because an anyOf constraint does not require the target to validate + * against all child schemas, if validation against a single schema fails, + * the results will not be added to a ValidationResults object. Only if + * validation fails for all child schemas will an error be added to the + * ValidationResults object. + * + * @param constraint Constraint that the target must validate against + * + * @return \c true if validation passes; \c false otherwise + */ + bool visit(const AnyOfConstraint &constraint) override + { + unsigned int numValidated = 0; + + ValidationResults newResults; + ValidationResults *childResults = (m_results) ? &newResults : nullptr; + + ValidationVisitor v(m_target, m_context, m_strictTypes, childResults, m_regexesCache); + constraint.applyToSubschemas( + ValidateSubschemas(m_target, m_context, false, true, v, childResults, &numValidated, nullptr)); + + if (numValidated == 0 && m_results) { + ValidationResults::Error childError; + while (childResults->popError(childError)) { + m_results->pushError( childError.context, childError.description); + } + m_results->pushError(m_context, "Failed to validate against any schemas allowed by anyOf constraint."); + } + + return numValidated > 0; + } + + /** + * @brief Validate current node using a set of 'if', 'then' and 'else' subschemas + * + * A conditional constraint allows a document to be validated against one of two additional + * subschemas (specified via 'then' or 'else' properties) depending on whether the document + * satifies an optional subschema (specified via the 'if' property). + * + * @param constraint ConditionalConstraint that the current node must validate against + * + * @return \c true if validation passes; \c false otherwise + */ + bool visit(const ConditionalConstraint &constraint) override + { + ValidationResults newResults; + ValidationResults* conditionalResults = (m_results) ? &newResults : nullptr; + + // Create a validator to evaluate the conditional + ValidationVisitor ifValidator(m_target, m_context, m_strictTypes, nullptr, m_regexesCache); + ValidationVisitor thenElseValidator(m_target, m_context, m_strictTypes, conditionalResults, m_regexesCache); + + bool validated = false; + if (ifValidator.validateSchema(*constraint.getIfSubschema())) { + const Subschema *thenSubschema = constraint.getThenSubschema(); + validated = thenSubschema == nullptr || thenElseValidator.validateSchema(*thenSubschema); + } else { + const Subschema *elseSubschema = constraint.getElseSubschema(); + validated = elseSubschema == nullptr || thenElseValidator.validateSchema(*elseSubschema); + } + + if (!validated && m_results) { + ValidationResults::Error conditionalError; + while (conditionalResults->popError(conditionalError)) { + m_results->pushError(conditionalError.context, conditionalError.description); + } + m_results->pushError(m_context, "Failed to validate against a conditional schema set by if-then-else constraints."); + } + + return validated; + } + + /** + * @brief Validate current node using a 'const' constraint + * + * A const constraint allows a document to be validated against a specific value. + * + * @param constraint ConstConstraint that the current node must validate against + * + * @return \c true if validation passes; \f false otherwise + */ + bool visit(const ConstConstraint &constraint) override + { + if (!constraint.getValue()->equalTo(m_target, m_strictTypes)) { + if (m_results) { + m_results->pushError(m_context, "Failed to match expected value set by 'const' constraint."); + } + return false; + } + + return true; + } + + /** + * @brief Validate current node using a 'contains' constraint + * + * A contains constraint is satisfied if the target is not an array, or if it is an array, + * only if it contains at least one value that matches the specified schema. + * + * @param constraint ContainsConstraint that the current node must validate against + * + * @return \c true if validation passes; \c false otherwise + */ + bool visit(const ContainsConstraint &constraint) override + { + if ((m_strictTypes && !m_target.isArray()) || !m_target.maybeArray()) { + return true; + } + + const Subschema *subschema = constraint.getSubschema(); + const typename AdapterType::Array arr = m_target.asArray(); + + bool validated = false; + for (const auto &el : arr) { + ValidationVisitor containsValidator(el, m_context, m_strictTypes, nullptr, m_regexesCache); + if (containsValidator.validateSchema(*subschema)) { + validated = true; + break; + } + } + + if (!validated) { + if (m_results) { + m_results->pushError(m_context, "Failed to any values against subschema in 'contains' constraint."); + } + + return false; + } + + return validated; + } + + /** + * @brief Validate current node against a 'dependencies' constraint + * + * A 'dependencies' constraint can be used to specify property-based or + * schema-based dependencies that must be fulfilled when a particular + * property is present in an object. + * + * Property-based dependencies define a set of properties that must be + * present in addition to a particular property, whereas a schema-based + * dependency defines an additional schema that the current document must + * validate against. + * + * @param constraint DependenciesConstraint that the current node + * must validate against + * + * @return \c true if validation passes; \c false otherwise + */ + bool visit(const DependenciesConstraint &constraint) override + { + // Ignore non-objects + if ((m_strictTypes && !m_target.isObject()) || (!m_target.maybeObject())) { + return true; + } + + // Object to be validated + const typename AdapterType::Object object = m_target.asObject(); + + // Cleared if validation fails + bool validated = true; + + // Iterate over all dependent properties defined by this constraint, + // invoking the DependentPropertyValidator functor once for each + // set of dependent properties + constraint.applyToPropertyDependencies(ValidatePropertyDependencies(object, m_context, m_results, &validated)); + if (!m_results && !validated) { + return false; + } + + // Iterate over all dependent schemas defined by this constraint, + // invoking the DependentSchemaValidator function once for each schema + // that must be validated if a given property is present + constraint.applyToSchemaDependencies(ValidateSchemaDependencies( + object, m_context, *this, m_results, &validated)); + if (!m_results && !validated) { + return false; + } + + return validated; + } + + /** + * @brief Validate current node against an EnumConstraint + * + * Validation succeeds if the target is equal to one of the values provided + * by the EnumConstraint. + * + * @param constraint Constraint that the target must validate against + * + * @return \c true if validation succeeds; \c false otherwise + */ + bool visit(const EnumConstraint &constraint) override + { + unsigned int numValidated = 0; + constraint.applyToValues( + ValidateEquality(m_target, m_context, false, true, m_strictTypes, nullptr, &numValidated)); + + if (numValidated == 0) { + if (m_results) { + m_results->pushError(m_context, "Failed to match against any enum values."); + } + + return false; + } + + return numValidated > 0; + } + + /** + * @brief Validate a value against a LinearItemsConstraint + * + * A LinearItemsConstraint represents an 'items' constraint that specifies, + * for each item in array, an individual sub-schema that the item must + * validate against. The LinearItemsConstraint class also captures the + * presence of an 'additionalItems' constraint, which specifies a default + * sub-schema that should be used if an array contains more items than + * there are sub-schemas in the 'items' constraint. + * + * If the current value is not an array, validation always succeeds. + * + * @param constraint SingularItemsConstraint to validate against + * + * @returns \c true if validation is successful; \c false otherwise + */ + bool visit(const LinearItemsConstraint &constraint) override + { + // Ignore values that are not arrays + if ((m_strictTypes && !m_target.isArray()) || (!m_target.maybeArray())) { + return true; + } + + // Sub-schema to validate against when number of items in array exceeds + // the number of sub-schemas provided by the 'items' constraint + const Subschema * const additionalItemsSubschema = constraint.getAdditionalItemsSubschema(); + + // Track how many items are validated using 'items' constraint + unsigned int numValidated = 0; + + // Array to validate + const typename AdapterType::Array arr = m_target.asArray(); + const size_t arrSize = arr.size(); + + // Track validation status + bool validated = true; + + // Validate as many items as possible using 'items' sub-schemas + const size_t itemSubschemaCount = constraint.getItemSubschemaCount(); + if (itemSubschemaCount > 0) { + if (!additionalItemsSubschema) { + if (arrSize > itemSubschemaCount) { + if (!m_results) { + return false; + } + m_results->pushError(m_context, "Array contains more items than allowed by items constraint."); + validated = false; + } + } + + constraint.applyToItemSubschemas( + ValidateItems(arr, m_context, true, m_results != nullptr, m_strictTypes, m_results, &numValidated, + &validated, m_regexesCache)); + + if (!m_results && !validated) { + return false; + } + } + + // Validate remaining items using 'additionalItems' sub-schema + if (numValidated < arrSize) { + if (additionalItemsSubschema) { + // Begin validation from the first item not validated against + // an sub-schema provided by the 'items' constraint + unsigned int index = numValidated; + typename AdapterType::Array::const_iterator begin = arr.begin(); + begin.advance(numValidated); + for (typename AdapterType::Array::const_iterator itr = begin; + itr != arr.end(); ++itr) { + + // Update context for current array item + std::vector newContext = m_context; + newContext.push_back("[" + std::to_string(index) + "]"); + + ValidationVisitor validator(*itr, newContext, m_strictTypes, m_results, m_regexesCache); + + if (!validator.validateSchema(*additionalItemsSubschema)) { + if (m_results) { + m_results->pushError(m_context, "Failed to validate item #" + std::to_string(index) + + " against additional items schema."); + validated = false; + } else { + return false; + } + } + + index++; + } + + } else if (m_results) { + m_results->pushError(m_context, "Cannot validate item #" + std::to_string(numValidated) + + " or greater using 'items' constraint or 'additionalItems' constraint."); + validated = false; + + } else { + return false; + } + } + + return validated; + } + + /** + * @brief Validate a value against a MaximumConstraint object + * + * @param constraint Constraint that the target must validate against + * + * @return \c true if constraints are satisfied; \c false otherwise + */ + bool visit(const MaximumConstraint &constraint) override + { + if ((m_strictTypes && !m_target.isNumber()) || !m_target.maybeDouble()) { + // Ignore values that are not numbers + return true; + } + + const double maximum = constraint.getMaximum(); + + if (constraint.getExclusiveMaximum()) { + if (m_target.asDouble() >= maximum) { + if (m_results) { + m_results->pushError(m_context, "Expected number less than " + std::to_string(maximum)); + } + + return false; + } + + } else if (m_target.asDouble() > maximum) { + if (m_results) { + m_results->pushError(m_context, "Expected number less than or equal to " + std::to_string(maximum)); + } + + return false; + } + + return true; + } + + /** + * @brief Validate a value against a MaxItemsConstraint + * + * @param constraint Constraint that the target must validate against + * + * @return \c true if constraint is satisfied; \c false otherwise + */ + bool visit(const MaxItemsConstraint &constraint) override + { + if ((m_strictTypes && !m_target.isArray()) || !m_target.maybeArray()) { + return true; + } + + const uint64_t maxItems = constraint.getMaxItems(); + if (m_target.asArray().size() <= maxItems) { + return true; + } + + if (m_results) { + m_results->pushError(m_context, "Array should contain no more than " + std::to_string(maxItems) + + " elements."); + } + + return false; + } + + /** + * @brief Validate a value against a MaxLengthConstraint + * + * @param constraint Constraint that the target must validate against + * + * @return \c true if constraint is satisfied; \c false otherwise + */ + bool visit(const MaxLengthConstraint &constraint) override + { + if ((m_strictTypes && !m_target.isString()) || !m_target.maybeString()) { + return true; + } + + const std::string s = m_target.asString(); + const uint64_t len = utils::u8_strlen(s.c_str()); + const uint64_t maxLength = constraint.getMaxLength(); + if (len <= maxLength) { + return true; + } + + if (m_results) { + m_results->pushError(m_context, "String should be no more than " + std::to_string(maxLength) + + " characters in length."); + } + + return false; + } + + /** + * @brief Validate a value against a MaxPropertiesConstraint + * + * @param constraint Constraint that the target must validate against + * + * @return \c true if the constraint is satisfied; \c false otherwise + */ + bool visit(const MaxPropertiesConstraint &constraint) override + { + if ((m_strictTypes && !m_target.isObject()) || !m_target.maybeObject()) { + return true; + } + + const uint64_t maxProperties = constraint.getMaxProperties(); + + if (m_target.asObject().size() <= maxProperties) { + return true; + } + + if (m_results) { + m_results->pushError(m_context, "Object should have no more than " + std::to_string(maxProperties) + + " properties."); + } + + return false; + } + + /** + * @brief Validate a value against a MinimumConstraint + * + * @param constraint Constraint that the target must validate against + * + * @return \c true if the constraint is satisfied; \c false otherwise + */ + bool visit(const MinimumConstraint &constraint) override + { + if ((m_strictTypes && !m_target.isNumber()) || !m_target.maybeDouble()) { + // Ignore values that are not numbers + return true; + } + + const double minimum = constraint.getMinimum(); + + if (constraint.getExclusiveMinimum()) { + if (m_target.asDouble() <= minimum) { + if (m_results) { + m_results->pushError(m_context, "Expected number greater than " + std::to_string(minimum)); + } + + return false; + } + } else if (m_target.asDouble() < minimum) { + if (m_results) { + m_results->pushError(m_context, "Expected number greater than or equal to " + std::to_string(minimum)); + } + + return false; + } + + return true; + } + + /** + * @brief Validate a value against a MinItemsConstraint + * + * @param constraint Constraint that the target must validate against + * + * @return \c true if the constraint is satisfied; \c false otherwise + */ + bool visit(const MinItemsConstraint &constraint) override + { + if ((m_strictTypes && !m_target.isArray()) || !m_target.maybeArray()) { + return true; + } + + const uint64_t minItems = constraint.getMinItems(); + if (m_target.asArray().size() >= minItems) { + return true; + } + + if (m_results) { + m_results->pushError(m_context, "Array should contain no fewer than " + std::to_string(minItems) + + " elements."); + } + + return false; + } + + /** + * @brief Validate a value against a MinLengthConstraint + * + * @param constraint Constraint that the target must validate against + * + * @return \c true if the constraint is satisfied; \c false otherwise + */ + bool visit(const MinLengthConstraint &constraint) override + { + if ((m_strictTypes && !m_target.isString()) || !m_target.maybeString()) { + return true; + } + + const std::string s = m_target.asString(); + const uint64_t len = utils::u8_strlen(s.c_str()); + const uint64_t minLength = constraint.getMinLength(); + if (len >= minLength) { + return true; + } + + if (m_results) { + m_results->pushError(m_context, "String should be no fewer than " + std::to_string(minLength) + + " characters in length."); + } + + return false; + } + + /** + * @brief Validate a value against a MinPropertiesConstraint + * + * @param constraint Constraint that the target must validate against + * + * @return \c true if the constraint is satisfied; \c false otherwise + */ + bool visit(const MinPropertiesConstraint &constraint) override + { + if ((m_strictTypes && !m_target.isObject()) || !m_target.maybeObject()) { + return true; + } + + const uint64_t minProperties = constraint.getMinProperties(); + + if (m_target.asObject().size() >= minProperties) { + return true; + } + + if (m_results) { + m_results->pushError(m_context, "Object should have no fewer than " + std::to_string(minProperties) + + " properties."); + } + + return false; + } + + /** + * @brief Validate a value against a MultipleOfDoubleConstraint + * + * @param constraint Constraint that the target must validate against + * + * @return \c true if the constraint is satisfied; \c false otherwise + */ + bool visit(const MultipleOfDoubleConstraint &constraint) override + { + const double divisor = constraint.getDivisor(); + + double d = 0.; + if (m_target.maybeDouble()) { + if (!m_target.asDouble(d)) { + if (m_results) { + m_results->pushError(m_context, "Value could not be converted " + "to a number to check if it is a multiple of " + std::to_string(divisor)); + } + return false; + } + } else if (m_target.maybeInteger()) { + int64_t i = 0; + if (!m_target.asInteger(i)) { + if (m_results) { + m_results->pushError(m_context, "Value could not be converted " + "to a number to check if it is a multiple of " + std::to_string(divisor)); + } + return false; + } + d = static_cast(i); + } else { + return true; + } + + if (d == 0) { + return true; + } + + const double r = remainder(d, divisor); + + if (fabs(r) > std::numeric_limits::epsilon()) { + if (m_results) { + m_results->pushError(m_context, "Value should be a multiple of " + std::to_string(divisor)); + } + return false; + } + + return true; + } + + /** + * @brief Validate a value against a MultipleOfIntConstraint + * + * @param constraint Constraint that the target must validate against + * + * @return \c true if the constraint is satisfied; \c false otherwise + */ + bool visit(const MultipleOfIntConstraint &constraint) override + { + const int64_t divisor = constraint.getDivisor(); + + int64_t i = 0; + if (m_target.maybeInteger()) { + if (!m_target.asInteger(i)) { + if (m_results) { + m_results->pushError(m_context, "Value could not be converted to an integer for multipleOf check"); + } + return false; + } + } else if (m_target.maybeDouble()) { + double d; + if (!m_target.asDouble(d)) { + if (m_results) { + m_results->pushError(m_context, "Value could not be converted to a double for multipleOf check"); + } + return false; + } + i = static_cast(d); + } else { + return true; + } + + if (i == 0) { + return true; + } + + if (i % divisor != 0) { + if (m_results) { + m_results->pushError(m_context, "Value should be a multiple of " + std::to_string(divisor)); + } + return false; + } + + return true; + } + + /** + * @brief Validate a value against a NotConstraint + * + * If the subschema NotConstraint currently holds a nullptr, the + * schema will be treated like the empty schema. Therefore validation + * will always fail. + * + * @param constraint Constraint that the target must validate against + * + * @return \c true if the constraint is satisfied; \c false otherwise + */ + bool visit(const NotConstraint &constraint) override + { + const Subschema *subschema = constraint.getSubschema(); + if (!subschema) { + // Treat nullptr like empty schema + return false; + } + + ValidationVisitor v(m_target, m_context, m_strictTypes, nullptr, m_regexesCache); + if (v.validateSchema(*subschema)) { + if (m_results) { + m_results->pushError(m_context, + "Target should not validate against schema specified in 'not' constraint."); + } + + return false; + } + + return true; + } + + /** + * @brief Validate a value against a OneOfConstraint + * + * @param constraint Constraint that the target must validate against + * + * @return \c true if the constraint is satisfied; \c false otherwise + */ + bool visit(const OneOfConstraint &constraint) override + { + unsigned int numValidated = 0; + + ValidationResults newResults; + ValidationResults *childResults = (m_results) ? &newResults : nullptr; + + ValidationVisitor v(m_target, m_context, m_strictTypes, childResults, m_regexesCache); + constraint.applyToSubschemas( + ValidateSubschemas(m_target, m_context, true, true, v, childResults, &numValidated, nullptr)); + + if (numValidated == 0) { + if (m_results) { + ValidationResults::Error childError; + while (childResults->popError(childError)) { + m_results->pushError( + childError.context, + childError.description); + } + m_results->pushError(m_context, "Failed to validate against any " + "child schemas allowed by oneOf constraint."); + } + return false; + } else if (numValidated != 1) { + if (m_results) { + m_results->pushError(m_context, "Failed to validate against exactly one child schema."); + } + return false; + } + + return true; + } + + /** + * @brief Validate a value against a PatternConstraint + * + * @param constraint Constraint that the target must validate against + * + * @return \c true if the constraint is satisfied; \c false otherwise + */ + bool visit(const PatternConstraint &constraint) override + { + if ((m_strictTypes && !m_target.isString()) || !m_target.maybeString()) { + return true; + } + + std::string pattern(constraint.getPattern()); + auto it = m_regexesCache.find(pattern); + if (it == m_regexesCache.end()) { + it = m_regexesCache.emplace(pattern, std::regex(pattern)).first; + } + + if (!std::regex_search(m_target.asString(), it->second)) { + if (m_results) { + m_results->pushError(m_context, "Failed to match regex specified by 'pattern' constraint."); + } + + return false; + } + + return true; + } + + /** + * @brief Validate a value against a PatternConstraint + * + * @param constraint Constraint that the target must validate against + * + * @return \c true if the constraint is satisfied; \c false otherwise + */ + bool visit(const constraints::PolyConstraint &constraint) override + { + return constraint.validate(m_target, m_context, m_results); + } + + /** + * @brief Validate a value against a PropertiesConstraint + * + * Validation of an object against a PropertiesConstraint proceeds in three + * stages. The first stage finds all properties in the object that have a + * corresponding subschema in the constraint, and validates those properties + * recursively. + * + * Next, the object's properties will be validated against the subschemas + * for any 'patternProperties' that match a given property name. A property + * is required to validate against the sub-schema for all patterns that it + * matches. + * + * Finally, any properties that have not yet been validated against at least + * one subschema will be validated against the 'additionalItems' subschema. + * If this subschema is not present, then all properties must have been + * validated at least once. + * + * Non-object values are always considered valid. + * + * @param constraint Constraint that the target must validate against + * + * @return \c true if the constraint is satisfied; \c false otherwise + */ + bool visit(const PropertiesConstraint &constraint) override + { + if ((m_strictTypes && !m_target.isObject()) || !m_target.maybeObject()) { + return true; + } + + bool validated = true; + + // Track which properties have already been validated + std::set propertiesMatched; + + // Validate properties against subschemas for matching 'properties' + // constraints + const typename AdapterType::Object object = m_target.asObject(); + constraint.applyToProperties( + ValidatePropertySubschemas( + object, m_context, true, m_results != nullptr, true, m_strictTypes, m_results, + &propertiesMatched, &validated, m_regexesCache)); + + // Exit early if validation failed, and we're not collecting exhaustive + // validation results + if (!validated && !m_results) { + return false; + } + + // Validate properties against subschemas for matching patternProperties + // constraints + constraint.applyToPatternProperties( + ValidatePatternPropertySubschemas( + object, m_context, true, false, true, m_strictTypes, m_results, &propertiesMatched, + &validated, m_regexesCache)); + + // Validate against additionalProperties subschema for any properties + // that have not yet been matched + const Subschema *additionalPropertiesSubschema = + constraint.getAdditionalPropertiesSubschema(); + if (!additionalPropertiesSubschema) { + if (propertiesMatched.size() != m_target.getObjectSize()) { + if (m_results) { + std::string unwanted; + for (const typename AdapterType::ObjectMember m : object) { + if (propertiesMatched.find(m.first) == propertiesMatched.end()) { + unwanted = m.first; + break; + } + } + m_results->pushError(m_context, "Object contains a property " + "that could not be validated using 'properties' " + "or 'additionalProperties' constraints: '" + unwanted + "'."); + } + + return false; + } + + return validated; + } + + for (const typename AdapterType::ObjectMember m : object) { + if (propertiesMatched.find(m.first) == propertiesMatched.end()) { + // Update context + std::vector newContext = m_context; + newContext.push_back("[" + m.first + "]"); + + // Create a validator to validate the property's value + ValidationVisitor validator(m.second, newContext, m_strictTypes, m_results, m_regexesCache); + if (!validator.validateSchema(*additionalPropertiesSubschema)) { + if (m_results) { + m_results->pushError(m_context, "Failed to validate against additional properties schema"); + } + + validated = false; + } + } + } + + return validated; + } + + /** + * @brief Validate a value against a PropertyNamesConstraint + * + * @param constraint Constraint that the target must validate against + * + * @return \c true if validation succeeds; \c false otherwise + */ + bool visit(const PropertyNamesConstraint &constraint) override + { + if ((m_strictTypes && !m_target.isObject()) || !m_target.maybeObject()) { + return true; + } + + for (const typename AdapterType::ObjectMember m : m_target.asObject()) { + adapters::StdStringAdapter stringAdapter(m.first); + ValidationVisitor validator(stringAdapter, m_context, m_strictTypes, nullptr, m_regexesCache); + if (!validator.validateSchema(*constraint.getSubschema())) { + return false; + } + } + + return true; + } + + /** + * @brief Validate a value against a RequiredConstraint + * + * A required constraint specifies a list of properties that must be present + * in the target. + * + * @param constraint Constraint that the target must validate against + * + * @return \c true if validation succeeds; \c false otherwise + */ + bool visit(const RequiredConstraint &constraint) override + { + if ((m_strictTypes && !m_target.isObject()) || !m_target.maybeObject()) { + return true; + } + + bool validated = true; + const typename AdapterType::Object object = m_target.asObject(); + constraint.applyToRequiredProperties( + ValidateProperties(object, m_context, true, m_results != nullptr, m_results, &validated)); + + return validated; + } + + /** + * @brief Validate a value against a SingularItemsConstraint + * + * A SingularItemsConstraint represents an 'items' constraint that specifies + * a sub-schema against which all items in an array must validate. If the + * current value is not an array, validation always succeeds. + * + * @param constraint SingularItemsConstraint to validate against + * + * @returns \c true if validation is successful; \c false otherwise + */ + bool visit(const SingularItemsConstraint &constraint) override + { + // Ignore values that are not arrays + if (!m_target.isArray()) { + return true; + } + + // Schema against which all items must validate + const Subschema *itemsSubschema = constraint.getItemsSubschema(); + + // Default items sub-schema accepts all values + if (!itemsSubschema) { + return true; + } + + // Track whether validation has failed + bool validated = true; + + unsigned int index = 0; + for (const AdapterType &item : m_target.getArray()) { + // Update context for current array item + std::vector newContext = m_context; + newContext.push_back("[" + std::to_string(index) + "]"); + + // Create a validator for the current array item + ValidationVisitor validationVisitor(item, newContext, m_strictTypes, m_results, m_regexesCache); + + // Perform validation + if (!validationVisitor.validateSchema(*itemsSubschema)) { + if (m_results) { + m_results->pushError(m_context, "Failed to validate item #" + std::to_string(index) + " in array."); + validated = false; + } else { + return false; + } + } + + index++; + } + + return validated; + } + + /** + * @brief Validate a value against a TypeConstraint + * + * Checks that the target is one of the valid named types, or matches one + * of a set of valid sub-schemas. + * + * @param constraint TypeConstraint to validate against + * + * @return \c true if validation is successful; \c false otherwise + */ + bool visit(const TypeConstraint &constraint) override + { + // Check named types + { + // ValidateNamedTypes functor assumes target is invalid + bool validated = false; + constraint.applyToNamedTypes(ValidateNamedTypes(m_target, false, true, m_strictTypes, &validated)); + if (validated) { + return true; + } + } + + // Check schema-based types + { + unsigned int numValidated = 0; + constraint.applyToSchemaTypes( + ValidateSubschemas(m_target, m_context, false, true, *this, nullptr, &numValidated, nullptr)); + if (numValidated > 0) { + return true; + } else if (m_results) { + m_results->pushError(m_context, "Value type not permitted by 'type' constraint."); + } + } + + return false; + } + + /** + * @brief Validate the uniqueItems constraint represented by a + * UniqueItems object. + * + * A uniqueItems constraint requires that each of the values in an array + * are unique. Comparison is performed recursively. + * + * @param constraint Constraint that the target must validate against + * + * @return true if validation succeeds, false otherwise + */ + bool visit(const UniqueItemsConstraint &) override + { + if ((m_strictTypes && !m_target.isArray()) || !m_target.maybeArray()) { + return true; + } + + // Empty arrays are always valid + if (m_target.getArraySize() == 0) { + return true; + } + + bool validated = true; + + const typename AdapterType::Array targetArray = m_target.asArray(); + const typename AdapterType::Array::const_iterator end = targetArray.end(); + const typename AdapterType::Array::const_iterator secondLast = --targetArray.end(); + unsigned int outerIndex = 0; + typename AdapterType::Array::const_iterator outerItr = targetArray.begin(); + for (; outerItr != secondLast; ++outerItr) { + unsigned int innerIndex = outerIndex + 1; + typename AdapterType::Array::const_iterator innerItr(outerItr); + for (++innerItr; innerItr != end; ++innerItr) { + if (outerItr->equalTo(*innerItr, true)) { + if (!m_results) { + return false; + } + m_results->pushError(m_context, "Elements at indexes #" + std::to_string(outerIndex) + + " and #" + std::to_string(innerIndex) + " violate uniqueness constraint."); + validated = false; + } + ++innerIndex; + } + ++outerIndex; + } + + return validated; + } + +private: + + /** + * @brief Functor to compare a node with a collection of values + */ + struct ValidateEquality + { + ValidateEquality( + const AdapterType &target, + const std::vector &context, + bool continueOnSuccess, + bool continueOnFailure, + bool strictTypes, + ValidationResults *results, + unsigned int *numValidated) + : m_target(target), + m_context(context), + m_continueOnSuccess(continueOnSuccess), + m_continueOnFailure(continueOnFailure), + m_strictTypes(strictTypes), + m_results(results), + m_numValidated(numValidated) { } + + template + bool operator()(const OtherValue &value) const + { + if (value.equalTo(m_target, m_strictTypes)) { + if (m_numValidated) { + (*m_numValidated)++; + } + + return m_continueOnSuccess; + } + + if (m_results) { + m_results->pushError(m_context, "Target value and comparison value are not equal"); + } + + return m_continueOnFailure; + } + + private: + const AdapterType &m_target; + const std::vector &m_context; + bool m_continueOnSuccess; + bool m_continueOnFailure; + bool m_strictTypes; + ValidationResults * const m_results; + unsigned int * const m_numValidated; + }; + + /** + * @brief Functor to validate the presence of a set of properties + */ + struct ValidateProperties + { + ValidateProperties( + const typename AdapterType::Object &object, + const std::vector &context, + bool continueOnSuccess, + bool continueOnFailure, + ValidationResults *results, + bool *validated) + : m_object(object), + m_context(context), + m_continueOnSuccess(continueOnSuccess), + m_continueOnFailure(continueOnFailure), + m_results(results), + m_validated(validated) { } + + template + bool operator()(const StringType &property) const + { + if (m_object.find(property.c_str()) == m_object.end()) { + if (m_validated) { + *m_validated = false; + } + + if (m_results) { + m_results->pushError(m_context, "Missing required property '" + + std::string(property.c_str()) + "'."); + } + + return m_continueOnFailure; + } + + return m_continueOnSuccess; + } + + private: + const typename AdapterType::Object &m_object; + const std::vector &m_context; + bool m_continueOnSuccess; + bool m_continueOnFailure; + ValidationResults * const m_results; + bool * const m_validated; + }; + + /** + * @brief Functor to validate property-based dependencies + */ + struct ValidatePropertyDependencies + { + ValidatePropertyDependencies( + const typename AdapterType::Object &object, + const std::vector &context, + ValidationResults *results, + bool *validated) + : m_object(object), + m_context(context), + m_results(results), + m_validated(validated) { } + + template + bool operator()(const StringType &propertyName, const ContainerType &dependencyNames) const + { + const std::string propertyNameKey(propertyName.c_str()); + if (m_object.find(propertyNameKey) == m_object.end()) { + return true; + } + + typedef typename ContainerType::value_type ValueType; + for (const ValueType &dependencyName : dependencyNames) { + const std::string dependencyNameKey(dependencyName.c_str()); + if (m_object.find(dependencyNameKey) == m_object.end()) { + if (m_validated) { + *m_validated = false; + } + if (m_results) { + m_results->pushError(m_context, "Missing dependency '" + dependencyNameKey + "'."); + } else { + return false; + } + } + } + + return true; + } + + private: + const typename AdapterType::Object &m_object; + const std::vector &m_context; + ValidationResults * const m_results; + bool * const m_validated; + }; + + /** + * @brief Functor to validate against sub-schemas in 'items' constraint + */ + struct ValidateItems + { + ValidateItems( + const typename AdapterType::Array &arr, + const std::vector &context, + bool continueOnSuccess, + bool continueOnFailure, + bool strictTypes, + ValidationResults *results, + unsigned int *numValidated, + bool *validated, + std::unordered_map& regexesCache) + : m_arr(arr), + m_context(context), + m_continueOnSuccess(continueOnSuccess), + m_continueOnFailure(continueOnFailure), + m_strictTypes(strictTypes), + m_results(results), + m_numValidated(numValidated), + m_validated(validated), + m_regexesCache(regexesCache) { } + + bool operator()(unsigned int index, const Subschema *subschema) const + { + // Check that there are more elements to validate + if (index >= m_arr.size()) { + return false; + } + + // Update context + std::vector newContext = m_context; + newContext.push_back("[" + std::to_string(index) + "]"); + + // Find array item + typename AdapterType::Array::const_iterator itr = m_arr.begin(); + itr.advance(index); + + // Validate current array item + ValidationVisitor validator(*itr, newContext, m_strictTypes, m_results, m_regexesCache); + if (validator.validateSchema(*subschema)) { + if (m_numValidated) { + (*m_numValidated)++; + } + + return m_continueOnSuccess; + } + + if (m_validated) { + *m_validated = false; + } + + if (m_results) { + m_results->pushError(newContext, "Failed to validate item #" + std::to_string(index) + + " against corresponding item schema."); + } + + return m_continueOnFailure; + } + + private: + const typename AdapterType::Array &m_arr; + const std::vector &m_context; + bool m_continueOnSuccess; + bool m_continueOnFailure; + bool m_strictTypes; + ValidationResults * const m_results; + unsigned int * const m_numValidated; + bool * const m_validated; + std::unordered_map& m_regexesCache; + }; + + /** + * @brief Functor to validate value against named JSON types + */ + struct ValidateNamedTypes + { + ValidateNamedTypes( + const AdapterType &target, + bool continueOnSuccess, + bool continueOnFailure, + bool strictTypes, + bool *validated) + : m_target(target), + m_continueOnSuccess(continueOnSuccess), + m_continueOnFailure(continueOnFailure), + m_strictTypes(strictTypes), + m_validated(validated) { } + + bool operator()(constraints::TypeConstraint::JsonType jsonType) const + { + typedef constraints::TypeConstraint TypeConstraint; + + bool valid = false; + + switch (jsonType) { + case TypeConstraint::kAny: + valid = true; + break; + case TypeConstraint::kArray: + valid = m_target.isArray(); + break; + case TypeConstraint::kBoolean: + valid = m_target.isBool() || (!m_strictTypes && m_target.maybeBool()); + break; + case TypeConstraint::kInteger: + valid = m_target.isInteger() || (!m_strictTypes && m_target.maybeInteger()); + break; + case TypeConstraint::kNull: + valid = m_target.isNull() || (!m_strictTypes && m_target.maybeNull()); + break; + case TypeConstraint::kNumber: + valid = m_target.isNumber() || (!m_strictTypes && m_target.maybeDouble()); + break; + case TypeConstraint::kObject: + valid = m_target.isObject(); + break; + case TypeConstraint::kString: + valid = m_target.isString(); + break; + default: + break; + } + + if (valid && m_validated) { + *m_validated = true; + } + + return (valid && m_continueOnSuccess) || m_continueOnFailure; + } + + private: + const AdapterType m_target; + const bool m_continueOnSuccess; + const bool m_continueOnFailure; + const bool m_strictTypes; + bool * const m_validated; + }; + + /** + * @brief Functor to validate object properties against sub-schemas + * defined by a 'patternProperties' constraint + */ + struct ValidatePatternPropertySubschemas + { + ValidatePatternPropertySubschemas( + const typename AdapterType::Object &object, + const std::vector &context, + bool continueOnSuccess, + bool continueOnFailure, + bool continueIfUnmatched, + bool strictTypes, + ValidationResults *results, + std::set *propertiesMatched, + bool *validated, + std::unordered_map& regexesCache) + : m_object(object), + m_context(context), + m_continueOnSuccess(continueOnSuccess), + m_continueOnFailure(continueOnFailure), + m_continueIfUnmatched(continueIfUnmatched), + m_strictTypes(strictTypes), + m_results(results), + m_propertiesMatched(propertiesMatched), + m_validated(validated), + m_regexesCache(regexesCache) { } + + template + bool operator()(const StringType &patternProperty, const Subschema *subschema) const + { + const std::string patternPropertyStr(patternProperty.c_str()); + + // It would be nice to store pre-allocated regex objects in the + // PropertiesConstraint. does std::regex currently support + // custom allocators? Anyway, this isn't an issue here, because Valijson's + // JSON Scheme validator does not yet support custom allocators. + const std::regex r(patternPropertyStr); + + bool matchFound = false; + + // Recursively validate all matching properties + typedef const typename AdapterType::ObjectMember ObjectMember; + for (const ObjectMember m : m_object) { + if (std::regex_search(m.first, r)) { + matchFound = true; + if (m_propertiesMatched) { + m_propertiesMatched->insert(m.first); + } + + // Update context + std::vector newContext = m_context; + newContext.push_back("[" + m.first + "]"); + + // Recursively validate property's value + ValidationVisitor validator(m.second, newContext, m_strictTypes, m_results, m_regexesCache); + if (validator.validateSchema(*subschema)) { + continue; + } + + if (m_results) { + m_results->pushError(m_context, "Failed to validate against schema associated with pattern '" + + patternPropertyStr + "'."); + } + + if (m_validated) { + *m_validated = false; + } + + if (!m_continueOnFailure) { + return false; + } + } + } + + // Allow iteration to terminate if there was not at least one match + if (!matchFound && !m_continueIfUnmatched) { + return false; + } + + return m_continueOnSuccess; + } + + private: + const typename AdapterType::Object &m_object; + const std::vector &m_context; + const bool m_continueOnSuccess; + const bool m_continueOnFailure; + const bool m_continueIfUnmatched; + const bool m_strictTypes; + ValidationResults * const m_results; + std::set * const m_propertiesMatched; + bool * const m_validated; + std::unordered_map& m_regexesCache; + }; + + /** + * @brief Functor to validate object properties against sub-schemas defined + * by a 'properties' constraint + */ + struct ValidatePropertySubschemas + { + ValidatePropertySubschemas( + const typename AdapterType::Object &object, + const std::vector &context, + bool continueOnSuccess, + bool continueOnFailure, + bool continueIfUnmatched, + bool strictTypes, + ValidationResults *results, + std::set *propertiesMatched, + bool *validated, + std::unordered_map& regexesCache) + : m_object(object), + m_context(context), + m_continueOnSuccess(continueOnSuccess), + m_continueOnFailure(continueOnFailure), + m_continueIfUnmatched(continueIfUnmatched), + m_strictTypes(strictTypes), + m_results(results), + m_propertiesMatched(propertiesMatched), + m_validated(validated), + m_regexesCache(regexesCache) { } + + template + bool operator()(const StringType &propertyName, const Subschema *subschema) const + { + const std::string propertyNameKey(propertyName.c_str()); + const typename AdapterType::Object::const_iterator itr = m_object.find(propertyNameKey); + if (itr == m_object.end()) { + return m_continueIfUnmatched; + } + + if (m_propertiesMatched) { + m_propertiesMatched->insert(propertyNameKey); + } + + // Update context + std::vector newContext = m_context; + newContext.push_back("[" + propertyNameKey + "]"); + + // Recursively validate property's value + ValidationVisitor validator(itr->second, newContext, m_strictTypes, m_results, m_regexesCache); + if (validator.validateSchema(*subschema)) { + return m_continueOnSuccess; + } + + if (m_results) { + m_results->pushError(m_context, "Failed to validate against schema associated with property name '" + + propertyNameKey + "'."); + } + + if (m_validated) { + *m_validated = false; + } + + return m_continueOnFailure; + } + + private: + const typename AdapterType::Object &m_object; + const std::vector &m_context; + const bool m_continueOnSuccess; + const bool m_continueOnFailure; + const bool m_continueIfUnmatched; + const bool m_strictTypes; + ValidationResults * const m_results; + std::set * const m_propertiesMatched; + bool * const m_validated; + std::unordered_map& m_regexesCache; + }; + + /** + * @brief Functor to validate schema-based dependencies + */ + struct ValidateSchemaDependencies + { + ValidateSchemaDependencies( + const typename AdapterType::Object &object, + const std::vector &context, + ValidationVisitor &validationVisitor, + ValidationResults *results, + bool *validated) + : m_object(object), + m_context(context), + m_validationVisitor(validationVisitor), + m_results(results), + m_validated(validated) { } + + template + bool operator()(const StringType &propertyName, const Subschema *schemaDependency) const + { + const std::string propertyNameKey(propertyName.c_str()); + if (m_object.find(propertyNameKey) == m_object.end()) { + return true; + } + + if (!m_validationVisitor.validateSchema(*schemaDependency)) { + if (m_validated) { + *m_validated = false; + } + if (m_results) { + m_results->pushError(m_context, "Failed to validate against dependent schema."); + } else { + return false; + } + } + + return true; + } + + private: + const typename AdapterType::Object &m_object; + const std::vector &m_context; + ValidationVisitor &m_validationVisitor; + ValidationResults * const m_results; + bool * const m_validated; + }; + + /** + * @brief Functor that can be used to validate one or more subschemas + * + * This functor is designed to be applied to collections of subschemas + * contained within 'allOf', 'anyOf' and 'oneOf' constraints. + * + * The return value depends on whether a given schema validates, with the + * actual return value for a given case being decided at construction time. + * The return value is used by the 'applyToSubschemas' functions in the + * AllOfConstraint, AnyOfConstraint and OneOfConstrant classes to decide + * whether to terminate early. + * + * The functor uses output parameters (provided at construction) to update + * validation state that may be needed by the caller. + */ + struct ValidateSubschemas + { + ValidateSubschemas( + const AdapterType &adapter, + const std::vector &context, + bool continueOnSuccess, + bool continueOnFailure, + ValidationVisitor &validationVisitor, + ValidationResults *results, + unsigned int *numValidated, + bool *validated) + : m_adapter(adapter), + m_context(context), + m_continueOnSuccess(continueOnSuccess), + m_continueOnFailure(continueOnFailure), + m_validationVisitor(validationVisitor), + m_results(results), + m_numValidated(numValidated), + m_validated(validated) { } + + bool operator()(unsigned int index, const Subschema *subschema) const + { + if (m_validationVisitor.validateSchema(*subschema)) { + if (m_numValidated) { + (*m_numValidated)++; + } + + return m_continueOnSuccess; + } + + if (m_validated) { + *m_validated = false; + } + + if (m_results) { + m_results->pushError(m_context, + "Failed to validate against child schema #" + std::to_string(index) + "."); + } + + return m_continueOnFailure; + } + + private: + const AdapterType &m_adapter; + const std::vector &m_context; + bool m_continueOnSuccess; + bool m_continueOnFailure; + ValidationVisitor &m_validationVisitor; + ValidationResults * const m_results; + unsigned int * const m_numValidated; + bool * const m_validated; + }; + + /** + * @brief Callback function that passes a visitor to a constraint. + * + * @param constraint Reference to constraint to be visited + * @param visitor Reference to visitor to be applied + * + * @return true if the visitor returns successfully, false otherwise. + */ + static bool validationCallback(const constraints::Constraint &constraint, ValidationVisitor &visitor) + { + return constraint.accept(visitor); + } + + /// The JSON value being validated + AdapterType m_target; + + /// Vector of strings describing the current object context + std::vector m_context; + + /// Optional pointer to a ValidationResults object to be populated + ValidationResults *m_results; + + /// Option to use strict type comparison + bool m_strictTypes; + + /// Cached regex objects for pattern constraint + std::unordered_map& m_regexesCache; +}; + +} // namespace valijson + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif +#pragma once + + +namespace valijson { + +class Schema; +class ValidationResults; + +/** + * @brief Class that provides validation functionality. + */ +class Validator +{ +public: + enum TypeCheckingMode + { + kStrongTypes, + kWeakTypes + }; + + /** + * @brief Construct a Validator that uses strong type checking by default + */ + Validator() + : strictTypes(true) { } + + /** + * @brief Construct a Validator using a specific type checking mode + * + * @param typeCheckingMode choice of strong or weak type checking + */ + Validator(TypeCheckingMode typeCheckingMode) + : strictTypes(typeCheckingMode == kStrongTypes) { } + + /** + * @brief Validate a JSON document and optionally return the results. + * + * When a ValidationResults object is provided via the \c results parameter, + * validation will be performed against each constraint defined by the + * schema, even if validation fails for some or all constraints. + * + * If a pointer to a ValidationResults instance is not provided, validation + * will only continue for as long as the constraints are validated + * successfully. + * + * @param schema The schema to validate against + * @param target A rapidjson::Value to be validated + * + * @param results An optional pointer to a ValidationResults instance that + * will be used to report validation errors + * + * @returns true if validation succeeds, false otherwise + */ + template + bool validate(const Subschema &schema, const AdapterType &target, + ValidationResults *results) + { + // Construct a ValidationVisitor to perform validation at the root level + ValidationVisitor v(target, + std::vector(1, ""), strictTypes, results, regexesCache); + + return v.validateSchema(schema); + } + +private: + + /// Flag indicating that strict type comparisons should be used + bool strictTypes; + + /// Cached regex objects for pattern constraint. Key - pattern. + std::unordered_map regexesCache; +}; + +} // namespace valijson +/** + * @file + * + * @brief Adapter implementation for the nlohmann json parser library. + * + * Include this file in your program to enable support for nlohmann json. + * + * This file defines the following classes (not in this order): + * - NlohmannJsonAdapter + * - NlohmannJsonArray + * - NlohmannJsonValueIterator + * - NlohmannJsonFrozenValue + * - NlohmannJsonObject + * - NlohmannJsonObjectMember + * - NlohmannJsonObjectMemberIterator + * - NlohmannJsonValue + * + * Due to the dependencies that exist between these classes, the ordering of + * class declarations and definitions may be a bit confusing. The best place to + * start is NlohmannJsonAdapter. This class definition is actually very small, + * since most of the functionality is inherited from the BasicAdapter class. + * Most of the classes in this file are provided as template arguments to the + * inherited BasicAdapter class. + */ + +#pragma once + +#include +#include + +#include + +namespace valijson { +namespace adapters { + +class NlohmannJsonAdapter; +class NlohmannJsonArrayValueIterator; +class NlohmannJsonObjectMemberIterator; + +typedef std::pair NlohmannJsonObjectMember; + +/** + * @brief Light weight wrapper for a NlohmannJson array value. + * + * This class is light weight wrapper for a NlohmannJson array. It provides a + * minimum set of container functions and typedefs that allow it to be used as + * an iterable container. + * + * An instance of this class contains a single reference to the underlying + * NlohmannJson value, assumed to be an array, so there is very little overhead + * associated with copy construction and passing by value. + */ +class NlohmannJsonArray +{ +public: + + typedef NlohmannJsonArrayValueIterator const_iterator; + typedef NlohmannJsonArrayValueIterator iterator; + + /// Construct a NlohmannJsonArray referencing an empty array. + NlohmannJsonArray() + : m_value(emptyArray()) { } + + /** + * @brief Construct a NlohmannJsonArray referencing a specific NlohmannJson + * value. + * + * @param value reference to a NlohmannJson value + * + * Note that this constructor will throw an exception if the value is not + * an array. + */ + NlohmannJsonArray(const nlohmann::json &value) + : m_value(value) + { + if (!value.is_array()) { + throwRuntimeError("Value is not an array."); + } + } + + /** + * @brief Return an iterator for the first element of the array. + * + * The iterator return by this function is effectively the iterator + * returned by the underlying NlohmannJson implementation. + */ + NlohmannJsonArrayValueIterator begin() const; + + /** + * @brief Return an iterator for one-past the last element of the array. + * + * The iterator return by this function is effectively the iterator + * returned by the underlying NlohmannJson implementation. + */ + NlohmannJsonArrayValueIterator end() const; + + /// Return the number of elements in the array + size_t size() const + { + return m_value.size(); + } + +private: + + /** + * @brief Return a reference to a NlohmannJson value that is an empty array. + * + * Note that the value returned by this function is a singleton. + */ + static const nlohmann::json & emptyArray() + { + static const nlohmann::json array = nlohmann::json::array(); + return array; + } + + /// Reference to the contained value + const nlohmann::json &m_value; +}; + +/** + * @brief Light weight wrapper for a NlohmannJson object. + * + * This class is light weight wrapper for a NlohmannJson object. It provides a + * minimum set of container functions and typedefs that allow it to be used as + * an iterable container. + * + * An instance of this class contains a single reference to the underlying + * NlohmannJson value, assumed to be an object, so there is very little overhead + * associated with copy construction and passing by value. + */ +class NlohmannJsonObject +{ +public: + + typedef NlohmannJsonObjectMemberIterator const_iterator; + typedef NlohmannJsonObjectMemberIterator iterator; + + /// Construct a NlohmannJsonObject referencing an empty object singleton. + NlohmannJsonObject() + : m_value(emptyObject()) { } + + /** + * @brief Construct a NlohmannJsonObject referencing a specific NlohmannJson + * value. + * + * @param value reference to a NlohmannJson value + * + * Note that this constructor will throw an exception if the value is not + * an object. + */ + NlohmannJsonObject(const nlohmann::json &value) + : m_value(value) + { + if (!value.is_object()) { + throwRuntimeError("Value is not an object."); + } + } + + /** + * @brief Return an iterator for this first object member + * + * The iterator return by this function is effectively a wrapper around + * the iterator value returned by the underlying NlohmannJson implementation. + */ + NlohmannJsonObjectMemberIterator begin() const; + + /** + * @brief Return an iterator for an invalid object member that indicates + * the end of the collection. + * + * The iterator return by this function is effectively a wrapper around + * the iterator value returned by the underlying NlohmannJson implementation. + */ + NlohmannJsonObjectMemberIterator end() const; + + /** + * @brief Return an iterator for the object member with the specified + * property name. + * + * If an object member with the specified name does not exist, the iterator + * returned will be the same as the iterator returned by the end() function. + * + * @param propertyName property name to search for + */ + NlohmannJsonObjectMemberIterator find(const std::string &propertyName) const; + + /// Returns the number of members belonging to this object. + size_t size() const + { + return m_value.size(); + } + +private: + + /** + * @brief Return a reference to a NlohmannJson value that is empty object. + * + * Note that the value returned by this function is a singleton. + */ + static const nlohmann::json & emptyObject() + { + static const nlohmann::json object = nlohmann::json::object(); + return object; + } + + /// Reference to the contained object + const nlohmann::json &m_value; +}; + + +/** + * @brief Stores an independent copy of a NlohmannJson value. + * + * This class allows a NlohmannJson value to be stored independent of its original + * document. NlohmannJson makes this easy to do, as it does not perform any + * custom memory management. + * + * @see FrozenValue + */ +class NlohmannJsonFrozenValue: public FrozenValue +{ +public: + + /** + * @brief Make a copy of a NlohmannJson value + * + * @param source the NlohmannJson value to be copied + */ + explicit NlohmannJsonFrozenValue(nlohmann::json source) + : m_value(std::move(source)) { } + + FrozenValue * clone() const override + { + return new NlohmannJsonFrozenValue(m_value); + } + + bool equalTo(const Adapter &other, bool strict) const override; + +private: + + /// Stored NlohmannJson value + nlohmann::json m_value; +}; + + +/** + * @brief Light weight wrapper for a NlohmannJson value. + * + * This class is passed as an argument to the BasicAdapter template class, + * and is used to provide access to a NlohmannJson value. This class is responsible + * for the mechanics of actually reading a NlohmannJson value, whereas the + * BasicAdapter class is responsible for the semantics of type comparisons + * and conversions. + * + * The functions that need to be provided by this class are defined implicitly + * by the implementation of the BasicAdapter template class. + * + * @see BasicAdapter + */ +class NlohmannJsonValue +{ +public: + + /// Construct a wrapper for the empty object singleton + NlohmannJsonValue() + : m_value(emptyObject()) { } + + /// Construct a wrapper for a specific NlohmannJson value + NlohmannJsonValue(const nlohmann::json &value) + : m_value(value) { } + + /** + * @brief Create a new NlohmannJsonFrozenValue instance that contains the + * value referenced by this NlohmannJsonValue instance. + * + * @returns pointer to a new NlohmannJsonFrozenValue instance, belonging to the + * caller. + */ + FrozenValue * freeze() const + { + return new NlohmannJsonFrozenValue(m_value); + } + + /** + * @brief Optionally return a NlohmannJsonArray instance. + * + * If the referenced NlohmannJson value is an array, this function will return + * a std::optional containing a NlohmannJsonArray instance referencing the + * array. + * + * Otherwise it will return an empty optional. + */ + opt::optional getArrayOptional() const + { + if (m_value.is_array()) { + return opt::make_optional(NlohmannJsonArray(m_value)); + } + + return {}; + } + + /** + * @brief Retrieve the number of elements in the array + * + * If the referenced NlohmannJson value is an array, this function will + * retrieve the number of elements in the array and store it in the output + * variable provided. + * + * @param result reference to size_t to set with result + * + * @returns true if the number of elements was retrieved, false otherwise. + */ + bool getArraySize(size_t &result) const + { + if (m_value.is_array()) { + result = m_value.size(); + return true; + } + + return false; + } + + bool getBool(bool &result) const + { + if (m_value.is_boolean()) { + result = m_value.get(); + return true; + } + + return false; + } + + bool getDouble(double &result) const + { + if (m_value.is_number_float()) { + result = m_value.get(); + return true; + } + + return false; + } + + bool getInteger(int64_t &result) const + { + if(m_value.is_number_integer()) { + result = m_value.get(); + return true; + } + return false; + } + + /** + * @brief Optionally return a NlohmannJsonObject instance. + * + * If the referenced NlohmannJson value is an object, this function will return a + * std::optional containing a NlohmannJsonObject instance referencing the + * object. + * + * Otherwise it will return an empty optional. + */ + opt::optional getObjectOptional() const + { + if (m_value.is_object()) { + return opt::make_optional(NlohmannJsonObject(m_value)); + } + + return {}; + } + + /** + * @brief Retrieve the number of members in the object + * + * If the referenced NlohmannJson value is an object, this function will + * retrieve the number of members in the object and store it in the output + * variable provided. + * + * @param result reference to size_t to set with result + * + * @returns true if the number of members was retrieved, false otherwise. + */ + bool getObjectSize(size_t &result) const + { + if (m_value.is_object()) { + result = m_value.size(); + return true; + } + + return false; + } + + bool getString(std::string &result) const + { + if (m_value.is_string()) { + result = m_value.get(); + return true; + } + + return false; + } + + static bool hasStrictTypes() + { + return true; + } + + bool isArray() const + { + return m_value.is_array(); + } + + bool isBool() const + { + return m_value.is_boolean(); + } + + bool isDouble() const + { + return m_value.is_number_float(); + } + + bool isInteger() const + { + return m_value.is_number_integer(); + } + + bool isNull() const + { + return m_value.is_null(); + } + + bool isNumber() const + { + return m_value.is_number(); + } + + bool isObject() const + { + return m_value.is_object(); + } + + bool isString() const + { + return m_value.is_string(); + } + +private: + + /// Return a reference to an empty object singleton + static const nlohmann::json & emptyObject() + { + static const nlohmann::json object = nlohmann::json::object(); + return object; + } + + /// Reference to the contained NlohmannJson value. + const nlohmann::json &m_value; +}; + +/** + * @brief An implementation of the Adapter interface supporting NlohmannJson. + * + * This class is defined in terms of the BasicAdapter template class, which + * helps to ensure that all of the Adapter implementations behave consistently. + * + * @see Adapter + * @see BasicAdapter + */ +class NlohmannJsonAdapter: + public BasicAdapter +{ +public: + /// Construct a NlohmannJsonAdapter that contains an empty object + NlohmannJsonAdapter() + : BasicAdapter() { } + + /// Construct a NlohmannJsonAdapter containing a specific Nlohmann Json object + NlohmannJsonAdapter(const nlohmann::json &value) + : BasicAdapter(NlohmannJsonValue{value}) { } +}; + +/** + * @brief Class for iterating over values held in a JSON array. + * + * This class provides a JSON array iterator that dereferences as an instance of + * NlohmannJsonAdapter representing a value stored in the array. It has been + * implemented using the boost iterator_facade template. + * + * @see NlohmannJsonArray + */ +class NlohmannJsonArrayValueIterator +{ +public: + using iterator_category = std::bidirectional_iterator_tag; + using value_type = NlohmannJsonAdapter; + using difference_type = NlohmannJsonAdapter; + using pointer = NlohmannJsonAdapter*; + using reference = NlohmannJsonAdapter&; + + /** + * @brief Construct a new NlohmannJsonArrayValueIterator using an existing + * NlohmannJson iterator. + * + * @param itr NlohmannJson iterator to store + */ + NlohmannJsonArrayValueIterator(const nlohmann::json::const_iterator &itr) + : m_itr(itr) { } + + /// Returns a NlohmannJsonAdapter that contains the value of the current + /// element. + NlohmannJsonAdapter operator*() const + { + return NlohmannJsonAdapter(*m_itr); + } + + DerefProxy operator->() const + { + return DerefProxy(**this); + } + + /** + * @brief Compare this iterator against another iterator. + * + * Note that this directly compares the iterators, not the underlying + * values, and assumes that two identical iterators will point to the same + * underlying object. + * + * @param other iterator to compare against + * + * @returns true if the iterators are equal, false otherwise. + */ + bool operator==(const NlohmannJsonArrayValueIterator &other) const + { + return m_itr == other.m_itr; + } + + bool operator!=(const NlohmannJsonArrayValueIterator &other) const + { + return !(m_itr == other.m_itr); + } + + const NlohmannJsonArrayValueIterator& operator++() + { + m_itr++; + + return *this; + } + + NlohmannJsonArrayValueIterator operator++(int) + { + NlohmannJsonArrayValueIterator iterator_pre(m_itr); + ++(*this); + return iterator_pre; + } + + const NlohmannJsonArrayValueIterator& operator--() + { + m_itr--; + + return *this; + } + + void advance(std::ptrdiff_t n) + { + m_itr += n; + } + +private: + nlohmann::json::const_iterator m_itr; +}; + + +/** + * @brief Class for iterating over the members belonging to a JSON object. + * + * This class provides a JSON object iterator that dereferences as an instance + * of NlohmannJsonObjectMember representing one of the members of the object. It + * has been implemented using the boost iterator_facade template. + * + * @see NlohmannJsonObject + * @see NlohmannJsonObjectMember + */ +class NlohmannJsonObjectMemberIterator +{ +public: + using iterator_category = std::bidirectional_iterator_tag; + using value_type = NlohmannJsonObjectMember; + using difference_type = NlohmannJsonObjectMember; + using pointer = NlohmannJsonObjectMember*; + using reference = NlohmannJsonObjectMember&; + + /** + * @brief Construct an iterator from a NlohmannJson iterator. + * + * @param itr NlohmannJson iterator to store + */ + NlohmannJsonObjectMemberIterator(const nlohmann::json::const_iterator &itr) + : m_itr(itr) { } + + /** + * @brief Returns a NlohmannJsonObjectMember that contains the key and value + * belonging to the object member identified by the iterator. + */ + NlohmannJsonObjectMember operator*() const + { + return NlohmannJsonObjectMember(m_itr.key(), m_itr.value()); + } + + DerefProxy operator->() const + { + return DerefProxy(**this); + } + + /** + * @brief Compare this iterator with another iterator. + * + * Note that this directly compares the iterators, not the underlying + * values, and assumes that two identical iterators will point to the same + * underlying object. + * + * @param other Iterator to compare with + * + * @returns true if the underlying iterators are equal, false otherwise + */ + bool operator==(const NlohmannJsonObjectMemberIterator &other) const + { + return m_itr == other.m_itr; + } + + bool operator!=(const NlohmannJsonObjectMemberIterator &other) const + { + return !(m_itr == other.m_itr); + } + + const NlohmannJsonObjectMemberIterator& operator++() + { + m_itr++; + + return *this; + } + + NlohmannJsonObjectMemberIterator operator++(int) + { + NlohmannJsonObjectMemberIterator iterator_pre(m_itr); + ++(*this); + return iterator_pre; + } + + const NlohmannJsonObjectMemberIterator& operator--() + { + m_itr--; + + return *this; + } + +private: + + /// Iternal copy of the original NlohmannJson iterator + nlohmann::json::const_iterator m_itr; +}; + +/// Specialisation of the AdapterTraits template struct for NlohmannJsonAdapter. +template<> +struct AdapterTraits +{ + typedef nlohmann::json DocumentType; + + static std::string adapterName() + { + return "NlohmannJsonAdapter"; + } +}; + +inline bool NlohmannJsonFrozenValue::equalTo(const Adapter &other, bool strict) const +{ + return NlohmannJsonAdapter(m_value).equalTo(other, strict); +} + +inline NlohmannJsonArrayValueIterator NlohmannJsonArray::begin() const +{ + return m_value.begin(); +} + +inline NlohmannJsonArrayValueIterator NlohmannJsonArray::end() const +{ + return m_value.end(); +} + +inline NlohmannJsonObjectMemberIterator NlohmannJsonObject::begin() const +{ + return m_value.begin(); +} + +inline NlohmannJsonObjectMemberIterator NlohmannJsonObject::end() const +{ + return m_value.end(); +} + +inline NlohmannJsonObjectMemberIterator NlohmannJsonObject::find( + const std::string &propertyName) const +{ + return m_value.find(propertyName); +} + +} // namespace adapters +} // namespace valijson +#pragma once + +#include + +#include + +namespace valijson { +namespace utils { + +inline bool loadDocument(const std::string &path, nlohmann::json &document) +{ + // Load schema JSON from file + std::string file; + if (!loadFile(path, file)) { + std::cerr << "Failed to load json from file '" << path << "'." + << std::endl; + return false; + } + + // Parse schema +#if VALIJSON_USE_EXCEPTION + try { + document = nlohmann::json::parse(file); + } catch (std::invalid_argument const& exception) { + std::cerr << "nlohmann::json failed to parse the document\n" + << "Parse error:" << exception.what() << "\n"; + return false; + } +#else + document = nlohmann::json::parse(file, nullptr, false); + if (document.is_discarded()) { + std::cerr << "nlohmann::json failed to parse the document."; + return false; + } +#endif + + return true; +} + +} // namespace utils +} // namespace valijson diff --git a/examples/valijson_nlohmann_bundled_test.cpp b/examples/valijson_nlohmann_bundled_test.cpp new file mode 100644 index 0000000000..50f4514bc2 --- /dev/null +++ b/examples/valijson_nlohmann_bundled_test.cpp @@ -0,0 +1,66 @@ +#include + +#include "valijson_nlohmann_bundled.hpp" + +using namespace std; +using namespace valijson; +using namespace valijson::adapters; + +int main(int argc, char *argv[]) +{ + if (argc != 3) { + cerr << "Usage: " << argv[0] << " " << endl; + return 1; + } + + // Load the document containing the schema + nlohmann::json schemaDocument; + if (!valijson::utils::loadDocument(argv[1], schemaDocument)) { + cerr << "Failed to load schema document." << endl; + return 1; + } + + // Load the document that is to be validated + nlohmann::json targetDocument; + if (!valijson::utils::loadDocument(argv[2], targetDocument)) { + cerr << "Failed to load target document." << endl; + return 1; + } + + // Parse the json schema into an internal schema format + Schema schema; + SchemaParser parser; + NlohmannJsonAdapter schemaDocumentAdapter(schemaDocument); + try { + parser.populateSchema(schemaDocumentAdapter, schema); + } catch (std::exception &e) { + cerr << "Failed to parse schema: " << e.what() << endl; + return 1; + } + + // Perform validation + Validator validator(Validator::kStrongTypes); + ValidationResults results; + NlohmannJsonAdapter targetDocumentAdapter(targetDocument); + if (!validator.validate(schema, targetDocumentAdapter, &results)) { + std::cerr << "Validation failed." << endl; + ValidationResults::Error error; + unsigned int errorNum = 1; + while (results.popError(error)) { + + std::string context; + std::vector::iterator itr = error.context.begin(); + for (; itr != error.context.end(); itr++) { + context += *itr; + } + + cerr << "Error #" << errorNum << std::endl + << " context: " << context << endl + << " desc: " << error.description << endl; + ++errorNum; + } + return 1; + } + + return 0; +} diff --git a/include/valijson/adapters/boost_json_adapter.hpp b/include/valijson/adapters/boost_json_adapter.hpp index e44e8c5d1e..2b83fd1684 100644 --- a/include/valijson/adapters/boost_json_adapter.hpp +++ b/include/valijson/adapters/boost_json_adapter.hpp @@ -29,9 +29,9 @@ #include -#include -#include -#include +#include +#include +#include namespace valijson { namespace adapters { @@ -485,12 +485,16 @@ class BoostJsonAdapter: { public: + // deleted to avoid holding references to temporaries + BoostJsonAdapter(boost::json::array &) = delete; + BoostJsonAdapter(boost::json::object &) = delete; + /// Construct a BoostJsonAdapter that contains an empty object BoostJsonAdapter() : BasicAdapter() { } /// Construct a BoostJsonAdapter using a specific Boost.JSON value - BoostJsonAdapter(const boost::json::value &value) + explicit BoostJsonAdapter(const boost::json::value &value) : BasicAdapter(value) { } }; diff --git a/include/valijson/adapters/json11_adapter.hpp b/include/valijson/adapters/json11_adapter.hpp index 41749b2fcb..6cab44569f 100644 --- a/include/valijson/adapters/json11_adapter.hpp +++ b/include/valijson/adapters/json11_adapter.hpp @@ -28,9 +28,9 @@ #include #include -#include -#include -#include +#include +#include +#include #include namespace valijson { diff --git a/include/valijson/adapters/jsoncpp_adapter.hpp b/include/valijson/adapters/jsoncpp_adapter.hpp index 2fd1fe74bf..51a986fe73 100644 --- a/include/valijson/adapters/jsoncpp_adapter.hpp +++ b/include/valijson/adapters/jsoncpp_adapter.hpp @@ -31,9 +31,9 @@ #include -#include -#include -#include +#include +#include +#include #include namespace valijson { diff --git a/include/valijson/adapters/nlohmann_json_adapter.hpp b/include/valijson/adapters/nlohmann_json_adapter.hpp index aeb1564768..87c270594c 100644 --- a/include/valijson/adapters/nlohmann_json_adapter.hpp +++ b/include/valijson/adapters/nlohmann_json_adapter.hpp @@ -28,9 +28,9 @@ #include #include -#include -#include -#include +#include +#include +#include #include #include diff --git a/include/valijson/adapters/picojson_adapter.hpp b/include/valijson/adapters/picojson_adapter.hpp index 4f5e5b6b74..ca3076fc0a 100644 --- a/include/valijson/adapters/picojson_adapter.hpp +++ b/include/valijson/adapters/picojson_adapter.hpp @@ -35,9 +35,9 @@ #include #endif -#include -#include -#include +#include +#include +#include #include namespace valijson { diff --git a/include/valijson/adapters/poco_json_adapter.hpp b/include/valijson/adapters/poco_json_adapter.hpp index a997debfa3..f9dfd2816f 100644 --- a/include/valijson/adapters/poco_json_adapter.hpp +++ b/include/valijson/adapters/poco_json_adapter.hpp @@ -28,9 +28,9 @@ #include #include -#include -#include -#include +#include +#include +#include #include namespace valijson { @@ -346,7 +346,7 @@ class PocoJsonValue bool getInteger(int64_t &result) const { if (m_value.isInteger()) { - result = m_value.convert(); + result = m_value.convert(); return true; } return false; diff --git a/include/valijson/adapters/property_tree_adapter.hpp b/include/valijson/adapters/property_tree_adapter.hpp index 425547fb6c..83e2eb1736 100644 --- a/include/valijson/adapters/property_tree_adapter.hpp +++ b/include/valijson/adapters/property_tree_adapter.hpp @@ -29,9 +29,9 @@ #include -#include -#include -#include +#include +#include +#include namespace valijson { namespace adapters { diff --git a/include/valijson/adapters/qtjson_adapter.hpp b/include/valijson/adapters/qtjson_adapter.hpp index 2307fb5c3b..023a075e61 100644 --- a/include/valijson/adapters/qtjson_adapter.hpp +++ b/include/valijson/adapters/qtjson_adapter.hpp @@ -32,9 +32,9 @@ #include #include -#include -#include -#include +#include +#include +#include #include namespace valijson { diff --git a/include/valijson/adapters/rapidjson_adapter.hpp b/include/valijson/adapters/rapidjson_adapter.hpp index c9ec75dcce..7df0b921da 100644 --- a/include/valijson/adapters/rapidjson_adapter.hpp +++ b/include/valijson/adapters/rapidjson_adapter.hpp @@ -63,9 +63,9 @@ #include -#include -#include -#include +#include +#include +#include #include namespace valijson { diff --git a/include/valijson/adapters/std_string_adapter.hpp b/include/valijson/adapters/std_string_adapter.hpp index 2084024f20..1da61b3d08 100644 --- a/include/valijson/adapters/std_string_adapter.hpp +++ b/include/valijson/adapters/std_string_adapter.hpp @@ -16,9 +16,9 @@ #include -#include -#include -#include +#include +#include +#include #include namespace valijson { diff --git a/include/valijson/adapters/yaml_cpp_adapter.hpp b/include/valijson/adapters/yaml_cpp_adapter.hpp new file mode 100644 index 0000000000..44fc8a7a9b --- /dev/null +++ b/include/valijson/adapters/yaml_cpp_adapter.hpp @@ -0,0 +1,697 @@ +/** + * @file + * + * @brief Adapter implementation for the yaml-cpp parser library. + * + * Include this file in your program to enable support for yaml-cpp. + * + * This file defines the following classes (not in this order): + * - YamlCppAdapter + * - YamlCppArray + * - YamlCppArrayValueIterator + * - YamlCppFrozenValue + * - YamlCppObject + * - YamlCppObjectMember + * - YamlCppObjectMemberIterator + * - YamlCppValue + * + * Due to the dependencies that exist between these classes, the ordering of + * class declarations and definitions may be a bit confusing. The best place to + * start is YamlCppAdapter. This class definition is actually very small, + * since most of the functionality is inherited from the BasicAdapter class. + * Most of the classes in this file are provided as template arguments to the + * inherited BasicAdapter class. + */ + +#pragma once + +#include +#include + +#include +#include +#include +#include +#include + +namespace valijson { +namespace adapters { + +class YamlCppAdapter; +class YamlCppArrayValueIterator; +class YamlCppObjectMemberIterator; + +typedef std::pair YamlCppObjectMember; + +/** + * @brief Light weight wrapper for a YamlCpp array value. + * + * This class is light weight wrapper for a YamlCpp array. It provides a + * minimum set of container functions and typedefs that allow it to be used as + * an iterable container. + * + * An instance of this class contains a single reference to the underlying + * YamlCpp value, assumed to be an array, so there is very little overhead + * associated with copy construction and passing by value. + */ +class YamlCppArray +{ + public: + typedef YamlCppArrayValueIterator const_iterator; + typedef YamlCppArrayValueIterator iterator; + + /// Construct a YamlCppArray referencing an empty array. + YamlCppArray() : m_value(emptyArray()) {} + + /** + * @brief Construct a YamlCppArray referencing a specific + * YamlCpp value. + * + * @param value reference to a YamlCpp value + * + * Note that this constructor will throw an exception if the value is not + * an array. + */ + YamlCppArray(const YAML::Node &value) : m_value(value) + { + if (!value.IsSequence()) { + throwRuntimeError("Value is not an array."); + } + } + + /** + * @brief Return an iterator for the first element of the array. + * + * The iterator return by this function is effectively the iterator + * returned by the underlying YamlCpp implementation. + */ + YamlCppArrayValueIterator begin() const; + + /** + * @brief Return an iterator for one-past the last element of the array. + * + * The iterator return by this function is effectively the iterator + * returned by the underlying YamlCpp implementation. + */ + YamlCppArrayValueIterator end() const; + + /// Return the number of elements in the array + size_t size() const + { + return m_value.size(); + } + + private: + /** + * @brief Return a reference to a YamlCpp value that is an empty + * array. + * + * Note that the value returned by this function is a singleton. + */ + static const YAML::Node &emptyArray() + { + static const YAML::Node array = YAML::Node(YAML::NodeType::Sequence); + return array; + } + + /// Reference to the contained value + const YAML::Node m_value; +}; + +/** + * @brief Light weight wrapper for a YamlCpp object. + * + * This class is light weight wrapper for a YamlCpp object. It provides a + * minimum set of container functions and typedefs that allow it to be used as + * an iterable container. + * + * An instance of this class contains a single reference to the underlying + * YamlCpp value, assumed to be an object, so there is very little overhead + * associated with copy construction and passing by value. + */ +class YamlCppObject +{ + public: + typedef YamlCppObjectMemberIterator const_iterator; + typedef YamlCppObjectMemberIterator iterator; + + /// Construct a YamlCppObject referencing an empty object singleton. + YamlCppObject() : m_value(emptyObject()) {} + + /** + * @brief Construct a YamlCppObject referencing a specific + * YamlCpp value. + * + * @param value reference to a YamlCpp value + * + * Note that this constructor will throw an exception if the value is not + * an object. + */ + YamlCppObject(const YAML::Node &value) : m_value(value) + { + if (!value.IsMap()) { + throwRuntimeError("Value is not an object."); + } + } + + /** + * @brief Return an iterator for this first object member + * + * The iterator return by this function is effectively a wrapper around + * the iterator value returned by the underlying YamlCpp + * implementation. + */ + YamlCppObjectMemberIterator begin() const; + + /** + * @brief Return an iterator for an invalid object member that indicates + * the end of the collection. + * + * The iterator return by this function is effectively a wrapper around + * the iterator value returned by the underlying YamlCpp + * implementation. + */ + YamlCppObjectMemberIterator end() const; + + /** + * @brief Return an iterator for the object member with the specified + * property name. + * + * If an object member with the specified name does not exist, the iterator + * returned will be the same as the iterator returned by the end() function. + * + * @param propertyName property name to search for + */ + YamlCppObjectMemberIterator find(const std::string &propertyName) const; + + /// Returns the number of members belonging to this object. + size_t size() const + { + return m_value.size(); + } + + private: + /** + * @brief Return a reference to a YamlCpp value that is empty object. + * + * Note that the value returned by this function is a singleton. + */ + static const YAML::Node &emptyObject() + { + static const YAML::Node object = YAML::Node(YAML::NodeType::Map); + return object; + } + + /// Reference to the contained object + const YAML::Node m_value; +}; + +/** + * @brief Stores an independent copy of a YamlCpp value. + * + * This class allows a YamlCpp value to be stored independent of its + * original document. + * + * @see FrozenValue + */ +class YamlCppFrozenValue : public FrozenValue +{ + public: + /** + * @brief Make a copy of a YamlCpp value + * + * @param source the YamlCpp value to be copied + */ + explicit YamlCppFrozenValue(YAML::Node source) + : m_value(YAML::Clone(source)) + { + } + + FrozenValue *clone() const override + { + return new YamlCppFrozenValue(m_value); + } + + bool equalTo(const Adapter &other, bool strict) const override; + + private: + /// Stored YamlCpp value + YAML::Node m_value; +}; + +/** + * @brief Light weight wrapper for a YamlCpp value. + * + * This class is passed as an argument to the BasicAdapter template class, + * and is used to provide access to a YamlCpp value. This class is + * responsible for the mechanics of actually reading a YamlCpp value, + * whereas the BasicAdapter class is responsible for the semantics of type + * comparisons and conversions. + * + * The functions that need to be provided by this class are defined implicitly + * by the implementation of the BasicAdapter template class. + * + * @see BasicAdapter + */ +class YamlCppValue +{ + public: + /// Construct a wrapper for the empty object singleton + YamlCppValue() : m_value(emptyObject()) {} + + /// Construct a wrapper for a specific YamlCpp value + YamlCppValue(const YAML::Node &value) : m_value(value) {} + + /** + * @brief Create a new YamlCppFrozenValue instance that contains the + * value referenced by this YamlCppValue instance. + * + * @returns pointer to a new YamlCppFrozenValue instance, belonging to + * the caller. + */ + FrozenValue *freeze() const + { + return new YamlCppFrozenValue(m_value); + } + + /** + * @brief Optionally return a YamlCppArray instance. + * + * If the referenced YamlCpp value is an array, this function will + * return a std::optional containing a YamlCppArray instance + * referencing the array. + * + * Otherwise it will return an empty optional. + */ + opt::optional getArrayOptional() const + { + if (m_value.IsSequence()) { + return opt::make_optional(YamlCppArray(m_value)); + } + + return {}; + } + + /** + * @brief Retrieve the number of elements in the array + * + * If the referenced YamlCpp value is an array, this function will + * retrieve the number of elements in the array and store it in the output + * variable provided. + * + * @param result reference to size_t to set with result + * + * @returns true if the number of elements was retrieved, false otherwise. + */ + bool getArraySize(size_t &result) const + { + if (m_value.IsSequence()) { + result = m_value.size(); + return true; + } + + return false; + } + + bool getBool(bool &result) const + { + if (m_value.IsScalar()) { + result = m_value.as(); + return true; + } + + return false; + } + + bool getDouble(double &result) const + { + if (m_value.IsScalar()) { + result = m_value.as(); + return true; + } + + return false; + } + + bool getInteger(int64_t &result) const + { + if (m_value.IsScalar()) { + result = m_value.as(); + return true; + } + return false; + } + + /** + * @brief Optionally return a YamlCppObject instance. + * + * If the referenced YamlCpp value is an object, this function will + * return a std::optional containing a YamlCppObject instance + * referencing the object. + * + * Otherwise it will return an empty optional. + */ + opt::optional getObjectOptional() const + { + if (m_value.IsMap()) { + return opt::make_optional(YamlCppObject(m_value)); + } + + return {}; + } + + /** + * @brief Retrieve the number of members in the object + * + * If the referenced YamlCpp value is an object, this function will + * retrieve the number of members in the object and store it in the output + * variable provided. + * + * @param result reference to size_t to set with result + * + * @returns true if the number of members was retrieved, false otherwise. + */ + bool getObjectSize(size_t &result) const + { + if (m_value.IsMap()) { + result = m_value.size(); + return true; + } + + return false; + } + + bool getString(std::string &result) const + { + if (m_value.IsScalar()) { + result = m_value.as(); + return true; + } + + return false; + } + + static bool hasStrictTypes() + { + return false; + } + + bool isArray() const + { + return m_value.IsSequence(); + } + + bool isBool() const + { + return false; + } + + bool isDouble() const + { + return false; + } + + bool isInteger() const + { + return false; + } + + bool isNull() const + { + return m_value.IsNull(); + } + + bool isNumber() const + { + return false; + } + + bool isObject() const + { + return m_value.IsMap(); + } + + bool isString() const + { + return true; + } + + private: + /// Return a reference to an empty object singleton + static const YAML::Node &emptyObject() + { + static const YAML::Node object = YAML::Node(YAML::NodeType::Map); + return object; + } + + /// Reference to the contained YamlCpp value. + const YAML::Node m_value; +}; + +/** + * @brief An implementation of the Adapter interface supporting YamlCpp. + * + * This class is defined in terms of the BasicAdapter template class, which + * helps to ensure that all of the Adapter implementations behave consistently. + * + * @see Adapter + * @see BasicAdapter + */ +class YamlCppAdapter + : public BasicAdapter +{ + public: + /// Construct a YamlCppAdapter that contains an empty object + YamlCppAdapter() : BasicAdapter() {} + + /// Construct a YamlCppAdapter containing a specific Nlohmann Json + /// object + YamlCppAdapter(const YAML::Node &value) : BasicAdapter(YamlCppValue{value}) + { + } +}; + +/** + * @brief Class for iterating over values held in a JSON array. + * + * This class provides a JSON array iterator that dereferences as an instance of + * YamlCppAdapter representing a value stored in the array. It has been + * implemented using the boost iterator_facade template. + * + * @see YamlCppArray + */ +class YamlCppArrayValueIterator +{ + public: + using iterator_category = std::forward_iterator_tag; + using value_type = YamlCppAdapter; + using difference_type = YamlCppAdapter; + using pointer = YamlCppAdapter *; + using reference = YamlCppAdapter &; + + /** + * @brief Construct a new YamlCppArrayValueIterator using an existing + * YamlCpp iterator. + * + * @param itr YamlCpp iterator to store + */ + YamlCppArrayValueIterator(const YAML::Node::const_iterator &itr) + : m_itr(itr) + { + } + + /// Returns a YamlCppAdapter that contains the value of the current + /// element. + YamlCppAdapter operator*() const + { + return YamlCppAdapter(*m_itr); + } + + DerefProxy operator->() const + { + return DerefProxy(**this); + } + + /** + * @brief Compare this iterator against another iterator. + * + * Note that this directly compares the iterators, not the underlying + * values, and assumes that two identical iterators will point to the same + * underlying object. + * + * @param other iterator to compare against + * + * @returns true if the iterators are equal, false otherwise. + */ + bool operator==(const YamlCppArrayValueIterator &other) const + { + return m_itr == other.m_itr; + } + + bool operator!=(const YamlCppArrayValueIterator &other) const + { + return !(m_itr == other.m_itr); + } + + const YamlCppArrayValueIterator &operator++() + { + m_itr++; + + return *this; + } + + YamlCppArrayValueIterator operator++(int) + { + YamlCppArrayValueIterator iterator_pre(m_itr); + ++(*this); + return iterator_pre; + } + + void advance(std::ptrdiff_t n) + { + for (auto i = 0; i < n; ++i) + m_itr++; + } + + private: + YAML::Node::const_iterator m_itr; +}; + +/** + * @brief Class for iterating over the members belonging to a JSON object. + * + * This class provides a JSON object iterator that dereferences as an instance + * of YamlCppObjectMember representing one of the members of the object. It + * has been implemented using the boost iterator_facade template. + * + * @see YamlCppObject + * @see YamlCppObjectMember + */ +class YamlCppObjectMemberIterator +{ + public: + using iterator_category = std::forward_iterator_tag; + using value_type = YamlCppObjectMember; + using difference_type = YamlCppObjectMember; + using pointer = YamlCppObjectMember *; + using reference = YamlCppObjectMember &; + + /** + * @brief Construct an iterator from a YamlCpp iterator. + * + * @param itr YamlCpp iterator to store + */ + YamlCppObjectMemberIterator(const YAML::Node::const_iterator &itr) + : m_itr(itr) + { + } + + /** + * @brief Returns a YamlCppObjectMember that contains the key and + * value belonging to the object member identified by the iterator. + */ + YamlCppObjectMember operator*() const + { + return YamlCppObjectMember(m_itr->first.as(), + m_itr->second); + } + + DerefProxy operator->() const + { + return DerefProxy(**this); + } + + /** + * @brief Compare this iterator with another iterator. + * + * Note that this directly compares the iterators, not the underlying + * values, and assumes that two identical iterators will point to the same + * underlying object. + * + * @param other Iterator to compare with + * + * @returns true if the underlying iterators are equal, false otherwise + */ + bool operator==(const YamlCppObjectMemberIterator &other) const + { + return m_itr == other.m_itr; + } + + bool operator!=(const YamlCppObjectMemberIterator &other) const + { + return !(m_itr == other.m_itr); + } + + const YamlCppObjectMemberIterator &operator++() + { + m_itr++; + + return *this; + } + + YamlCppObjectMemberIterator operator++(int) + { + YamlCppObjectMemberIterator iterator_pre(m_itr); + ++(*this); + return iterator_pre; + } + + private: + /// Iternal copy of the original YamlCpp iterator + YAML::Node::const_iterator m_itr; +}; + +/// Specialisation of the AdapterTraits template struct for YamlCppAdapter. +template <> struct AdapterTraits +{ + typedef YAML::Node DocumentType; + + static std::string adapterName() + { + return "YamlCppAdapter"; + } +}; + +inline bool YamlCppFrozenValue::equalTo(const Adapter &other, bool strict) const +{ + return YamlCppAdapter(m_value).equalTo(other, strict); +} + +inline YamlCppArrayValueIterator YamlCppArray::begin() const +{ + return m_value.begin(); +} + +inline YamlCppArrayValueIterator YamlCppArray::end() const +{ + return m_value.end(); +} + +inline YamlCppObjectMemberIterator YamlCppObject::begin() const +{ + return m_value.begin(); +} + +inline YamlCppObjectMemberIterator YamlCppObject::end() const +{ + return m_value.end(); +} + +inline YamlCppObjectMemberIterator +YamlCppObject::find(const std::string &propertyName) const +{ + YAML::Node result = m_value[propertyName]; + if (!result.IsDefined()) + return end(); + + // yaml-cpp does not offer an iterator-based lookup, + // so instead we create a new placeholder object and + // return an iterator to that container instead. + YAML::Node wrapper = YAML::Node(YAML::NodeType::Map); + wrapper[propertyName] = result; + return YamlCppObjectMemberIterator(wrapper.begin()); +} + +} // namespace adapters +} // namespace valijson diff --git a/include/valijson/constraints/basic_constraint.hpp b/include/valijson/constraints/basic_constraint.hpp index fd149a0dbc..f7d3ce6c67 100644 --- a/include/valijson/constraints/basic_constraint.hpp +++ b/include/valijson/constraints/basic_constraint.hpp @@ -36,14 +36,20 @@ struct BasicConstraint: Constraint return visitor.visit(*static_cast(this)); } - Constraint * clone(CustomAlloc allocFn, CustomFree) const override + OwningPointer clone(CustomAlloc allocFn, CustomFree freeFn) const override { - void *ptr = allocFn(sizeof(ConstraintType)); + // smart pointer to automatically free raw memory on exception + typedef std::unique_ptr RawOwningPointer; + auto ptr = RawOwningPointer(static_cast(allocFn(sizeof(ConstraintType))), freeFn); if (!ptr) { throwRuntimeError("Failed to allocate memory for cloned constraint"); } - return new (ptr) ConstraintType(*static_cast(this)); + // constructor might throw but the memory will be taken care of anyways + (void)new (ptr.get()) ConstraintType(*static_cast(this)); + + // implicitly convert to smart pointer that will also destroy object instance + return ptr; } protected: diff --git a/include/valijson/constraints/concrete_constraints.hpp b/include/valijson/constraints/concrete_constraints.hpp index 55cc1248a3..536f7ed2c2 100644 --- a/include/valijson/constraints/concrete_constraints.hpp +++ b/include/valijson/constraints/concrete_constraints.hpp @@ -20,9 +20,9 @@ #include #include -#include #include #include +#include #include #include @@ -427,6 +427,33 @@ class EnumConstraint: public BasicConstraint EnumValues m_enumValues; }; +/** + * @brief Represent a 'format' constraint + * + * A format constraint restricts the content of string values, as defined by a set of commonly used formats. + * + * As this is an optional feature in JSON Schema, unrecognised formats will be treated as valid for any string value. + */ +class FormatConstraint: public BasicConstraint +{ +public: + FormatConstraint() + : m_format() { } + + const std::string & getFormat() const + { + return m_format; + } + + void setFormat(const std::string & format) + { + m_format = format; + } + +private: + std::string m_format; +}; + /** * @brief Represents non-singular 'items' and 'additionalItems' constraints * @@ -903,26 +930,20 @@ class PolyConstraint : public Constraint return visitor.visit(*static_cast(this)); } - Constraint * clone(CustomAlloc allocFn, CustomFree freeFn) const override + OwningPointer clone(CustomAlloc allocFn, CustomFree freeFn) const override { - void *ptr = allocFn(sizeOf()); + // smart pointer to automatically free raw memory on exception + typedef std::unique_ptr RawOwningPointer; + auto ptr = RawOwningPointer(static_cast(allocFn(sizeOf())), freeFn); if (!ptr) { throwRuntimeError("Failed to allocate memory for cloned constraint"); } -#if VALIJSON_USE_EXCEPTIONS - try { -#endif - return cloneInto(ptr); -#if VALIJSON_USE_EXCEPTIONS - } catch (...) { - freeFn(ptr); - throw; - } -#else - // pretend to use freeFn to avoid warning in GCC 8.3 - (void)freeFn; -#endif + // constructor might throw but the memory will be taken care of anyways + (void)cloneInto(ptr.get()); + + // implicitly convert to smart pointer that will also destroy object instance + return ptr; } virtual bool validate(const adapters::Adapter &target, diff --git a/include/valijson/constraints/constraint.hpp b/include/valijson/constraints/constraint.hpp index 03b6444cdd..5b3ae3dc1d 100644 --- a/include/valijson/constraints/constraint.hpp +++ b/include/valijson/constraints/constraint.hpp @@ -1,5 +1,8 @@ #pragma once +#include +#include + namespace valijson { namespace constraints { @@ -18,6 +21,28 @@ struct Constraint /// Typedef for custom free-like function typedef void (*CustomFree)(void *); + /// Deleter type to be used with std::unique_ptr / std::shared_ptr + /// @tparam T Const or non-const type (same as the one used in unique_ptr/shared_ptr) + template + struct CustomDeleter + { + CustomDeleter(CustomFree freeFn) + : m_freeFn(freeFn) { } + + void operator()(T *ptr) const + { + auto *nonconst = const_cast::type *>(ptr); + nonconst->~T(); + m_freeFn(nonconst); + } + + private: + CustomFree m_freeFn; + }; + + /// Exclusive-ownership pointer to automatically handle deallocation + typedef std::unique_ptr> OwningPointer; + /** * @brief Virtual destructor. */ @@ -42,7 +67,7 @@ struct Constraint * * @returns an owning-pointer to the new constraint. */ - virtual Constraint * clone(CustomAlloc, CustomFree) const = 0; + virtual OwningPointer clone(CustomAlloc, CustomFree) const = 0; }; diff --git a/include/valijson/constraints/constraint_visitor.hpp b/include/valijson/constraints/constraint_visitor.hpp index da3f3d7cfd..6faffbddce 100644 --- a/include/valijson/constraints/constraint_visitor.hpp +++ b/include/valijson/constraints/constraint_visitor.hpp @@ -10,6 +10,7 @@ class ConstConstraint; class ContainsConstraint; class DependenciesConstraint; class EnumConstraint; +class FormatConstraint; class LinearItemsConstraint; class MaxItemsConstraint; class MaximumConstraint; @@ -46,6 +47,7 @@ class ConstraintVisitor typedef constraints::ContainsConstraint ContainsConstraint; typedef constraints::DependenciesConstraint DependenciesConstraint; typedef constraints::EnumConstraint EnumConstraint; + typedef constraints::FormatConstraint FormatConstraint; typedef constraints::LinearItemsConstraint LinearItemsConstraint; typedef constraints::MaximumConstraint MaximumConstraint; typedef constraints::MaxItemsConstraint MaxItemsConstraint; @@ -77,6 +79,7 @@ class ConstraintVisitor virtual bool visit(const ContainsConstraint &) = 0; virtual bool visit(const DependenciesConstraint &) = 0; virtual bool visit(const EnumConstraint &) = 0; + virtual bool visit(const FormatConstraint &) = 0; virtual bool visit(const LinearItemsConstraint &) = 0; virtual bool visit(const MaximumConstraint &) = 0; virtual bool visit(const MaxItemsConstraint &) = 0; diff --git a/include/valijson/exceptions.hpp b/include/valijson/exceptions.hpp index a4188259ee..3ddd5a9666 100644 --- a/include/valijson/exceptions.hpp +++ b/include/valijson/exceptions.hpp @@ -33,9 +33,7 @@ VALIJSON_NORETURN inline void throwLogicError(const std::string& msg) { #endif VALIJSON_NORETURN inline void throwNotSupported() { - throwRuntimeError("Not supported"); \ + throwRuntimeError("Not supported"); } } // namespace valijson - - diff --git a/include/valijson/adapters/adapter.hpp b/include/valijson/internal/adapter.hpp similarity index 100% rename from include/valijson/adapters/adapter.hpp rename to include/valijson/internal/adapter.hpp diff --git a/include/valijson/adapters/basic_adapter.hpp b/include/valijson/internal/basic_adapter.hpp similarity index 99% rename from include/valijson/adapters/basic_adapter.hpp rename to include/valijson/internal/basic_adapter.hpp index 84b2abdd1c..a03847c816 100644 --- a/include/valijson/adapters/basic_adapter.hpp +++ b/include/valijson/internal/basic_adapter.hpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include #include diff --git a/include/valijson/adapters/frozen_value.hpp b/include/valijson/internal/frozen_value.hpp similarity index 97% rename from include/valijson/adapters/frozen_value.hpp rename to include/valijson/internal/frozen_value.hpp index 56b5eafc5a..bf8ddc465f 100644 --- a/include/valijson/adapters/frozen_value.hpp +++ b/include/valijson/internal/frozen_value.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include namespace valijson { namespace adapters { diff --git a/include/valijson/internal/json_pointer.hpp b/include/valijson/internal/json_pointer.hpp index b290c02a2c..6328407a81 100644 --- a/include/valijson/internal/json_pointer.hpp +++ b/include/valijson/internal/json_pointer.hpp @@ -7,7 +7,7 @@ #include #include -#include +#include #include #include diff --git a/include/valijson/internal/optional_bundled.hpp b/include/valijson/internal/optional_bundled.hpp new file mode 100644 index 0000000000..a129e215f9 --- /dev/null +++ b/include/valijson/internal/optional_bundled.hpp @@ -0,0 +1,3 @@ +#pragma once + +namespace opt = std::experimental; diff --git a/include/valijson/schema_parser.hpp b/include/valijson/schema_parser.hpp index aeff3421c5..0c0d5da21a 100644 --- a/include/valijson/schema_parser.hpp +++ b/include/valijson/schema_parser.hpp @@ -6,8 +6,8 @@ #include #include -#include #include +#include #include #include #include @@ -694,6 +694,10 @@ class SchemaParser rootSchema.addConstraintToSubschema(makeEnumConstraint(itr->second), &subschema); } + if ((itr = object.find("format")) != object.end()) { + rootSchema.addConstraintToSubschema(makeFormatConstraint(itr->second), &subschema); + } + { const typename AdapterType::Object::const_iterator itemsItr = object.find("items"); @@ -1421,6 +1425,29 @@ class SchemaParser return constraint; } + /** + * @brief Make a new FormatConstraint object + * + * @param node JSON node containing the configuration for this constraint + * + * @return pointer to a new FormatConstraint that belongs to the caller + */ + template + constraints::FormatConstraint makeFormatConstraint( + const AdapterType &node) + { + if (node.isString()) { + const std::string value = node.asString(); + if (!value.empty()) { + constraints::FormatConstraint constraint; + constraint.setFormat(value); + return constraint; + } + } + + throwRuntimeError("Expected a string value for 'format' constraint."); + } + /** * @brief Make a new ItemsConstraint object. * @@ -2291,7 +2318,7 @@ class SchemaParser private: /// Version of JSON Schema that should be expected when parsing - const Version m_version; + Version m_version; }; } // namespace valijson diff --git a/include/valijson/subschema.hpp b/include/valijson/subschema.hpp index abee6c42b7..3cb8ba6f20 100644 --- a/include/valijson/subschema.hpp +++ b/include/valijson/subschema.hpp @@ -1,8 +1,8 @@ #pragma once -#include - +#include #include +#include #include #include @@ -79,11 +79,6 @@ class Subschema #if VALIJSON_USE_EXCEPTIONS try { #endif - for (auto constConstraint : m_constraints) { - auto *constraint = const_cast(constConstraint); - constraint->~Constraint(); - m_freeFn(constraint); - } m_constraints.clear(); #if VALIJSON_USE_EXCEPTIONS } catch (const std::exception &e) { @@ -106,36 +101,26 @@ class Subschema */ void addConstraint(const Constraint &constraint) { - Constraint *newConstraint = constraint.clone(m_allocFn, m_freeFn); -#if VALIJSON_USE_EXCEPTIONS - try { -#endif - m_constraints.push_back(newConstraint); -#if VALIJSON_USE_EXCEPTIONS - } catch (...) { - newConstraint->~Constraint(); - m_freeFn(newConstraint); - throw; - } -#endif + // the vector allocation might throw but the constraint memory will be taken care of anyways + m_constraints.push_back(constraint.clone(m_allocFn, m_freeFn)); } /** * @brief Invoke a function on each child Constraint * * This function will apply the callback function to each constraint in - * the Subschema, even if one of the invokations returns \c false. However, - * if one or more invokations of the callback function return \c false, + * the Subschema, even if one of the invocations returns \c false. However, + * if one or more invocations of the callback function return \c false, * this function will also return \c false. * - * @returns \c true if all invokations of the callback function are + * @returns \c true if all invocations of the callback function are * successful, \c false otherwise */ bool apply(ApplyFunction &applyFunction) const { bool allTrue = true; - for (const Constraint *constraint : m_constraints) { - allTrue = allTrue && applyFunction(*constraint); + for (auto &&constraint : m_constraints) { + allTrue = applyFunction(*constraint) && allTrue; } return allTrue; @@ -145,15 +130,15 @@ class Subschema * @brief Invoke a function on each child Constraint * * This is a stricter version of the apply() function that will return - * immediately if any of the invokations of the callback function return + * immediately if any of the invocations of the callback function return * \c false. * - * @returns \c true if all invokations of the callback function are + * @returns \c true if all invocations of the callback function are * successful, \c false otherwise */ bool applyStrict(ApplyFunction &applyFunction) const { - for (const Constraint *constraint : m_constraints) { + for (auto &&constraint : m_constraints) { if (!applyFunction(*constraint)) { return false; } @@ -296,7 +281,7 @@ class Subschema bool m_alwaysInvalid; /// List of pointers to constraints that apply to this schema. - std::vector m_constraints; + std::vector m_constraints; /// Schema description (optional) opt::optional m_description; diff --git a/include/valijson/utils/yaml_cpp_utils.hpp b/include/valijson/utils/yaml_cpp_utils.hpp new file mode 100644 index 0000000000..6c93800cec --- /dev/null +++ b/include/valijson/utils/yaml_cpp_utils.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include + +#include + +#include + +namespace valijson { +namespace utils { + +inline bool loadDocument(const std::string &path, YAML::Node &document) +{ + try { + document = YAML::LoadFile(path); + return true; + } catch (const YAML::BadFile &ex) { + std::cerr << "Failed to load YAML from file '" << path << "'." << std::endl; + return false; + } catch (const YAML::ParserException &ex) { + std::cout << "yaml-cpp failed to parse the document '" << ex.what() << std::endl; + return false; + } +} + +} // namespace utils +} // namespace valijson diff --git a/include/valijson/validation_visitor.hpp b/include/valijson/validation_visitor.hpp index cd4442d7c7..152d1014f2 100644 --- a/include/valijson/validation_visitor.hpp +++ b/include/valijson/validation_visitor.hpp @@ -347,6 +347,64 @@ class ValidationVisitor: public constraints::ConstraintVisitor return numValidated > 0; } + /** + * @brief Validate current node against a FormatConstraint + * + * @param constraint Constraint that the target must validate against + * + * @return \c true if validation succeeds; \c false otherwise + */ + bool visit(const FormatConstraint &constraint) override + { + const std::string s = m_target.asString(); + const std::string format = constraint.getFormat(); + if (format == "date") { + // Matches dates like: 2022-07-18 + std::regex date_regex("^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$"); + std::smatch matches; + if (std::regex_match(s, matches, date_regex)) { + const auto month = std::stoi(matches[2].str()); + const auto day = std::stoi(matches[3].str()); + return validate_date_range(month, day); + } else { + if (m_results) { + m_results->pushError(m_context, + "String should be a valid date"); + } + return false; + } + } else if (format == "time") { + // Matches times like: 16:52:45Z, 16:52:45+02:00 + std::regex time_regex("^([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\\.[0-9]+)?(([Zz])|([\\+|\\-]([01][0-9]|2[0-3]):[0-5][0-9]))$"); + if (std::regex_match(s, time_regex)) { + return true; + } else { + if (m_results) { + m_results->pushError(m_context, + "String should be a valid time"); + } + return false; + } + } else if (format == "date-time") { + // Matches data times like: 2022-07-18T16:52:45Z, 2022-07-18T16:52:45+02:00 + std::regex datetime_regex("^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\\.[0-9]+)?(([Zz])|([\\+|\\-]([01][0-9]|2[0-3]):[0-5][0-9]))$"); + std::smatch matches; + if (std::regex_match(s, matches, datetime_regex)) { + const auto month = std::stoi(matches[2].str()); + const auto day = std::stoi(matches[3].str()); + return validate_date_range(month, day); + } else { + if (m_results) { + m_results->pushError(m_context, + "String should be a valid date-time"); + } + return false; + } + } + + return true; + } + /** * @brief Validate a value against a LinearItemsConstraint * @@ -1769,17 +1827,58 @@ class ValidationVisitor: public constraints::ConstraintVisitor return constraint.accept(visitor); } + /** + * @brief Helper function to validate if day is valid for given month + * + * @param month Month, 1-12 + * @param day Day, 1-31 + * + * @return true if day is valid for given month, false otherwise. + */ + bool validate_date_range(int month, int day) + { + if (month == 2) { + if (day < 0 || day > 29) { + if (m_results) { + m_results->pushError(m_context, + "String should be a valid date-time"); + } + return false; + } + } else { + int limit = 31; + if (month <= 7) { + if (month % 2 == 0) { + limit = 30; + } + } else { + if (month % 2 != 0) { + limit = 30; + } + } + if (day < 0 || day > limit) { + if (m_results) { + m_results->pushError(m_context, + "String should be a valid date-time"); + } + return false; + } + + } + return true; + } + /// The JSON value being validated - const AdapterType m_target; + AdapterType m_target; /// Vector of strings describing the current object context - const std::vector m_context; + std::vector m_context; /// Optional pointer to a ValidationResults object to be populated ValidationResults *m_results; /// Option to use strict type comparison - const bool m_strictTypes; + bool m_strictTypes; /// Cached regex objects for pattern constraint std::unordered_map& m_regexesCache; diff --git a/include/valijson/validator.hpp b/include/valijson/validator.hpp index b26f625f28..b60c593a6a 100644 --- a/include/valijson/validator.hpp +++ b/include/valijson/validator.hpp @@ -67,11 +67,10 @@ class Validator private: /// Flag indicating that strict type comparisons should be used - const bool strictTypes; + bool strictTypes; /// Cached regex objects for pattern constraint. Key - pattern. std::unordered_map regexesCache; - }; } // namespace valijson diff --git a/inspector/CMakeLists.txt b/inspector/CMakeLists.txt index 8320921335..009f748205 100644 --- a/inspector/CMakeLists.txt +++ b/inspector/CMakeLists.txt @@ -1,5 +1,3 @@ -# Reference: http://qt-project.org/doc/qt-5.0/qtdoc/cmake-manual.html - cmake_minimum_required(VERSION 3.0) # Add folder where are supportive functions @@ -17,7 +15,7 @@ project(inspector VERSION 1.0) fix_project_version() # Set additional project information -set(COPYRIGHT "Copyright (c) 2020 Tristan Penman. All rights reserved.") +set(COPYRIGHT "Copyright (c) 2021 Tristan Penman. All rights reserved.") set(IDENTIFIER "com.tristanpenman.valijson.inspector") set(SOURCE_FILES @@ -30,10 +28,16 @@ include_directories(SYSTEM ../include) add_project_meta(META_FILES_TO_INCLUDE) -find_package(Qt5 COMPONENTS Widgets REQUIRED) +find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Widgets REQUIRED) +find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Widgets REQUIRED) + +add_definitions(-DVALIJSON_USE_EXCEPTIONS=1) add_executable(${PROJECT_NAME} ${OS_BUNDLE} # Expands to WIN32 or MACOS_BUNDLE depending on OS ${SOURCE_FILES} ${META_FILES_TO_INCLUDE} ${RESOURCE_FILES} ) -target_link_libraries(${PROJECT_NAME} Qt5::Widgets) +target_link_libraries(${PROJECT_NAME} PRIVATE + Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Widgets +) diff --git a/inspector/src/main.cpp b/inspector/src/main.cpp index b294385144..8591946db3 100644 --- a/inspector/src/main.cpp +++ b/inspector/src/main.cpp @@ -3,7 +3,9 @@ int main(int argc, char *argv[]) { +#if QT_VERSION < 0x060000 QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); +#endif QApplication app(argc, argv); Window window; diff --git a/inspector/src/window.cpp b/inspector/src/window.cpp index c59db2439e..ac3fb0a867 100644 --- a/inspector/src/window.cpp +++ b/inspector/src/window.cpp @@ -47,8 +47,10 @@ Window::Window(QWidget * parent) setCentralWidget(verticalSplitter); setStatusBar(statusBar); - connect(m_documentEditor, SIGNAL(textChanged()), this, SLOT(refreshDocumentJson())); - connect(m_schemaEditor, SIGNAL(textChanged()), this, SLOT(refreshSchemaJson())); + connect(m_documentEditor, SIGNAL(textChanged()), this, SLOT(refreshJson())); + connect(m_schemaEditor, SIGNAL(textChanged()), this, SLOT(refreshJson())); + + refreshJson(); } QTextEdit * Window::createEditor(bool readOnly) @@ -114,46 +116,61 @@ QToolBar * Window::createToolBar() return toolbar; } -void Window::refreshDocumentJson() +void Window::refreshJson() { - QJsonParseError error; - m_document = QJsonDocument::fromJson(m_documentEditor->toPlainText().toUtf8(), &error); - if (m_document.isNull()) { - m_errors->setText(error.errorString()); - return; + QString errors; + m_errors->setText(""); + + const auto schema = m_schemaEditor->toPlainText().toUtf8(); + const auto doc = m_documentEditor->toPlainText().toUtf8(); + + if (schema.isEmpty()) { + if (doc.isEmpty()) { + m_errors->setText( + "Please provide a schema and a document to be validated.\n\n" + "Note that this example uses QtJson, which does not consider non-array and " + "non-object values to be valid JSON documents."); + return; + } else { + errors += "Schema error: must not be empty\n\n"; + } + } else { + QJsonParseError error; + m_schemaJson = QJsonDocument::fromJson(schema, &error); + if (m_schemaJson.isNull()) { + errors += QString("Schema error: ") + error.errorString() + "\n\n"; + } } - if (m_schema) { - validate(); + if (doc.isEmpty()) { + if (!schema.isEmpty()) { + errors += "Document error: must not be empty\n\n"; + } } else { - m_errors->setText(""); + QJsonParseError error; + m_documentJson = QJsonDocument::fromJson(doc, &error); + if (m_documentJson.isNull()) { + errors += QString("Document error: ") + error.errorString() + "\n\n"; + } } -} -void Window::refreshSchemaJson() -{ - QJsonParseError error; - auto schemaDoc = QJsonDocument::fromJson(m_schemaEditor->toPlainText().toUtf8(), &error); - if (schemaDoc.isNull()) { - m_errors->setText(error.errorString()); - return; + if (!errors.isEmpty()) { + m_errors->setText(errors); + return; } try { - valijson::adapters::QtJsonAdapter adapter(schemaDoc.object()); + valijson::adapters::QtJsonAdapter adapter(m_schemaJson.object()); valijson::SchemaParser parser; delete m_schema; m_schema = new valijson::Schema(); parser.populateSchema(adapter, *m_schema); - m_errors->setText(""); - - } catch (std::runtime_error error) { + validate(); + } catch (std::runtime_error & error) { delete m_schema; m_schema = nullptr; - m_errors->setText(error.what()); + m_errors->setText(QString("Schema error: ") + error.what()); } - - validate(); } void Window::showOpenDocumentDialog() @@ -180,7 +197,7 @@ void Window::validate() { valijson::ValidationResults results; valijson::Validator validator; - valijson::adapters::QtJsonAdapter adapter(m_document.object()); + valijson::adapters::QtJsonAdapter adapter(m_documentJson.object()); if (validator.validate(*m_schema, adapter, &results)) { m_errors->setText("Document is valid."); @@ -192,11 +209,11 @@ void Window::validate() std::stringstream ss; while (results.popError(error)) { std::string context; - for (auto itr = error.context.begin(); itr != error.context.end(); itr++) { - context += *itr; + for (auto & itr : error.context) { + context += itr; } - ss << "Error #" << errorNum << std::endl + ss << "Validation error #" << errorNum << std::endl << " context: " << context << std::endl << " desc: " << error.description << std::endl; ++errorNum; diff --git a/inspector/src/window.h b/inspector/src/window.h index 5315126f75..a7fe5168ec 100644 --- a/inspector/src/window.h +++ b/inspector/src/window.h @@ -19,11 +19,10 @@ class Window : public QMainWindow Q_OBJECT public: - Window(QWidget * parent = 0); + explicit Window(QWidget * parent = 0); public slots: - void refreshDocumentJson(); - void refreshSchemaJson(); + void refreshJson(); void showOpenDocumentDialog(); void showOpenSchemaDialog(); @@ -42,7 +41,8 @@ public slots: QTextEdit * m_errors; - QJsonDocument m_document; + QJsonDocument m_documentJson; + QJsonDocument m_schemaJson; valijson::Schema * m_schema; }; diff --git a/shellcheck.sh b/shellcheck.sh new file mode 100644 index 0000000000..4957df171d --- /dev/null +++ b/shellcheck.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash + +# +# Shellcheck is a static analyzer for shell scripts: https://shellcheck.net/ +# It is available in several operating systems and also as a docker image. +# +# If it finds any issues, it will output a small blurb describing the affected +# line(s) and will have a generic issue ID. The issue ID can be opened on its +# website to learn more about what the underlying problem is, why it's a +# problem, and (usually) suggests a way to fix. +# Specific shellcheck issues can be disabled (aka silenced). Doing so is +# usually pretty loud during code review. +# https://github.com/koalaman/shellcheck/wiki/Directive + +# https://stackoverflow.com/a/2871034/1111557 +set -euo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +SHELLCHECK="${SHELLCHECK:-"/usr/bin/shellcheck"}" +SEARCH_DIR="${SEARCH_DIR:-"$HERE"}" +cd "${SEARCH_DIR}" #so that we can call git + +# +# This block will: +# 1) `find` files under `SEARCH_DIR` +# 2) skip anything under `/thirdparty/`, `/.git/` +# 3) in a loop reading each path: +# 3a) ignore files that git also ignores +# 3b) use `file` to filter only script files +# 3c) run shellcheck against that script +# 4) if any paths are found to have an error, their paths are collated. +FAILED_PATHS=() +while read -r file_path +do + if git rev-parse --git-dir > /dev/null 2>&1; + then + git check-ignore --quiet "${file_path}" && continue + fi + file "${file_path}" | grep -q 'shell script' || continue + SCRIPT_PATH="${file_path}" + echo "Checking: ${SCRIPT_PATH}" + "${SHELLCHECK}" \ + "${SCRIPT_PATH}" \ + || FAILED_PATHS+=( "${SCRIPT_PATH}" ) +done < <( + find "${SEARCH_DIR}" -type f \ + | grep -v '/\.git/\|/thirdparty/' +) + +# +# If there are any failed paths, summarize them here. +# Then report a failing status to our caller. +if [[ 0 -lt "${#FAILED_PATHS[@]}" ]]; then + >&2 echo "These scripts aren't shellcheck-clean:" + for path in "${FAILED_PATHS[@]}"; do + >&2 echo "${path}" + done + exit 1 +fi + +# If we get here, then none of the scripts had any warnings. +echo "All scripts found (listed above) passed shellcheck" diff --git a/tests/fuzzing/oss-fuzz-build.sh b/tests/fuzzing/oss-fuzz-build.sh index 346dc7290c..63aa082546 100755 --- a/tests/fuzzing/oss-fuzz-build.sh +++ b/tests/fuzzing/oss-fuzz-build.sh @@ -5,17 +5,21 @@ sed -i '27d' include/valijson/utils/rapidjson_utils.hpp mkdir build cd build -cmake -Dvalijson_BUILD_EXAMPLES=FALSE \ +cmake \ + -Dvalijson_BUILD_TESTS=TRUE \ + -Dvalijson_BUILD_EXAMPLES=FALSE \ -Dvalijson_EXCLUDE_BOOST=TRUE \ .. -make -j$(nproc) +make -j"$(nproc)" cd ../tests/fuzzing find ../.. -name "*.o" -exec ar rcs fuzz_lib.a {} \; -$CXX $CXXFLAGS -DVALIJSON_USE_EXCEPTIONS=1 \ +# CXXFLAGS may contain spaces +# shellcheck disable=SC2086 +"$CXX" $CXXFLAGS -DVALIJSON_USE_EXCEPTIONS=1 \ -I/src/valijson/thirdparty/rapidjson-48fbd8c/include \ -I/src/valijson/thirdparty/rapidjson-48fbd8c/include/rapidjson \ -I/src/valijson/include \ @@ -23,10 +27,11 @@ $CXX $CXXFLAGS -DVALIJSON_USE_EXCEPTIONS=1 \ -I/src/valijson/include/valijson/adapters \ -c fuzzer.cpp -o fuzzer.o -$CXX $CXXFLAGS $LIB_FUZZING_ENGINE \ +# shellcheck disable=SC2086 +"$CXX" $CXXFLAGS "$LIB_FUZZING_ENGINE" \ -DVALIJSON_USE_EXCEPTIONS=1 \ -rdynamic fuzzer.o \ - -o $OUT/fuzzer fuzz_lib.a + -o "${OUT}/fuzzer" fuzz_lib.a -zip $OUT/fuzzer_seed_corpus.zip \ - $SRC/valijson/doc/schema/draft-03.json +zip "${OUT}/fuzzer_seed_corpus.zip" \ + "${SRC}/valijson/doc/schema/draft-03.json" diff --git a/tests/test_adapter_comparison.cpp b/tests/test_adapter_comparison.cpp index b531efdbe4..f559ef0dc0 100644 --- a/tests/test_adapter_comparison.cpp +++ b/tests/test_adapter_comparison.cpp @@ -20,13 +20,16 @@ #include #include -#ifdef VALIJSON_BUILD_BOOST_ADAPTERS -#include -#include +#ifdef VALIJSON_BUILD_BOOST_JSON_ADAPTER #include #include #endif +#ifdef VALIJSON_BUILD_BOOST_PROPERTY_TREE_ADAPTER +#include +#include +#endif + #ifdef VALIJSON_BUILD_QT_ADAPTER #include #include @@ -159,23 +162,27 @@ TEST_F(TestAdapterComparison, JsonCppVsPicoJson) valijson::adapters::PicoJsonAdapter>(); } -#ifdef VALIJSON_BUILD_BOOST_ADAPTERS +#ifdef VALIJSON_BUILD_BOOST_JSON_ADAPTER -TEST_F(TestAdapterComparison, JsonCppVsPropertyTree) +TEST_F(TestAdapterComparison, JsonCppVsBoostJson) { testComparison< valijson::adapters::JsonCppAdapter, - valijson::adapters::PropertyTreeAdapter>(); + valijson::adapters::BoostJsonAdapter>(); } -TEST_F(TestAdapterComparison, JsonCppVsBoostJson) +#endif // VALIJSON_BUILD_BOOST_JSON_ADAPTER + +#ifdef VALIJSON_BUILD_BOOST_PROPERTY_TREE_ADAPTER + +TEST_F(TestAdapterComparison, JsonCppVsPropertyTree) { testComparison< valijson::adapters::JsonCppAdapter, - valijson::adapters::BoostJsonAdapter>(); + valijson::adapters::PropertyTreeAdapter>(); } -#endif +#endif // VALIJSON_BUILD_BOOST_PROPERTY_TREE_ADAPTER TEST_F(TestAdapterComparison, JsonCppVsRapidJson) { @@ -193,77 +200,80 @@ TEST_F(TestAdapterComparison, JsonCppVsRapidJsonCrtAlloc) rapidjson::CrtAllocator> > >(); } +#ifdef VALIJSON_BUILD_BOOST_JSON_ADAPTER -#ifdef VALIJSON_BUILD_BOOST_ADAPTERS // -// PropertyTreeAdapter vs X +// BoostJsonAdapter vs X // ------------------------------------------------------------------------------------------------ -TEST_F(TestAdapterComparison, PropertyTreeVsPicoJson) +TEST_F(TestAdapterComparison, BoostJsonVsPicoJson) { testComparison< - valijson::adapters::PropertyTreeAdapter, + valijson::adapters::BoostJsonAdapter, valijson::adapters::PicoJsonAdapter>(); } -TEST_F(TestAdapterComparison, PropertyTreeVsPropertyTree) +TEST_F(TestAdapterComparison, BoostJsonVsBoostJson) { testComparison< - valijson::adapters::PropertyTreeAdapter, - valijson::adapters::PropertyTreeAdapter>(); + valijson::adapters::BoostJsonAdapter, + valijson::adapters::BoostJsonAdapter>(); } -TEST_F(TestAdapterComparison, PropertyTreeVsRapidJson) +TEST_F(TestAdapterComparison, BoostJsonVsRapidJson) { testComparison< - valijson::adapters::PropertyTreeAdapter, + valijson::adapters::BoostJsonAdapter, valijson::adapters::RapidJsonAdapter>(); } -TEST_F(TestAdapterComparison, PropertyTreeVsRapidJsonCrtAlloc) +TEST_F(TestAdapterComparison, BoostJsonVsRapidJsonCrtAlloc) { testComparison< - valijson::adapters::PropertyTreeAdapter, + valijson::adapters::BoostJsonAdapter, valijson::adapters::GenericRapidJsonAdapter< rapidjson::GenericValue, rapidjson::CrtAllocator> > >(); } +#endif // VALIJSON_BUILD_BOOST_JSON_ADAPTER + +#ifdef VALIJSON_BUILD_BOOST_PROPERTY_TREE_ADAPTER // -// BoostJsonAdapter vs X +// PropertyTreeAdapter vs X // ------------------------------------------------------------------------------------------------ -TEST_F(TestAdapterComparison, BoostJsonVsPicoJson) +TEST_F(TestAdapterComparison, PropertyTreeVsPicoJson) { testComparison< - valijson::adapters::BoostJsonAdapter, + valijson::adapters::PropertyTreeAdapter, valijson::adapters::PicoJsonAdapter>(); } -TEST_F(TestAdapterComparison, BoostJsonVsBoostJson) +TEST_F(TestAdapterComparison, PropertyTreeVsPropertyTree) { testComparison< - valijson::adapters::BoostJsonAdapter, - valijson::adapters::BoostJsonAdapter>(); + valijson::adapters::PropertyTreeAdapter, + valijson::adapters::PropertyTreeAdapter>(); } -TEST_F(TestAdapterComparison, BoostJsonVsRapidJson) +TEST_F(TestAdapterComparison, PropertyTreeVsRapidJson) { testComparison< - valijson::adapters::BoostJsonAdapter, + valijson::adapters::PropertyTreeAdapter, valijson::adapters::RapidJsonAdapter>(); } -TEST_F(TestAdapterComparison, BoostJsonVsRapidJsonCrtAlloc) +TEST_F(TestAdapterComparison, PropertyTreeVsRapidJsonCrtAlloc) { testComparison< - valijson::adapters::BoostJsonAdapter, + valijson::adapters::PropertyTreeAdapter, valijson::adapters::GenericRapidJsonAdapter< rapidjson::GenericValue, rapidjson::CrtAllocator> > >(); } -#endif +#endif // VALIJSON_BUILD_BOOST_PROPERTY_TREE_ADAPTER // // RapidJson vs X @@ -365,23 +375,27 @@ TEST_F(TestAdapterComparison, Json11VsPicoJson) valijson::adapters::PicoJsonAdapter>(); } -#ifdef VALIJSON_BUILD_BOOST_ADAPTERS +#ifdef VALIJSON_BUILD_BOOST_JSON_ADAPTER -TEST_F(TestAdapterComparison, Json11VsPropertyTree) +TEST_F(TestAdapterComparison, Json11VsBoostJson) { testComparison< valijson::adapters::Json11Adapter, - valijson::adapters::PropertyTreeAdapter>(); + valijson::adapters::BoostJsonAdapter>(); } -TEST_F(TestAdapterComparison, Json11VsBoostJson) +#endif // VALIJSON_BUILD_BOOST_JSON_ADAPTER + +#ifdef VALIJSON_BUILD_BOOST_PROPERTY_TREE_ADAPTER + +TEST_F(TestAdapterComparison, Json11VsPropertyTree) { testComparison< valijson::adapters::Json11Adapter, - valijson::adapters::BoostJsonAdapter>(); + valijson::adapters::PropertyTreeAdapter>(); } -#endif // VALIJSON_BUILD_BOOST_ADAPTERS +#endif // VALIJSON_BUILD_BOOST_PROPERTY_TREE_ADAPTER // // NlohmannJsonAdapter vs X @@ -431,23 +445,27 @@ TEST_F(TestAdapterComparison, NlohmannJsonVsPicoJson) valijson::adapters::PicoJsonAdapter>(); } -#ifdef VALIJSON_BUILD_BOOST_ADAPTERS +#ifdef VALIJSON_BUILD_BOOST_JSON_ADAPTER -TEST_F(TestAdapterComparison, NlohmannJsonVsPropertyTree) +TEST_F(TestAdapterComparison, NlohmannJsonVsBoostJson) { testComparison< valijson::adapters::NlohmannJsonAdapter, - valijson::adapters::PropertyTreeAdapter>(); + valijson::adapters::BoostJsonAdapter>(); } -TEST_F(TestAdapterComparison, NlohmannJsonVsBoostJson) +#endif // VALIJSON_BUILD_BOOST_JSON_ADAPTER + +#ifdef VALIJSON_BUILD_BOOST_PROPERTY_TREE_ADAPTER + +TEST_F(TestAdapterComparison, NlohmannJsonVsPropertyTree) { testComparison< valijson::adapters::NlohmannJsonAdapter, - valijson::adapters::BoostJsonAdapter>(); + valijson::adapters::PropertyTreeAdapter>(); } -#endif // VALIJSON_BUILD_BOOST_ADAPTERS +#endif // VALIJSON_BUILD_BOOST_PROPERTY_TREE_ADAPTER // // QtJsonAdapter vs X @@ -491,23 +509,27 @@ TEST_F(TestAdapterComparison, QtJsonVsPicoJson) valijson::adapters::PicoJsonAdapter>(); } -#ifdef VALIJSON_BUILD_BOOST_ADAPTERS +#ifdef VALIJSON_BUILD_BOOST_JSON_ADAPTER -TEST_F(TestAdapterComparison, QtJsonVsPropertyTree) +TEST_F(TestAdapterComparison, QtJsonVsBoostJson) { testComparison< valijson::adapters::QtJsonAdapter, - valijson::adapters::PropertyTreeAdapter>(); + valijson::adapters::BoostJsonAdapter>(); } -TEST_F(TestAdapterComparison, QtJsonVsBoostJson) +#endif // VALIJSON_BUILD_BOOST_JSON_ADAPTER + +#ifdef VALIJSON_BUILD_BOOST_PROPERTY_TREE_ADAPTER + +TEST_F(TestAdapterComparison, QtJsonVsPropertyTree) { testComparison< valijson::adapters::QtJsonAdapter, - valijson::adapters::BoostJsonAdapter>(); + valijson::adapters::PropertyTreeAdapter>(); } -#endif // VALIJSON_BUILD_BOOST_ADAPTERS +#endif // VALIJSON_BUILD_BOOST_PROPERTY_TREE_ADAPTER TEST_F(TestAdapterComparison, QtJsonVsJson11) { @@ -568,23 +590,27 @@ TEST_F(TestAdapterComparison, PocoJsonVsPicoJson) valijson::adapters::PicoJsonAdapter>(); } -#ifdef VALIJSON_BUILD_BOOST_ADAPTERS +#ifdef VALIJSON_BUILD_BOOST_JSON_ADAPTER -TEST_F(TestAdapterComparison, PocoJsonVsPropertyTree) +TEST_F(TestAdapterComparison, PocoJsonVsBoostJson) { testComparison< valijson::adapters::PocoJsonAdapter, - valijson::adapters::PropertyTreeAdapter>(); + valijson::adapters::BoostJsonAdapter>(); } -TEST_F(TestAdapterComparison, PocoJsonVsBoostJson) +#endif // VALIJSON_BUILD_BOOST_JSON_ADAPTER + +#ifdef VALIJSON_BUILD_BOOST_PROPERTY_TREE_ADAPTER + +TEST_F(TestAdapterComparison, PocoJsonVsPropertyTree) { testComparison< valijson::adapters::PocoJsonAdapter, - valijson::adapters::BoostJsonAdapter>(); + valijson::adapters::PropertyTreeAdapter>(); } -#endif // VALIJSON_BUILD_BOOST_ADAPTERS +#endif // VALIJSON_BUILD_BOOST_PROPERTY_TREE_ADAPTER TEST_F(TestAdapterComparison, PocoJsonVsJson11) { diff --git a/tests/test_validator.cpp b/tests/test_validator.cpp index 6e8d971350..e2bf78e360 100644 --- a/tests/test_validator.cpp +++ b/tests/test_validator.cpp @@ -15,10 +15,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -176,6 +178,7 @@ class TestValidator : public ::testing::TestWithParam processTestFile(testFile, version); processTestFile(testFile, version); processTestFile(testFile, version); + processTestFile(testFile, version); #ifdef VALIJSON_BUILD_POCO_ADAPTER processTestFile(testFile, version); @@ -605,3 +608,18 @@ TEST_F(TestValidator, Draft7_UniqueItems) { processDraft7TestFile(TEST_SUITE_DIR "draft7/uniqueItems.json"); } + +TEST_F(TestValidator, Draft7_OptionalFormatDate) +{ + processDraft7TestFile(TEST_SUITE_DIR "draft7/optional/format/date.json"); +} + +TEST_F(TestValidator, Draft7_OptionalFormatTime) +{ + processDraft7TestFile(TEST_SUITE_DIR "draft7/optional/format/time.json"); +} + +TEST_F(TestValidator, Draft7_OptionalFormatDateTime) +{ + processDraft7TestFile(TEST_SUITE_DIR "draft7/optional/format/date-time.json"); +} \ No newline at end of file diff --git a/tests/test_yaml_cpp_adapter.cpp b/tests/test_yaml_cpp_adapter.cpp new file mode 100644 index 0000000000..1e7bed0716 --- /dev/null +++ b/tests/test_yaml_cpp_adapter.cpp @@ -0,0 +1,110 @@ +#include + +#include + +class TestYamlCppAdapter : public testing::Test +{ +}; + +TEST_F(TestYamlCppAdapter, BasicArrayIteration) +{ + const unsigned int numElements = 10; + + // Create a Json document that consists of an array of numbers + YAML::Node document; + + for (unsigned int i = 0; i < numElements; i++) { + document.push_back(static_cast(i)); + } + + // Ensure that wrapping the document preserves the array and does not allow + // it to be cast to other types + valijson::adapters::YamlCppAdapter adapter(document); +#if VALIJSON_USE_EXCEPTIONS + ASSERT_NO_THROW(adapter.getArray()); + ASSERT_ANY_THROW(adapter.getBool()); + ASSERT_ANY_THROW(adapter.getDouble()); + ASSERT_ANY_THROW(adapter.getObject()); + ASSERT_ANY_THROW(adapter.getString()); +#endif + + // Ensure that the array contains the expected number of elements + EXPECT_EQ(numElements, adapter.getArray().size()); + + // Ensure that the elements are returned in the order they were inserted + unsigned int expectedValue = 0; + for (const valijson::adapters::YamlCppAdapter value : adapter.getArray()) { + ASSERT_TRUE(value.isString()); + ASSERT_FALSE(value.isNumber()); + ASSERT_TRUE(value.maybeDouble()); + EXPECT_EQ(double(expectedValue), value.getDouble()); + expectedValue++; + } + + // Ensure that the correct number of elements were iterated over + EXPECT_EQ(numElements, expectedValue); +} + +TEST_F(TestYamlCppAdapter, BasicObjectIteration) +{ + const unsigned int numElements = 10; + + // Create a document that consists of an object that maps + // numeric strings their corresponding numeric values + YAML::Node document; + for (uint32_t i = 0; i < numElements; i++) { + document[std::to_string(i)] = static_cast(i); + } + + // Ensure that wrapping the document preserves the object and does not + // allow it to be cast to other types + valijson::adapters::YamlCppAdapter adapter(document); +#if VALIJSON_USE_EXCEPTIONS + ASSERT_NO_THROW(adapter.getObject()); + ASSERT_ANY_THROW(adapter.getArray()); + ASSERT_ANY_THROW(adapter.getBool()); + ASSERT_ANY_THROW(adapter.getDouble()); + ASSERT_ANY_THROW(adapter.getString()); +#endif + + // Ensure that the object contains the expected number of members + EXPECT_EQ(numElements, adapter.getObject().size()); + + // Ensure that the members are returned in the order they were inserted + unsigned int expectedValue = 0; + for (const valijson::adapters::YamlCppAdapter::ObjectMember member : + adapter.getObject()) { + ASSERT_TRUE(member.second.isString()); + ASSERT_FALSE(member.second.isNumber()); + ASSERT_TRUE(member.second.maybeDouble()); + EXPECT_EQ(std::to_string(expectedValue), member.first); + EXPECT_EQ(double(expectedValue), member.second.getDouble()); + expectedValue++; + } + + // Ensure that the correct number of elements were iterated over + EXPECT_EQ(numElements, expectedValue); +} + +TEST_F(TestYamlCppAdapter, BasicObjectMemberAccess) +{ + const unsigned int numElements = 10; + + // Create a document that consists of an object that maps + // numeric strings their corresponding numeric values + YAML::Node document; + for (uint32_t i = 0; i < numElements; i++) { + document[std::to_string(i)] = static_cast(i); + } + valijson::adapters::YamlCppAdapter adapter(document); + const auto adapterObject = adapter.asObject(); + + // Ensure that accessing an element that exists produces the expected result. + const auto result3 = adapterObject.find("3"); + EXPECT_NE(result3, adapterObject.end()); + EXPECT_EQ(result3->second.asDouble(), 3); + + // Ensure that accessing an element that does not exists. + const auto result12 = adapterObject.find("12"); + EXPECT_EQ(result12, adapterObject.end()); +} diff --git a/thirdparty/jsoncpp-1.9.4/AUTHORS b/thirdparty/jsoncpp-1.9.5/AUTHORS similarity index 100% rename from thirdparty/jsoncpp-1.9.4/AUTHORS rename to thirdparty/jsoncpp-1.9.5/AUTHORS diff --git a/thirdparty/jsoncpp-1.9.4/LICENSE b/thirdparty/jsoncpp-1.9.5/LICENSE similarity index 97% rename from thirdparty/jsoncpp-1.9.4/LICENSE rename to thirdparty/jsoncpp-1.9.5/LICENSE index 89280a6c48..c41a1d1c77 100644 --- a/thirdparty/jsoncpp-1.9.4/LICENSE +++ b/thirdparty/jsoncpp-1.9.5/LICENSE @@ -1,25 +1,25 @@ -The JsonCpp library's source code, including accompanying documentation, +The JsonCpp library's source code, including accompanying documentation, tests and demonstration applications, are licensed under the following conditions... -Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all -jurisdictions which recognize such a disclaimer. In such jurisdictions, +Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all +jurisdictions which recognize such a disclaimer. In such jurisdictions, this software is released into the Public Domain. In jurisdictions which do not recognize Public Domain property (e.g. Germany as of 2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur and The JsonCpp Authors, and is released under the terms of the MIT License (see below). -In jurisdictions which recognize Public Domain property, the user of this -software may choose to accept it either as 1) Public Domain, 2) under the -conditions of the MIT License (see below), or 3) under the terms of dual +In jurisdictions which recognize Public Domain property, the user of this +software may choose to accept it either as 1) Public Domain, 2) under the +conditions of the MIT License (see below), or 3) under the terms of dual Public Domain/MIT License conditions described here, as they choose. The MIT License is about as close to Public Domain as a license can get, and is described in clear, concise terms at: http://en.wikipedia.org/wiki/MIT_License - + The full text of the MIT License follows: ======================================================================== diff --git a/thirdparty/jsoncpp-1.9.4/README.md b/thirdparty/jsoncpp-1.9.5/README.md similarity index 100% rename from thirdparty/jsoncpp-1.9.4/README.md rename to thirdparty/jsoncpp-1.9.5/README.md diff --git a/thirdparty/jsoncpp-1.9.4/cmake/JoinPaths.cmake b/thirdparty/jsoncpp-1.9.5/cmake/JoinPaths.cmake similarity index 100% rename from thirdparty/jsoncpp-1.9.4/cmake/JoinPaths.cmake rename to thirdparty/jsoncpp-1.9.5/cmake/JoinPaths.cmake diff --git a/thirdparty/jsoncpp-1.9.4/include/json/allocator.h b/thirdparty/jsoncpp-1.9.5/include/json/allocator.h similarity index 100% rename from thirdparty/jsoncpp-1.9.4/include/json/allocator.h rename to thirdparty/jsoncpp-1.9.5/include/json/allocator.h diff --git a/thirdparty/jsoncpp-1.9.4/include/json/assertions.h b/thirdparty/jsoncpp-1.9.5/include/json/assertions.h similarity index 100% rename from thirdparty/jsoncpp-1.9.4/include/json/assertions.h rename to thirdparty/jsoncpp-1.9.5/include/json/assertions.h diff --git a/thirdparty/jsoncpp-1.9.4/include/json/config.h b/thirdparty/jsoncpp-1.9.5/include/json/config.h similarity index 100% rename from thirdparty/jsoncpp-1.9.4/include/json/config.h rename to thirdparty/jsoncpp-1.9.5/include/json/config.h diff --git a/thirdparty/jsoncpp-1.9.4/include/json/forwards.h b/thirdparty/jsoncpp-1.9.5/include/json/forwards.h similarity index 100% rename from thirdparty/jsoncpp-1.9.4/include/json/forwards.h rename to thirdparty/jsoncpp-1.9.5/include/json/forwards.h diff --git a/thirdparty/jsoncpp-1.9.4/include/json/json.h b/thirdparty/jsoncpp-1.9.5/include/json/json.h similarity index 100% rename from thirdparty/jsoncpp-1.9.4/include/json/json.h rename to thirdparty/jsoncpp-1.9.5/include/json/json.h diff --git a/thirdparty/jsoncpp-1.9.4/include/json/json_features.h b/thirdparty/jsoncpp-1.9.5/include/json/json_features.h similarity index 100% rename from thirdparty/jsoncpp-1.9.4/include/json/json_features.h rename to thirdparty/jsoncpp-1.9.5/include/json/json_features.h diff --git a/thirdparty/jsoncpp-1.9.4/include/json/reader.h b/thirdparty/jsoncpp-1.9.5/include/json/reader.h similarity index 98% rename from thirdparty/jsoncpp-1.9.4/include/json/reader.h rename to thirdparty/jsoncpp-1.9.5/include/json/reader.h index 9175466089..be0d7676ab 100644 --- a/thirdparty/jsoncpp-1.9.4/include/json/reader.h +++ b/thirdparty/jsoncpp-1.9.5/include/json/reader.h @@ -33,8 +33,7 @@ namespace Json { * \deprecated Use CharReader and CharReaderBuilder. */ -class JSONCPP_DEPRECATED( - "Use CharReader and CharReaderBuilder instead.") JSON_API Reader { +class JSON_API Reader { public: using Char = char; using Location = const Char*; @@ -51,13 +50,13 @@ class JSONCPP_DEPRECATED( }; /** \brief Constructs a Reader allowing all features for parsing. + * \deprecated Use CharReader and CharReaderBuilder. */ - JSONCPP_DEPRECATED("Use CharReader and CharReaderBuilder instead") Reader(); /** \brief Constructs a Reader allowing the specified feature set for parsing. + * \deprecated Use CharReader and CharReaderBuilder. */ - JSONCPP_DEPRECATED("Use CharReader and CharReaderBuilder instead") Reader(const Features& features); /** \brief Read a Value from a JSON @@ -324,6 +323,9 @@ class JSON_API CharReaderBuilder : public CharReader::Factory { * - `"allowSpecialFloats": false or true` * - If true, special float values (NaNs and infinities) are allowed and * their values are lossfree restorable. + * - `"skipBom": false or true` + * - If true, if the input starts with the Unicode byte order mark (BOM), + * it is skipped. * * You can examine 'settings_` yourself to see the defaults. You can also * write and read them just like any JSON Value. diff --git a/thirdparty/jsoncpp-1.9.4/include/json/value.h b/thirdparty/jsoncpp-1.9.5/include/json/value.h similarity index 98% rename from thirdparty/jsoncpp-1.9.4/include/json/value.h rename to thirdparty/jsoncpp-1.9.5/include/json/value.h index df1eba6acb..0edeb050ca 100644 --- a/thirdparty/jsoncpp-1.9.4/include/json/value.h +++ b/thirdparty/jsoncpp-1.9.5/include/json/value.h @@ -50,7 +50,7 @@ // be used by... #if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) #pragma warning(push) -#pragma warning(disable : 4251) +#pragma warning(disable : 4251 4275) #endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) #pragma pack(push, 8) @@ -263,10 +263,10 @@ class JSON_API Value { CZString(ArrayIndex index); CZString(char const* str, unsigned length, DuplicationPolicy allocate); CZString(CZString const& other); - CZString(CZString&& other); + CZString(CZString&& other) noexcept; ~CZString(); CZString& operator=(const CZString& other); - CZString& operator=(CZString&& other); + CZString& operator=(CZString&& other) noexcept; bool operator<(CZString const& other) const; bool operator==(CZString const& other) const; @@ -344,13 +344,13 @@ class JSON_API Value { Value(bool value); Value(std::nullptr_t ptr) = delete; Value(const Value& other); - Value(Value&& other); + Value(Value&& other) noexcept; ~Value(); /// \note Overwrite existing comments. To preserve comments, use /// #swapPayload(). Value& operator=(const Value& other); - Value& operator=(Value&& other); + Value& operator=(Value&& other) noexcept; /// Swap everything. void swap(Value& other); @@ -635,9 +635,9 @@ class JSON_API Value { public: Comments() = default; Comments(const Comments& that); - Comments(Comments&& that); + Comments(Comments&& that) noexcept; Comments& operator=(const Comments& that); - Comments& operator=(Comments&& that); + Comments& operator=(Comments&& that) noexcept; bool has(CommentPlacement slot) const; String get(CommentPlacement slot) const; void set(CommentPlacement slot, String comment); @@ -918,8 +918,8 @@ class JSON_API ValueIterator : public ValueIteratorBase { * because the returned references/pointers can be used * to change state of the base class. */ - reference operator*() { return deref(); } - pointer operator->() { return &deref(); } + reference operator*() const { return const_cast(deref()); } + pointer operator->() const { return const_cast(&deref()); } }; inline void swap(Value& a, Value& b) { a.swap(b); } diff --git a/thirdparty/jsoncpp-1.9.4/include/json/version.h b/thirdparty/jsoncpp-1.9.5/include/json/version.h similarity index 92% rename from thirdparty/jsoncpp-1.9.4/include/json/version.h rename to thirdparty/jsoncpp-1.9.5/include/json/version.h index 87cf7e2fbc..e931d0383e 100644 --- a/thirdparty/jsoncpp-1.9.4/include/json/version.h +++ b/thirdparty/jsoncpp-1.9.5/include/json/version.h @@ -9,10 +9,10 @@ // 3. /CMakeLists.txt // IMPORTANT: also update the SOVERSION!! -#define JSONCPP_VERSION_STRING "1.9.4" +#define JSONCPP_VERSION_STRING "1.9.5" #define JSONCPP_VERSION_MAJOR 1 #define JSONCPP_VERSION_MINOR 9 -#define JSONCPP_VERSION_PATCH 4 +#define JSONCPP_VERSION_PATCH 5 #define JSONCPP_VERSION_QUALIFIER #define JSONCPP_VERSION_HEXA \ ((JSONCPP_VERSION_MAJOR << 24) | (JSONCPP_VERSION_MINOR << 16) | \ diff --git a/thirdparty/jsoncpp-1.9.4/include/json/writer.h b/thirdparty/jsoncpp-1.9.5/include/json/writer.h similarity index 97% rename from thirdparty/jsoncpp-1.9.4/include/json/writer.h rename to thirdparty/jsoncpp-1.9.5/include/json/writer.h index fb0852a0ca..88a3b12e9d 100644 --- a/thirdparty/jsoncpp-1.9.4/include/json/writer.h +++ b/thirdparty/jsoncpp-1.9.5/include/json/writer.h @@ -110,6 +110,8 @@ class JSON_API StreamWriterBuilder : public StreamWriter::Factory { * - Number of precision digits for formatting of real values. * - "precisionType": "significant"(default) or "decimal" * - Type of precision for formatting of real values. + * - "emitUTF8": false or true + * - If true, outputs raw UTF8 strings instead of escaping them. * You can examine 'settings_` yourself * to see the defaults. You can also write and read them just like any @@ -145,7 +147,7 @@ class JSON_API StreamWriterBuilder : public StreamWriter::Factory { /** \brief Abstract class for writers. * \deprecated Use StreamWriter. (And really, this is an implementation detail.) */ -class JSONCPP_DEPRECATED("Use StreamWriter instead") JSON_API Writer { +class JSON_API Writer { public: virtual ~Writer(); @@ -165,7 +167,7 @@ class JSONCPP_DEPRECATED("Use StreamWriter instead") JSON_API Writer { #pragma warning(push) #pragma warning(disable : 4996) // Deriving from deprecated class #endif -class JSONCPP_DEPRECATED("Use StreamWriterBuilder instead") JSON_API FastWriter +class JSON_API FastWriter : public Writer { public: FastWriter(); @@ -225,7 +227,7 @@ class JSONCPP_DEPRECATED("Use StreamWriterBuilder instead") JSON_API FastWriter #pragma warning(push) #pragma warning(disable : 4996) // Deriving from deprecated class #endif -class JSONCPP_DEPRECATED("Use StreamWriterBuilder instead") JSON_API +class JSON_API StyledWriter : public Writer { public: StyledWriter(); @@ -294,7 +296,7 @@ class JSONCPP_DEPRECATED("Use StreamWriterBuilder instead") JSON_API #pragma warning(push) #pragma warning(disable : 4996) // Deriving from deprecated class #endif -class JSONCPP_DEPRECATED("Use StreamWriterBuilder instead") JSON_API +class JSON_API StyledStreamWriter { public: /** diff --git a/thirdparty/jsoncpp-1.9.4/src/lib_json/json_reader.cpp b/thirdparty/jsoncpp-1.9.5/src/lib_json/json_reader.cpp similarity index 99% rename from thirdparty/jsoncpp-1.9.4/src/lib_json/json_reader.cpp rename to thirdparty/jsoncpp-1.9.5/src/lib_json/json_reader.cpp index 19922a823c..a6a3f4e30d 100644 --- a/thirdparty/jsoncpp-1.9.4/src/lib_json/json_reader.cpp +++ b/thirdparty/jsoncpp-1.9.5/src/lib_json/json_reader.cpp @@ -104,8 +104,7 @@ bool Reader::parse(std::istream& is, Value& root, bool collectComments) { // Since String is reference-counted, this at least does not // create an extra copy. - String doc; - std::getline(is, doc, static_cast EOF); + String doc(std::istreambuf_iterator(is), {}); return parse(doc.data(), doc.data() + doc.size(), root, collectComments); } @@ -1921,7 +1920,7 @@ bool CharReaderBuilder::validate(Json::Value* invalid) const { if (valid_keys.count(key)) continue; if (invalid) - (*invalid)[std::move(key)] = *si; + (*invalid)[key] = *si; else return false; } diff --git a/thirdparty/jsoncpp-1.9.4/src/lib_json/json_tool.h b/thirdparty/jsoncpp-1.9.5/src/lib_json/json_tool.h similarity index 93% rename from thirdparty/jsoncpp-1.9.4/src/lib_json/json_tool.h rename to thirdparty/jsoncpp-1.9.5/src/lib_json/json_tool.h index 2d7b7d9a00..b952c19167 100644 --- a/thirdparty/jsoncpp-1.9.4/src/lib_json/json_tool.h +++ b/thirdparty/jsoncpp-1.9.5/src/lib_json/json_tool.h @@ -116,14 +116,18 @@ template void fixNumericLocaleInput(Iter begin, Iter end) { * Return iterator that would be the new end of the range [begin,end), if we * were to delete zeros in the end of string, but not the last zero before '.'. */ -template Iter fixZerosInTheEnd(Iter begin, Iter end) { +template +Iter fixZerosInTheEnd(Iter begin, Iter end, unsigned int precision) { for (; begin != end; --end) { if (*(end - 1) != '0') { return end; } // Don't delete the last zero before the decimal point. - if (begin != (end - 1) && *(end - 2) == '.') { - return end; + if (begin != (end - 1) && begin != (end - 2) && *(end - 2) == '.') { + if (precision) { + return end; + } + return end - 2; } } return end; diff --git a/thirdparty/jsoncpp-1.9.4/src/lib_json/json_value.cpp b/thirdparty/jsoncpp-1.9.5/src/lib_json/json_value.cpp similarity index 98% rename from thirdparty/jsoncpp-1.9.4/src/lib_json/json_value.cpp rename to thirdparty/jsoncpp-1.9.5/src/lib_json/json_value.cpp index 0872ff5487..aa2b744ca8 100644 --- a/thirdparty/jsoncpp-1.9.4/src/lib_json/json_value.cpp +++ b/thirdparty/jsoncpp-1.9.5/src/lib_json/json_value.cpp @@ -259,7 +259,7 @@ Value::CZString::CZString(const CZString& other) { storage_.length_ = other.storage_.length_; } -Value::CZString::CZString(CZString&& other) +Value::CZString::CZString(CZString&& other) noexcept : cstr_(other.cstr_), index_(other.index_) { other.cstr_ = nullptr; } @@ -285,7 +285,7 @@ Value::CZString& Value::CZString::operator=(const CZString& other) { return *this; } -Value::CZString& Value::CZString::operator=(CZString&& other) { +Value::CZString& Value::CZString::operator=(CZString&& other) noexcept { cstr_ = other.cstr_; index_ = other.index_; other.cstr_ = nullptr; @@ -433,7 +433,7 @@ Value::Value(const Value& other) { dupMeta(other); } -Value::Value(Value&& other) { +Value::Value(Value&& other) noexcept { initBasic(nullValue); swap(other); } @@ -448,7 +448,7 @@ Value& Value::operator=(const Value& other) { return *this; } -Value& Value::operator=(Value&& other) { +Value& Value::operator=(Value&& other) noexcept { other.swap(*this); return *this; } @@ -912,7 +912,8 @@ void Value::resize(ArrayIndex newSize) { if (newSize == 0) clear(); else if (newSize > oldSize) - this->operator[](newSize - 1); + for (ArrayIndex i = oldSize; i < newSize; ++i) + (*this)[i]; else { for (ArrayIndex index = newSize; index < oldSize; ++index) { value_.map_->erase(index); @@ -1373,14 +1374,15 @@ bool Value::isObject() const { return type() == objectValue; } Value::Comments::Comments(const Comments& that) : ptr_{cloneUnique(that.ptr_)} {} -Value::Comments::Comments(Comments&& that) : ptr_{std::move(that.ptr_)} {} +Value::Comments::Comments(Comments&& that) noexcept + : ptr_{std::move(that.ptr_)} {} Value::Comments& Value::Comments::operator=(const Comments& that) { ptr_ = cloneUnique(that.ptr_); return *this; } -Value::Comments& Value::Comments::operator=(Comments&& that) { +Value::Comments& Value::Comments::operator=(Comments&& that) noexcept { ptr_ = std::move(that.ptr_); return *this; } @@ -1396,13 +1398,11 @@ String Value::Comments::get(CommentPlacement slot) const { } void Value::Comments::set(CommentPlacement slot, String comment) { - if (!ptr_) { + if (slot >= CommentPlacement::numberOfCommentPlacement) + return; + if (!ptr_) ptr_ = std::unique_ptr(new Array()); - } - // check comments array boundry. - if (slot < CommentPlacement::numberOfCommentPlacement) { - (*ptr_)[slot] = std::move(comment); - } + (*ptr_)[slot] = std::move(comment); } void Value::setComment(String comment, CommentPlacement placement) { diff --git a/thirdparty/jsoncpp-1.9.4/src/lib_json/json_valueiterator.inl b/thirdparty/jsoncpp-1.9.5/src/lib_json/json_valueiterator.inl similarity index 100% rename from thirdparty/jsoncpp-1.9.4/src/lib_json/json_valueiterator.inl rename to thirdparty/jsoncpp-1.9.5/src/lib_json/json_valueiterator.inl diff --git a/thirdparty/jsoncpp-1.9.4/src/lib_json/json_writer.cpp b/thirdparty/jsoncpp-1.9.5/src/lib_json/json_writer.cpp similarity index 97% rename from thirdparty/jsoncpp-1.9.4/src/lib_json/json_writer.cpp rename to thirdparty/jsoncpp-1.9.5/src/lib_json/json_writer.cpp index 8bf02dbdbc..0dd160e452 100644 --- a/thirdparty/jsoncpp-1.9.4/src/lib_json/json_writer.cpp +++ b/thirdparty/jsoncpp-1.9.5/src/lib_json/json_writer.cpp @@ -68,7 +68,7 @@ #if !defined(isnan) // IEEE standard states that NaN values will not compare to themselves -#define isnan(x) (x != x) +#define isnan(x) ((x) != (x)) #endif #if !defined(__APPLE__) @@ -154,16 +154,18 @@ String valueToString(double value, bool useSpecialFloats, buffer.erase(fixNumericLocale(buffer.begin(), buffer.end()), buffer.end()); - // strip the zero padding from the right - if (precisionType == PrecisionType::decimalPlaces) { - buffer.erase(fixZerosInTheEnd(buffer.begin(), buffer.end()), buffer.end()); - } - // try to ensure we preserve the fact that this was given to us as a double on // input if (buffer.find('.') == buffer.npos && buffer.find('e') == buffer.npos) { buffer += ".0"; } + + // strip the zero padding from the right + if (precisionType == PrecisionType::decimalPlaces) { + buffer.erase(fixZerosInTheEnd(buffer.begin(), buffer.end(), precision), + buffer.end()); + } + return buffer; } } // namespace @@ -270,7 +272,7 @@ static void appendHex(String& result, unsigned ch) { result.append("\\u").append(toHex16Bit(ch)); } -static String valueToQuotedStringN(const char* value, unsigned length, +static String valueToQuotedStringN(const char* value, size_t length, bool emitUTF8 = false) { if (value == nullptr) return ""; @@ -348,7 +350,7 @@ static String valueToQuotedStringN(const char* value, unsigned length, } String valueToQuotedString(const char* value) { - return valueToQuotedStringN(value, static_cast(strlen(value))); + return valueToQuotedStringN(value, strlen(value)); } // Class Writer @@ -397,7 +399,7 @@ void FastWriter::writeValue(const Value& value) { char const* end; bool ok = value.getString(&str, &end); if (ok) - document_ += valueToQuotedStringN(str, static_cast(end - str)); + document_ += valueToQuotedStringN(str, static_cast(end - str)); break; } case booleanValue: @@ -420,8 +422,7 @@ void FastWriter::writeValue(const Value& value) { const String& name = *it; if (it != members.begin()) document_ += ','; - document_ += valueToQuotedStringN(name.data(), - static_cast(name.length())); + document_ += valueToQuotedStringN(name.data(), name.length()); document_ += yamlCompatibilityEnabled_ ? ": " : ":"; writeValue(value[name]); } @@ -466,7 +467,7 @@ void StyledWriter::writeValue(const Value& value) { char const* end; bool ok = value.getString(&str, &end); if (ok) - pushValue(valueToQuotedStringN(str, static_cast(end - str))); + pushValue(valueToQuotedStringN(str, static_cast(end - str))); else pushValue(""); break; @@ -507,7 +508,7 @@ void StyledWriter::writeValue(const Value& value) { } void StyledWriter::writeArrayValue(const Value& value) { - unsigned size = value.size(); + size_t size = value.size(); if (size == 0) pushValue("[]"); else { @@ -516,7 +517,7 @@ void StyledWriter::writeArrayValue(const Value& value) { writeWithIndent("["); indent(); bool hasChildValue = !childValues_.empty(); - unsigned index = 0; + ArrayIndex index = 0; for (;;) { const Value& childValue = value[index]; writeCommentBeforeValue(childValue); @@ -539,7 +540,7 @@ void StyledWriter::writeArrayValue(const Value& value) { { assert(childValues_.size() == size); document_ += "[ "; - for (unsigned index = 0; index < size; ++index) { + for (size_t index = 0; index < size; ++index) { if (index > 0) document_ += ", "; document_ += childValues_[index]; @@ -684,7 +685,7 @@ void StyledStreamWriter::writeValue(const Value& value) { char const* end; bool ok = value.getString(&str, &end); if (ok) - pushValue(valueToQuotedStringN(str, static_cast(end - str))); + pushValue(valueToQuotedStringN(str, static_cast(end - str))); else pushValue(""); break; @@ -958,8 +959,8 @@ void BuiltStyledStreamWriter::writeValue(Value const& value) { char const* end; bool ok = value.getString(&str, &end); if (ok) - pushValue(valueToQuotedStringN(str, static_cast(end - str), - emitUTF8_)); + pushValue( + valueToQuotedStringN(str, static_cast(end - str), emitUTF8_)); else pushValue(""); break; @@ -982,8 +983,8 @@ void BuiltStyledStreamWriter::writeValue(Value const& value) { String const& name = *it; Value const& childValue = value[name]; writeCommentBeforeValue(childValue); - writeWithIndent(valueToQuotedStringN( - name.data(), static_cast(name.length()), emitUTF8_)); + writeWithIndent( + valueToQuotedStringN(name.data(), name.length(), emitUTF8_)); *sout_ << colonSymbol_; writeValue(childValue); if (++it == members.end()) { @@ -1217,7 +1218,7 @@ bool StreamWriterBuilder::validate(Json::Value* invalid) const { if (valid_keys.count(key)) continue; if (invalid) - (*invalid)[std::move(key)] = *si; + (*invalid)[key] = *si; else return false; } diff --git a/thirdparty/jsoncpp-1.9.4/version.in b/thirdparty/jsoncpp-1.9.5/version.in similarity index 100% rename from thirdparty/jsoncpp-1.9.4/version.in rename to thirdparty/jsoncpp-1.9.5/version.in diff --git a/thirdparty/urdl-2013-08-15/COPYING b/thirdparty/urdl-2013-08-15/COPYING deleted file mode 100644 index fb7e21a59f..0000000000 --- a/thirdparty/urdl-2013-08-15/COPYING +++ /dev/null @@ -1,4 +0,0 @@ -Copyright (c) 2009-2013 Christopher M. Kohlhoff (chris at kohlhoff dot com) - -Distributed under the Boost Software License, Version 1.0. (See accompanying -file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) diff --git a/thirdparty/urdl-2013-08-15/Jamroot b/thirdparty/urdl-2013-08-15/Jamroot deleted file mode 100644 index e7bae9ff90..0000000000 --- a/thirdparty/urdl-2013-08-15/Jamroot +++ /dev/null @@ -1,55 +0,0 @@ -import modules ; -import package ; - -path-constant URDL_ROOT : . ; - -BOOST_ROOT = [ modules.peek : BOOST_ROOT ] ; -if $(BOOST_ROOT) -{ - use-project /boost : $(BOOST_ROOT) ; -} - -rule handle-static-runtime ( properties * ) -{ - if shared in $(properties) && static in $(properties) - { - return no ; - } -} - -project - : - build-dir build/bin - : - requirements - multi - @handle-static-runtime - : - default-build - debug release - multi - shared static - shared static - ; - -install lib - : - build - : - lib - LIB - ; - -local patterns = *.hpp *.ipp ; - -local dirs = include/urdl include/urdl/* ; - -package.install install - : - include - : - : - build - : - [ glob $(dirs)/$(patterns) ] - ; diff --git a/thirdparty/urdl-2013-08-15/LICENSE_1_0.txt b/thirdparty/urdl-2013-08-15/LICENSE_1_0.txt deleted file mode 100644 index 36b7cd93cd..0000000000 --- a/thirdparty/urdl-2013-08-15/LICENSE_1_0.txt +++ /dev/null @@ -1,23 +0,0 @@ -Boost Software License - Version 1.0 - August 17th, 2003 - -Permission is hereby granted, free of charge, to any person or organization -obtaining a copy of the software and accompanying documentation covered by -this license (the "Software") to use, reproduce, display, distribute, -execute, and transmit the Software, and to prepare derivative works of the -Software, and to permit third-parties to whom the Software is furnished to -do so, all subject to the following: - -The copyright notices in the Software and this entire statement, including -the above license grant, this restriction and the following disclaimer, -must be included in all copies of the Software, in whole or in part, and -all derivative works of the Software, unless such copies or derivative -works are solely in the form of machine-executable object code generated by -a source language processor. - -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, TITLE AND NON-INFRINGEMENT. IN NO EVENT -SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE -FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN 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/thirdparty/urdl-2013-08-15/README b/thirdparty/urdl-2013-08-15/README deleted file mode 100644 index b118837880..0000000000 --- a/thirdparty/urdl-2013-08-15/README +++ /dev/null @@ -1,4 +0,0 @@ -urdl version 0.1 -Released Wednesday, 03 June 2009. - -See doc/index.html for build information and API documentation. diff --git a/thirdparty/urdl-2013-08-15/build/Jamfile b/thirdparty/urdl-2013-08-15/build/Jamfile deleted file mode 100644 index b435fcc4b1..0000000000 --- a/thirdparty/urdl-2013-08-15/build/Jamfile +++ /dev/null @@ -1,102 +0,0 @@ -import common ; -import os ; -import modules ; - -if [ os.name ] = SOLARIS -{ - lib socket ; - lib nsl ; -} -else if [ os.name ] = NT -{ - lib ws2_32 ; - lib mswsock ; -} -else if [ os.name ] = HPUX -{ - lib ipv6 ; -} - -local SSL_OPTIONS ; -if [ modules.peek : URDL_DISABLE_SSL ] = 1 -{ - SSL_OPTIONS = URDL_DISABLE_SSL=1 ; -} -else if [ os.name ] = NT -{ - OPENSSL_ROOT = [ modules.peek : OPENSSL_ROOT ] ; - lib user32 ; - lib advapi32 ; - lib gdi32 ; - SSL_OPTIONS = $(OPENSSL_ROOT)/inc32 - $(OPENSSL_ROOT)/out32dll/libeay32.lib - $(OPENSSL_ROOT)/out32dll/ssleay32.lib - user32 advapi32 gdi32 ; -} -else -{ - lib ssl ; - lib crypto ; - SSL_OPTIONS = ssl crypto ; -} - -project urdl - : - source-location ../src - : - requirements - multi - : - usage-requirements - shared:URDL_DYN_LINK=1 - static:URDL_STATIC_LINK=1 - $(URDL_ROOT)/include - /boost/system//boost_system - /boost/date_time//boost_date_time - BOOST_ALL_NO_LIB=1 - SOLARIS:socket - SOLARIS:nsl - NT:_WIN32_WINNT=0x0501 - NT:WIN32_LEAN_AND_MEAN=1 - NT,msvc:_SCL_SECURE_NO_WARNINGS=1 - NT,msvc:_CRT_SECURE_NO_WARNINGS=1 - NT,gcc:ws2_32 - NT,gcc:mswsock - NT,gcc-cygwin:__USE_W32_SOCKETS - HPUX,gcc:_XOPEN_SOURCE_EXTENDED - HPUX:ipv6 - $(SSL_OPTIONS) - ; - -lib urdl - : - urdl.cpp - : - shared:BOOST_ALL_DYN_LINK=1 - static:BOOST_ALL_STATIC_LINK=1 - $(URDL_ROOT)/include - /boost/system//boost_system - /boost/date_time//boost_date_time - BOOST_ALL_NO_LIB=1 - SOLARIS:socket - SOLARIS:nsl - NT:_WIN32_WINNT=0x0501 - NT,msvc:_SCL_SECURE_NO_WARNINGS=1 - NT,msvc:_CRT_SECURE_NO_WARNINGS=1 - NT,gcc:ws2_32 - NT,gcc:mswsock - NT,gcc-cygwin:__USE_W32_SOCKETS - HPUX,gcc:_XOPEN_SOURCE_EXTENDED - HPUX:ipv6 - on - @tag - $(SSL_OPTIONS) - ; - -rule tag ( name : type ? : property-set ) -{ - local result = [ common.format-name - - : $(name) : $(type) : $(property-set) ] ; - return "$(result)" ; -} diff --git a/thirdparty/urdl-2013-08-15/doc/Jamfile b/thirdparty/urdl-2013-08-15/doc/Jamfile deleted file mode 100644 index 65e583849a..0000000000 --- a/thirdparty/urdl-2013-08-15/doc/Jamfile +++ /dev/null @@ -1,62 +0,0 @@ -# -# Copyright (c) 2003-2013 Christopher M. Kohlhoff (chris at kohlhoff dot com) -# -# Distributed under the Boost Software License, Version 1.0. (See accompanying -# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -# - -using quickbook ; - -project - : - default-build - debug - multi - shared - shared - ; - -xml standalone_doc - : - urdl.qbk - ; - -boostbook standalone - : - standalone_doc - : - boost.image.src=urdl.png - boost.image.alt="Urdl C++ Library" - boost.image.w=160 - boost.image.h=60 - nav.layout=none - navig.graphics.path="" - admon.graphics.path="" - chapter.autolabel=0 - chunk.section.depth=8 - chunk.first.sections=1 - toc.section.depth=3 - toc.max.depth=3 - generate.section.toc.level=2 - generate.toc="chapter toc section toc" - ; - -install html - : - /boost//doc/src/boostbook.css - /boost//doc/src/images/blank.png - /boost//doc/src/images/caution.png - /boost//doc/src/images/draft.png - /boost//doc/src/images/home.png - /boost//doc/src/images/important.png - /boost//doc/src/images/next_disabled.png - /boost//doc/src/images/next.png - /boost//doc/src/images/note.png - /boost//doc/src/images/prev_disabled.png - /boost//doc/src/images/prev.png - /boost//doc/src/images/tip.png - /boost//doc/src/images/up_disabled.png - /boost//doc/src/images/up.png - /boost//doc/src/images/warning.png - urdl.png - ; diff --git a/thirdparty/urdl-2013-08-15/doc/doxy2qbk.pl b/thirdparty/urdl-2013-08-15/doc/doxy2qbk.pl deleted file mode 100755 index 1bc0a09175..0000000000 --- a/thirdparty/urdl-2013-08-15/doc/doxy2qbk.pl +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/perl -w - -# Copyright (c) 2003-2013 Christopher M. Kohlhoff (chris at kohlhoff dot com) -# -# Distributed under the Boost Software License, Version 1.0. (See accompanying -# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) - -use strict; - -system("doxygen reference.dox"); -chdir("xml"); -system("xsltproc combine.xslt index.xml > all.xml"); -chdir(".."); -system("xsltproc reference.xsl xml/all.xml > reference.qbk"); -system("rm -rf xml"); diff --git a/thirdparty/urdl-2013-08-15/doc/index.html b/thirdparty/urdl-2013-08-15/doc/index.html deleted file mode 100644 index 15253e097e..0000000000 --- a/thirdparty/urdl-2013-08-15/doc/index.html +++ /dev/null @@ -1,21 +0,0 @@ - - - Urdl - - - -

- Automatic redirection failed, please go to - html/index.html -

-
-

- Copyright (c) 2009-2013 Christopher M. Kohlhoff -

-

- Distributed under the Boost Software License, Version 1.0. (See accompanying - file LICENSE_1_0.txt or copy at - www.boost.org/LICENSE_1_0.txt) -

- - diff --git a/thirdparty/urdl-2013-08-15/doc/index.xml b/thirdparty/urdl-2013-08-15/doc/index.xml deleted file mode 100644 index 973bc4c62d..0000000000 --- a/thirdparty/urdl-2013-08-15/doc/index.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - -
- Index - - - -
diff --git a/thirdparty/urdl-2013-08-15/doc/reference.dox b/thirdparty/urdl-2013-08-15/doc/reference.dox deleted file mode 100644 index 001f113c5d..0000000000 --- a/thirdparty/urdl-2013-08-15/doc/reference.dox +++ /dev/null @@ -1,223 +0,0 @@ -# Doxyfile 1.4.5 - -#--------------------------------------------------------------------------- -# Project related configuration options -#--------------------------------------------------------------------------- -PROJECT_NAME = "Urdl Reference" -PROJECT_NUMBER = -OUTPUT_DIRECTORY = . -CREATE_SUBDIRS = NO -OUTPUT_LANGUAGE = English -USE_WINDOWS_ENCODING = NO -BRIEF_MEMBER_DESC = YES -REPEAT_BRIEF = YES -ABBREVIATE_BRIEF = -ALWAYS_DETAILED_SEC = YES -INLINE_INHERITED_MEMB = YES -FULL_PATH_NAMES = YES -STRIP_FROM_PATH = ./../../../ -STRIP_FROM_INC_PATH = -SHORT_NAMES = NO -JAVADOC_AUTOBRIEF = NO -MULTILINE_CPP_IS_BRIEF = YES -DETAILS_AT_TOP = YES -INHERIT_DOCS = NO -SEPARATE_MEMBER_PAGES = NO -TAB_SIZE = 2 -ALIASES = -OPTIMIZE_OUTPUT_FOR_C = NO -OPTIMIZE_OUTPUT_JAVA = NO -BUILTIN_STL_SUPPORT = NO -DISTRIBUTE_GROUP_DOC = NO -SUBGROUPING = YES -#--------------------------------------------------------------------------- -# Build related configuration options -#--------------------------------------------------------------------------- -EXTRACT_ALL = YES -EXTRACT_PRIVATE = YES -EXTRACT_STATIC = YES -EXTRACT_LOCAL_CLASSES = NO -EXTRACT_LOCAL_METHODS = NO -HIDE_UNDOC_MEMBERS = YES -HIDE_UNDOC_CLASSES = YES -HIDE_FRIEND_COMPOUNDS = NO -HIDE_IN_BODY_DOCS = NO -INTERNAL_DOCS = NO -CASE_SENSE_NAMES = YES -HIDE_SCOPE_NAMES = NO -SHOW_INCLUDE_FILES = NO -INLINE_INFO = NO -SORT_MEMBER_DOCS = NO -SORT_BRIEF_DOCS = NO -SORT_BY_SCOPE_NAME = NO -GENERATE_TODOLIST = NO -GENERATE_TESTLIST = NO -GENERATE_BUGLIST = NO -GENERATE_DEPRECATEDLIST= NO -ENABLED_SECTIONS = -MAX_INITIALIZER_LINES = 30 -SHOW_USED_FILES = NO -SHOW_DIRECTORIES = NO -FILE_VERSION_FILTER = -#--------------------------------------------------------------------------- -# configuration options related to warning and progress messages -#--------------------------------------------------------------------------- -QUIET = NO -WARNINGS = YES -WARN_IF_UNDOCUMENTED = YES -WARN_IF_DOC_ERROR = YES -WARN_NO_PARAMDOC = NO -WARN_FORMAT = "$file:$line: $text" -WARN_LOGFILE = -#--------------------------------------------------------------------------- -# configuration options related to the input files -#--------------------------------------------------------------------------- -INPUT = ./../include/urdl std_dox.txt -FILE_PATTERNS = *.hpp -RECURSIVE = NO -EXCLUDE = -EXCLUDE_SYMLINKS = NO -EXCLUDE_PATTERNS = -EXAMPLE_PATH = -EXAMPLE_PATTERNS = -EXAMPLE_RECURSIVE = YES -IMAGE_PATH = -INPUT_FILTER = -FILTER_PATTERNS = -FILTER_SOURCE_FILES = NO -#--------------------------------------------------------------------------- -# configuration options related to source browsing -#--------------------------------------------------------------------------- -SOURCE_BROWSER = NO -INLINE_SOURCES = NO -STRIP_CODE_COMMENTS = YES -REFERENCED_BY_RELATION = NO -REFERENCES_RELATION = NO -USE_HTAGS = NO -VERBATIM_HEADERS = NO -#--------------------------------------------------------------------------- -# configuration options related to the alphabetical class index -#--------------------------------------------------------------------------- -ALPHABETICAL_INDEX = YES -COLS_IN_ALPHA_INDEX = 1 -IGNORE_PREFIX = -#--------------------------------------------------------------------------- -# configuration options related to the HTML output -#--------------------------------------------------------------------------- -GENERATE_HTML = NO -HTML_OUTPUT = . -HTML_FILE_EXTENSION = .html -HTML_HEADER = -HTML_FOOTER = -HTML_STYLESHEET = -HTML_ALIGN_MEMBERS = YES -GENERATE_HTMLHELP = NO -CHM_FILE = -HHC_LOCATION = -GENERATE_CHI = NO -BINARY_TOC = NO -TOC_EXPAND = NO -DISABLE_INDEX = YES -ENUM_VALUES_PER_LINE = 1 -GENERATE_TREEVIEW = NO -TREEVIEW_WIDTH = 250 -#--------------------------------------------------------------------------- -# configuration options related to the LaTeX output -#--------------------------------------------------------------------------- -GENERATE_LATEX = NO -LATEX_OUTPUT = latex -LATEX_CMD_NAME = latex -MAKEINDEX_CMD_NAME = makeindex -COMPACT_LATEX = NO -PAPER_TYPE = a4wide -EXTRA_PACKAGES = -LATEX_HEADER = -PDF_HYPERLINKS = NO -USE_PDFLATEX = NO -LATEX_BATCHMODE = NO -LATEX_HIDE_INDICES = NO -#--------------------------------------------------------------------------- -# configuration options related to the RTF output -#--------------------------------------------------------------------------- -GENERATE_RTF = NO -RTF_OUTPUT = rtf -COMPACT_RTF = NO -RTF_HYPERLINKS = NO -RTF_STYLESHEET_FILE = -RTF_EXTENSIONS_FILE = -#--------------------------------------------------------------------------- -# configuration options related to the man page output -#--------------------------------------------------------------------------- -GENERATE_MAN = NO -MAN_OUTPUT = man -MAN_EXTENSION = .3 -MAN_LINKS = NO -#--------------------------------------------------------------------------- -# configuration options related to the XML output -#--------------------------------------------------------------------------- -GENERATE_XML = YES -XML_OUTPUT = xml -XML_SCHEMA = -XML_DTD = -XML_PROGRAMLISTING = NO -#--------------------------------------------------------------------------- -# configuration options for the AutoGen Definitions output -#--------------------------------------------------------------------------- -GENERATE_AUTOGEN_DEF = NO -#--------------------------------------------------------------------------- -# configuration options related to the Perl module output -#--------------------------------------------------------------------------- -GENERATE_PERLMOD = NO -PERLMOD_LATEX = NO -PERLMOD_PRETTY = YES -PERLMOD_MAKEVAR_PREFIX = -#--------------------------------------------------------------------------- -# Configuration options related to the preprocessor -#--------------------------------------------------------------------------- -ENABLE_PREPROCESSING = YES -MACRO_EXPANSION = YES -EXPAND_ONLY_PREDEF = YES -SEARCH_INCLUDES = YES -INCLUDE_PATH = -INCLUDE_FILE_PATTERNS = -PREDEFINED = URDL_DECL -EXPAND_AS_DEFINED = -SKIP_FUNCTION_MACROS = YES -#--------------------------------------------------------------------------- -# Configuration::additions related to external references -#--------------------------------------------------------------------------- -TAGFILES = -GENERATE_TAGFILE = -ALLEXTERNALS = NO -EXTERNAL_GROUPS = YES -PERL_PATH = /usr/bin/perl -#--------------------------------------------------------------------------- -# Configuration options related to the dot tool -#--------------------------------------------------------------------------- -CLASS_DIAGRAMS = NO -HIDE_UNDOC_RELATIONS = YES -HAVE_DOT = YES -CLASS_GRAPH = YES -COLLABORATION_GRAPH = NO -GROUP_GRAPHS = NO -UML_LOOK = NO -TEMPLATE_RELATIONS = YES -INCLUDE_GRAPH = NO -INCLUDED_BY_GRAPH = NO -CALL_GRAPH = NO -GRAPHICAL_HIERARCHY = NO -DIRECTORY_GRAPH = NO -DOT_IMAGE_FORMAT = png -DOT_PATH = -DOTFILE_DIRS = -MAX_DOT_GRAPH_WIDTH = 640 -MAX_DOT_GRAPH_HEIGHT = 640 -MAX_DOT_GRAPH_DEPTH = 0 -DOT_TRANSPARENT = NO -DOT_MULTI_TARGETS = NO -GENERATE_LEGEND = NO -DOT_CLEANUP = YES -#--------------------------------------------------------------------------- -# Configuration::additions related to the search engine -#--------------------------------------------------------------------------- -SEARCHENGINE = NO diff --git a/thirdparty/urdl-2013-08-15/doc/reference.qbk b/thirdparty/urdl-2013-08-15/doc/reference.qbk deleted file mode 100644 index 08f20a042b..0000000000 --- a/thirdparty/urdl-2013-08-15/doc/reference.qbk +++ /dev/null @@ -1,4535 +0,0 @@ -[/ - / Copyright (c) 2003-2013 Christopher M. Kohlhoff (chris at kohlhoff dot com) - / - / Distributed under the Boost Software License, Version 1.0. (See accompanying - / file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) - /] - -[section:reference Reference] - - - -[section:core Core Classes] - - -[section:istream istream] - -[indexterm2 istream..class] -The class `istream` supports reading content from a specified URL. - - - class istream : - public std::basic_istream< char > - - -['[*Member Functions]] -[table - [[Name][Description]] - - [ - [[link urdl.reference.core.istream.close [*close]]] - [Closes the stream. ] - ] - - [ - [[link urdl.reference.core.istream.content_length [*content_length]]] - [Gets the length of the content obtained from the URL. ] - ] - - [ - [[link urdl.reference.core.istream.content_type [*content_type]]] - [Gets the MIME type of the content obtained from the URL. ] - ] - - [ - [[link urdl.reference.core.istream.error [*error]]] - [Gets the last error associated with the stream. ] - ] - - [ - [[link urdl.reference.core.istream.get_option [*get_option]]] - [Gets the current value of an option that controls the behaviour of the stream. ] - ] - - [ - [[link urdl.reference.core.istream.get_options [*get_options]]] - [Gets the values of all options set on the stream. ] - ] - - [ - [[link urdl.reference.core.istream.headers [*headers]]] - [Gets the protocol-specific headers obtained from the URL. ] - ] - - [ - [[link urdl.reference.core.istream.is_open [*is_open]]] - [Determines whether the stream is open. ] - ] - - [ - [[link urdl.reference.core.istream.istream [*istream]]] - [Constructs an object of class istream. ] - ] - - [ - [[link urdl.reference.core.istream.open [*open]]] - [Opens the specified URL. ] - ] - - [ - [[link urdl.reference.core.istream.open_timeout [*open_timeout]]] - [Gets the open timeout of the stream. - - Sets the open timeout of the stream. ] - ] - - [ - [[link urdl.reference.core.istream.rdbuf [*rdbuf]]] - [Gets the underlying stream buffer. ] - ] - - [ - [[link urdl.reference.core.istream.read_timeout [*read_timeout]]] - [Gets the read timeout of the stream. - - Sets the read timeout of the stream. ] - ] - - [ - [[link urdl.reference.core.istream.set_option [*set_option]]] - [Sets an option to control the behaviour of the stream. ] - ] - - [ - [[link urdl.reference.core.istream.set_options [*set_options]]] - [Sets options to control the behaviour of the stream. ] - ] - -] - - -['[*Remarks]] - -The class stores an object of class `istreambuf`. - -Currently supported URL protocols are `http`, `https` and `file`. - - -['[*Example]] - -To read the entire content of a resource located by a URL into a string: - - urdl::istream is("http://www.boost.org/LICENSE_1_0.txt"); - if (is) - { - std::string content; - if (std::getline(is, content, std::char_traits::eof())) - { - ... - } - } - - - - -['[*Requirements]] - -[*Header:] `` - -[*Namespace:] `urdl` - - - -[section:close istream::close] - -[indexterm2 close..istream] -Closes the stream. - - - void close(); - - - -['[*Remarks]] - -Calls `rdbuf()->close()` and, if that function returns a null pointer, calls `setstate(failbit)` (which may throw `ios_base::failure`). - - - -[endsect] - - - -[section:content_length istream::content_length] - -[indexterm2 content_length..istream] -Gets the length of the content obtained from the URL. - - - std::size_t content_length() const; - - - -['[*Return Value]] - -The length, in bytes, of the content. If the content associated with the URL does not specify a length, `std::numeric_limits::max()`. - -['[*Remarks]] - -Returns `rdbuf()->content_length()`. - - - -[endsect] - - - -[section:content_type istream::content_type] - -[indexterm2 content_type..istream] -Gets the MIME type of the content obtained from the URL. - - - std::string content_type() const; - - - -['[*Return Value]] - -A string specifying the MIME type. Examples of possible return values include `text/plain`, `text/html` and `image/png`. - -['[*Remarks]] - -Returns `rdbuf()->content_type()`. - -Not all URL protocols support a content type. For these protocols, this function returns an empty string. - - -[endsect] - - - -[section:error istream::error] - -[indexterm2 error..istream] -Gets the last error associated with the stream. - - - const boost::system::error_code & error() const; - - - -['[*Return Value]] - -An `error_code` corresponding to the last error from the stream. - -['[*Remarks]] - -Returns a reference to an `error_code` object representing the last failure reported by an `istreambuf` function. The set of possible `error_code` values and categories depends on the protocol of the URL used to open the stream. - -['[*Example]] - -To take action given a specific error: - - urdl::istream is("http://somesite/page"); - if (!is) - { - if (is.error() == urdl::http::errc::forbidden) - { - std::cout << "Computer says no" << std::endl; - } - } - - - - - - -[endsect] - - - -[section:get_option istream::get_option] - -[indexterm2 get_option..istream] -Gets the current value of an option that controls the behaviour of the stream. - - - template< - typename Option> - Option get_option() const; - - - -['[*Return Value]] - -The current value of the option. - -['[*Remarks]] - -Returns `rdbuf()->get_option