Add valijson. Currently it seg faults due to stack overflow when validating with `glTF.schema.json`
diff --git a/examples/validator/CMakeLists.txt b/examples/validator/CMakeLists.txt new file mode 100644 index 0000000..1e98b07 --- /dev/null +++ b/examples/validator/CMakeLists.txt
@@ -0,0 +1,53 @@ +project(tinygltf-validator CXX) + +cmake_minimum_required(VERSION 3.2) + +# Use C++11 +set(CMAKE_CXX_STANDARD 11) + +# exe +add_executable(tinygltf-validator + tinygltf-validate.cc + json11.cpp) + +target_include_directories(tinygltf-validator + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/valijson/include + ${CMAKE_CURRENT_SOURCE_DIR}/ + ) + +# Enable more compiler warnings, except when using Visual Studio compiler +if(NOT MSVC) + target_compile_options(tinygltf-validator + PUBLIC + -Wall -Wextra) +endif() +target_compile_definitions(tinygltf-validator + PRIVATE + -DJSON_SCHEMA_VALIDATOR_EXPORTS) + +# regex with boost if gcc < 4.8 - default is std::regex +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "4.9.0") + find_package(Boost COMPONENTS regex) + if(NOT Boost_FOUND) + message(STATUS "GCC less then 4.9 and boost-regex NOT found - no regex used") + target_compile_definitions(tinygltf-validator PRIVATE -DJSON_SCHEMA_NO_REGEX) + else() + message(STATUS "GCC less then 4.9 and boost-regex FOUND - using boost::regex") + target_compile_definitions(tinygltf-validator PRIVATE -DJSON_SCHEMA_BOOST_REGEX) + target_include_directories(tinygltf-validator PRIVATE ${Boost_INCLUDE_DIRS}) + target_link_libraries(tinygltf-validator PRIVATE ${Boost_LIBRARIES}) + endif() + endif() +endif() + +# test-zone +# enable_testing() + +install ( TARGETS + tinygltf-validator + DESTINATION + bin + ) +
diff --git a/examples/validator/LICENSE.json11.txt b/examples/validator/LICENSE.json11.txt new file mode 100755 index 0000000..691742e --- /dev/null +++ b/examples/validator/LICENSE.json11.txt
@@ -0,0 +1,19 @@ +Copyright (c) 2013 Dropbox, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE.
diff --git a/examples/validator/Makefile b/examples/validator/Makefile new file mode 100644 index 0000000..6b3abde --- /dev/null +++ b/examples/validator/Makefile
@@ -0,0 +1,4 @@ +EXTRA_CXXFLAGS=-fsanitize=address + +all: + clang++ $(EXTRA_CXXFLAGS) -g -O1 -I. -Ivalijson/include tinygltf-validate.cc json11.cpp -o tinygltf-validate
diff --git a/examples/validator/README.md b/examples/validator/README.md new file mode 100644 index 0000000..2bca145 --- /dev/null +++ b/examples/validator/README.md
@@ -0,0 +1,15 @@ +Currently it causes stack-overflow in runtime. + +``` +$ ./tinygltf-validate schema_dir ../../models/Cube/Cube.gltf + +... + +==17033==ERROR: AddressSanitizer: stack-overflow on address 0x7fff55415d68 (pc 0x0000004c62de bp 0x7fff554165c0 sp 0x7fff55415d70 T0) + #0 0x4c62dd in __asan_memset /tmp/final/llvm.src/projects/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cc:27:3 + #1 0x53a7a7 in std::_Vector_base<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >::_Vector_impl::_Vector_impl(std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > const&) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/stl_vector.h:91:37 + #2 0x53a6f1 in std::_Vector_base<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >::_Vector_base(unsigned long, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > const&) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/stl_vector.h:135:9 + #3 0x53a9e2 in std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >::vector(std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > const&) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/stl_vector.h:319:9 + #4 0x584313 in valijson::ValidationVisitor<valijson::adapters::Json11Adapter>::ValidationVisitor(valijson::ValidationVisitor<valijson::adapters::Json11Adapter> const&) /home/syoyo/work/tinygltf/examples/validator/valijson/include/valijson/validation_visitor.hpp:26:7 +``` +
diff --git a/examples/validator/json11.cpp b/examples/validator/json11.cpp new file mode 100644 index 0000000..9647846 --- /dev/null +++ b/examples/validator/json11.cpp
@@ -0,0 +1,788 @@ +/* Copyright (c) 2013 Dropbox, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "json11.hpp" +#include <cassert> +#include <cmath> +#include <cstdlib> +#include <cstdio> +#include <limits> + +namespace json11 { + +static const int max_depth = 200; + +using std::string; +using std::vector; +using std::map; +using std::make_shared; +using std::initializer_list; +using std::move; + +/* Helper for representing null - just a do-nothing struct, plus comparison + * operators so the helpers in JsonValue work. We can't use nullptr_t because + * it may not be orderable. + */ +struct NullStruct { + bool operator==(NullStruct) const { return true; } + bool operator<(NullStruct) const { return false; } +}; + +/* * * * * * * * * * * * * * * * * * * * + * Serialization + */ + +static void dump(NullStruct, string &out) { + out += "null"; +} + +static void dump(double value, string &out) { + if (std::isfinite(value)) { + char buf[32]; + snprintf(buf, sizeof buf, "%.17g", value); + out += buf; + } else { + out += "null"; + } +} + +static void dump(int value, string &out) { + char buf[32]; + snprintf(buf, sizeof buf, "%d", value); + out += buf; +} + +static void dump(bool value, string &out) { + out += value ? "true" : "false"; +} + +static void dump(const string &value, string &out) { + out += '"'; + for (size_t i = 0; i < value.length(); i++) { + const char ch = value[i]; + if (ch == '\\') { + out += "\\\\"; + } else if (ch == '"') { + out += "\\\""; + } else if (ch == '\b') { + out += "\\b"; + } else if (ch == '\f') { + out += "\\f"; + } else if (ch == '\n') { + out += "\\n"; + } else if (ch == '\r') { + out += "\\r"; + } else if (ch == '\t') { + out += "\\t"; + } else if (static_cast<uint8_t>(ch) <= 0x1f) { + char buf[8]; + snprintf(buf, sizeof buf, "\\u%04x", ch); + out += buf; + } else if (static_cast<uint8_t>(ch) == 0xe2 && static_cast<uint8_t>(value[i+1]) == 0x80 + && static_cast<uint8_t>(value[i+2]) == 0xa8) { + out += "\\u2028"; + i += 2; + } else if (static_cast<uint8_t>(ch) == 0xe2 && static_cast<uint8_t>(value[i+1]) == 0x80 + && static_cast<uint8_t>(value[i+2]) == 0xa9) { + out += "\\u2029"; + i += 2; + } else { + out += ch; + } + } + out += '"'; +} + +static void dump(const Json::array &values, string &out) { + bool first = true; + out += "["; + for (const auto &value : values) { + if (!first) + out += ", "; + value.dump(out); + first = false; + } + out += "]"; +} + +static void dump(const Json::object &values, string &out) { + bool first = true; + out += "{"; + for (const auto &kv : values) { + if (!first) + out += ", "; + dump(kv.first, out); + out += ": "; + kv.second.dump(out); + first = false; + } + out += "}"; +} + +void Json::dump(string &out) const { + m_ptr->dump(out); +} + +/* * * * * * * * * * * * * * * * * * * * + * Value wrappers + */ + +template <Json::Type tag, typename T> +class Value : public JsonValue { +protected: + + // Constructors + explicit Value(const T &value) : m_value(value) {} + explicit Value(T &&value) : m_value(move(value)) {} + + // Get type tag + Json::Type type() const override { + return tag; + } + + // Comparisons + bool equals(const JsonValue * other) const override { + return m_value == static_cast<const Value<tag, T> *>(other)->m_value; + } + bool less(const JsonValue * other) const override { + return m_value < static_cast<const Value<tag, T> *>(other)->m_value; + } + + const T m_value; + void dump(string &out) const override { json11::dump(m_value, out); } +}; + +class JsonDouble final : public Value<Json::NUMBER, double> { + double number_value() const override { return m_value; } + int int_value() const override { return static_cast<int>(m_value); } + bool equals(const JsonValue * other) const override { return m_value == other->number_value(); } + bool less(const JsonValue * other) const override { return m_value < other->number_value(); } +public: + explicit JsonDouble(double value) : Value(value) {} +}; + +class JsonInt final : public Value<Json::NUMBER, int> { + double number_value() const override { return m_value; } + int int_value() const override { return m_value; } + bool equals(const JsonValue * other) const override { return m_value == other->number_value(); } + bool less(const JsonValue * other) const override { return m_value < other->number_value(); } +public: + explicit JsonInt(int value) : Value(value) {} +}; + +class JsonBoolean final : public Value<Json::BOOL, bool> { + bool bool_value() const override { return m_value; } +public: + explicit JsonBoolean(bool value) : Value(value) {} +}; + +class JsonString final : public Value<Json::STRING, string> { + const string &string_value() const override { return m_value; } +public: + explicit JsonString(const string &value) : Value(value) {} + explicit JsonString(string &&value) : Value(move(value)) {} +}; + +class JsonArray final : public Value<Json::ARRAY, Json::array> { + const Json::array &array_items() const override { return m_value; } + const Json & operator[](size_t i) const override; +public: + explicit JsonArray(const Json::array &value) : Value(value) {} + explicit JsonArray(Json::array &&value) : Value(move(value)) {} +}; + +class JsonObject final : public Value<Json::OBJECT, Json::object> { + const Json::object &object_items() const override { return m_value; } + const Json & operator[](const string &key) const override; +public: + explicit JsonObject(const Json::object &value) : Value(value) {} + explicit JsonObject(Json::object &&value) : Value(move(value)) {} +}; + +class JsonNull final : public Value<Json::NUL, NullStruct> { +public: + JsonNull() : Value({}) {} +}; + +/* * * * * * * * * * * * * * * * * * * * + * Static globals - static-init-safe + */ +struct Statics { + const std::shared_ptr<JsonValue> null = make_shared<JsonNull>(); + const std::shared_ptr<JsonValue> t = make_shared<JsonBoolean>(true); + const std::shared_ptr<JsonValue> f = make_shared<JsonBoolean>(false); + const string empty_string; + const vector<Json> empty_vector; + const map<string, Json> empty_map; + Statics() {} +}; + +static const Statics & statics() { + static const Statics s {}; + return s; +} + +static const Json & static_null() { + // This has to be separate, not in Statics, because Json() accesses statics().null. + static const Json json_null; + return json_null; +} + +/* * * * * * * * * * * * * * * * * * * * + * Constructors + */ + +Json::Json() noexcept : m_ptr(statics().null) {} +Json::Json(std::nullptr_t) noexcept : m_ptr(statics().null) {} +Json::Json(double value) : m_ptr(make_shared<JsonDouble>(value)) {} +Json::Json(int value) : m_ptr(make_shared<JsonInt>(value)) {} +Json::Json(bool value) : m_ptr(value ? statics().t : statics().f) {} +Json::Json(const string &value) : m_ptr(make_shared<JsonString>(value)) {} +Json::Json(string &&value) : m_ptr(make_shared<JsonString>(move(value))) {} +Json::Json(const char * value) : m_ptr(make_shared<JsonString>(value)) {} +Json::Json(const Json::array &values) : m_ptr(make_shared<JsonArray>(values)) {} +Json::Json(Json::array &&values) : m_ptr(make_shared<JsonArray>(move(values))) {} +Json::Json(const Json::object &values) : m_ptr(make_shared<JsonObject>(values)) {} +Json::Json(Json::object &&values) : m_ptr(make_shared<JsonObject>(move(values))) {} + +/* * * * * * * * * * * * * * * * * * * * + * Accessors + */ + +Json::Type Json::type() const { return m_ptr->type(); } +double Json::number_value() const { return m_ptr->number_value(); } +int Json::int_value() const { return m_ptr->int_value(); } +bool Json::bool_value() const { return m_ptr->bool_value(); } +const string & Json::string_value() const { return m_ptr->string_value(); } +const vector<Json> & Json::array_items() const { return m_ptr->array_items(); } +const map<string, Json> & Json::object_items() const { return m_ptr->object_items(); } +const Json & Json::operator[] (size_t i) const { return (*m_ptr)[i]; } +const Json & Json::operator[] (const string &key) const { return (*m_ptr)[key]; } + +double JsonValue::number_value() const { return 0; } +int JsonValue::int_value() const { return 0; } +bool JsonValue::bool_value() const { return false; } +const string & JsonValue::string_value() const { return statics().empty_string; } +const vector<Json> & JsonValue::array_items() const { return statics().empty_vector; } +const map<string, Json> & JsonValue::object_items() const { return statics().empty_map; } +const Json & JsonValue::operator[] (size_t) const { return static_null(); } +const Json & JsonValue::operator[] (const string &) const { return static_null(); } + +const Json & JsonObject::operator[] (const string &key) const { + auto iter = m_value.find(key); + return (iter == m_value.end()) ? static_null() : iter->second; +} +const Json & JsonArray::operator[] (size_t i) const { + if (i >= m_value.size()) return static_null(); + else return m_value[i]; +} + +/* * * * * * * * * * * * * * * * * * * * + * Comparison + */ + +bool Json::operator== (const Json &other) const { + if (m_ptr == other.m_ptr) + return true; + if (m_ptr->type() != other.m_ptr->type()) + return false; + + return m_ptr->equals(other.m_ptr.get()); +} + +bool Json::operator< (const Json &other) const { + if (m_ptr == other.m_ptr) + return false; + if (m_ptr->type() != other.m_ptr->type()) + return m_ptr->type() < other.m_ptr->type(); + + return m_ptr->less(other.m_ptr.get()); +} + +/* * * * * * * * * * * * * * * * * * * * + * Parsing + */ + +/* esc(c) + * + * Format char c suitable for printing in an error message. + */ +static inline string esc(char c) { + char buf[12]; + if (static_cast<uint8_t>(c) >= 0x20 && static_cast<uint8_t>(c) <= 0x7f) { + snprintf(buf, sizeof buf, "'%c' (%d)", c, c); + } else { + snprintf(buf, sizeof buf, "(%d)", c); + } + return string(buf); +} + +static inline bool in_range(long x, long lower, long upper) { + return (x >= lower && x <= upper); +} + +namespace { +/* JsonParser + * + * Object that tracks all state of an in-progress parse. + */ +struct JsonParser final { + + /* State + */ + const string &str; + size_t i; + string &err; + bool failed; + const JsonParse strategy; + + /* fail(msg, err_ret = Json()) + * + * Mark this parse as failed. + */ + Json fail(string &&msg) { + return fail(move(msg), Json()); + } + + template <typename T> + T fail(string &&msg, const T err_ret) { + if (!failed) + err = std::move(msg); + failed = true; + return err_ret; + } + + /* consume_whitespace() + * + * Advance until the current character is non-whitespace. + */ + void consume_whitespace() { + while (str[i] == ' ' || str[i] == '\r' || str[i] == '\n' || str[i] == '\t') + i++; + } + + /* consume_comment() + * + * Advance comments (c-style inline and multiline). + */ + bool consume_comment() { + bool comment_found = false; + if (str[i] == '/') { + i++; + if (i == str.size()) + return fail("unexpected end of input after start of comment", false); + if (str[i] == '/') { // inline comment + i++; + // advance until next line, or end of input + while (i < str.size() && str[i] != '\n') { + i++; + } + comment_found = true; + } + else if (str[i] == '*') { // multiline comment + i++; + if (i > str.size()-2) + return fail("unexpected end of input inside multi-line comment", false); + // advance until closing tokens + while (!(str[i] == '*' && str[i+1] == '/')) { + i++; + if (i > str.size()-2) + return fail( + "unexpected end of input inside multi-line comment", false); + } + i += 2; + comment_found = true; + } + else + return fail("malformed comment", false); + } + return comment_found; + } + + /* consume_garbage() + * + * Advance until the current character is non-whitespace and non-comment. + */ + void consume_garbage() { + consume_whitespace(); + if(strategy == JsonParse::COMMENTS) { + bool comment_found = false; + do { + comment_found = consume_comment(); + if (failed) return; + consume_whitespace(); + } + while(comment_found); + } + } + + /* get_next_token() + * + * Return the next non-whitespace character. If the end of the input is reached, + * flag an error and return 0. + */ + char get_next_token() { + consume_garbage(); + if (failed) return (char)0; + if (i == str.size()) + return fail("unexpected end of input", (char)0); + + return str[i++]; + } + + /* encode_utf8(pt, out) + * + * Encode pt as UTF-8 and add it to out. + */ + void encode_utf8(long pt, string & out) { + if (pt < 0) + return; + + if (pt < 0x80) { + out += static_cast<char>(pt); + } else if (pt < 0x800) { + out += static_cast<char>((pt >> 6) | 0xC0); + out += static_cast<char>((pt & 0x3F) | 0x80); + } else if (pt < 0x10000) { + out += static_cast<char>((pt >> 12) | 0xE0); + out += static_cast<char>(((pt >> 6) & 0x3F) | 0x80); + out += static_cast<char>((pt & 0x3F) | 0x80); + } else { + out += static_cast<char>((pt >> 18) | 0xF0); + out += static_cast<char>(((pt >> 12) & 0x3F) | 0x80); + out += static_cast<char>(((pt >> 6) & 0x3F) | 0x80); + out += static_cast<char>((pt & 0x3F) | 0x80); + } + } + + /* parse_string() + * + * Parse a string, starting at the current position. + */ + string parse_string() { + string out; + long last_escaped_codepoint = -1; + while (true) { + if (i == str.size()) + return fail("unexpected end of input in string", ""); + + char ch = str[i++]; + + if (ch == '"') { + encode_utf8(last_escaped_codepoint, out); + return out; + } + + if (in_range(ch, 0, 0x1f)) + return fail("unescaped " + esc(ch) + " in string", ""); + + // The usual case: non-escaped characters + if (ch != '\\') { + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = -1; + out += ch; + continue; + } + + // Handle escapes + if (i == str.size()) + return fail("unexpected end of input in string", ""); + + ch = str[i++]; + + if (ch == 'u') { + // Extract 4-byte escape sequence + string esc = str.substr(i, 4); + // Explicitly check length of the substring. The following loop + // relies on std::string returning the terminating NUL when + // accessing str[length]. Checking here reduces brittleness. + if (esc.length() < 4) { + return fail("bad \\u escape: " + esc, ""); + } + for (size_t j = 0; j < 4; j++) { + if (!in_range(esc[j], 'a', 'f') && !in_range(esc[j], 'A', 'F') + && !in_range(esc[j], '0', '9')) + return fail("bad \\u escape: " + esc, ""); + } + + long codepoint = strtol(esc.data(), nullptr, 16); + + // JSON specifies that characters outside the BMP shall be encoded as a pair + // of 4-hex-digit \u escapes encoding their surrogate pair components. Check + // whether we're in the middle of such a beast: the previous codepoint was an + // escaped lead (high) surrogate, and this is a trail (low) surrogate. + if (in_range(last_escaped_codepoint, 0xD800, 0xDBFF) + && in_range(codepoint, 0xDC00, 0xDFFF)) { + // Reassemble the two surrogate pairs into one astral-plane character, per + // the UTF-16 algorithm. + encode_utf8((((last_escaped_codepoint - 0xD800) << 10) + | (codepoint - 0xDC00)) + 0x10000, out); + last_escaped_codepoint = -1; + } else { + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = codepoint; + } + + i += 4; + continue; + } + + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = -1; + + if (ch == 'b') { + out += '\b'; + } else if (ch == 'f') { + out += '\f'; + } else if (ch == 'n') { + out += '\n'; + } else if (ch == 'r') { + out += '\r'; + } else if (ch == 't') { + out += '\t'; + } else if (ch == '"' || ch == '\\' || ch == '/') { + out += ch; + } else { + return fail("invalid escape character " + esc(ch), ""); + } + } + } + + /* parse_number() + * + * Parse a double. + */ + Json parse_number() { + size_t start_pos = i; + + if (str[i] == '-') + i++; + + // Integer part + if (str[i] == '0') { + i++; + if (in_range(str[i], '0', '9')) + return fail("leading 0s not permitted in numbers"); + } else if (in_range(str[i], '1', '9')) { + i++; + while (in_range(str[i], '0', '9')) + i++; + } else { + return fail("invalid " + esc(str[i]) + " in number"); + } + + if (str[i] != '.' && str[i] != 'e' && str[i] != 'E' + && (i - start_pos) <= static_cast<size_t>(std::numeric_limits<int>::digits10)) { + return std::atoi(str.c_str() + start_pos); + } + + // Decimal part + if (str[i] == '.') { + i++; + if (!in_range(str[i], '0', '9')) + return fail("at least one digit required in fractional part"); + + while (in_range(str[i], '0', '9')) + i++; + } + + // Exponent part + if (str[i] == 'e' || str[i] == 'E') { + i++; + + if (str[i] == '+' || str[i] == '-') + i++; + + if (!in_range(str[i], '0', '9')) + return fail("at least one digit required in exponent"); + + while (in_range(str[i], '0', '9')) + i++; + } + + return std::strtod(str.c_str() + start_pos, nullptr); + } + + /* expect(str, res) + * + * Expect that 'str' starts at the character that was just read. If it does, advance + * the input and return res. If not, flag an error. + */ + Json expect(const string &expected, Json res) { + assert(i != 0); + i--; + if (str.compare(i, expected.length(), expected) == 0) { + i += expected.length(); + return res; + } else { + return fail("parse error: expected " + expected + ", got " + str.substr(i, expected.length())); + } + } + + /* parse_json() + * + * Parse a JSON object. + */ + Json parse_json(int depth) { + if (depth > max_depth) { + return fail("exceeded maximum nesting depth"); + } + + char ch = get_next_token(); + if (failed) + return Json(); + + if (ch == '-' || (ch >= '0' && ch <= '9')) { + i--; + return parse_number(); + } + + if (ch == 't') + return expect("true", true); + + if (ch == 'f') + return expect("false", false); + + if (ch == 'n') + return expect("null", Json()); + + if (ch == '"') + return parse_string(); + + if (ch == '{') { + map<string, Json> data; + ch = get_next_token(); + if (ch == '}') + return data; + + while (1) { + if (ch != '"') + return fail("expected '\"' in object, got " + esc(ch)); + + string key = parse_string(); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch != ':') + return fail("expected ':' in object, got " + esc(ch)); + + data[std::move(key)] = parse_json(depth + 1); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch == '}') + break; + if (ch != ',') + return fail("expected ',' in object, got " + esc(ch)); + + ch = get_next_token(); + } + return data; + } + + if (ch == '[') { + vector<Json> data; + ch = get_next_token(); + if (ch == ']') + return data; + + while (1) { + i--; + data.push_back(parse_json(depth + 1)); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch == ']') + break; + if (ch != ',') + return fail("expected ',' in list, got " + esc(ch)); + + ch = get_next_token(); + (void)ch; + } + return data; + } + + return fail("expected value, got " + esc(ch)); + } +}; +}//namespace { + +Json Json::parse(const string &in, string &err, JsonParse strategy) { + JsonParser parser { in, 0, err, false, strategy }; + Json result = parser.parse_json(0); + + // Check for any trailing garbage + parser.consume_garbage(); + if (parser.failed) + return Json(); + if (parser.i != in.size()) + return parser.fail("unexpected trailing " + esc(in[parser.i])); + + return result; +} + +// Documented in json11.hpp +vector<Json> Json::parse_multi(const string &in, + std::string::size_type &parser_stop_pos, + string &err, + JsonParse strategy) { + JsonParser parser { in, 0, err, false, strategy }; + parser_stop_pos = 0; + vector<Json> json_vec; + while (parser.i != in.size() && !parser.failed) { + json_vec.push_back(parser.parse_json(0)); + if (parser.failed) + break; + + // Check for another object + parser.consume_garbage(); + if (parser.failed) + break; + parser_stop_pos = parser.i; + } + return json_vec; +} + +/* * * * * * * * * * * * * * * * * * * * + * Shape-checking + */ + +bool Json::has_shape(const shape & types, string & err) const { + if (!is_object()) { + err = "expected JSON object, got " + dump(); + return false; + } + + for (auto & item : types) { + if ((*this)[item.first].type() != item.second) { + err = "bad type for " + item.first + " in " + dump(); + return false; + } + } + + return true; +} + +} // namespace json11
diff --git a/examples/validator/json11.hpp b/examples/validator/json11.hpp new file mode 100644 index 0000000..0c47d05 --- /dev/null +++ b/examples/validator/json11.hpp
@@ -0,0 +1,232 @@ +/* json11 + * + * json11 is a tiny JSON library for C++11, providing JSON parsing and serialization. + * + * The core object provided by the library is json11::Json. A Json object represents any JSON + * value: null, bool, number (int or double), string (std::string), array (std::vector), or + * object (std::map). + * + * Json objects act like values: they can be assigned, copied, moved, compared for equality or + * order, etc. There are also helper methods Json::dump, to serialize a Json to a string, and + * Json::parse (static) to parse a std::string as a Json object. + * + * Internally, the various types of Json object are represented by the JsonValue class + * hierarchy. + * + * A note on numbers - JSON specifies the syntax of number formatting but not its semantics, + * so some JSON implementations distinguish between integers and floating-point numbers, while + * some don't. In json11, we choose the latter. Because some JSON implementations (namely + * Javascript itself) treat all numbers as the same type, distinguishing the two leads + * to JSON that will be *silently* changed by a round-trip through those implementations. + * Dangerous! To avoid that risk, json11 stores all numbers as double internally, but also + * provides integer helpers. + * + * Fortunately, double-precision IEEE754 ('double') can precisely store any integer in the + * range +/-2^53, which includes every 'int' on most systems. (Timestamps often use int64 + * or long long to avoid the Y2038K problem; a double storing microseconds since some epoch + * will be exact for +/- 275 years.) + */ + +/* Copyright (c) 2013 Dropbox, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <string> +#include <vector> +#include <map> +#include <memory> +#include <initializer_list> + +#ifdef _MSC_VER + #if _MSC_VER <= 1800 // VS 2013 + #ifndef noexcept + #define noexcept throw() + #endif + + #ifndef snprintf + #define snprintf _snprintf_s + #endif + #endif +#endif + +namespace json11 { + +enum JsonParse { + STANDARD, COMMENTS +}; + +class JsonValue; + +class Json final { +public: + // Types + enum Type { + NUL, NUMBER, BOOL, STRING, ARRAY, OBJECT + }; + + // Array and object typedefs + typedef std::vector<Json> array; + typedef std::map<std::string, Json> object; + + // Constructors for the various types of JSON value. + Json() noexcept; // NUL + Json(std::nullptr_t) noexcept; // NUL + Json(double value); // NUMBER + Json(int value); // NUMBER + Json(bool value); // BOOL + Json(const std::string &value); // STRING + Json(std::string &&value); // STRING + Json(const char * value); // STRING + Json(const array &values); // ARRAY + Json(array &&values); // ARRAY + Json(const object &values); // OBJECT + Json(object &&values); // OBJECT + + // Implicit constructor: anything with a to_json() function. + template <class T, class = decltype(&T::to_json)> + Json(const T & t) : Json(t.to_json()) {} + + // Implicit constructor: map-like objects (std::map, std::unordered_map, etc) + template <class M, typename std::enable_if< + std::is_constructible<std::string, decltype(std::declval<M>().begin()->first)>::value + && std::is_constructible<Json, decltype(std::declval<M>().begin()->second)>::value, + int>::type = 0> + Json(const M & m) : Json(object(m.begin(), m.end())) {} + + // Implicit constructor: vector-like objects (std::list, std::vector, std::set, etc) + template <class V, typename std::enable_if< + std::is_constructible<Json, decltype(*std::declval<V>().begin())>::value, + int>::type = 0> + Json(const V & v) : Json(array(v.begin(), v.end())) {} + + // This prevents Json(some_pointer) from accidentally producing a bool. Use + // Json(bool(some_pointer)) if that behavior is desired. + Json(void *) = delete; + + // Accessors + Type type() const; + + bool is_null() const { return type() == NUL; } + bool is_number() const { return type() == NUMBER; } + bool is_bool() const { return type() == BOOL; } + bool is_string() const { return type() == STRING; } + bool is_array() const { return type() == ARRAY; } + bool is_object() const { return type() == OBJECT; } + + // Return the enclosed value if this is a number, 0 otherwise. Note that json11 does not + // distinguish between integer and non-integer numbers - number_value() and int_value() + // can both be applied to a NUMBER-typed object. + double number_value() const; + int int_value() const; + + // Return the enclosed value if this is a boolean, false otherwise. + bool bool_value() const; + // Return the enclosed string if this is a string, "" otherwise. + const std::string &string_value() const; + // Return the enclosed std::vector if this is an array, or an empty vector otherwise. + const array &array_items() const; + // Return the enclosed std::map if this is an object, or an empty map otherwise. + const object &object_items() const; + + // Return a reference to arr[i] if this is an array, Json() otherwise. + const Json & operator[](size_t i) const; + // Return a reference to obj[key] if this is an object, Json() otherwise. + const Json & operator[](const std::string &key) const; + + // Serialize. + void dump(std::string &out) const; + std::string dump() const { + std::string out; + dump(out); + return out; + } + + // Parse. If parse fails, return Json() and assign an error message to err. + static Json parse(const std::string & in, + std::string & err, + JsonParse strategy = JsonParse::STANDARD); + static Json parse(const char * in, + std::string & err, + JsonParse strategy = JsonParse::STANDARD) { + if (in) { + return parse(std::string(in), err, strategy); + } else { + err = "null input"; + return nullptr; + } + } + // Parse multiple objects, concatenated or separated by whitespace + static std::vector<Json> parse_multi( + const std::string & in, + std::string::size_type & parser_stop_pos, + std::string & err, + JsonParse strategy = JsonParse::STANDARD); + + static inline std::vector<Json> parse_multi( + const std::string & in, + std::string & err, + JsonParse strategy = JsonParse::STANDARD) { + std::string::size_type parser_stop_pos; + return parse_multi(in, parser_stop_pos, err, strategy); + } + + bool operator== (const Json &rhs) const; + bool operator< (const Json &rhs) const; + bool operator!= (const Json &rhs) const { return !(*this == rhs); } + bool operator<= (const Json &rhs) const { return !(rhs < *this); } + bool operator> (const Json &rhs) const { return (rhs < *this); } + bool operator>= (const Json &rhs) const { return !(*this < rhs); } + + /* has_shape(types, err) + * + * Return true if this is a JSON object and, for each item in types, has a field of + * the given type. If not, return false and set err to a descriptive message. + */ + typedef std::initializer_list<std::pair<std::string, Type>> shape; + bool has_shape(const shape & types, std::string & err) const; + +private: + std::shared_ptr<JsonValue> m_ptr; +}; + +// Internal class hierarchy - JsonValue objects are not exposed to users of this API. +class JsonValue { +protected: + friend class Json; + friend class JsonInt; + friend class JsonDouble; + virtual Json::Type type() const = 0; + virtual bool equals(const JsonValue * other) const = 0; + virtual bool less(const JsonValue * other) const = 0; + virtual void dump(std::string &out) const = 0; + virtual double number_value() const; + virtual int int_value() const; + virtual bool bool_value() const; + virtual const std::string &string_value() const; + virtual const Json::array &array_items() const; + virtual const Json &operator[](size_t i) const; + virtual const Json::object &object_items() const; + virtual const Json &operator[](const std::string &key) const; + virtual ~JsonValue() {} +}; + +} // namespace json11
diff --git a/examples/validator/tinygltf-validate.cc b/examples/validator/tinygltf-validate.cc new file mode 100644 index 0000000..faeb463 --- /dev/null +++ b/examples/validator/tinygltf-validate.cc
@@ -0,0 +1,170 @@ +#include <cstdio> +#include <cstdlib> +#include <fstream> +#include <iostream> + +#include "json11.hpp" + +#include "valijson/adapters/json11_adapter.hpp" +#include "valijson/utils/json11_utils.hpp" + +#include "valijson/schema.hpp" +#include "valijson/schema_parser.hpp" +#include "valijson/validator.hpp" + +static void usage(const char *name) { + std::cerr << "Usage: " << name << " <gltf schema dir> <gltf file>\n"; + std::cerr << " schema dir is usually : $glTF/specification/2.0/schema\n"; + std::cerr << " where $glTF is a directory of https://github.com/KhronosGroup/glTF\n"; + + exit(EXIT_FAILURE); +} + +#if 0 + resolver r(nlohmann::json_schema_draft4::root_schema, + nlohmann::json_schema_draft4::root_schema["id"]); + schema_refs_.insert(r.schema_refs.begin(), r.schema_refs.end()); + assert(r.undefined_refs.size() == 0); +#endif + +#if 0 +static void loader(const json_uri &uri, json &schema) +{ + std::fstream lf("." + uri.path()); + if (!lf.good()) + throw std::invalid_argument("could not open " + uri.url() + " tried with " + uri.path()); + + try { + lf >> schema; + } catch (std::exception &e) { + throw e; + } +} + +bool validate(const std::string &schema_dir, const std::string &filename) +{ + std::string gltf_schema = schema_dir + "/glTF.schema.json"; + + std::fstream f(gltf_schema); + if (!f.good()) { + std::cerr << "could not open " << gltf_schema << " for reading\n"; + return false; + } + + // 1) Read the schema for the document you want to validate + json schema; + try { + f >> schema; + } catch (std::exception &e) { + std::cerr << e.what() << " at " << f.tellp() << " - while parsing the schema\n"; + return false; + } + + // 2) create the validator and + json_validator validator([&schema_dir](const json_uri &uri, json &schema) { + std::cout << "uri.url : " << uri.url() << std::endl; + std::cout << "uri.path : " << uri.path() << std::endl; + + std::fstream lf(schema_dir + "/" + uri.path()); + if (!lf.good()) + throw std::invalid_argument("could not open " + uri.url() + " tried with " + uri.path()); + + try { + lf >> schema; + } catch (std::exception &e) { + throw e; + } + }, [](const std::string &, const std::string &) {}); + + try { + // insert this schema as the root to the validator + // this resolves remote-schemas, sub-schemas and references via the given loader-function + validator.set_root_schema(schema); + } catch (std::exception &e) { + std::cerr << "setting root schema failed\n"; + std::cerr << e.what() << "\n"; + } + + // 3) do the actual validation of the document + json document; + + std::fstream d(filename); + if (!d.good()) { + std::cerr << "could not open " << filename << " for reading\n"; + return false; + } + + try { + d >> document; + validator.validate(document); + } catch (std::exception &e) { + std::cerr << "schema validation failed\n"; + std::cerr << e.what() << " at offset: " << d.tellg() << "\n"; + return false; + } + + std::cerr << "document is valid\n"; + + return true; + +} +#endif + +bool validate(const std::string &schema_dir, const std::string &filename) { + std::string gltf_schema = schema_dir + "/glTF.schema.json"; + + // 1) Read the schema for the document you want to validate + json11::Json schema_doc; + bool ret = valijson::utils::loadDocument(gltf_schema, schema_doc); + if (!ret) { + std::cerr << "Failed to load schema file : " << gltf_schema << "\n"; + return false; + } + + // 2) Parse JSON schema content using valijson + valijson::Schema mySchema; + valijson::SchemaParser parser; + valijson::adapters::Json11Adapter mySchemaAdapter(schema_doc); + parser.populateSchema(mySchemaAdapter, mySchema); + + // 3) Load a document to validate + json11::Json target_doc; + if (!valijson::utils::loadDocument(filename, target_doc)) { + std::cerr << "Failed to load JSON file to validate : " << filename << "\n"; + return false; + } + + valijson::Validator validator; + valijson::ValidationResults results; + valijson::adapters::Json11Adapter myTargetAdapter(target_doc); + if (!validator.validate(mySchema, myTargetAdapter, &results)) { + std::cerr << "Validation failed.\n"; + + 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 false; + } + + std::cout << "Valid glTF file!\n"; + + return true; +} + +int main(int argc, char *argv[]) { + if (argc != 3) usage(argv[0]); + + bool ret = validate(argv[1], argv[2]); + + return ret ? EXIT_SUCCESS : EXIT_FAILURE; +}
diff --git a/examples/validator/valijson/LICENSE b/examples/validator/valijson/LICENSE new file mode 100644 index 0000000..77d3816 --- /dev/null +++ b/examples/validator/valijson/LICENSE
@@ -0,0 +1,23 @@ +Copyright (c) 2016, Tristan Penman +Copyright (c) 2016, Akamai Technolgies, Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/examples/validator/valijson/include/compat/optional.hpp b/examples/validator/valijson/include/compat/optional.hpp new file mode 100644 index 0000000..3ba123f --- /dev/null +++ b/examples/validator/valijson/include/compat/optional.hpp
@@ -0,0 +1,1042 @@ +// 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 <utility> +# include <type_traits> +# include <initializer_list> +# include <cassert> +# include <functional> +# include <string> +# include <stdexcept> + +# 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 <typename T> + using is_trivially_destructible = std::has_trivial_destructor<T>; +# 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 <class T> + struct is_nothrow_move_constructible + { + constexpr static bool value = std::is_nothrow_constructible<T, T&&>::value; + }; + + + template <class T, class U> + struct is_assignable + { + template <class X, class Y> + constexpr static bool has_assign(...) { return false; } + + template <class X, class Y, size_t S = sizeof((std::declval<X>() = std::declval<Y>(), 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<T, U>(true); + }; + + + template <class T> + struct is_nothrow_move_assignable + { + template <class X, bool has_any_move_assign> + struct has_nothrow_move_assign { + constexpr static bool value = false; + }; + + template <class X> + struct has_nothrow_move_assign<X, true> { + constexpr static bool value = noexcept( std::declval<X&>() = std::declval<X&&>() ); + }; + + constexpr static bool value = has_nothrow_move_assign<T, is_assignable<T&, T&&>::value>::value; + }; + // end workaround + + +# endif + + + + // 20.5.4, optional for object types + template <class T> class optional; + + // 20.5.5, optional for lvalue reference types + template <class T> class optional<T&>; + + + // workaround: std utility functions aren't constexpr yet + template <class T> inline constexpr T&& constexpr_forward(typename std::remove_reference<T>::type& t) noexcept + { + return static_cast<T&&>(t); + } + + template <class T> inline constexpr T&& constexpr_forward(typename std::remove_reference<T>::type&& t) noexcept + { + static_assert(!std::is_lvalue_reference<T>::value, "!!"); + return static_cast<T&&>(t); + } + + template <class T> inline constexpr typename std::remove_reference<T>::type&& constexpr_move(T&& t) noexcept + { + return static_cast<typename std::remove_reference<T>::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 <typename T> + struct has_overloaded_addressof + { + template <class X> + constexpr static bool has_overload(...) { return false; } + + template <class X, size_t S = sizeof(std::declval<X&>().operator&()) > + constexpr static bool has_overload(bool) { return true; } + + constexpr static bool value = has_overload<T>(true); + }; + + template <typename T, TR2_OPTIONAL_REQUIRES(!has_overloaded_addressof<T>)> + constexpr T* static_addressof(T& ref) + { + return &ref; + } + + template <typename T, TR2_OPTIONAL_REQUIRES(has_overloaded_addressof<T>)> + T* static_addressof(T& ref) + { + return std::addressof(ref); + } + + + // the call to convert<A>(b) has return type A and converts b to type A iff b decltype(b) is implicitly convertible to A + template <class U> + 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 <class T> + union storage_t + { + unsigned char dummy_; + T value_; + + constexpr storage_t( trivial_init_t ) noexcept : dummy_() {}; + + template <class... Args> + constexpr storage_t( Args&&... args ) : value_(constexpr_forward<Args>(args)...) {} + + ~storage_t(){} + }; + + + template <class T> + union constexpr_storage_t + { + unsigned char dummy_; + T value_; + + constexpr constexpr_storage_t( trivial_init_t ) noexcept : dummy_() {}; + + template <class... Args> + constexpr constexpr_storage_t( Args&&... args ) : value_(constexpr_forward<Args>(args)...) {} + + ~constexpr_storage_t() = default; + }; + + + template <class T> + struct optional_base + { + bool init_; + storage_t<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 <class... Args> explicit optional_base(in_place_t, Args&&... args) + : init_(true), storage_(constexpr_forward<Args>(args)...) {} + + template <class U, class... Args, TR2_OPTIONAL_REQUIRES(is_constructible<T, std::initializer_list<U>>)> + explicit optional_base(in_place_t, std::initializer_list<U> il, Args&&... args) + : init_(true), storage_(il, std::forward<Args>(args)...) {} + + ~optional_base() { if (init_) storage_.value_.T::~T(); } + }; + + + template <class T> + struct constexpr_optional_base + { + bool init_; + constexpr_storage_t<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 <class... Args> explicit constexpr constexpr_optional_base(in_place_t, Args&&... args) + : init_(true), storage_(constexpr_forward<Args>(args)...) {} + + template <class U, class... Args, TR2_OPTIONAL_REQUIRES(is_constructible<T, std::initializer_list<U>>)> + OPTIONAL_CONSTEXPR_INIT_LIST explicit constexpr_optional_base(in_place_t, std::initializer_list<U> il, Args&&... args) + : init_(true), storage_(il, std::forward<Args>(args)...) {} + + ~constexpr_optional_base() = default; + }; + + template <class T> + using OptionalBase = typename std::conditional< + is_trivially_destructible<T>::value, + constexpr_optional_base<typename std::remove_const<T>::type>, + optional_base<typename std::remove_const<T>::type> + >::type; + + + + template <class T> + class optional : private OptionalBase<T> + { + static_assert( !std::is_same<typename std::decay<T>::type, nullopt_t>::value, "bad T" ); + static_assert( !std::is_same<typename std::decay<T>::type, in_place_t>::value, "bad T" ); + + + constexpr bool initialized() const noexcept { return OptionalBase<T>::init_; } + typename std::remove_const<T>::type* dataptr() { return std::addressof(OptionalBase<T>::storage_.value_); } + constexpr const T* dataptr() const { return detail_::static_addressof(OptionalBase<T>::storage_.value_); } + +# if OPTIONAL_HAS_THIS_RVALUE_REFS == 1 + constexpr const T& contained_val() const& { return OptionalBase<T>::storage_.value_; } +# if OPTIONAL_HAS_MOVE_ACCESSORS == 1 + OPTIONAL_MUTABLE_CONSTEXPR T&& contained_val() && { return std::move(OptionalBase<T>::storage_.value_); } + OPTIONAL_MUTABLE_CONSTEXPR T& contained_val() & { return OptionalBase<T>::storage_.value_; } +# else + T& contained_val() & { return OptionalBase<T>::storage_.value_; } + T&& contained_val() && { return std::move(OptionalBase<T>::storage_.value_); } +# endif +# else + constexpr const T& contained_val() const { return OptionalBase<T>::storage_.value_; } + T& contained_val() { return OptionalBase<T>::storage_.value_; } +# endif + + void clear() noexcept { + if (initialized()) dataptr()->T::~T(); + OptionalBase<T>::init_ = false; + } + + template <class... Args> + void initialize(Args&&... args) noexcept(noexcept(T(std::forward<Args>(args)...))) + { + assert(!OptionalBase<T>::init_); + ::new (static_cast<void*>(dataptr())) T(std::forward<Args>(args)...); + OptionalBase<T>::init_ = true; + } + + template <class U, class... Args> + void initialize(std::initializer_list<U> il, Args&&... args) noexcept(noexcept(T(il, std::forward<Args>(args)...))) + { + assert(!OptionalBase<T>::init_); + ::new (static_cast<void*>(dataptr())) T(il, std::forward<Args>(args)...); + OptionalBase<T>::init_ = true; + } + + public: + typedef T value_type; + + // 20.5.5.1, constructors + constexpr optional() noexcept : OptionalBase<T>() {}; + constexpr optional(nullopt_t) noexcept : OptionalBase<T>() {}; + + optional(const optional& rhs) + : OptionalBase<T>() + { + if (rhs.initialized()) { + ::new (static_cast<void*>(dataptr())) T(*rhs); + OptionalBase<T>::init_ = true; + } + } + + optional(optional&& rhs) noexcept(is_nothrow_move_constructible<T>::value) + : OptionalBase<T>() + { + if (rhs.initialized()) { + ::new (static_cast<void*>(dataptr())) T(std::move(*rhs)); + OptionalBase<T>::init_ = true; + } + } + + constexpr optional(const T& v) : OptionalBase<T>(v) {} + + constexpr optional(T&& v) : OptionalBase<T>(constexpr_move(v)) {} + + template <class... Args> + explicit constexpr optional(in_place_t, Args&&... args) + : OptionalBase<T>(in_place_t{}, constexpr_forward<Args>(args)...) {} + + template <class U, class... Args, TR2_OPTIONAL_REQUIRES(is_constructible<T, std::initializer_list<U>>)> + OPTIONAL_CONSTEXPR_INIT_LIST explicit optional(in_place_t, std::initializer_list<U> il, Args&&... args) + : OptionalBase<T>(in_place_t{}, il, constexpr_forward<Args>(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<T>::value && is_nothrow_move_constructible<T>::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 <class U> + auto operator=(U&& v) + -> typename enable_if + < + is_same<typename decay<U>::type, T>::value, + optional& + >::type + { + if (initialized()) { contained_val() = std::forward<U>(v); } + else { initialize(std::forward<U>(v)); } + return *this; + } + + + template <class... Args> + void emplace(Args&&... args) + { + clear(); + initialize(std::forward<Args>(args)...); + } + + template <class U, class... Args> + void emplace(initializer_list<U> il, Args&&... args) + { + clear(); + initialize<U, Args...>(il, std::forward<Args>(args)...); + } + + // 20.5.4.4, Swap + void swap(optional<T>& rhs) noexcept(is_nothrow_move_constructible<T>::value && noexcept(swap(declval<T&>(), declval<T&>()))) + { + 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() : (throw bad_optional_access("bad optional access"), contained_val()); + } + + OPTIONAL_MUTABLE_CONSTEXPR T& value() & { + return initialized() ? contained_val() : (throw bad_optional_access("bad optional access"), contained_val()); + } + + OPTIONAL_MUTABLE_CONSTEXPR T&& value() && { + if (!initialized()) throw bad_optional_access("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() : (throw bad_optional_access("bad optional access"), contained_val()); + } + + T& value() { + return initialized() ? contained_val() : (throw bad_optional_access("bad optional access"), contained_val()); + } + +# endif + +# if OPTIONAL_HAS_THIS_RVALUE_REFS == 1 + + template <class V> + constexpr T value_or(V&& v) const& + { + return *this ? **this : detail_::convert<T>(constexpr_forward<V>(v)); + } + +# if OPTIONAL_HAS_MOVE_ACCESSORS == 1 + + template <class V> + OPTIONAL_MUTABLE_CONSTEXPR T value_or(V&& v) && + { + return *this ? constexpr_move(const_cast<optional<T>&>(*this).contained_val()) : detail_::convert<T>(constexpr_forward<V>(v)); + } + +# else + + template <class V> + T value_or(V&& v) && + { + return *this ? constexpr_move(const_cast<optional<T>&>(*this).contained_val()) : detail_::convert<T>(constexpr_forward<V>(v)); + } + +# endif + +# else + + template <class V> + constexpr T value_or(V&& v) const + { + return *this ? **this : detail_::convert<T>(constexpr_forward<V>(v)); + } + +# endif + + }; + + + template <class T> + class optional<T&> + { + static_assert( !std::is_same<T, nullopt_t>::value, "bad T" ); + static_assert( !std::is_same<T, in_place_t>::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 <typename U> + auto operator=(U&& rhs) noexcept + -> typename enable_if + < + is_same<typename decay<U>::type, optional<T&>>::value, + optional& + >::type + { + ref = rhs.ref; + return *this; + } + + template <typename U> + auto operator=(U&& rhs) noexcept + -> typename enable_if + < + !is_same<typename decay<U>::type, optional<T&>>::value, + optional& + >::type + = delete; + + void emplace(T& v) noexcept { + ref = detail_::static_addressof(v); + } + + void emplace(T&&) = delete; + + + void swap(optional<T&>& 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 : (throw bad_optional_access("bad optional access"), *ref); + } + + explicit constexpr operator bool() const noexcept { + return ref != nullptr; + } + + template <class V> + constexpr typename decay<T>::type value_or(V&& v) const + { + return *this ? **this : detail_::convert<typename decay<T>::type>(constexpr_forward<V>(v)); + } + }; + + + template <class T> + class optional<T&&> + { + static_assert( sizeof(T) == 0, "optional rvalue references disallowed" ); + }; + + + // 20.5.8, Relational operators + template <class T> constexpr bool operator==(const optional<T>& x, const optional<T>& y) + { + return bool(x) != bool(y) ? false : bool(x) == false ? true : *x == *y; + } + + template <class T> constexpr bool operator!=(const optional<T>& x, const optional<T>& y) + { + return !(x == y); + } + + template <class T> constexpr bool operator<(const optional<T>& x, const optional<T>& y) + { + return (!y) ? false : (!x) ? true : *x < *y; + } + + template <class T> constexpr bool operator>(const optional<T>& x, const optional<T>& y) + { + return (y < x); + } + + template <class T> constexpr bool operator<=(const optional<T>& x, const optional<T>& y) + { + return !(y < x); + } + + template <class T> constexpr bool operator>=(const optional<T>& x, const optional<T>& y) + { + return !(x < y); + } + + + // 20.5.9, Comparison with nullopt + template <class T> constexpr bool operator==(const optional<T>& x, nullopt_t) noexcept + { + return (!x); + } + + template <class T> constexpr bool operator==(nullopt_t, const optional<T>& x) noexcept + { + return (!x); + } + + template <class T> constexpr bool operator!=(const optional<T>& x, nullopt_t) noexcept + { + return bool(x); + } + + template <class T> constexpr bool operator!=(nullopt_t, const optional<T>& x) noexcept + { + return bool(x); + } + + template <class T> constexpr bool operator<(const optional<T>&, nullopt_t) noexcept + { + return false; + } + + template <class T> constexpr bool operator<(nullopt_t, const optional<T>& x) noexcept + { + return bool(x); + } + + template <class T> constexpr bool operator<=(const optional<T>& x, nullopt_t) noexcept + { + return (!x); + } + + template <class T> constexpr bool operator<=(nullopt_t, const optional<T>&) noexcept + { + return true; + } + + template <class T> constexpr bool operator>(const optional<T>& x, nullopt_t) noexcept + { + return bool(x); + } + + template <class T> constexpr bool operator>(nullopt_t, const optional<T>&) noexcept + { + return false; + } + + template <class T> constexpr bool operator>=(const optional<T>&, nullopt_t) noexcept + { + return true; + } + + template <class T> constexpr bool operator>=(nullopt_t, const optional<T>& x) noexcept + { + return (!x); + } + + + + // 20.5.10, Comparison with T + template <class T> constexpr bool operator==(const optional<T>& x, const T& v) + { + return bool(x) ? *x == v : false; + } + + template <class T> constexpr bool operator==(const T& v, const optional<T>& x) + { + return bool(x) ? v == *x : false; + } + + template <class T> constexpr bool operator!=(const optional<T>& x, const T& v) + { + return bool(x) ? *x != v : true; + } + + template <class T> constexpr bool operator!=(const T& v, const optional<T>& x) + { + return bool(x) ? v != *x : true; + } + + template <class T> constexpr bool operator<(const optional<T>& x, const T& v) + { + return bool(x) ? *x < v : true; + } + + template <class T> constexpr bool operator>(const T& v, const optional<T>& x) + { + return bool(x) ? v > *x : true; + } + + template <class T> constexpr bool operator>(const optional<T>& x, const T& v) + { + return bool(x) ? *x > v : false; + } + + template <class T> constexpr bool operator<(const T& v, const optional<T>& x) + { + return bool(x) ? v < *x : false; + } + + template <class T> constexpr bool operator>=(const optional<T>& x, const T& v) + { + return bool(x) ? *x >= v : false; + } + + template <class T> constexpr bool operator<=(const T& v, const optional<T>& x) + { + return bool(x) ? v <= *x : false; + } + + template <class T> constexpr bool operator<=(const optional<T>& x, const T& v) + { + return bool(x) ? *x <= v : true; + } + + template <class T> constexpr bool operator>=(const T& v, const optional<T>& x) + { + return bool(x) ? v >= *x : true; + } + + + // Comparison of optional<T&> with T + template <class T> constexpr bool operator==(const optional<T&>& x, const T& v) + { + return bool(x) ? *x == v : false; + } + + template <class T> constexpr bool operator==(const T& v, const optional<T&>& x) + { + return bool(x) ? v == *x : false; + } + + template <class T> constexpr bool operator!=(const optional<T&>& x, const T& v) + { + return bool(x) ? *x != v : true; + } + + template <class T> constexpr bool operator!=(const T& v, const optional<T&>& x) + { + return bool(x) ? v != *x : true; + } + + template <class T> constexpr bool operator<(const optional<T&>& x, const T& v) + { + return bool(x) ? *x < v : true; + } + + template <class T> constexpr bool operator>(const T& v, const optional<T&>& x) + { + return bool(x) ? v > *x : true; + } + + template <class T> constexpr bool operator>(const optional<T&>& x, const T& v) + { + return bool(x) ? *x > v : false; + } + + template <class T> constexpr bool operator<(const T& v, const optional<T&>& x) + { + return bool(x) ? v < *x : false; + } + + template <class T> constexpr bool operator>=(const optional<T&>& x, const T& v) + { + return bool(x) ? *x >= v : false; + } + + template <class T> constexpr bool operator<=(const T& v, const optional<T&>& x) + { + return bool(x) ? v <= *x : false; + } + + template <class T> constexpr bool operator<=(const optional<T&>& x, const T& v) + { + return bool(x) ? *x <= v : true; + } + + template <class T> constexpr bool operator>=(const T& v, const optional<T&>& x) + { + return bool(x) ? v >= *x : true; + } + + // Comparison of optional<T const&> with T + template <class T> constexpr bool operator==(const optional<const T&>& x, const T& v) + { + return bool(x) ? *x == v : false; + } + + template <class T> constexpr bool operator==(const T& v, const optional<const T&>& x) + { + return bool(x) ? v == *x : false; + } + + template <class T> constexpr bool operator!=(const optional<const T&>& x, const T& v) + { + return bool(x) ? *x != v : true; + } + + template <class T> constexpr bool operator!=(const T& v, const optional<const T&>& x) + { + return bool(x) ? v != *x : true; + } + + template <class T> constexpr bool operator<(const optional<const T&>& x, const T& v) + { + return bool(x) ? *x < v : true; + } + + template <class T> constexpr bool operator>(const T& v, const optional<const T&>& x) + { + return bool(x) ? v > *x : true; + } + + template <class T> constexpr bool operator>(const optional<const T&>& x, const T& v) + { + return bool(x) ? *x > v : false; + } + + template <class T> constexpr bool operator<(const T& v, const optional<const T&>& x) + { + return bool(x) ? v < *x : false; + } + + template <class T> constexpr bool operator>=(const optional<const T&>& x, const T& v) + { + return bool(x) ? *x >= v : false; + } + + template <class T> constexpr bool operator<=(const T& v, const optional<const T&>& x) + { + return bool(x) ? v <= *x : false; + } + + template <class T> constexpr bool operator<=(const optional<const T&>& x, const T& v) + { + return bool(x) ? *x <= v : true; + } + + template <class T> constexpr bool operator>=(const T& v, const optional<const T&>& x) + { + return bool(x) ? v >= *x : true; + } + + + // 20.5.12, Specialized algorithms + template <class T> + void swap(optional<T>& x, optional<T>& y) noexcept(noexcept(x.swap(y))) + { + x.swap(y); + } + + + template <class T> + constexpr optional<typename decay<T>::type> make_optional(T&& v) + { + return optional<typename decay<T>::type>(constexpr_forward<T>(v)); + } + + template <class X> + constexpr optional<X&> make_optional(reference_wrapper<X> v) + { + return optional<X&>(v.get()); + } + + + } // namespace experimental +} // namespace std + +namespace std +{ + template <typename T> + struct hash<std::experimental::optional<T>> + { + typedef typename hash<T>::result_type result_type; + typedef std::experimental::optional<T> argument_type; + + constexpr result_type operator()(argument_type const& arg) const { + return arg ? std::hash<T>{}(*arg) : result_type{}; + } + }; + + template <typename T> + struct hash<std::experimental::optional<T&>> + { + typedef typename hash<T>::result_type result_type; + typedef std::experimental::optional<T&> argument_type; + + constexpr result_type operator()(argument_type const& arg) const { + return arg ? std::hash<T>{}(*arg) : result_type{}; + } + }; +} + +# undef TR2_OPTIONAL_REQUIRES +# undef TR2_OPTIONAL_ASSERTED_EXPRESSION + +# endif //___OPTIONAL_HPP___
diff --git a/examples/validator/valijson/include/valijson/adapters/adapter.hpp b/examples/validator/valijson/include/valijson/adapters/adapter.hpp new file mode 100644 index 0000000..fba50dd --- /dev/null +++ b/examples/validator/valijson/include/valijson/adapters/adapter.hpp
@@ -0,0 +1,476 @@ +#pragma once +#ifndef __VALIJSON_ADAPTERS_ADAPTER_HPP +#define __VALIJSON_ADAPTERS_ADAPTER_HPP + +#include <functional> + +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<bool (const Adapter &)> + ArrayValueCallback; + + /// Typedef for callback function supplied to applyToObject. + typedef std::function<bool (const std::string &, const Adapter &)> + ObjectMemberCallback; + + /** + * @brief Virtual destructor defined to ensure deletion via base-class + * pointers is safe. + */ + virtual ~Adapter() { }; + + /** + * @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<typename T> +struct AdapterTraits +{ + +}; + +} // namespace adapters +} // namespace valijson + +#endif
diff --git a/examples/validator/valijson/include/valijson/adapters/basic_adapter.hpp b/examples/validator/valijson/include/valijson/adapters/basic_adapter.hpp new file mode 100644 index 0000000..28b188c --- /dev/null +++ b/examples/validator/valijson/include/valijson/adapters/basic_adapter.hpp
@@ -0,0 +1,868 @@ +#pragma once +#ifndef __VALIJSON_ADAPTERS_BASIC_ADAPTER_HPP +#define __VALIJSON_ADAPTERS_BASIC_ADAPTER_HPP + +#include <stdint.h> +#include <sstream> + +#include <valijson/adapters/adapter.hpp> +#include <valijson/internal/optional.hpp> + +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<class Value> +struct DerefProxy +{ + explicit DerefProxy(const Value& x) + : m_ref(x) { } + + Value* operator->() + { + return std::addressof(m_ref); + } + + 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) + : itr(array.begin()), + end(array.end()), + 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 (itr == end) { + return false; + } + + return AdapterType(*itr++).equalTo(adapter, strict); + } + + private: + + /// Iterator for current element in the array + typename ArrayType::const_iterator itr; + + /// Iterator for one-past the last element of the array + typename ArrayType::const_iterator end; + + /// Flag to use strict type comparison + const bool 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) + : object(object), + 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 = object.find(key); + if (itr == object.end()) { + return false; + } + + return (*itr).second.equalTo(value, strict); + } + + private: + + /// Object to be used as a comparison baseline + const ObjectType &object; + + /// Flag to use strict type-checking + bool 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() { } + + /** + * @brief Construct an Adapter using a specified ValueType object. + * + * This constructor relies on the copy constructor of the ValueType + * class provided as template argument. + */ + BasicAdapter(const ValueType &value) + : value(value) { } + + virtual bool applyToArray(ArrayValueCallback fn) const + { + 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 (value.isArray()) { + const opt::optional<Array> array = value.getArrayOptional(); + for (const AdapterType element : *array) { + if (!fn(element)) { + return false; + } + } + } + + return true; + } + + virtual bool applyToObject(ObjectMemberCallback fn) const + { + if (!maybeObject()) { + return false; + } + + if (value.isObject()) { + const opt::optional<Object> object = 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 (value.isArray()) { + return *value.getArrayOptional(); + } else if (value.isObject()) { + size_t objectSize; + if (value.getObjectSize(objectSize) && objectSize == 0) { + return ArrayType(); + } + } else if (value.isString()) { + std::string stringValue; + if (value.getString(stringValue) && stringValue.empty()) { + return ArrayType(); + } + } + + throw std::runtime_error("JSON value cannot be cast to an array."); + } + + virtual bool asBool() const + { + bool result; + if (asBool(result)) { + return result; + } + + throw std::runtime_error("JSON value cannot be cast to a boolean."); + } + + virtual bool asBool(bool &result) const + { + if (value.isBool()) { + return value.getBool(result); + } else if (value.isString()) { + std::string s; + if (value.getString(s)) { + if (s.compare("true") == 0) { + result = true; + return true; + } else if (s.compare("false") == 0) { + result = false; + return true; + } + } + } + + return false; + } + + virtual double asDouble() const + { + double result; + if (asDouble(result)) { + return result; + } + + throw std::runtime_error("JSON value cannot be cast to a double."); + } + + virtual bool asDouble(double &result) const + { + if (value.isDouble()) { + return value.getDouble(result); + } else if (value.isInteger()) { + int64_t i; + if (value.getInteger(i)) { + result = double(i); + return true; + } + } else if (value.isString()) { + std::string s; + if (value.getString(s)) { + const char *b = s.c_str(); + char *e = NULL; + double x = strtod(b, &e); + if (e == b || e != b + s.length()) { + return false; + } + result = x; + return true; + } + } + + return false; + } + + virtual int64_t asInteger() const + { + int64_t result; + if (asInteger(result)) { + return result; + } + + throw std::runtime_error("JSON value cannot be cast as an integer."); + } + + virtual bool asInteger(int64_t &result) const + { + if (value.isInteger()) { + return value.getInteger(result); + } else if (value.isString()) { + std::string s; + if (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 (value.isObject()) { + return *value.getObjectOptional(); + } else if (value.isArray()) { + size_t arraySize; + if (value.getArraySize(arraySize) && arraySize == 0) { + return ObjectType(); + } + } else if (value.isString()) { + std::string stringValue; + if (value.getString(stringValue) && stringValue.empty()) { + return ObjectType(); + } + } + + throw std::runtime_error("JSON value cannot be cast to an object."); + } + + virtual std::string asString() const + { + std::string result; + if (asString(result)) { + return result; + } + + throw std::runtime_error("JSON value cannot be cast to a string."); + } + + virtual bool asString(std::string &result) const + { + if (value.isString()) { + return value.getString(result); + } else if (value.isNull()) { + result.clear(); + return true; + } else if (value.isArray()) { + size_t arraySize; + if (value.getArraySize(arraySize) && arraySize == 0) { + result.clear(); + return true; + } + } else if (value.isObject()) { + size_t objectSize; + if (value.getObjectSize(objectSize) && objectSize == 0) { + result.clear(); + return true; + } + } else if (value.isBool()) { + bool boolValue; + if (value.getBool(boolValue)) { + result = boolValue ? "true" : "false"; + return true; + } + } else if (value.isInteger()) { + int64_t integerValue; + if (value.getInteger(integerValue)) { + result = std::to_string(integerValue); + return true; + } + } else if (value.isDouble()) { + double doubleValue; + if (value.getDouble(doubleValue)) { + result = std::to_string(doubleValue); + return true; + } + } + + return false; + } + + virtual bool equalTo(const Adapter &other, bool strict) const + { + 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<ArrayType> array = 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<ObjectType> object = 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<ArrayType> arrayValue = value.getArrayOptional(); + if (arrayValue) { + return *arrayValue; + } + + throw std::runtime_error("JSON value is not an array."); + } + + virtual size_t getArraySize() const + { + size_t result; + if (value.getArraySize(result)) { + return result; + } + + throw std::runtime_error("JSON value is not an array."); + } + + virtual bool getArraySize(size_t &result) const + { + return value.getArraySize(result); + } + + virtual bool getBool() const + { + bool result; + if (getBool(result)) { + return result; + } + + throw std::runtime_error("JSON value is not a boolean."); + } + + virtual bool getBool(bool &result) const + { + return value.getBool(result); + } + + virtual double getDouble() const + { + double result; + if (getDouble(result)) { + return result; + } + + throw std::runtime_error("JSON value is not a double."); + } + + virtual bool getDouble(double &result) const + { + return value.getDouble(result); + } + + virtual int64_t getInteger() const + { + int64_t result; + if (getInteger(result)) { + return result; + } + + throw std::runtime_error("JSON value is not an integer."); + } + + virtual bool getInteger(int64_t &result) const + { + return value.getInteger(result); + } + + virtual double getNumber() const + { + double result; + if (getNumber(result)) { + return result; + } + + throw std::runtime_error("JSON value is not a number."); + } + + virtual bool getNumber(double &result) const + { + if (isDouble()) { + return getDouble(result); + } else if (isInteger()) { + int64_t integerResult; + if (getInteger(integerResult)) { + result = static_cast<double>(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<ObjectType> objectValue = value.getObjectOptional(); + if (objectValue) { + return *objectValue; + } + + throw std::runtime_error("JSON value is not an object."); + } + + virtual size_t getObjectSize() const + { + size_t result; + if (getObjectSize(result)) { + return result; + } + + throw std::runtime_error("JSON value is not an object."); + } + + virtual bool getObjectSize(size_t &result) const + { + return value.getObjectSize(result); + } + + virtual std::string getString() const + { + std::string result; + if (getString(result)) { + return result; + } + + throw std::runtime_error("JSON value is not a string."); + } + + virtual bool getString(std::string &result) const + { + return value.getString(result); + } + + virtual FrozenValue * freeze() const + { + return value.freeze(); + } + + virtual bool hasStrictTypes() const + { + return ValueType::hasStrictTypes(); + } + + virtual bool isArray() const + { + return value.isArray(); + } + + virtual bool isBool() const + { + return value.isBool(); + } + + virtual bool isDouble() const + { + return value.isDouble(); + } + + virtual bool isInteger() const + { + return value.isInteger(); + } + + virtual bool isNull() const + { + return value.isNull(); + } + + virtual bool isNumber() const + { + return value.isInteger() || value.isDouble(); + } + + virtual bool isObject() const + { + return value.isObject(); + } + + virtual bool isString() const + { + return value.isString(); + } + + virtual bool maybeArray() const + { + if (value.isArray()) { + return true; + } else if (value.isObject()) { + size_t objectSize; + if (value.getObjectSize(objectSize) && objectSize == 0) { + return true; + } + } + + return false; + } + + virtual bool maybeBool() const + { + if (value.isBool()) { + return true; + } else if (value.isString()) { + std::string stringValue; + if (value.getString(stringValue)) { + if (stringValue.compare("true") == 0 || stringValue.compare("false") == 0) { + return true; + } + } + } + + return false; + } + + virtual bool maybeDouble() const + { + if (value.isNumber()) { + return true; + } else if (value.isString()) { + std::string s; + if (value.getString(s)) { + const char *b = s.c_str(); + char *e = NULL; + strtod(b, &e); + return e != b && e == b + s.length(); + } + } + + return false; + } + + virtual bool maybeInteger() const + { + if (value.isInteger()) { + return true; + } else if (value.isString()) { + std::string s; + if (value.getString(s)) { + std::istringstream i(s); + int64_t x; + char c; + if (!(i >> x) || i.get(c)) { + return false; + } + return true; + } + } + + return false; + } + + virtual bool maybeNull() const + { + if (value.isNull()) { + return true; + } else if (value.isString()) { + std::string stringValue; + if (value.getString(stringValue)) { + if (stringValue.empty()) { + return true; + } + } + } + + return false; + } + + virtual bool maybeObject() const + { + if (value.isObject()) { + return true; + } else if (value.isArray()) { + size_t arraySize; + if (value.getArraySize(arraySize) && arraySize == 0) { + return true; + } + } + + return false; + } + + virtual bool maybeString() const + { + if (value.isString() || value.isBool() || value.isInteger() || + value.isDouble()) { + return true; + } else if (value.isObject()) { + size_t objectSize; + if (value.getObjectSize(objectSize) && objectSize == 0) { + return true; + } + } else if (value.isArray()) { + size_t arraySize; + if (value.getArraySize(arraySize) && arraySize == 0) { + return true; + } + } + + return false; + } + +private: + + const ValueType value; + +}; + +} // namespace adapters +} // namespace valijson + +#endif
diff --git a/examples/validator/valijson/include/valijson/adapters/frozen_value.hpp b/examples/validator/valijson/include/valijson/adapters/frozen_value.hpp new file mode 100644 index 0000000..1b6aa11 --- /dev/null +++ b/examples/validator/valijson/include/valijson/adapters/frozen_value.hpp
@@ -0,0 +1,56 @@ +#pragma once +#ifndef __VALIJSON_ADAPTERS_FROZEN_VALUE_HPP +#define __VALIJSON_ADAPTERS_FROZEN_VALUE_HPP + +#include <valijson/adapters/adapter.hpp> + +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 + +#endif
diff --git a/examples/validator/valijson/include/valijson/adapters/json11_adapter.hpp b/examples/validator/valijson/include/valijson/adapters/json11_adapter.hpp new file mode 100644 index 0000000..d913679 --- /dev/null +++ b/examples/validator/valijson/include/valijson/adapters/json11_adapter.hpp
@@ -0,0 +1,713 @@ +/** + * @file + * + * @brief Adapter implementation for the json11 parser library. + * + * Include this file in your program to enable support for json11. + * + * This file defines the following classes (not in this order): + * - Json11Adapter + * - Json11Array + * - Json11ArrayValueIterator + * - Json11FrozenValue + * - Json11Object + * - Json11ObjectMember + * - Json11ObjectMemberIterator + * - Json11Value + * + * 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 Json11Adapter. 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 +#ifndef __VALIJSON_ADAPTERS_JSON11_ADAPTER_HPP +#define __VALIJSON_ADAPTERS_JSON11_ADAPTER_HPP + +#include <string> +#include <json11.hpp> + +#include <valijson/adapters/adapter.hpp> +#include <valijson/adapters/basic_adapter.hpp> +#include <valijson/adapters/frozen_value.hpp> + +namespace valijson { +namespace adapters { + +class Json11Adapter; +class Json11ArrayValueIterator; +class Json11ObjectMemberIterator; + +typedef std::pair<std::string, Json11Adapter> Json11ObjectMember; + +/** + * @brief Light weight wrapper for a Json11 array value. + * + * This class is light weight wrapper for a Json11 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 + * Json11 value, assumed to be an array, so there is very little overhead + * associated with copy construction and passing by value. + */ +class Json11Array +{ +public: + + typedef Json11ArrayValueIterator const_iterator; + typedef Json11ArrayValueIterator iterator; + + /// Construct a Json11Array referencing an empty array. + Json11Array() + : value(emptyArray()) { } + + /** + * @brief Construct a Json11Array referencing a specific Json11 + * value. + * + * @param value reference to a Json11 value + * + * Note that this constructor will throw an exception if the value is not + * an array. + */ + Json11Array(const json11::Json &value) + : value(value) + { + if (!value.is_array()) { + throw std::runtime_error("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 Json11 implementation. + */ + Json11ArrayValueIterator 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 Json11 implementation. + */ + Json11ArrayValueIterator end() const; + + /// Return the number of elements in the array + size_t size() const + { + return value.array_items().size(); + } + +private: + + /** + * @brief Return a reference to a Json11 value that is an empty array. + * + * Note that the value returned by this function is a singleton. + */ + static const json11::Json & emptyArray() + { + static const json11::Json array((json11::Json::array())); + return array; + } + + /// Reference to the contained value + const json11::Json &value; +}; + +/** + * @brief Light weight wrapper for a Json11 object. + * + * This class is light weight wrapper for a Json11 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 + * Json11 value, assumed to be an object, so there is very little overhead + * associated with copy construction and passing by value. + */ +class Json11Object +{ +public: + + typedef Json11ObjectMemberIterator const_iterator; + typedef Json11ObjectMemberIterator iterator; + + /// Construct a Json11Object referencing an empty object singleton. + Json11Object() + : value(emptyObject()) { } + + /** + * @brief Construct a Json11Object referencing a specific Json11 + * value. + * + * @param value reference to a Json11 value + * + * Note that this constructor will throw an exception if the value is not + * an object. + */ + Json11Object(const json11::Json &value) + : value(value) + { + if (!value.is_object()) { + throw std::runtime_error("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 Json11 implementation. + */ + Json11ObjectMemberIterator 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 Json11 implementation. + */ + Json11ObjectMemberIterator 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 + */ + Json11ObjectMemberIterator find(const std::string &propertyName) const; + + /// Returns the number of members belonging to this object. + size_t size() const + { + return value.object_items().size(); + } + +private: + + /** + * @brief Return a reference to a Json11 value that is empty object. + * + * Note that the value returned by this function is a singleton. + */ + static const json11::Json & emptyObject() + { + static const json11::Json object((json11::Json::object())); + return object; + } + + /// Reference to the contained object + const json11::Json &value; +}; + +/** + * @brief Stores an independent copy of a Json11 value. + * + * This class allows a Json11 value to be stored independent of its original + * document. Json11 makes this easy to do, as it does not perform any + * custom memory management. + * + * @see FrozenValue + */ +class Json11FrozenValue: public FrozenValue +{ +public: + + /** + * @brief Make a copy of a Json11 value + * + * @param source the Json11 value to be copied + */ + explicit Json11FrozenValue(const json11::Json &source) + : value(source) { } + + virtual FrozenValue * clone() const + { + return new Json11FrozenValue(value); + } + + virtual bool equalTo(const Adapter &other, bool strict) const; + +private: + + /// Stored Json11 value + json11::Json value; +}; + +/** + * @brief Light weight wrapper for a Json11 value. + * + * This class is passed as an argument to the BasicAdapter template class, + * and is used to provide access to a Json11 value. This class is responsible + * for the mechanics of actually reading a Json11 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 Json11Value +{ +public: + + /// Construct a wrapper for the empty object singleton + Json11Value() + : value(emptyObject()) { } + + /// Construct a wrapper for a specific Json11 value + Json11Value(const json11::Json &value) + : value(value) { } + + /** + * @brief Create a new Json11FrozenValue instance that contains the + * value referenced by this Json11Value instance. + * + * @returns pointer to a new Json11FrozenValue instance, belonging to the + * caller. + */ + FrozenValue * freeze() const + { + return new Json11FrozenValue(value); + } + + /** + * @brief Optionally return a Json11Array instance. + * + * If the referenced Json11 value is an array, this function will return + * a std::optional containing a Json11Array instance referencing the + * array. + * + * Otherwise it will return an empty optional. + */ + opt::optional<Json11Array> getArrayOptional() const + { + if (value.is_array()) { + return opt::make_optional(Json11Array(value)); + } + + return opt::optional<Json11Array>(); + } + + /** + * @brief Retrieve the number of elements in the array + * + * If the referenced Json11 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 (value.is_array()) { + result = value.array_items().size(); + return true; + } + + return false; + } + + bool getBool(bool &result) const + { + if (value.is_bool()) { + result = value.bool_value(); + return true; + } + + return false; + } + + bool getDouble(double &result) const + { + if (value.is_number()) { + result = value.number_value(); + return true; + } + + return false; + } + + bool getInteger(int64_t &result) const + { + if(isInteger()) { + result = value.int_value(); + return true; + } + return false; + } + + /** + * @brief Optionally return a Json11Object instance. + * + * If the referenced Json11 value is an object, this function will return a + * std::optional containing a Json11Object instance referencing the + * object. + * + * Otherwise it will return an empty optional. + */ + opt::optional<Json11Object> getObjectOptional() const + { + if (value.is_object()) { + return opt::make_optional(Json11Object(value)); + } + + return opt::optional<Json11Object>(); + } + + /** + * @brief Retrieve the number of members in the object + * + * If the referenced Json11 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 (value.is_object()) { + result = value.object_items().size(); + return true; + } + + return false; + } + + bool getString(std::string &result) const + { + if (value.is_string()) { + result = value.string_value(); + return true; + } + + return false; + } + + static bool hasStrictTypes() + { + return true; + } + + bool isArray() const + { + return value.is_array(); + } + + bool isBool() const + { + return value.is_bool(); + } + + bool isDouble() const + { + return value.is_number(); + } + + bool isInteger() const + { + return value.is_number() + && value.int_value() == value.number_value(); + } + + bool isNull() const + { + return value.is_null(); + } + + bool isNumber() const + { + return value.is_number(); + } + + bool isObject() const + { + return value.is_object(); + } + + bool isString() const + { + return value.is_string(); + } + +private: + + /// Return a reference to an empty object singleton + static const json11::Json & emptyObject() + { + static const json11::Json object((json11::Json::object())); + return object; + } + + /// Reference to the contained Json11 value. + const json11::Json &value; +}; + +/** + * @brief An implementation of the Adapter interface supporting Json11. + * + * 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 Json11Adapter: + public BasicAdapter<Json11Adapter, + Json11Array, + Json11ObjectMember, + Json11Object, + Json11Value> +{ +public: + + /// Construct a Json11Adapter that contains an empty object + Json11Adapter() + : BasicAdapter() { } + + /// Construct a Json11Adapter containing a specific Json11 value + Json11Adapter(const json11::Json &value) + : BasicAdapter(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 + * Json11Adapter representing a value stored in the array. It has been + * implemented using the boost iterator_facade template. + * + * @see Json11Array + */ +class Json11ArrayValueIterator: + public std::iterator< + std::bidirectional_iterator_tag, // bi-directional iterator + Json11Adapter> // value type +{ +public: + + /** + * @brief Construct a new Json11ArrayValueIterator using an existing + * Json11 iterator. + * + * @param itr Json11 iterator to store + */ + Json11ArrayValueIterator( + const json11::Json::array::const_iterator &itr) + : itr(itr) { } + + /// Returns a Json11Adapter that contains the value of the current + /// element. + Json11Adapter operator*() const + { + return Json11Adapter(*itr); + } + + DerefProxy<Json11Adapter> operator->() const + { + return DerefProxy<Json11Adapter>(**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 Json11ArrayValueIterator &other) const + { + return itr == other.itr; + } + + bool operator!=(const Json11ArrayValueIterator &other) const + { + return !(itr == other.itr); + } + + const Json11ArrayValueIterator& operator++() + { + itr++; + + return *this; + } + + Json11ArrayValueIterator operator++(int) + { + Json11ArrayValueIterator iterator_pre(itr); + ++(*this); + return iterator_pre; + } + + const Json11ArrayValueIterator& operator--() + { + itr--; + + return *this; + } + + void advance(std::ptrdiff_t n) + { + itr += n; + } + +private: + + json11::Json::array::const_iterator 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 Json11ObjectMember representing one of the members of the object. It + * has been implemented using the boost iterator_facade template. + * + * @see Json11Object + * @see Json11ObjectMember + */ +class Json11ObjectMemberIterator: + public std::iterator< + std::bidirectional_iterator_tag, // bi-directional iterator + Json11ObjectMember> // value type +{ +public: + + /** + * @brief Construct an iterator from a Json11 iterator. + * + * @param itr Json11 iterator to store + */ + Json11ObjectMemberIterator( + const json11::Json::object::const_iterator &itr) + : itr(itr) { } + + /** + * @brief Returns a Json11ObjectMember that contains the key and value + * belonging to the object member identified by the iterator. + */ + Json11ObjectMember operator*() const + { + return Json11ObjectMember(itr->first, itr->second); + } + + DerefProxy<Json11ObjectMember> operator->() const + { + return DerefProxy<Json11ObjectMember>(**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 Json11ObjectMemberIterator &other) const + { + return itr == other.itr; + } + + bool operator!=(const Json11ObjectMemberIterator &other) const + { + return !(itr == other.itr); + } + + const Json11ObjectMemberIterator& operator++() + { + itr++; + + return *this; + } + + Json11ObjectMemberIterator operator++(int) + { + Json11ObjectMemberIterator iterator_pre(itr); + ++(*this); + return iterator_pre; + } + + const Json11ObjectMemberIterator& operator--() + { + itr--; + + return *this; + } + +private: + + /// Iternal copy of the original Json11 iterator + json11::Json::object::const_iterator itr; +}; + +/// Specialisation of the AdapterTraits template struct for Json11Adapter. +template<> +struct AdapterTraits<valijson::adapters::Json11Adapter> +{ + typedef json11::Json DocumentType; + + static std::string adapterName() + { + return "Json11Adapter"; + } +}; + +inline bool Json11FrozenValue::equalTo(const Adapter &other, bool strict) const +{ + return Json11Adapter(value).equalTo(other, strict); +} + +inline Json11ArrayValueIterator Json11Array::begin() const +{ + return value.array_items().begin(); +} + +inline Json11ArrayValueIterator Json11Array::end() const +{ + return value.array_items().end(); +} + +inline Json11ObjectMemberIterator Json11Object::begin() const +{ + return value.object_items().begin(); +} + +inline Json11ObjectMemberIterator Json11Object::end() const +{ + return value.object_items().end(); +} + +inline Json11ObjectMemberIterator Json11Object::find( + const std::string &propertyName) const +{ + return value.object_items().find(propertyName); +} + +} // namespace adapters +} // namespace valijson + +#endif
diff --git a/examples/validator/valijson/include/valijson/adapters/jsoncpp_adapter.hpp b/examples/validator/valijson/include/valijson/adapters/jsoncpp_adapter.hpp new file mode 100644 index 0000000..4831f02 --- /dev/null +++ b/examples/validator/valijson/include/valijson/adapters/jsoncpp_adapter.hpp
@@ -0,0 +1,724 @@ +/** + * @file + * + * @brief Adapter implementation for the JsonCpp parser library. + * + * Include this file in your program to enable support for JsonCpp. + * + * This file defines the following classes (not in this order): + * - JsonCppAdapter + * - JsonCppArray + * - JsonCppArrayValueIterator + * - JsonCppFrozenValue + * - JsonCppObject + * - JsonCppObjectMember + * - JsonCppObjectMemberIterator + * - JsonCppValue + * + * 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 JsonCppAdapter. 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 +#ifndef __VALIJSON_ADAPTERS_JSONCPP_ADAPTER_HPP +#define __VALIJSON_ADAPTERS_JSONCPP_ADAPTER_HPP + +#include <stdint.h> +#include <string> +#include <iterator> + +#include <json/json.h> + +#include <valijson/adapters/adapter.hpp> +#include <valijson/adapters/basic_adapter.hpp> +#include <valijson/adapters/frozen_value.hpp> + +namespace valijson { +namespace adapters { + +class JsonCppAdapter; +class JsonCppArrayValueIterator; +class JsonCppObjectMemberIterator; + +typedef std::pair<std::string, JsonCppAdapter> JsonCppObjectMember; + +/** + * @brief Light weight wrapper for a JsonCpp array value. + * + * This class is light weight wrapper for a JsonCpp 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 + * JsonCpp value, assumed to be an array, so there is very little overhead + * associated with copy construction and passing by value. + */ +class JsonCppArray +{ +public: + + typedef JsonCppArrayValueIterator const_iterator; + typedef JsonCppArrayValueIterator iterator; + + /// Construct a JsonCppArray referencing an empty array. + JsonCppArray() + : value(emptyArray()) { } + + /** + * @brief Construct a JsonCppArray referencing a specific JsonCpp value. + * + * @param value reference to a JsonCpp value + * + * Note that this constructor will throw an exception if the value is not + * an array. + */ + JsonCppArray(const Json::Value &value) + : value(value) + { + if (!value.isArray()) { + throw std::runtime_error("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 JsonCpp implementation. + */ + JsonCppArrayValueIterator 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 JsonCpp implementation. + */ + JsonCppArrayValueIterator end() const; + + /// Return the number of elements in the array. + size_t size() const + { + return value.size(); + } + +private: + + /** + * @brief Return a reference to a JsonCpp value that is an empty array. + * + * Note that the value returned by this function is a singleton. + */ + static const Json::Value & emptyArray() + { + static const Json::Value array(Json::arrayValue); + return array; + } + + /// Reference to the contained array + const Json::Value &value; + +}; + +/** + * @brief Light weight wrapper for a JsonCpp object. + * + * This class is light weight wrapper for a JsonCpp 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 + * JsonCpp object, assumed to be an object, so there is very little overhead + * associated with copy construction and passing by value. + */ +class JsonCppObject +{ +public: + + typedef JsonCppObjectMemberIterator const_iterator; + typedef JsonCppObjectMemberIterator iterator; + + /// Construct a JsonCppObject referencing an empty object singleton. + JsonCppObject() + : value(emptyObject()) { } + + /** + * @brief Construct a JsonCppObject referencing a specific JsonCpp value. + * + * @param value reference to a JsonCpp value + * + * Note that this constructor will throw an exception if the value is not + * an object. + */ + JsonCppObject(const Json::Value &value) + : value(value) + { + if (!value.isObject()) { + throw std::runtime_error("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 JsonCpp implementation. + */ + JsonCppObjectMemberIterator 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 JsonCpp implementation. + */ + JsonCppObjectMemberIterator end() const; + + /** + * @brief Return an iterator for a member/property with the given name + * + * @param propertyName Property name + * + * @returns a valid iterator if found, or an invalid iterator if not found + */ + JsonCppObjectMemberIterator find(const std::string &propertyName) const; + + /// Return the number of members in the object + size_t size() const + { + return value.size(); + } + +private: + + /// Return a reference to an empty JsonCpp object + static const Json::Value & emptyObject() + { + static const Json::Value object(Json::objectValue); + return object; + } + + /// Reference to the contained object + const Json::Value &value; +}; + +/** + * @brief Stores an independent copy of a JsonCpp value. + * + * This class allows a JsonCpp value to be stored independent of its original + * document. JsonCpp makes this easy to do, as it does not perform any + * custom memory management. + * + * @see FrozenValue + */ +class JsonCppFrozenValue: public FrozenValue +{ +public: + + /** + * @brief Make a copy of a JsonCpp value + * + * @param source the JsonCpp value to be copied + */ + explicit JsonCppFrozenValue(const Json::Value &source) + : value(source) { } + + virtual FrozenValue * clone() const + { + return new JsonCppFrozenValue(value); + } + + virtual bool equalTo(const Adapter &other, bool strict) const; + +private: + + /// Stored JsonCpp value + Json::Value value; +}; + +/** + * @brief Light weight wrapper for a JsonCpp value. + * + * This class is passed as an argument to the BasicAdapter template class, + * and is used to provide access to a JsonCpp value. This class is responsible + * for the mechanics of actually reading a JsonCpp 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 JsonCppValue +{ +public: + + /// Construct a wrapper for the empty object singleton + JsonCppValue() + : value(emptyObject()) { } + + /// Construct a wrapper for a specific JsonCpp value + JsonCppValue(const Json::Value &value) + : value(value) { } + + /** + * @brief Create a new JsonCppFrozenValue instance that contains the + * value referenced by this JsonCppValue instance. + * + * @returns pointer to a new JsonCppFrozenValue instance, belonging to the + * caller. + */ + FrozenValue * freeze() const + { + return new JsonCppFrozenValue(value); + } + + /** + * @brief Optionally return a JsonCppArray instance. + * + * If the referenced JsonCpp value is an array, this function will return a + * std::optional containing a JsonCppArray instance referencing the + * array. + * + * Otherwise it will return an empty optional. + */ + opt::optional<JsonCppArray> getArrayOptional() const + { + if (value.isArray()) { + return opt::make_optional(JsonCppArray(value)); + } + + return opt::optional<JsonCppArray>(); + } + + /** + * @brief Retrieve the number of elements in the array + * + * If the referenced JsonCpp 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 (value.isArray()) { + result = value.size(); + return true; + } + + return false; + } + + bool getBool(bool &result) const + { + if (value.isBool()) { + result = value.asBool(); + return true; + } + + return false; + } + + bool getDouble(double &result) const + { + if (value.isDouble()) { + result = value.asDouble(); + return true; + } + + return false; + } + + bool getInteger(int64_t &result) const + { + if (value.isIntegral()) { + result = static_cast<int64_t>(value.asInt()); + return true; + } + + return false; + } + + /** + * @brief Optionally return a JsonCppObject instance. + * + * If the referenced JsonCpp value is an object, this function will return a + * std::optional containing a JsonCppObject instance referencing the + * object. + * + * Otherwise it will return an empty optional. + */ + opt::optional<JsonCppObject> getObjectOptional() const + { + if (value.isObject()) { + return opt::make_optional(JsonCppObject(value)); + } + + return opt::optional<JsonCppObject>(); + } + + /** + * @brief Retrieve the number of members in the object + * + * If the referenced JsonCpp 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 (value.isObject()) { + result = value.size(); + return true; + } + + return false; + } + + bool getString(std::string &result) const + { + if (value.isString()) { + result = value.asString(); + return true; + } + + return false; + } + + static bool hasStrictTypes() + { + return true; + } + + bool isArray() const + { + return value.isArray() && !value.isNull(); + } + + bool isBool() const + { + return value.isBool(); + } + + bool isDouble() const + { + return value.isDouble(); + } + + bool isInteger() const + { + return value.isIntegral() && !value.isBool(); + } + + bool isNull() const + { + return value.isNull(); + } + + bool isNumber() const + { + return value.isNumeric() && !value.isBool(); + } + + bool isObject() const + { + return value.isObject() && !value.isNull(); + } + + bool isString() const + { + return value.isString(); + } + +private: + + /// Return a reference to an empty object singleton. + static const Json::Value &emptyObject() + { + static Json::Value object(Json::objectValue); + return object; + } + + /// Reference to the contained JsonCpp value + const Json::Value &value; +}; + +/** + * @brief An implementation of the Adapter interface supporting JsonCpp. + * + * 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 JsonCppAdapter: + public BasicAdapter<JsonCppAdapter, + JsonCppArray, + JsonCppObjectMember, + JsonCppObject, + JsonCppValue> +{ +public: + + /// Construct a JsonCppAdapter that contains an empty object + JsonCppAdapter() + : BasicAdapter() { } + + /// Construct a JsonCppAdapter containing a specific JsonCpp value + JsonCppAdapter(const Json::Value &value) + : BasicAdapter(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 + * JsonCppAdapter representing a value stored in the array. It has been + * implemented using the boost iterator_facade template. + * + * @see JsonCppArray + */ +class JsonCppArrayValueIterator: + public std::iterator< + std::bidirectional_iterator_tag, // bi-directional iterator + JsonCppAdapter> // value type +{ + +public: + + /** + * @brief Construct a new JsonCppArrayValueIterator using an existing + * JsonCpp iterator. + * + * @param itr JsonCpp iterator to store + */ + JsonCppArrayValueIterator(const Json::Value::const_iterator &itr) + : itr(itr) { } + + /// Returns a JsonCppAdapter that contains the value of the current element. + JsonCppAdapter operator*() const + { + return JsonCppAdapter(*itr); + } + + DerefProxy<JsonCppAdapter> operator->() const + { + return DerefProxy<JsonCppAdapter>(**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 rhs iterator to compare against + * + * @returns true if the iterators are equal, false otherwise. + */ + bool operator==(const JsonCppArrayValueIterator &rhs) const + { + return itr == rhs.itr; + } + + bool operator!=(const JsonCppArrayValueIterator &rhs) const + { + return !(itr == rhs.itr); + } + + JsonCppArrayValueIterator& operator++() + { + itr++; + + return *this; + } + + JsonCppArrayValueIterator operator++(int) + { + JsonCppArrayValueIterator iterator_pre(itr); + ++(*this); + return iterator_pre; + } + + JsonCppArrayValueIterator& operator--() + { + itr--; + + return *this; + } + + void advance(std::ptrdiff_t n) + { + if (n > 0) { + while (n-- > 0) { + itr++; + } + } else { + while (n++ < 0) { + itr--; + } + } + } + +private: + + Json::Value::const_iterator 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 JsonCppObjectMember representing one of the members of the object. It has + * been implemented using the boost iterator_facade template. + * + * @see JsonCppObject + * @see JsonCppObjectMember + */ +class JsonCppObjectMemberIterator: + public std::iterator< + std::bidirectional_iterator_tag, // bi-directional iterator + JsonCppObjectMember> // value type +{ +public: + + /** + * @brief Construct an iterator from a JsonCpp iterator. + * + * @param itr JsonCpp iterator to store + */ + JsonCppObjectMemberIterator(const Json::ValueConstIterator &itr) + : itr(itr) { } + + /** + * @brief Returns a JsonCppObjectMember that contains the key and value + * belonging to the object member identified by the iterator. + */ + JsonCppObjectMember operator*() const + { + return JsonCppObjectMember(itr.key().asString(), *itr); + } + + DerefProxy<JsonCppObjectMember> operator->() const + { + return DerefProxy<JsonCppObjectMember>(**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 rhs Iterator to compare with + * + * @returns true if the underlying iterators are equal, false otherwise + */ + bool operator==(const JsonCppObjectMemberIterator &rhs) const + { + return itr == rhs.itr; + } + + bool operator!=(const JsonCppObjectMemberIterator &rhs) const + { + return !(itr == rhs.itr); + } + + const JsonCppObjectMemberIterator& operator++() + { + itr++; + + return *this; + } + + JsonCppObjectMemberIterator operator++(int) + { + JsonCppObjectMemberIterator iterator_pre(itr); + ++(*this); + return iterator_pre; + } + + JsonCppObjectMemberIterator operator--() + { + itr--; + + return *this; + } + +private: + + /// Iternal copy of the original JsonCpp iterator + Json::ValueConstIterator itr; +}; + +/// Specialisation of the AdapterTraits template struct for JsonCppAdapter. +template<> +struct AdapterTraits<valijson::adapters::JsonCppAdapter> +{ + typedef Json::Value DocumentType; + + static std::string adapterName() + { + return "JsonCppAdapter"; + } +}; + +inline bool JsonCppFrozenValue::equalTo(const Adapter &other, bool strict) const +{ + return JsonCppAdapter(value).equalTo(other, strict); +} + +inline JsonCppArrayValueIterator JsonCppArray::begin() const +{ + return value.begin(); +} + +inline JsonCppArrayValueIterator JsonCppArray::end() const +{ + return value.end(); +} + +inline JsonCppObjectMemberIterator JsonCppObject::begin() const +{ + return value.begin(); +} + +inline JsonCppObjectMemberIterator JsonCppObject::end() const +{ + return value.end(); +} + +inline JsonCppObjectMemberIterator JsonCppObject::find( + const std::string &propertyName) const +{ + if (value.isMember(propertyName)) { + Json::ValueConstIterator itr; + for ( itr = value.begin(); itr != value.end(); ++itr) { + if (itr.key() == propertyName) { + return itr; + } + } + } + + return value.end(); +} + +} // namespace adapters +} // namespace valijson + +#endif
diff --git a/examples/validator/valijson/include/valijson/adapters/nlohmann_json_adapter.hpp b/examples/validator/valijson/include/valijson/adapters/nlohmann_json_adapter.hpp new file mode 100644 index 0000000..f77a2bf --- /dev/null +++ b/examples/validator/valijson/include/valijson/adapters/nlohmann_json_adapter.hpp
@@ -0,0 +1,712 @@ +/** + * @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 +#ifndef __VALIJSON_ADAPTERS_NLOHMANN_JSON_ADAPTER_HPP +#define __VALIJSON_ADAPTERS_NLOHMANN_JSON_ADAPTER_HPP + +#include <string> +#include <json.hpp> + +#include <valijson/adapters/adapter.hpp> +#include <valijson/adapters/basic_adapter.hpp> +#include <valijson/adapters/frozen_value.hpp> + +namespace valijson { +namespace adapters { + +class NlohmannJsonAdapter; +class NlohmannJsonArrayValueIterator; +class NlohmannJsonObjectMemberIterator; + +typedef std::pair<std::string, NlohmannJsonAdapter> 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() + : 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) + : value(value) + { + if (!value.is_array()) { + throw std::runtime_error("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 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 &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() + : 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) + : value(value) + { + if (!value.is_object()) { + throw std::runtime_error("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 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 &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(const nlohmann::json &source) + : value(source) { } + + virtual FrozenValue * clone() const + { + return new NlohmannJsonFrozenValue(value); + } + + virtual bool equalTo(const Adapter &other, bool strict) const; + +private: + + /// Stored NlohmannJson value + nlohmann::json 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() + : value(emptyObject()) { } + + /// Construct a wrapper for a specific NlohmannJson value + NlohmannJsonValue(const nlohmann::json &value) + : 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(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<NlohmannJsonArray> getArrayOptional() const + { + if (value.is_array()) { + return opt::make_optional(NlohmannJsonArray(value)); + } + + return opt::optional<NlohmannJsonArray>(); + } + + /** + * @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 (value.is_array()) { + result = value.size(); + return true; + } + + return false; + } + + bool getBool(bool &result) const + { + if (value.is_boolean()) { + result = value.get<bool>(); + return true; + } + + return false; + } + + bool getDouble(double &result) const + { + if (value.is_number_float()) { + result = value.get<double>(); + return true; + } + + return false; + } + + bool getInteger(int64_t &result) const + { + if(value.is_number_integer()) { + result = value.get<int64_t>(); + 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<NlohmannJsonObject> getObjectOptional() const + { + if (value.is_object()) { + return opt::make_optional(NlohmannJsonObject(value)); + } + + return opt::optional<NlohmannJsonObject>(); + } + + /** + * @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 (value.is_object()) { + result = value.size(); + return true; + } + + return false; + } + + bool getString(std::string &result) const + { + if (value.is_string()) { + result = value.get<std::string>(); + return true; + } + + return false; + } + + static bool hasStrictTypes() + { + return true; + } + + bool isArray() const + { + return value.is_array(); + } + + bool isBool() const + { + return value.is_boolean(); + } + + bool isDouble() const + { + return value.is_number_float(); + } + + bool isInteger() const + { + return value.is_number_integer(); + } + + bool isNull() const + { + return value.is_null(); + } + + bool isNumber() const + { + return value.is_number(); + } + + bool isObject() const + { + return value.is_object(); + } + + bool isString() const + { + return 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 &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<NlohmannJsonAdapter, + NlohmannJsonArray, + NlohmannJsonObjectMember, + NlohmannJsonObject, + NlohmannJsonValue> +{ +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 std::iterator< + std::bidirectional_iterator_tag, // bi-directional iterator + NlohmannJsonAdapter> // value type +{ +public: + + /** + * @brief Construct a new NlohmannJsonArrayValueIterator using an existing + * NlohmannJson iterator. + * + * @param itr NlohmannJson iterator to store + */ + NlohmannJsonArrayValueIterator(const nlohmann::json::const_iterator &itr) + : itr(itr) { } + + /// Returns a NlohmannJsonAdapter that contains the value of the current + /// element. + NlohmannJsonAdapter operator*() const + { + return NlohmannJsonAdapter(*itr); + } + + DerefProxy<NlohmannJsonAdapter> operator->() const + { + return DerefProxy<NlohmannJsonAdapter>(**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 itr == other.itr; + } + + bool operator!=(const NlohmannJsonArrayValueIterator &other) const + { + return !(itr == other.itr); + } + + const NlohmannJsonArrayValueIterator& operator++() + { + itr++; + + return *this; + } + + NlohmannJsonArrayValueIterator operator++(int) + { + NlohmannJsonArrayValueIterator iterator_pre(itr); + ++(*this); + return iterator_pre; + } + + const NlohmannJsonArrayValueIterator& operator--() + { + itr--; + + return *this; + } + + void advance(std::ptrdiff_t n) + { + itr += n; + } + +private: + nlohmann::json::const_iterator 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 std::iterator< + std::bidirectional_iterator_tag, // bi-directional iterator + NlohmannJsonObjectMember> // value type +{ +public: + + /** + * @brief Construct an iterator from a NlohmannJson iterator. + * + * @param itr NlohmannJson iterator to store + */ + NlohmannJsonObjectMemberIterator(const nlohmann::json::const_iterator &itr) + : 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(itr.key(), itr.value()); + } + + DerefProxy<NlohmannJsonObjectMember> operator->() const + { + return DerefProxy<NlohmannJsonObjectMember>(**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 itr == other.itr; + } + + bool operator!=(const NlohmannJsonObjectMemberIterator &other) const + { + return !(itr == other.itr); + } + + const NlohmannJsonObjectMemberIterator& operator++() + { + itr++; + + return *this; + } + + NlohmannJsonObjectMemberIterator operator++(int) + { + NlohmannJsonObjectMemberIterator iterator_pre(itr); + ++(*this); + return iterator_pre; + } + + const NlohmannJsonObjectMemberIterator& operator--() + { + itr--; + + return *this; + } + +private: + + /// Iternal copy of the original NlohmannJson iterator + nlohmann::json::const_iterator itr; +}; + +/// Specialisation of the AdapterTraits template struct for NlohmannJsonAdapter. +template<> +struct AdapterTraits<valijson::adapters::NlohmannJsonAdapter> +{ + typedef nlohmann::json DocumentType; + + static std::string adapterName() + { + return "NlohmannJsonAdapter"; + } +}; + +inline bool NlohmannJsonFrozenValue::equalTo(const Adapter &other, bool strict) const +{ + return NlohmannJsonAdapter(value).equalTo(other, strict); +} + +inline NlohmannJsonArrayValueIterator NlohmannJsonArray::begin() const +{ + return value.begin(); +} + +inline NlohmannJsonArrayValueIterator NlohmannJsonArray::end() const +{ + return value.end(); +} + +inline NlohmannJsonObjectMemberIterator NlohmannJsonObject::begin() const +{ + return value.begin(); +} + +inline NlohmannJsonObjectMemberIterator NlohmannJsonObject::end() const +{ + return value.end(); +} + +inline NlohmannJsonObjectMemberIterator NlohmannJsonObject::find( + const std::string &propertyName) const +{ + return value.find(propertyName); +} + +} // namespace adapters +} // namespace valijson + +#endif +
diff --git a/examples/validator/valijson/include/valijson/adapters/picojson_adapter.hpp b/examples/validator/valijson/include/valijson/adapters/picojson_adapter.hpp new file mode 100644 index 0000000..b6d17c9 --- /dev/null +++ b/examples/validator/valijson/include/valijson/adapters/picojson_adapter.hpp
@@ -0,0 +1,727 @@ +/** + * @file + * + * @brief Adapter implementation for the PicoJson parser library. + * + * Include this file in your program to enable support for PicoJson. + * + * This file defines the following classes (not in this order): + * - PicoJsonAdapter + * - PicoJsonArray + * - PicoJsonArrayValueIterator + * - PicoJsonFrozenValue + * - PicoJsonObject + * - PicoJsonObjectMember + * - PicoJsonObjectMemberIterator + * - PicoJsonValue + * + * 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 PicoJsonAdapter. 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 +#ifndef __VALIJSON_ADAPTERS_PICOJSON_ADAPTER_HPP +#define __VALIJSON_ADAPTERS_PICOJSON_ADAPTER_HPP + +#include <string> + +#include <picojson.h> + +#include <valijson/adapters/adapter.hpp> +#include <valijson/adapters/basic_adapter.hpp> +#include <valijson/adapters/frozen_value.hpp> + +namespace valijson { +namespace adapters { + +class PicoJsonAdapter; +class PicoJsonArrayValueIterator; +class PicoJsonObjectMemberIterator; + +typedef std::pair<std::string, PicoJsonAdapter> PicoJsonObjectMember; + +/** + * @brief Light weight wrapper for a PicoJson array value. + * + * This class is light weight wrapper for a PicoJson 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 + * PicoJson value, assumed to be an array, so there is very little overhead + * associated with copy construction and passing by value. + */ +class PicoJsonArray +{ +public: + + typedef PicoJsonArrayValueIterator const_iterator; + typedef PicoJsonArrayValueIterator iterator; + + /// Construct a PicoJsonArray referencing an empty array. + PicoJsonArray() + : value(emptyArray()) { } + + /** + * @brief Construct a PicoJsonArray referencing a specific PicoJson + * value. + * + * @param value reference to a PicoJson value + * + * Note that this constructor will throw an exception if the value is not + * an array. + */ + explicit PicoJsonArray(const picojson::value &value) + : value(value) + { + if (!value.is<picojson::array>()) { + throw std::runtime_error("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 PicoJson implementation. + */ + PicoJsonArrayValueIterator 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 PicoJson implementation. + */ + PicoJsonArrayValueIterator end() const; + + /// Return the number of elements in the array + size_t size() const + { + const picojson::array &array = value.get<picojson::array>(); + return array.size(); + } + +private: + + /** + * @brief Return a reference to a PicoJson value that is an empty array. + * + * Note that the value returned by this function is a singleton. + */ + static const picojson::value & emptyArray() + { + static const picojson::value array(picojson::array_type, false); + return array; + } + + /// Reference to the contained value + const picojson::value &value; +}; + +/** + * @brief Light weight wrapper for a PicoJson object. + * + * This class is light weight wrapper for a PicoJson 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 + * PicoJson value, assumed to be an object, so there is very little overhead + * associated with copy construction and passing by value. + */ +class PicoJsonObject +{ +public: + + typedef PicoJsonObjectMemberIterator const_iterator; + typedef PicoJsonObjectMemberIterator iterator; + + /// Construct a PicoJsonObject referencing an empty object singleton. + PicoJsonObject() + : value(emptyObject()) { } + + /** + * @brief Construct a PicoJsonObject referencing a specific PicoJson + * value. + * + * @param value reference to a PicoJson value + * + * Note that this constructor will throw an exception if the value is not + * an object. + */ + PicoJsonObject(const picojson::value &value) + : value(value) + { + if (!value.is<picojson::object>()) { + throw std::runtime_error("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 PicoJson implementation. + */ + PicoJsonObjectMemberIterator 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 PicoJson implementation. + */ + PicoJsonObjectMemberIterator 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 + */ + PicoJsonObjectMemberIterator find(const std::string &propertyName) const; + + /// Returns the number of members belonging to this object. + size_t size() const + { + const picojson::object &object = value.get<picojson::object>(); + return object.size(); + } + +private: + + /** + * @brief Return a reference to a PicoJson value that is empty object. + * + * Note that the value returned by this function is a singleton. + */ + static const picojson::value & emptyObject() + { + static const picojson::value object(picojson::object_type, false); + return object; + } + + /// Reference to the contained object + const picojson::value &value; +}; + +/** + * @brief Stores an independent copy of a PicoJson value. + * + * This class allows a PicoJson value to be stored independent of its original + * document. PicoJson makes this easy to do, as it does not perform any + * custom memory management. + * + * @see FrozenValue + */ +class PicoJsonFrozenValue: public FrozenValue +{ +public: + + /** + * @brief Make a copy of a PicoJson value + * + * @param source the PicoJson value to be copied + */ + explicit PicoJsonFrozenValue(const picojson::value &source) + : value(source) { } + + virtual FrozenValue * clone() const + { + return new PicoJsonFrozenValue(value); + } + + virtual bool equalTo(const Adapter &other, bool strict) const; + +private: + + /// Stored PicoJson value + picojson::value value; +}; + +/** + * @brief Light weight wrapper for a PicoJson value. + * + * This class is passed as an argument to the BasicAdapter template class, + * and is used to provide access to a PicoJson value. This class is responsible + * for the mechanics of actually reading a PicoJson 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 PicoJsonValue +{ +public: + + /// Construct a wrapper for the empty object singleton + PicoJsonValue() + : value(emptyObject()) { } + + /// Construct a wrapper for a specific PicoJson value + PicoJsonValue(const picojson::value &value) + : value(value) { } + + /** + * @brief Create a new PicoJsonFrozenValue instance that contains the + * value referenced by this PicoJsonValue instance. + * + * @returns pointer to a new PicoJsonFrozenValue instance, belonging to the + * caller. + */ + FrozenValue * freeze() const + { + return new PicoJsonFrozenValue(value); + } + + /** + * @brief Optionally return a PicoJsonArray instance. + * + * If the referenced PicoJson value is an array, this function will return + * a std::optional containing a PicoJsonArray instance referencing the + * array. + * + * Otherwise it will return an empty optional. + */ + opt::optional<PicoJsonArray> getArrayOptional() const + { + if (value.is<picojson::array>()) { + return opt::make_optional(PicoJsonArray(value)); + } + + return opt::optional<PicoJsonArray>(); + } + + /** + * @brief Retrieve the number of elements in the array + * + * If the referenced PicoJson 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 (value.is<picojson::array>()) { + const picojson::array& array = value.get<picojson::array>(); + result = array.size(); + return true; + } + + return false; + } + + bool getBool(bool &result) const + { + if (value.is<bool>()) { + result = value.get<bool>(); + return true; + } + + return false; + } + + bool getDouble(double &result) const + { + if (value.is<double>()) { + result = value.get<double>(); + return true; + } + + return false; + } + + bool getInteger(int64_t &result) const + { + if (value.is<int64_t>()) { + result = value.get<int64_t>(); + return true; + } + + return false; + } + + /** + * @brief Optionally return a PicoJsonObject instance. + * + * If the referenced PicoJson value is an object, this function will return a + * std::optional containing a PicoJsonObject instance referencing the + * object. + * + * Otherwise it will return an empty optional. + */ + opt::optional<PicoJsonObject> getObjectOptional() const + { + if (value.is<picojson::object>()) { + return opt::make_optional(PicoJsonObject(value)); + } + + return opt::optional<PicoJsonObject>(); + } + + /** + * @brief Retrieve the number of members in the object + * + * If the referenced PicoJson 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 (value.is<picojson::object>()) { + const picojson::object &object = value.get<picojson::object>(); + result = object.size(); + return true; + } + + return false; + } + + bool getString(std::string &result) const + { + if (value.is<std::string>()) { + result = value.get<std::string>(); + return true; + } + + return false; + } + + static bool hasStrictTypes() + { + return true; + } + + bool isArray() const + { + return value.is<picojson::array>(); + } + + bool isBool() const + { + return value.is<bool>(); + } + + bool isDouble() const + { + if (value.is<int64_t>()) { + return false; + } + + return value.is<double>(); + } + + bool isInteger() const + { + return value.is<int64_t>(); + } + + bool isNull() const + { + return value.is<picojson::null>(); + } + + bool isNumber() const + { + return value.is<double>(); + } + + bool isObject() const + { + return value.is<picojson::object>(); + } + + bool isString() const + { + return value.is<std::string>(); + } + +private: + + /// Return a reference to an empty object singleton + static const picojson::value & emptyObject() + { + static const picojson::value object(picojson::object_type, false); + return object; + } + + /// Reference to the contained PicoJson value. + const picojson::value &value; +}; + +/** + * @brief An implementation of the Adapter interface supporting PicoJson. + * + * 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 PicoJsonAdapter: + public BasicAdapter<PicoJsonAdapter, + PicoJsonArray, + PicoJsonObjectMember, + PicoJsonObject, + PicoJsonValue> +{ +public: + + /// Construct a PicoJsonAdapter that contains an empty object + PicoJsonAdapter() + : BasicAdapter() { } + + /// Construct a PicoJsonAdapter containing a specific PicoJson value + PicoJsonAdapter(const picojson::value &value) + : BasicAdapter(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 + * PicoJsonAdapter representing a value stored in the array. It has been + * implemented using the std::iterator template. + * + * @see PicoJsonArray + */ +class PicoJsonArrayValueIterator: + public std::iterator< + std::bidirectional_iterator_tag, // bi-directional iterator + PicoJsonAdapter> // value type +{ +public: + + /** + * @brief Construct a new PicoJsonArrayValueIterator using an existing + * PicoJson iterator. + * + * @param itr PicoJson iterator to store + */ + PicoJsonArrayValueIterator( + const picojson::array::const_iterator &itr) + : itr(itr) { } + + /// Returns a PicoJsonAdapter that contains the value of the current + /// element. + PicoJsonAdapter operator*() const + { + return PicoJsonAdapter(*itr); + } + + DerefProxy<PicoJsonAdapter> operator->() const + { + return DerefProxy<PicoJsonAdapter>(**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 PicoJsonArrayValueIterator &other) const + { + return itr == other.itr; + } + + bool operator!=(const PicoJsonArrayValueIterator &other) const + { + return !(itr == other.itr); + } + + const PicoJsonArrayValueIterator& operator++() + { + itr++; + + return *this; + } + + PicoJsonArrayValueIterator operator++(int) + { + PicoJsonArrayValueIterator iterator_pre(itr); + ++(*this); + return iterator_pre; + } + + const PicoJsonArrayValueIterator& operator--() + { + itr--; + + return *this; + } + + void advance(std::ptrdiff_t n) + { + itr += n; + } + +private: + + picojson::array::const_iterator 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 PicoJsonObjectMember representing one of the members of the object. It + * has been implemented using the boost iterator_facade template. + * + * @see PicoJsonObject + * @see PicoJsonObjectMember + */ +class PicoJsonObjectMemberIterator: + public std::iterator< + std::bidirectional_iterator_tag, // bi-directional iterator + PicoJsonObjectMember> // value type +{ +public: + + /** + * @brief Construct an iterator from a PicoJson iterator. + * + * @param itr PicoJson iterator to store + */ + PicoJsonObjectMemberIterator( + const picojson::object::const_iterator &itr) + : itr(itr) { } + + /** + * @brief Returns a PicoJsonObjectMember that contains the key and value + * belonging to the object member identified by the iterator. + */ + PicoJsonObjectMember operator*() const + { + return PicoJsonObjectMember(itr->first, itr->second); + } + + DerefProxy<PicoJsonObjectMember> operator->() const + { + return DerefProxy<PicoJsonObjectMember>(**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 PicoJsonObjectMemberIterator &other) const + { + return itr == other.itr; + } + + bool operator!=(const PicoJsonObjectMemberIterator &other) const + { + return !(itr == other.itr); + } + + const PicoJsonObjectMemberIterator& operator++() + { + itr++; + + return *this; + } + + PicoJsonObjectMemberIterator operator++(int) + { + PicoJsonObjectMemberIterator iterator_pre(itr); + ++(*this); + return iterator_pre; + } + + const PicoJsonObjectMemberIterator& operator--(int) + { + itr--; + + return *this; + } + +private: + + /// Iternal copy of the original PicoJson iterator + picojson::object::const_iterator itr; +}; + +/// Specialisation of the AdapterTraits template struct for PicoJsonAdapter. +template<> +struct AdapterTraits<valijson::adapters::PicoJsonAdapter> +{ + typedef picojson::value DocumentType; + + static std::string adapterName() + { + return "PicoJsonAdapter"; + } +}; + +inline bool PicoJsonFrozenValue::equalTo(const Adapter &other, bool strict) const +{ + return PicoJsonAdapter(value).equalTo(other, strict); +} + +inline PicoJsonArrayValueIterator PicoJsonArray::begin() const +{ + const picojson::array &array = value.get<picojson::array>(); + return array.begin(); +} + +inline PicoJsonArrayValueIterator PicoJsonArray::end() const +{ + const picojson::array &array = value.get<picojson::array>(); + return array.end(); +} + +inline PicoJsonObjectMemberIterator PicoJsonObject::begin() const +{ + const picojson::object &object = value.get<picojson::object>(); + return object.begin(); +} + +inline PicoJsonObjectMemberIterator PicoJsonObject::end() const +{ + const picojson::object &object = value.get<picojson::object>(); + return object.end(); +} + +inline PicoJsonObjectMemberIterator PicoJsonObject::find( + const std::string &propertyName) const +{ + const picojson::object &object = value.get<picojson::object>(); + return object.find(propertyName); +} + +} // namespace adapters +} // namespace valijson + +#endif
diff --git a/examples/validator/valijson/include/valijson/adapters/poco_json_adapter.hpp b/examples/validator/valijson/include/valijson/adapters/poco_json_adapter.hpp new file mode 100644 index 0000000..915befc --- /dev/null +++ b/examples/validator/valijson/include/valijson/adapters/poco_json_adapter.hpp
@@ -0,0 +1,728 @@ +/** +* @file +* +* @brief Adapter implementation for the Poco json parser library. +* +* Include this file in your program to enable support for Poco json. +* +* This file defines the following classes (not in this order): +* - PocoJsonAdapter +* - PocoJsonArray +* - PocoJsonValueIterator +* - PocoJsonFrozenValue +* - PocoJsonObject +* - PocoJsonObjectMember +* - PocoJsonObjectMemberIterator +* - PocoJsonValue +* +* 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 PocoJsonAdapter. 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 +#ifndef __VALIJSON_ADAPTERS_POCO_JSON_ADAPTER_HPP +#define __VALIJSON_ADAPTERS_POCO_JSON_ADAPTER_HPP + +#include <string> +#include <Poco/JSON/Object.h> + +#include <valijson/adapters/adapter.hpp> +#include <valijson/adapters/basic_adapter.hpp> +#include <valijson/adapters/frozen_value.hpp> + +namespace valijson +{ + namespace adapters + { + + class PocoJsonAdapter; + class PocoJsonArrayValueIterator; + class PocoJsonObjectMemberIterator; + + typedef std::pair<std::string, PocoJsonAdapter> PocoJsonObjectMember; + + /** + * @brief Light weight wrapper for a PocoJson array value. + * + * This class is light weight wrapper for a PocoJson 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 + * PocoJson value, assumed to be an array, so there is very little overhead + * associated with copy construction and passing by value. + */ + class PocoJsonArray + { + public: + + typedef PocoJsonArrayValueIterator const_iterator; + typedef PocoJsonArrayValueIterator iterator; + + /// Construct a PocoJsonArray referencing an empty array. + PocoJsonArray() + : value(emptyArray()) + { } + + /** + * @brief Construct a PocoJsonArray referencing a specific PocoJson + * value. + * + * @param value reference to a PocoJson value + * + * Note that this constructor will throw an exception if the value is not + * an array. + */ + PocoJsonArray(const Poco::Dynamic::Var &value) + : value(value) + { + if (value.type() != typeid(Poco::JSON::Array::Ptr)) { + throw std::runtime_error("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 PocoJson implementation. + */ + PocoJsonArrayValueIterator 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 PocoJson implementation. + */ + PocoJsonArrayValueIterator end() const; + + /// Return the number of elements in the array + size_t size() const + { + return value.extract<Poco::JSON::Array::Ptr>()->size(); + } + + private: + + /** + * @brief Return a PocoJson value that is an empty array. + */ + static Poco::Dynamic::Var emptyArray() + { + Poco::Dynamic::Var array = Poco::JSON::Array::Ptr(); + return array; + } + + /// Contained value + Poco::Dynamic::Var value; + }; + + /** + * @brief Light weight wrapper for a PocoJson object. + * + * This class is light weight wrapper for a PocoJson 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 + * PocoJson value, assumed to be an object, so there is very little overhead + * associated with copy construction and passing by value. + */ + class PocoJsonObject + { + public: + + typedef PocoJsonObjectMemberIterator const_iterator; + typedef PocoJsonObjectMemberIterator iterator; + + /// Construct a PocoJsonObject an empty object. + PocoJsonObject() + : value(emptyObject()) + { } + + /** + * @brief Construct a PocoJsonObject referencing a specific PocoJson + * value. + * + * @param value reference to a PocoJson value + * + * Note that this constructor will throw an exception if the value is not + * an object. + */ + PocoJsonObject(const Poco::Dynamic::Var &value) + : value(value) + { + if (value.type() != typeid(Poco::JSON::Object::Ptr)) { + throw std::runtime_error("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 PocoJson implementation. + */ + PocoJsonObjectMemberIterator 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 PocoJson implementation. + */ + PocoJsonObjectMemberIterator 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 + */ + PocoJsonObjectMemberIterator find(const std::string &propertyName) const; + + /// Returns the number of members belonging to this object. + size_t size() const + { + return value.extract<Poco::JSON::Object::Ptr>()->size(); + } + + private: + + /** + * @brief Return a PocoJson value that is empty object. + */ + static Poco::Dynamic::Var emptyObject() + { + Poco::Dynamic::Var object = Poco::JSON::Object::Ptr(); + return object; + } + + /// Contained value + Poco::Dynamic::Var value; + }; + + /** + * @brief Stores an independent copy of a PocoJson value. + * + * This class allows a PocoJson value to be stored independent of its original + * document. PocoJson makes this easy to do, as it does not perform any + * custom memory management. + * + * @see FrozenValue + */ + class PocoJsonFrozenValue : public FrozenValue + { + public: + + /** + * @brief Make a copy of a PocoJson value + * + * @param source the PocoJson value to be copied + */ + explicit PocoJsonFrozenValue(const Poco::Dynamic::Var &source) + : value(source) + { } + + virtual FrozenValue * clone() const + { + return new PocoJsonFrozenValue(value); + } + + virtual bool equalTo(const Adapter &other, bool strict) const; + + private: + + /// Stored PocoJson value + Poco::Dynamic::Var value; + }; + + + /** + * @brief Light weight wrapper for a PocoJson value. + * + * This class is passed as an argument to the BasicAdapter template class, + * and is used to provide access to a PocoJson value. This class is responsible + * for the mechanics of actually reading a PocoJson 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 PocoJsonValue + { + public: + + /// Construct a wrapper for the empty object + PocoJsonValue() + : value(emptyObject()) + { } + + /// Construct a wrapper for a specific PocoJson value + PocoJsonValue(const Poco::Dynamic::Var& value) + : value(value) + { } + + /** + * @brief Create a new PocoJsonFrozenValue instance that contains the + * value referenced by this PocoJsonValue instance. + * + * @returns pointer to a new PocoJsonFrozenValue instance, belonging to the + * caller. + */ + FrozenValue * freeze() const + { + return new PocoJsonFrozenValue(value); + } + + /** + * @brief Optionally return a PocoJsonArray instance. + * + * If the referenced PocoJson value is an array, this function will return + * a std::optional containing a PocoJsonArray instance referencing the + * array. + * + * Otherwise it will return an empty optional. + */ + opt::optional<PocoJsonArray> getArrayOptional() const + { + if (value.type() == typeid(Poco::JSON::Array::Ptr)) { + return opt::make_optional(PocoJsonArray(value)); + } + + return opt::optional<PocoJsonArray>(); + } + + /** + * @brief Retrieve the number of elements in the array + * + * If the referenced PocoJson 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 (value.type() == typeid(Poco::JSON::Array::Ptr)) { + result = value.extract<Poco::JSON::Array::Ptr>()->size(); + return true; + } + + return false; + } + + bool getBool(bool &result) const + { + if (value.isBoolean()) { + result = value.convert<bool>(); + return true; + } + + return false; + } + + bool getDouble(double &result) const + { + if (value.isNumeric() && !value.isInteger()) { + result = value.convert<double>(); + return true; + } + + return false; + } + + bool getInteger(int64_t &result) const + { + if (value.isInteger()) { + result = value.convert<int>(); + return true; + } + return false; + } + + /** + * @brief Optionally return a PocoJsonObject instance. + * + * If the referenced PocoJson value is an object, this function will return a + * std::optional containing a PocoJsonObject instance referencing the + * object. + * + * Otherwise it will return an empty optional. + */ + opt::optional<PocoJsonObject> getObjectOptional() const + { + if (value.type() == typeid(Poco::JSON::Object::Ptr)) { + return opt::make_optional(PocoJsonObject(value)); + } + + return opt::optional<PocoJsonObject>(); + } + + /** + * @brief Retrieve the number of members in the object + * + * If the referenced PocoJson 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 (value.type() == typeid(Poco::JSON::Object::Ptr)) { + result = value.extract<Poco::JSON::Object::Ptr>()->size(); + return true; + } + + return false; + } + + bool getString(std::string &result) const + { + if (value.isString()) { + result = value.convert<std::string>(); + return true; + } + + return false; + } + + static bool hasStrictTypes() + { + return true; + } + + bool isArray() const + { + return value.type() == typeid(Poco::JSON::Array::Ptr); + } + + bool isBool() const + { + return value.isBoolean(); + } + + bool isDouble() const + { + return value.isNumeric() && !value.isInteger(); + } + + bool isInteger() const + { + return !isBool() && value.isInteger(); + } + + bool isNull() const + { + return value.isEmpty(); + } + + bool isNumber() const + { + return value.isNumeric(); + } + + bool isObject() const + { + return value.type() == typeid(Poco::JSON::Object::Ptr); + } + + bool isString() const + { + return value.isString(); + } + + private: + + /// Return an empty object + static Poco::Dynamic::Var emptyObject() + { + Poco::Dynamic::Var object = Poco::JSON::Object::Ptr(); + return object; + } + + /// Contained value + Poco::Dynamic::Var value; + }; + + /** + * @brief An implementation of the Adapter interface supporting PocoJson. + * + * 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 PocoJsonAdapter : + public BasicAdapter<PocoJsonAdapter, + PocoJsonArray, + PocoJsonObjectMember, + PocoJsonObject, + PocoJsonValue> + { + public: + /// Construct a PocoJsonAdapter that contains an empty object + PocoJsonAdapter() + : BasicAdapter() + { } + + /// Construct a PocoJsonAdapter containing a specific Poco Json object + PocoJsonAdapter(const Poco::Dynamic::Var &value) + : BasicAdapter(PocoJsonValue {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 + * PocoJsonAdapter representing a value stored in the array. It has been + * implemented using the boost iterator_facade template. + * + * @see PocoJsonArray + */ + class PocoJsonArrayValueIterator : + public std::iterator< + std::bidirectional_iterator_tag, // bi-directional iterator + PocoJsonAdapter> // value type + { + public: + + /** + * @brief Construct a new PocoJsonArrayValueIterator using an existing + * PocoJson iterator. + * + * @param itr PocoJson iterator to store + */ + PocoJsonArrayValueIterator(const Poco::JSON::Array::ConstIterator &itr) + : itr(itr) + { } + + /// Returns a PocoJsonAdapter that contains the value of the current + /// element. + PocoJsonAdapter operator*() const + { + return PocoJsonAdapter(*itr); + } + + DerefProxy<PocoJsonAdapter> operator->() const + { + return DerefProxy<PocoJsonAdapter>(**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 PocoJsonArrayValueIterator &other) const + { + return itr == other.itr; + } + + bool operator!=(const PocoJsonArrayValueIterator &other) const + { + return !(itr == other.itr); + } + + const PocoJsonArrayValueIterator& operator++() + { + itr++; + + return *this; + } + + PocoJsonArrayValueIterator operator++(int) + { + PocoJsonArrayValueIterator iterator_pre(itr); + ++(*this); + return iterator_pre; + } + + const PocoJsonArrayValueIterator& operator--() + { + itr--; + + return *this; + } + + void advance(std::ptrdiff_t n) + { + itr += n; + } + + private: + Poco::JSON::Array::ConstIterator 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 PocoJsonObjectMember representing one of the members of the object. It + * has been implemented using the boost iterator_facade template. + * + * @see PocoJsonObject + * @see PocoJsonObjectMember + */ + class PocoJsonObjectMemberIterator : + public std::iterator< + std::bidirectional_iterator_tag, // bi-directional iterator + PocoJsonObjectMember> // value type + { + public: + + /** + * @brief Construct an iterator from a PocoJson iterator. + * + * @param itr PocoJson iterator to store + */ + PocoJsonObjectMemberIterator(const Poco::JSON::Object::ConstIterator &itr) + : itr(itr) + { } + + /** + * @brief Returns a PocoJsonObjectMember that contains the key and value + * belonging to the object member identified by the iterator. + */ + PocoJsonObjectMember operator*() const + { + return PocoJsonObjectMember(itr->first, itr->second); + } + + DerefProxy<PocoJsonObjectMember> operator->() const + { + return DerefProxy<PocoJsonObjectMember>(**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 PocoJsonObjectMemberIterator &other) const + { + return itr == other.itr; + } + + bool operator!=(const PocoJsonObjectMemberIterator &other) const + { + return !(itr == other.itr); + } + + const PocoJsonObjectMemberIterator& operator++() + { + itr++; + + return *this; + } + + PocoJsonObjectMemberIterator operator++(int) + { + PocoJsonObjectMemberIterator iterator_pre(itr); + ++(*this); + return iterator_pre; + } + + const PocoJsonObjectMemberIterator& operator--() + { + itr--; + + return *this; + } + + private: + + /// Iternal copy of the original PocoJson iterator + Poco::JSON::Object::ConstIterator itr; + }; + + /// Specialisation of the AdapterTraits template struct for PocoJsonAdapter. + template<> + struct AdapterTraits<valijson::adapters::PocoJsonAdapter> + { + typedef Poco::Dynamic::Var DocumentType; + + static std::string adapterName() + { + return "PocoJsonAdapter"; + } + }; + + inline PocoJsonArrayValueIterator PocoJsonArray::begin() const + { + return value.extract<Poco::JSON::Array::Ptr>()->begin(); + } + + inline PocoJsonArrayValueIterator PocoJsonArray::end() const + { + return value.extract<Poco::JSON::Array::Ptr>()->end(); + } + + inline PocoJsonObjectMemberIterator PocoJsonObject::begin() const + { + return value.extract<Poco::JSON::Object::Ptr>()->begin(); + } + + inline PocoJsonObjectMemberIterator PocoJsonObject::end() const + { + return value.extract<Poco::JSON::Object::Ptr>()->end(); + } + + inline PocoJsonObjectMemberIterator PocoJsonObject::find( + const std::string &propertyName) const + { + auto& ptr = value.extract<Poco::JSON::Object::Ptr>(); + + auto it = std::find_if(ptr->begin(), ptr->end(), + [&propertyName](const Poco::JSON::Object::ValueType& p) + { + return p.first == propertyName; + }); + return it; + } + + inline bool PocoJsonFrozenValue::equalTo(const Adapter &other, bool strict) const + { + return PocoJsonAdapter(value).equalTo(other, strict); + } + + + + + } // namespace adapters +} // namespace valijson + +#endif // __VALIJSON_ADAPTERS_POCO_JSON_ADAPTER_HPP +
diff --git a/examples/validator/valijson/include/valijson/adapters/property_tree_adapter.hpp b/examples/validator/valijson/include/valijson/adapters/property_tree_adapter.hpp new file mode 100644 index 0000000..5ee7117 --- /dev/null +++ b/examples/validator/valijson/include/valijson/adapters/property_tree_adapter.hpp
@@ -0,0 +1,757 @@ +/** + * @file + * + * @brief Adapter implementation for the Boost property tree library. + * + * Include this file in your program to enable support for boost property trees. + * + * This file defines the following classes (not in this order): + * - PropertyTreeAdapter + * - PropertyTreeArray + * - PropertyTreeArrayValueIterator + * - PropertyTreeFrozenValue + * - PropertyTreeObject + * - PropertyTreeObjectMember + * - PropertyTreeObjectMemberIterator + * - PropertyTreeValue + * + * 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 PropertyTreeAdapter. 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 +#ifndef __VALIJSON_ADAPTERS_PROPERTY_TREE_ADAPTER_HPP +#define __VALIJSON_ADAPTERS_PROPERTY_TREE_ADAPTER_HPP + +#include <string> + +#include <boost/property_tree/ptree.hpp> + +#include <valijson/adapters/adapter.hpp> +#include <valijson/adapters/basic_adapter.hpp> +#include <valijson/adapters/frozen_value.hpp> + +namespace valijson { +namespace adapters { + +class PropertyTreeAdapter; +class PropertyTreeArrayValueIterator; +class PropertyTreeObjectMemberIterator; + +typedef std::pair<std::string, PropertyTreeAdapter> PropertyTreeObjectMember; + +/** + * @brief Light weight wrapper for a Boost property tree that contains + * array-like data. + * + * This class is light weight wrapper for a Boost property tree. 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 a Boost property + * tree that is assumed to contain unnamed key-value pairs. There is very little + * associated with copy construction and passing by value. + */ +class PropertyTreeArray +{ +public: + + typedef PropertyTreeArrayValueIterator const_iterator; + typedef PropertyTreeArrayValueIterator iterator; + + /// Construct a PropertyTreeArra7 referencing an empty property tree + /// singleton. + PropertyTreeArray() + : array(emptyTree()) { } + + /** + * @brief Construct PropertyTreeArray referencing a specific Boost + * property tree. + * + * @param array reference to a property tree containing an array + * + * It is assumed that this value contains array-like data, but this is not + * checked due to runtime cost. + */ + explicit PropertyTreeArray(const boost::property_tree::ptree &array) + : array(array) { } + + /// Return an iterator for the first element in the array. + PropertyTreeArrayValueIterator begin() const; + + /// Return an iterator for one-past the last element of the array. + PropertyTreeArrayValueIterator end() const; + + /// Return the number of elements in the array + size_t size() const + { + return array.size(); + } + +private: + + /** + * @brief Return a reference to a property tree that looks like an + * empty array. + * + * Note that the value returned by this function is a singleton. + */ + static const boost::property_tree::ptree & emptyTree() + { + static const boost::property_tree::ptree tree; + return tree; + } + + /// Reference to the contained value + const boost::property_tree::ptree &array; +}; + +/** + * @brief Light weight wrapper for a Boost property tree that contains + * object-like data. + * + * This class is light weight wrapper for a Boost property tree. 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 + * property tree value, assumed to be object-like, so there is very little + * overhead associated with copy construction and passing by value. + */ +class PropertyTreeObject +{ +public: + + typedef PropertyTreeObjectMemberIterator const_iterator; + typedef PropertyTreeObjectMemberIterator iterator; + + /// Construct a PropertyTreeObject referencing an empty property tree. + PropertyTreeObject() + : object(emptyTree()) { } + + /** + * @brief Construct a PropertyTreeObject referencing a specific property + * tree. + * + * @param object reference to a property tree containing an object + * + * Note that the value of the property tree is not checked, due to the + * runtime cost of doing so. + */ + PropertyTreeObject(const boost::property_tree::ptree &object) + : object(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 property tree + * implementation. + */ + PropertyTreeObjectMemberIterator 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 pointer value returned by the underlying property tree + * implementation. + */ + PropertyTreeObjectMemberIterator 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 property property name to search for + */ + PropertyTreeObjectMemberIterator find(const std::string &property) const; + + /// Returns the number of members belonging to this object. + size_t size() const + { + return object.size(); + } + +private: + + /** + * @brief Return a reference to an empty property tree. + * + * Note that the value returned by this function is a singleton. + */ + static const boost::property_tree::ptree & emptyTree() + { + static const boost::property_tree::ptree tree; + return tree; + } + + /// Reference to the contained object + const boost::property_tree::ptree &object; + +}; + +/** + * @brief Stores an independent copy of a Boost property tree. + * + * This class allows a property tree value to be stored independent of its + * original 'document'. Boost property trees make this easy to do, as they do + * not perform any custom memory management. + * + * @see FrozenValue + */ +class PropertyTreeFrozenValue: public FrozenValue +{ +public: + + /** + * @brief Make a copy of a Boost property tree POD value + * + * @param source string containing the POD vlaue + */ + explicit PropertyTreeFrozenValue( + const boost::property_tree::ptree::data_type &source) + : value(source) { } + + /** + * @brief Make a copy of a Boost property tree object or array value + * + * @param source the property tree to be copied + */ + explicit PropertyTreeFrozenValue( + const boost::property_tree::ptree &source) + : value(source) { } + + virtual FrozenValue * clone() const + { + return new PropertyTreeFrozenValue(value); + } + + virtual bool equalTo(const Adapter &other, bool strict) const; + +private: + + /// Stored value + boost::property_tree::ptree value; +}; + +/** + * @brief Light weight wrapper for a Boost property tree. + * + * This class is passed as an argument to the BasicAdapter template class, + * and is used to provide access to a Boost property tree value. This class + * is responsible for the mechanics of actually reading a property tree, whereas + * 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 PropertyTreeValue +{ +public: + + /// Construct a wrapper for an empty property tree + PropertyTreeValue() + : object(emptyTree()) { } + + /** + * @brief Construct a PropertyTreeValue from a tree object + * + * This function will determine whether the tree object represents an array + * or an object by scanning the key names for any non-empty strings. In the + * case of an empty tree object, it is not possible to determine whether it + * is an array or an object, so it will be treated as an array by default. + * Empty arrays are considered equal to empty objects when compared using + * non-strict type comparison. Empty strings will also be stored as empty + * arrays. + * + * @param tree Tree object to be wrapped + */ + PropertyTreeValue(const boost::property_tree::ptree &tree) + { + if (tree.data().empty()) { // No string content + if (tree.size() == 0) { // No children + array.emplace(tree); // Treat as empty array + } else { + bool isArray = true; + boost::property_tree::ptree::const_iterator itr; + for (itr = tree.begin(); itr != tree.end(); itr++) { + if (!itr->first.empty()) { + isArray = false; + break; + } + } + + if (isArray) { + array.emplace(tree); + } else { + object.emplace(tree); + } + } + } else { + value = tree.data(); + } + } + + /** + * @brief Create a new PropertyTreeFrozenValue instance that contains the + * value referenced by this PropertyTreeValue instance. + * + * @returns pointer to a new PropertyTreeFrozenValue instance, belonging to + * the caller. + */ + FrozenValue* freeze() const + { + if (array) { + return new PropertyTreeFrozenValue(*array); + } else if (object) { + return new PropertyTreeFrozenValue(*object); + } else { + return new PropertyTreeFrozenValue(*value); + } + } + + /** + * @brief Return an instance of PropertyTreeArrayAdapter. + * + * If the referenced property tree value is an array, this function will + * return a std::optional containing a PropertyTreeArray instance + * referencing the array. + * + * Otherwise it will return an empty optional. + */ + opt::optional<PropertyTreeArray> getArrayOptional() const + { + if (array) { + return opt::make_optional(PropertyTreeArray(*array)); + } + + return opt::optional<PropertyTreeArray>(); + } + + /** + * @brief Retrieve the number of elements in the array + * + * If the referenced property tree 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 (array) { + result = array->size(); + return true; + } + + return false; + } + + bool getBool(bool &) const + { + return false; + } + + bool getDouble(double &) const + { + return false; + } + + bool getInteger(int64_t &) const + { + return false; + } + + /** + * @brief Optionally return a PropertyTreeObject instance. + * + * If the referenced property tree is an object, this function will return a + * std::optional containing a PropertyTreeObject instance referencing the + * object. + * + * Otherwise it will return an empty optional. + */ + opt::optional<PropertyTreeObject> getObjectOptional() const + { + if (object) { + return opt::make_optional(PropertyTreeObject(*object)); + } + + return opt::optional<PropertyTreeObject>(); + } + + /** + * @brief Retrieve the number of members in the object + * + * If the referenced property tree 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 (object) { + result = object->size(); + return true; + } + + return false; + } + + bool getString(std::string &result) const + { + if (value) { + result = *value; + return true; + } + + return false; + } + + static bool hasStrictTypes() + { + return false; + } + + bool isArray() const + { + return static_cast<bool>(array); + } + + bool isBool() const + { + return false; + } + + bool isDouble() const + { + return false; + } + + bool isInteger() const + { + return false; + } + + bool isNull() const + { + return false; + } + + bool isNumber() const + { + return false; + } + + bool isObject() const + { + return static_cast<bool>(object); + } + + bool isString() const + { + return static_cast<bool>(value); + } + +private: + + static const boost::property_tree::ptree & emptyTree() + { + static const boost::property_tree::ptree tree; + return tree; + } + + /// Reference used if the value is known to be an array + opt::optional<const boost::property_tree::ptree &> array; + + /// Reference used if the value is known to be an object + opt::optional<const boost::property_tree::ptree &> object; + + /// Reference used if the value is known to be a POD type + opt::optional<std::string> value; +}; + +/** + * @brief An implementation of the Adapter interface supporting the Boost + * property tree library. + * + * 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 PropertyTreeAdapter: + public BasicAdapter<PropertyTreeAdapter, + PropertyTreeArray, + PropertyTreeObjectMember, + PropertyTreeObject, + PropertyTreeValue> +{ +public: + + /// Construct a PropertyTreeAdapter for an empty property tree + PropertyTreeAdapter() + : BasicAdapter() { } + + /// Construct a PropertyTreeAdapter using a specific property tree + PropertyTreeAdapter(const boost::property_tree::ptree &value) + : BasicAdapter(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 + * PropertyTreeAdapter representing a value stored in the array. It has been + * implemented using the boost iterator_facade template. + * + * @see PropertyTreeArray + */ +class PropertyTreeArrayValueIterator: + public std::iterator< + std::bidirectional_iterator_tag, // bi-directional iterator + PropertyTreeAdapter> // value type +{ +public: + + /** + * @brief Construct a new PropertyTreeArrayValueIterator using an existing + * property tree iterator. + * + * @param itr property tree iterator to store + */ + PropertyTreeArrayValueIterator( + const boost::property_tree::ptree::const_iterator &itr) + : itr(itr) { } + + /// Returns a PropertyTreeAdapter that contains the value of the current + /// element. + PropertyTreeAdapter operator*() const + { + return PropertyTreeAdapter(itr->second); + } + + DerefProxy<PropertyTreeAdapter> operator->() const + { + return DerefProxy<PropertyTreeAdapter>(**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 rhs iterator to compare against + * + * @returns true if the iterators are equal, false otherwise. + */ + bool operator==(const PropertyTreeArrayValueIterator &rhs) const + { + return itr == rhs.itr; + } + + bool operator!=(const PropertyTreeArrayValueIterator &rhs) const + { + return !(itr == rhs.itr); + } + + const PropertyTreeArrayValueIterator& operator++() + { + itr++; + + return *this; + } + + PropertyTreeArrayValueIterator operator++(int) + { + PropertyTreeArrayValueIterator iterator_pre(itr); + ++(*this); + return iterator_pre; + } + + const PropertyTreeArrayValueIterator& operator--() + { + itr--; + + return *this; + } + + void advance(std::ptrdiff_t n) + { + if (n > 0) { + while (n-- > 0) { + itr++; + } + } else { + while (n++ < 0) { + itr--; + } + } + } + +private: + + boost::property_tree::ptree::const_iterator 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 PropertyTreeObjectMember representing one of the members of the object. + * It has been implemented using the boost iterator_facade template. + * + * @see PropertyTreeObject + * @see PropertyTreeObjectMember + */ +class PropertyTreeObjectMemberIterator: + public std::iterator< + std::bidirectional_iterator_tag, // bi-directional iterator + PropertyTreeObjectMember> // value type +{ +public: + + /** + * @brief Construct an iterator from a PropertyTree iterator. + * + * @param itr PropertyTree iterator to store + */ + PropertyTreeObjectMemberIterator( + boost::property_tree::ptree::const_assoc_iterator itr) + : itr(itr) { } + + /** + * @brief Returns a PropertyTreeObjectMember that contains the key and + * value belonging to the object member identified by the iterator. + */ + PropertyTreeObjectMember operator*() const + { + return PropertyTreeObjectMember(itr->first, itr->second); + } + + DerefProxy<PropertyTreeObjectMember> operator->() const + { + return DerefProxy<PropertyTreeObjectMember>(**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 rhs Iterator to compare with + * + * @returns true if the underlying iterators are equal, false otherwise + */ + bool operator==(const PropertyTreeObjectMemberIterator &rhs) const + { + return itr == rhs.itr; + } + + bool operator!=(const PropertyTreeObjectMemberIterator &rhs) const + { + return !(itr == rhs.itr); + } + + const PropertyTreeObjectMemberIterator& operator++() + { + itr++; + + return *this; + } + + PropertyTreeObjectMemberIterator operator++(int) + { + PropertyTreeObjectMemberIterator iterator_pre(itr); + ++(*this); + return iterator_pre; + } + + const PropertyTreeObjectMemberIterator& operator--() + { + itr--; + + return *this; + } + +private: + + boost::property_tree::ptree::const_assoc_iterator itr; +}; + +/// Specialisation of the AdapterTraits template struct for PropertyTreeAdapter. +template<> +struct AdapterTraits<valijson::adapters::PropertyTreeAdapter> +{ + typedef boost::property_tree::ptree DocumentType; + + static std::string adapterName() + { + return "PropertyTreeAdapter"; + } +}; + +inline bool PropertyTreeFrozenValue::equalTo(const Adapter &other, bool strict) const +{ + return PropertyTreeAdapter(value).equalTo(other, strict); +} + +inline PropertyTreeArrayValueIterator PropertyTreeArray::begin() const +{ + return array.begin(); +} + +inline PropertyTreeArrayValueIterator PropertyTreeArray::end() const +{ + return array.end(); +} + +inline PropertyTreeObjectMemberIterator PropertyTreeObject::begin() const +{ + return object.ordered_begin(); +} + +inline PropertyTreeObjectMemberIterator PropertyTreeObject::end() const +{ + return object.not_found(); +} + +inline PropertyTreeObjectMemberIterator PropertyTreeObject::find( + const std::string &propertyName) const +{ + const boost::property_tree::ptree::const_assoc_iterator + itr = object.find(propertyName); + + if (itr != object.not_found()) { + return itr; + } + + return object.not_found(); +} + +} // namespace adapters +} // namespace valijson + +#endif
diff --git a/examples/validator/valijson/include/valijson/adapters/qtjson_adapter.hpp b/examples/validator/valijson/include/valijson/adapters/qtjson_adapter.hpp new file mode 100644 index 0000000..9422d25 --- /dev/null +++ b/examples/validator/valijson/include/valijson/adapters/qtjson_adapter.hpp
@@ -0,0 +1,725 @@ +/** + * @file + * + * @brief Adapter implementation for the QtJson parser library. + * + * Include this file in your program to enable support for QtJson. + * + * This file defines the following classes (not in this order): + * - QtJsonAdapter + * - QtJsonArray + * - QtJsonArrayValueIterator + * - QtJsonFrozenValue + * - QtJsonObject + * - QtJsonObjectMember + * - QtJsonObjectMemberIterator + * - QtJsonValue + * + * 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 QtJsonAdapter. 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 +#ifndef __VALIJSON_ADAPTERS_QTJSON_ADAPTER_HPP +#define __VALIJSON_ADAPTERS_QTJSON_ADAPTER_HPP + +#include <string> + +#include <QJsonObject> +#include <QJsonValue> +#include <QJsonArray> + + +#include <valijson/adapters/adapter.hpp> +#include <valijson/adapters/basic_adapter.hpp> +#include <valijson/adapters/frozen_value.hpp> + +namespace valijson { +namespace adapters { + +class QtJsonAdapter; +class QtJsonArrayValueIterator; +class QtJsonObjectMemberIterator; + +typedef std::pair<std::string, QtJsonAdapter> QtJsonObjectMember; + +/** + * @brief Light weight wrapper for a QtJson array value. + * + * This class is light weight wrapper for a QtJson 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 + * QtJson value, assumed to be an array, so there is very little overhead + * associated with copy construction and passing by value. + */ +class QtJsonArray +{ +public: + + typedef QtJsonArrayValueIterator const_iterator; + typedef QtJsonArrayValueIterator iterator; + + /// Construct a QtJsonArray referencing an empty array. + QtJsonArray() + : value(emptyArray()) + { + } + + /** + * @brief Construct a QtJsonArray referencing a specific QtJson + * value. + * + * @param value reference to a QtJson value + * + * Note that this constructor will throw an exception if the value is not + * an array. + */ + explicit QtJsonArray(const QJsonValue &value) + : value(value.toArray()) + { + if (!value.isArray()) { + throw std::runtime_error("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 QtJson implementation. + */ + QtJsonArrayValueIterator 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 QtJson implementation. + */ + QtJsonArrayValueIterator end() const; + + /// Return the number of elements in the array + size_t size() const + { + return value.size(); + } + +private: + + /** + * @brief Return a reference to a QtJson value that is an empty array. + * + * Note that the value returned by this function is a singleton. + */ + static const QJsonArray emptyArray() + { + static const QJsonArray array; + return array; + } + + /// Reference to the contained value + const QJsonArray value; +}; + +/** + * @brief Light weight wrapper for a QtJson object. + * + * This class is light weight wrapper for a QtJson 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 + * QtJson value, assumed to be an object, so there is very little overhead + * associated with copy construction and passing by value. + */ +class QtJsonObject +{ +public: + + typedef QtJsonObjectMemberIterator const_iterator; + typedef QtJsonObjectMemberIterator iterator; + + /// Construct a QtJsonObject referencing an empty object singleton. + QtJsonObject() + : value(emptyObject()) + { + } + + /** + * @brief Construct a QtJsonObject referencing a specific QtJson + * value. + * + * @param value reference to a QtJson value + * + * Note that this constructor will throw an exception if the value is not + * an object. + */ + QtJsonObject(const QJsonValue &value) + : value(value.toObject()) + { + if (!value.isObject()) { + throw std::runtime_error("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 QtJson implementation. + */ + QtJsonObjectMemberIterator 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 QtJson implementation. + */ + QtJsonObjectMemberIterator 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 + */ + QtJsonObjectMemberIterator find(const std::string &propertyName) const; + + /// Returns the number of members belonging to this object. + size_t size() const + { + return value.size(); + } + +private: + + /** + * @brief Return a reference to a QtJson value that is empty object. + * + * Note that the value returned by this function is a singleton. + */ + static const QJsonObject emptyObject() + { + static const QJsonObject object; + return object; + } + + /// Reference to the contained object + const QJsonObject value; +}; + +/** + * @brief Stores an independent copy of a QtJson value. + * + * This class allows a QtJson value to be stored independent of its original + * document. QtJson makes this easy to do, as it does not perform any + * custom memory management. + * + * @see FrozenValue + */ +class QtJsonFrozenValue: public FrozenValue +{ +public: + + /** + * @brief Make a copy of a QtJson value + * + * @param source the QtJson value to be copied + */ + explicit QtJsonFrozenValue(const QJsonValue &source) + : value(source) { } + + virtual FrozenValue * clone() const + { + return new QtJsonFrozenValue(value); + } + + virtual bool equalTo(const Adapter &other, bool strict) const; + +private: + + /// Stored QtJson value + QJsonValue value; +}; + +/** + * @brief Light weight wrapper for a QtJson value. + * + * This class is passed as an argument to the BasicAdapter template class, + * and is used to provide access to a QtJson value. This class is responsible + * for the mechanics of actually reading a QtJson 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 QtJsonValue +{ +public: + + /// Construct a wrapper for the empty object singleton + QtJsonValue() + : value(emptyObject()) { } + + /// Construct a wrapper for a specific QtJson value + QtJsonValue(const QJsonValue &value) + : value(value) { } + + /** + * @brief Create a new QtJsonFrozenValue instance that contains the + * value referenced by this QtJsonValue instance. + * + * @returns pointer to a new QtJsonFrozenValue instance, belonging to the + * caller. + */ + FrozenValue * freeze() const + { + return new QtJsonFrozenValue(value); + } + + /** + * @brief Optionally return a QtJsonArray instance. + * + * If the referenced QtJson value is an array, this function will return + * a std::optional containing a QtJsonArray instance referencing the + * array. + * + * Otherwise it will return an empty optional. + */ + opt::optional<QtJsonArray> getArrayOptional() const + { + if (value.isArray()) { + return opt::make_optional(QtJsonArray(value)); + } + + return opt::optional<QtJsonArray>(); + } + + /** + * @brief Retrieve the number of elements in the array + * + * If the referenced QtJson 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 (value.isArray()) { + const QJsonArray array = value.toArray(); + result = array.size(); + return true; + } + + return false; + } + + bool getBool(bool &result) const + { + if (value.isBool()) { + result = value.toBool(); + return true; + } + + return false; + } + + bool getDouble(double &result) const + { + if (value.isDouble()) { + result = value.toDouble(); + return true; + } + + return false; + } + + bool getInteger(int64_t &result) const + { + if (value.isDouble()) { + result = value.toInt(); + return true; + } + + return false; + } + + /** + * @brief Optionally return a QtJsonObject instance. + * + * If the referenced QtJson value is an object, this function will return a + * std::optional containing a QtJsonObject instance referencing the + * object. + * + * Otherwise it will return an empty optional. + */ + opt::optional<QtJsonObject> getObjectOptional() const + { + if (value.isObject()) { + return opt::make_optional(QtJsonObject(value)); + } + + return opt::optional<QtJsonObject>(); + } + + /** + * @brief Retrieve the number of members in the object + * + * If the referenced QtJson 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 (value.isObject()) { + const QJsonObject &object = value.toObject(); + result = object.size(); + return true; + } + + return false; + } + + bool getString(std::string &result) const + { + if (value.isString()) { + result = value.toString().toStdString(); + return true; + } + + return false; + } + + static bool hasStrictTypes() + { + return true; + } + + bool isArray() const + { + return value.isArray(); + } + + bool isBool() const + { + return value.isBool(); + } + + bool isDouble() const + { + return value.isDouble(); + } + + bool isInteger() const + { + //toInt returns the default value (0, 1) if the value is not a whole number + return value.isDouble() && (value.toInt(0) == value.toInt(1)); + } + + bool isNull() const + { + return value.isNull(); + } + + bool isNumber() const + { + return value.isDouble(); + } + + bool isObject() const + { + return value.isObject(); + } + + bool isString() const + { + return value.isString(); + } + +private: + + /// Return a reference to an empty object singleton + static const QJsonValue emptyObject() + { + static const QJsonValue object; + return object; + } + + /// Reference to the contained QtJson value. + const QJsonValue value; +}; + +/** + * @brief An implementation of the Adapter interface supporting QtJson. + * + * 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 QtJsonAdapter: + public BasicAdapter<QtJsonAdapter, + QtJsonArray, + QtJsonObjectMember, + QtJsonObject, + QtJsonValue> +{ +public: + + /// Construct a QtJsonAdapter that contains an empty object + QtJsonAdapter() + : BasicAdapter() { } + + /// Construct a QtJsonAdapter containing a specific QtJson value + QtJsonAdapter(const QJsonValue &value) + : BasicAdapter(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 + * QtJsonAdapter representing a value stored in the array. It has been + * implemented using the std::iterator template. + * + * @see QtJsonArray + */ +class QtJsonArrayValueIterator: + public std::iterator< + std::bidirectional_iterator_tag, // bi-directional iterator + QtJsonAdapter> // value type +{ +public: + + /** + * @brief Construct a new QtJsonArrayValueIterator using an existing + * QtJson iterator. + * + * @param itr QtJson iterator to store + */ + QtJsonArrayValueIterator( + const QJsonArray::const_iterator &itr) + : itr(itr) { } + + /// Returns a QtJsonAdapter that contains the value of the current + /// element. + QtJsonAdapter operator*() const + { + return QtJsonAdapter(*itr); + } + + DerefProxy<QtJsonAdapter> operator->() const + { + return DerefProxy<QtJsonAdapter>(**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 QtJsonArrayValueIterator &other) const + { + return itr == other.itr; + } + + bool operator!=(const QtJsonArrayValueIterator &other) const + { + return !(itr == other.itr); + } + + const QtJsonArrayValueIterator& operator++() + { + itr++; + + return *this; + } + + QtJsonArrayValueIterator operator++(int) + { + QtJsonArrayValueIterator iterator_pre(itr); + ++(*this); + return iterator_pre; + } + + const QtJsonArrayValueIterator& operator--() + { + itr--; + + return *this; + } + + void advance(std::ptrdiff_t n) + { + itr += n; + } + +private: + + QJsonArray::const_iterator 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 QtJsonObjectMember representing one of the members of the object. It + * has been implemented using the boost iterator_facade template. + * + * @see QtJsonObject + * @see QtJsonObjectMember + */ +class QtJsonObjectMemberIterator: + public std::iterator< + std::bidirectional_iterator_tag, // bi-directional iterator + QtJsonObjectMember> // value type +{ +public: + + /** + * @brief Construct an iterator from a QtJson iterator. + * + * @param itr QtJson iterator to store + */ + QtJsonObjectMemberIterator( + const QJsonObject::const_iterator &itr) + : itr(itr) { } + + /** + * @brief Returns a QtJsonObjectMember that contains the key and value + * belonging to the object member identified by the iterator. + */ + QtJsonObjectMember operator*() const + { + std::string key = itr.key().toStdString(); + return QtJsonObjectMember(key, itr.value()); + } + + DerefProxy<QtJsonObjectMember> operator->() const + { + return DerefProxy<QtJsonObjectMember>(**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 QtJsonObjectMemberIterator &other) const + { + return itr == other.itr; + } + + bool operator!=(const QtJsonObjectMemberIterator &other) const + { + return !(itr == other.itr); + } + + const QtJsonObjectMemberIterator& operator++() + { + itr++; + + return *this; + } + + QtJsonObjectMemberIterator operator++(int) + { + QtJsonObjectMemberIterator iterator_pre(itr); + ++(*this); + return iterator_pre; + } + + const QtJsonObjectMemberIterator& operator--(int) + { + itr--; + + return *this; + } + +private: + + /// Iternal copy of the original QtJson iterator + QJsonObject::const_iterator itr; +}; + +/// Specialisation of the AdapterTraits template struct for QtJsonAdapter. +template<> +struct AdapterTraits<valijson::adapters::QtJsonAdapter> +{ + typedef QJsonValue DocumentType; + + static std::string adapterName() + { + return "QtJsonAdapter"; + } +}; + +inline bool QtJsonFrozenValue::equalTo(const Adapter &other, bool strict) const +{ + return QtJsonAdapter(value).equalTo(other, strict); +} + +inline QtJsonArrayValueIterator QtJsonArray::begin() const +{ + return value.begin(); +} + +inline QtJsonArrayValueIterator QtJsonArray::end() const +{ + return value.end(); +} + +inline QtJsonObjectMemberIterator QtJsonObject::begin() const +{ + return value.begin(); +} + +inline QtJsonObjectMemberIterator QtJsonObject::end() const +{ + return value.end(); +} + +inline QtJsonObjectMemberIterator QtJsonObject::find( + const std::string &propertyName) const +{ + return value.find(QString::fromStdString(propertyName)); +} + +} // namespace adapters +} // namespace valijson + +#endif
diff --git a/examples/validator/valijson/include/valijson/adapters/rapidjson_adapter.hpp b/examples/validator/valijson/include/valijson/adapters/rapidjson_adapter.hpp new file mode 100644 index 0000000..38b44b9 --- /dev/null +++ b/examples/validator/valijson/include/valijson/adapters/rapidjson_adapter.hpp
@@ -0,0 +1,933 @@ +/** + * @file + * + * @brief Adapter implementation for the RapidJson parser library. + * + * Include this file in your program to enable support for RapidJson. + * + * This file defines the following template classes (not in this order): + * - GenericRapidJsonAdapter + * - GenericRapidJsonArray + * - GenericRapidJsonArrayValueIterator + * - GenericRapidJsonFrozenValue + * - GenericRapidJsonObject + * - GenericRapidJsonObjectMember + * - GenericRapidJsonObjectMemberIterator + * - GenericRapidJsonValue + * + * All of these classes share a template argument called 'ValueType', which can + * be used to choose the underlying the RapidJson value type that is used. This + * allows different RapidJson encodings and allocators to be used. + * + * This file also defines the following typedefs, which use RapidJson's default + * ValueType: + * - RapidJsonAdapter + * - RapidJsonArray + * - RapidJsonArrayValueIterator + * - RapidJsonFrozenValue + * - RapidJsonObject + * - RapidJsonObjectMember + * - RapidJsonObjectMemberIterator + * - RapidJsonValue + * + * 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 RapidJsonAdapter. 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 +#ifndef __VALIJSON_ADAPTERS_RAPIDJSON_ADAPTER_HPP +#define __VALIJSON_ADAPTERS_RAPIDJSON_ADAPTER_HPP + +#include <string> +#include <iterator> + +#include <rapidjson/document.h> + +#include <valijson/adapters/adapter.hpp> +#include <valijson/adapters/basic_adapter.hpp> +#include <valijson/adapters/frozen_value.hpp> + +namespace valijson { +namespace adapters { + +template<class ValueType = rapidjson::Value> +class GenericRapidJsonAdapter; + +template<class ValueType = rapidjson::Value> +class GenericRapidJsonArrayValueIterator; + +template<class ValueType = rapidjson::Value> +class GenericRapidJsonObjectMemberIterator; + +/// Container for a property name and an associated RapidJson value +template<class ValueType = rapidjson::Value> +class GenericRapidJsonObjectMember : + public std::pair<std::string, GenericRapidJsonAdapter<ValueType> > +{ +private: + typedef std::pair<std::string, GenericRapidJsonAdapter<ValueType> > Super; + +public: + GenericRapidJsonObjectMember( + const std::string &name, + const GenericRapidJsonAdapter<ValueType> &value) + : Super(name, value) { } +}; + +/** + * @brief Light weight wrapper for a RapidJson array value. + * + * This class is light weight wrapper for a RapidJson 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 an underlying + * RapidJson value, assumed to be an array, so there is very little overhead + * associated with copy construction and passing by value. + */ +template<class ValueType = rapidjson::Value> +class GenericRapidJsonArray +{ +public: + + typedef GenericRapidJsonArrayValueIterator<ValueType> const_iterator; + typedef GenericRapidJsonArrayValueIterator<ValueType> iterator; + + /// Construct a RapidJsonArray referencing an empty array singleton. + GenericRapidJsonArray() + : value(emptyArray()) { } + + /** + * @brief Construct a RapidJsonArray referencing a specific RapidJson + * value. + * + * @param value reference to a RapidJson value + * + * Note that this constructor will throw an exception if the value is not + * an array. + */ + GenericRapidJsonArray(const ValueType &value) + : value(value) + { + if (!value.IsArray()) { + throw std::runtime_error("Value is not an array."); + } + } + + /// Return an iterator for the first element in the array. + iterator begin() const; + + /// Return an iterator for one-past the last element of the array. + iterator end() const; + + /// Return the number of elements in the array + size_t size() const + { + return value.Size(); + } + +private: + + /** + * @brief Return a reference to a RapidJson value that is an empty array. + * + * Note that the value returned by this function is a singleton. + */ + static const ValueType & emptyArray() + { + static const ValueType array(rapidjson::kArrayType); + return array; + } + + /// Reference to the contained value + const ValueType &value; +}; + +/** + * @brief Light weight wrapper for a RapidJson object. + * + * This class is light weight wrapper for a RapidJson 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 + * RapidJson value, assumed to be an object, so there is very little overhead + * associated with copy construction and passing by value. + */ +template <class ValueType = rapidjson::Value> +class GenericRapidJsonObject +{ +public: + + typedef GenericRapidJsonObjectMemberIterator<ValueType> const_iterator; + typedef GenericRapidJsonObjectMemberIterator<ValueType> iterator; + + /// Construct a GenericRapidJsonObject referencing an empty object singleton. + GenericRapidJsonObject() + : value(emptyObject()) { } + + /** + * @brief Construct a GenericRapidJsonObject referencing a specific + * RapidJson value. + * + * @param value reference to a RapidJson value + * + * Note that this constructor will throw an exception if the value is not + * an object. + */ + GenericRapidJsonObject(const ValueType &value) + : value(value) + { + if (!value.IsObject()) { + throw std::runtime_error("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 pointer value returned by the underlying RapidJson implementation. + */ + iterator 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 pointer value returned by the underlying RapidJson implementation. + */ + iterator 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 property property name to search for + */ + iterator find(const std::string &property) const; + + /// Returns the number of members belonging to this object. + size_t size() const + { + return value.MemberEnd() - value.MemberBegin(); + } + +private: + + /** + * @brief Return a reference to a RapidJson value that is empty object. + * + * Note that the value returned by this function is a singleton. + */ + static const ValueType & emptyObject() + { + static ValueType object(rapidjson::kObjectType); + return object; + } + + /// Reference to the contained object + const ValueType &value; +}; + +/** + * @brief Stores an independent copy of a RapidJson value. + * + * This class allows a RapidJson value to be stored independent of its original + * document. RapidJson makes this a bit harder than usual, because RapidJson + * values are associated with a custom memory allocator. As such, RapidJson + * values have to be copied recursively, referencing a custom allocator held + * by this class. + * + * @see FrozenValue + */ +template<class ValueType = rapidjson::Value> +class GenericRapidJsonFrozenValue: public FrozenValue +{ +public: + + explicit GenericRapidJsonFrozenValue(const char *str) + { + value.SetString(str, allocator); + } + + explicit GenericRapidJsonFrozenValue(const std::string &str) + { + value.SetString(str.c_str(), (unsigned int)str.length(), allocator); + } + + /** + * @brief Make a copy of a RapidJson value + * + * @param source the RapidJson value to be copied + */ + explicit GenericRapidJsonFrozenValue(const ValueType &source) + { + if (!copy(source, value, allocator)) { + throw std::runtime_error("Failed to copy ValueType"); + } + } + + virtual FrozenValue * clone() const + { + return new GenericRapidJsonFrozenValue(value); + } + + virtual bool equalTo(const Adapter &other, bool strict) const; + +private: + + /** + * @brief Recursively copy a RapidJson value using a separate allocator + * + * @param source value to copy from + * @param dest value to copy into + * @param allocator reference to an allocator held by this class + * + * @tparam Allocator type of RapidJson Allocator to be used + * + * @returns true if copied successfully, false otherwise. + */ + template<typename Allocator> + static bool copy(const ValueType &source, + ValueType &dest, + Allocator &allocator) + { + switch (source.GetType()) { + case rapidjson::kNullType: + dest.SetNull(); + return true; + case rapidjson::kFalseType: + dest.SetBool(false); + return true; + case rapidjson::kTrueType: + dest.SetBool(true); + return true; + case rapidjson::kObjectType: + dest.SetObject(); + for (typename ValueType::ConstMemberIterator itr = source.MemberBegin(); + itr != source.MemberEnd(); ++itr) { + ValueType name(itr->name.GetString(), itr->name.GetStringLength(), allocator); + ValueType value; + copy(itr->value, value, allocator); + dest.AddMember(name, value, allocator); + } + return true; + case rapidjson::kArrayType: + dest.SetArray(); + for (typename ValueType::ConstValueIterator itr = source.Begin(); itr != source.End(); ++itr) { + ValueType value; + copy(*itr, value, allocator); + dest.PushBack(value, allocator); + } + return true; + case rapidjson::kStringType: + dest.SetString(source.GetString(), source.GetStringLength(), allocator); + return true; + case rapidjson::kNumberType: + if (source.IsInt()) { + dest.SetInt(source.GetInt()); + } else if (source.IsUint()) { + dest.SetUint(source.GetUint()); + } else if (source.IsInt64()) { + dest.SetInt64(source.GetInt64()); + } else if (source.IsUint64()) { + dest.SetUint64(source.GetUint64()); + } else { + dest.SetDouble(source.GetDouble()); + } + return true; + default: + break; + } + + return false; + } + + /// Local memory allocator for RapidJson value + typename ValueType::AllocatorType allocator; + + /// Local RapidJson value + ValueType value; +}; + +/** + * @brief Light weight wrapper for a RapidJson value. + * + * This class is passed as an argument to the BasicAdapter template class, + * and is used to provide access to a RapidJson value. This class is responsible + * for the mechanics of actually reading a RapidJson 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 + */ +template<class ValueType = rapidjson::Value> +class GenericRapidJsonValue +{ +public: + /// Construct a wrapper for the empty object singleton + GenericRapidJsonValue() + : value(emptyObject()) { } + + /// Construct a wrapper for a specific RapidJson value + GenericRapidJsonValue(const ValueType &value) + : value(value) { } + + /** + * @brief Create a new GenericRapidJsonFrozenValue instance that contains + * the value referenced by this GenericRapidJsonValue instance. + * + * @returns pointer to a new GenericRapidJsonFrozenValue instance, belonging + * to the caller. + */ + FrozenValue * freeze() const + { + return new GenericRapidJsonFrozenValue<ValueType>(value); + } + + /** + * @brief Optionally return a GenericRapidJsonArray instance. + * + * If the referenced RapidJson value is an array, this function will return + * a std::optional containing a GenericRapidJsonArray instance referencing + * the array. + * + * Otherwise it will return an empty optional. + */ + opt::optional<GenericRapidJsonArray<ValueType> > getArrayOptional() const + { + if (value.IsArray()) { + return opt::make_optional(GenericRapidJsonArray<ValueType>(value)); + } + + return opt::optional<GenericRapidJsonArray<ValueType> >(); + } + + /** + * @brief Retrieve the number of elements in the array + * + * If the referenced RapidJson 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 (value.IsArray()) { + result = value.Size(); + return true; + } + + return false; + } + + bool getBool(bool &result) const + { + if (value.IsBool()) { + result = value.GetBool(); + return true; + } + + return false; + } + + bool getDouble(double &result) const + { + if (value.IsDouble()) { + result = value.GetDouble(); + return true; + } + + return false; + } + + bool getInteger(int64_t &result) const + { + if (value.IsInt()) { + result = value.GetInt(); + return true; + } else if (value.IsInt64()) { + result = value.GetInt64(); + return true; + } else if (value.IsUint()) { + result = static_cast<int64_t>(value.GetUint()); + return true; + } else if (value.IsUint64()) { + result = static_cast<int64_t>(value.GetUint64()); + return true; + } + + return false; + } + + /** + * @brief Optionally return a GenericRapidJsonObject instance. + * + * If the referenced RapidJson value is an object, this function will return + * a std::optional containing a GenericRapidJsonObject instance + * referencing the object. + * + * Otherwise it will return an empty optional. + */ + opt::optional<GenericRapidJsonObject<ValueType> > getObjectOptional() const + { + if (value.IsObject()) { + return opt::make_optional(GenericRapidJsonObject<ValueType>(value)); + } + + return opt::optional<GenericRapidJsonObject<ValueType> >(); + } + + /** + * @brief Retrieve the number of members in the object + * + * If the referenced RapidJson 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 (value.IsObject()) { + result = value.MemberEnd() - value.MemberBegin(); + return true; + } + + return false; + } + + bool getString(std::string &result) const + { + if (value.IsString()) { + result.assign(value.GetString(), value.GetStringLength()); + return true; + } + + return false; + } + + static bool hasStrictTypes() + { + return true; + } + + bool isArray() const + { + return value.IsArray(); + } + + bool isBool() const + { + return value.IsBool(); + } + + bool isDouble() const + { + return value.IsDouble(); + } + + bool isInteger() const + { + return value.IsInt() || value.IsInt64() || value.IsUint() || + value.IsUint64(); + } + + bool isNull() const + { + return value.IsNull(); + } + + bool isNumber() const + { + return value.IsNumber(); + } + + bool isObject() const + { + return value.IsObject(); + } + + bool isString() const + { + return value.IsString(); + } + +private: + + /// Return a reference to an empty object singleton + static const ValueType & emptyObject() + { + static const ValueType object(rapidjson::kObjectType); + return object; + } + + /// Reference to the contained RapidJson value. + const ValueType &value; +}; + +/** + * @brief An implementation of the Adapter interface supporting RapidJson. + * + * 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 + */ +template<class ValueType> +class GenericRapidJsonAdapter: + public BasicAdapter<GenericRapidJsonAdapter<ValueType>, + GenericRapidJsonArray<ValueType>, + GenericRapidJsonObjectMember<ValueType>, + GenericRapidJsonObject<ValueType>, + GenericRapidJsonValue<ValueType> > +{ +public: + + /// Construct a RapidJsonAdapter that contains an empty object + GenericRapidJsonAdapter() + : BasicAdapter<GenericRapidJsonAdapter<ValueType>, + GenericRapidJsonArray<ValueType>, + GenericRapidJsonObjectMember<ValueType>, + GenericRapidJsonObject<ValueType>, + GenericRapidJsonValue<ValueType> >() { } + + /// Construct a RapidJsonAdapter containing a specific RapidJson value + GenericRapidJsonAdapter(const ValueType &value) + : BasicAdapter<GenericRapidJsonAdapter<ValueType>, + GenericRapidJsonArray<ValueType>, + GenericRapidJsonObjectMember<ValueType>, + GenericRapidJsonObject<ValueType>, + GenericRapidJsonValue<ValueType> >(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 + * RapidJsonAdapter representing a value stored in the array. It has been + * implemented using the std::iterator template. + * + * @see RapidJsonArray + */ +template<class ValueType> +class GenericRapidJsonArrayValueIterator: + public std::iterator< + std::bidirectional_iterator_tag, // bi-directional iterator + GenericRapidJsonAdapter<ValueType> > // value type +{ +public: + + /** + * @brief Construct a new GenericRapidJsonArrayValueIterator using an + * existing RapidJson iterator. + * + * @param itr RapidJson iterator to store + */ + GenericRapidJsonArrayValueIterator( + const typename ValueType::ConstValueIterator &itr) + : itr(itr) { } + + /// Returns a GenericRapidJsonAdapter that contains the value of the current + /// element. + GenericRapidJsonAdapter<ValueType> operator*() const + { + return GenericRapidJsonAdapter<ValueType>(*itr); + } + + /// Returns a proxy for the value of the current element + DerefProxy<GenericRapidJsonAdapter<ValueType> > operator->() const + { + return DerefProxy<GenericRapidJsonAdapter<ValueType> >(**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 GenericRapidJsonArrayValueIterator<ValueType> &other) const + { + return itr == other.itr; + } + + bool operator!=(const GenericRapidJsonArrayValueIterator<ValueType>& other) const + { + return !(itr == other.itr); + } + + GenericRapidJsonArrayValueIterator<ValueType>& operator++() + { + itr++; + + return *this; + } + + GenericRapidJsonArrayValueIterator<ValueType> operator++(int) { + GenericRapidJsonArrayValueIterator<ValueType> iterator_pre(itr); + ++(*this); + return iterator_pre; + } + + GenericRapidJsonArrayValueIterator<ValueType>& operator--() + { + itr--; + + return *this; + } + + void advance(std::ptrdiff_t n) + { + itr += n; + } + + std::ptrdiff_t difference(const GenericRapidJsonArrayValueIterator<ValueType> &other) + { + return std::distance(itr, other.itr); + } + +private: + + typename ValueType::ConstValueIterator 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 GenericRapidJsonObjectMember representing one of the members of the + * object. It has been implemented using the std::iterator template. + * + * @see GenericRapidJsonObject + * @see GenericRapidJsonObjectMember + */ +template<class ValueType> +class GenericRapidJsonObjectMemberIterator: + public std::iterator< + std::bidirectional_iterator_tag, // bi-directional iterator + GenericRapidJsonObjectMember<ValueType> > // value type +{ +public: + + /** + * @brief Construct an iterator from a RapidJson iterator. + * + * @param itr RapidJson iterator to store + */ + GenericRapidJsonObjectMemberIterator( + const typename ValueType::ConstMemberIterator &itr) + : itr(itr) { } + + + /** + * @brief Returns a GenericRapidJsonObjectMember that contains the key and + * value belonging to the object member identified by the iterator. + */ + GenericRapidJsonObjectMember<ValueType> operator*() const + { + return GenericRapidJsonObjectMember<ValueType>( + std::string(itr->name.GetString(), itr->name.GetStringLength()), + itr->value); + } + + /// Returns a proxy for the value of the current element + DerefProxy<GenericRapidJsonObjectMember<ValueType> > operator->() const + { + return DerefProxy<GenericRapidJsonObjectMember<ValueType> >(**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 GenericRapidJsonObjectMemberIterator<ValueType> &other) const + { + return itr == other.itr; + } + + bool operator!=(const GenericRapidJsonObjectMemberIterator<ValueType> &other) const + { + return !(itr == other.itr); + } + + GenericRapidJsonObjectMemberIterator<ValueType>& operator++() + { + itr++; + + return *this; + } + + GenericRapidJsonObjectMemberIterator<ValueType> operator++(int) + { + GenericRapidJsonObjectMemberIterator<ValueType> iterator_pre(itr); + ++(*this); + return iterator_pre; + } + + GenericRapidJsonObjectMemberIterator<ValueType>& operator--() + { + itr--; + + return *this; + } + + std::ptrdiff_t difference(const GenericRapidJsonObjectMemberIterator &other) + { + return std::distance(itr, other.itr); + } + +private: + + /// Iternal copy of the original RapidJson iterator + typename ValueType::ConstMemberIterator itr; +}; + +template<class ValueType> +inline bool GenericRapidJsonFrozenValue<ValueType>::equalTo( + const Adapter &other, bool strict) const +{ + return GenericRapidJsonAdapter<ValueType>(value).equalTo(other, strict); +} + +template<class ValueType> +inline typename GenericRapidJsonArray<ValueType>::iterator + GenericRapidJsonArray<ValueType>::begin() const +{ + return value.Begin(); +} + +template<class ValueType> +inline typename GenericRapidJsonArray<ValueType>::iterator + GenericRapidJsonArray<ValueType>::end() const +{ + return value.End(); +} + +template<class ValueType> +inline typename GenericRapidJsonObject<ValueType>::iterator + GenericRapidJsonObject<ValueType>::begin() const +{ + return value.MemberBegin(); +} + +template<class ValueType> +inline typename GenericRapidJsonObject<ValueType>::iterator + GenericRapidJsonObject<ValueType>::end() const +{ + return value.MemberEnd(); +} + +template<class ValueType> +inline typename GenericRapidJsonObject<ValueType>::iterator + GenericRapidJsonObject<ValueType>::find( + const std::string &propertyName) const +{ + // Hack to support older versions of rapidjson where pointers are used as + // the built in iterator type. In those versions, the FindMember function + // would return a null pointer when the requested member could not be + // found. After calling FindMember on an empty object, we compare the + // result against what we would expect if a non-null-pointer iterator was + // returned. + const ValueType empty(rapidjson::kObjectType); + const typename ValueType::ConstMemberIterator maybeEnd = empty.FindMember(""); + if (maybeEnd != empty.MemberBegin() + 1) { + // In addition to the pointer-based iterator issue, RapidJson's internal + // string comparison code seemed to rely on the query string being + // initialised to a length greater than or equal to that of the + // properties being compared. We get around this by implementing our + // own linear scan. + const size_t propertyNameLength = propertyName.length(); + for (typename ValueType::ConstMemberIterator itr = value.MemberBegin(); + itr != value.MemberEnd(); ++itr) { + const size_t memberNameLength = itr->name.GetStringLength(); + if (memberNameLength == propertyNameLength && + strncmp(itr->name.GetString(), propertyName.c_str(), + itr->name.GetStringLength()) == 0) { + return itr; + } + } + + return value.MemberEnd(); + } + + return value.FindMember(propertyName.c_str()); // Times are good. +} + +typedef GenericRapidJsonAdapter<> RapidJsonAdapter; +typedef GenericRapidJsonArray<> RapidJsonArray; +typedef GenericRapidJsonArrayValueIterator<> RapidJsonArrayValue; +typedef GenericRapidJsonFrozenValue<> RapidJsonFrozenValue; +typedef GenericRapidJsonObject<> RapidJsonObject; +typedef GenericRapidJsonObjectMember<> RapidJsonObjectMember; +typedef GenericRapidJsonObjectMemberIterator<> RapidJsonObjectMemberIterator; +typedef GenericRapidJsonValue<> RapidJsonValue; + +/** + * @brief Specialisation of the AdapterTraits template struct for a + * RapidJsonAdapter that uses a pool allocator + */ +template<> +struct AdapterTraits<valijson::adapters::RapidJsonAdapter> +{ + typedef rapidjson::Document DocumentType; + + static std::string adapterName() + { + return "RapidJsonAdapter"; + } +}; + +typedef rapidjson::GenericValue<rapidjson::UTF8<>, rapidjson::CrtAllocator> + RapidJsonCrt; + +/** + * @brief Specialisation of the AdapterTraits template struct for a + * RapidJsonAdapter that uses the default CRT allocator + */ +template<> +struct AdapterTraits<valijson::adapters::GenericRapidJsonAdapter<RapidJsonCrt> > +{ + typedef rapidjson::GenericDocument<rapidjson::UTF8<>, + rapidjson::CrtAllocator> DocumentType; + + static std::string adapterName() + { + return "GenericRapidJsonAdapter (using CrtAllocator)"; + } +}; + +} // namespace adapters +} // namespace valijson + +#endif
diff --git a/examples/validator/valijson/include/valijson/constraint_builder.hpp b/examples/validator/valijson/include/valijson/constraint_builder.hpp new file mode 100644 index 0000000..7b1ab79 --- /dev/null +++ b/examples/validator/valijson/include/valijson/constraint_builder.hpp
@@ -0,0 +1,25 @@ +#pragma once +#ifndef __VALIJSON_CONSTRAINT_BUILDER_HPP +#define __VALIJSON_CONSTRAINT_BUILDER_HPP + +namespace valijson { + +namespace adapters { + class Adapter; +} + +namespace constraints { + struct Constraint; +} + +class ConstraintBuilder +{ +public: + virtual ~ConstraintBuilder() {} + + virtual constraints::Constraint * make(const adapters::Adapter &) const = 0; +}; + +} // namespace valijson + +#endif
diff --git a/examples/validator/valijson/include/valijson/constraints/basic_constraint.hpp b/examples/validator/valijson/include/valijson/constraints/basic_constraint.hpp new file mode 100644 index 0000000..d086684 --- /dev/null +++ b/examples/validator/valijson/include/valijson/constraints/basic_constraint.hpp
@@ -0,0 +1,69 @@ +#pragma once +#ifndef __VALIJSON_CONSTRAINTS_BASIC_CONSTRAINT_HPP +#define __VALIJSON_CONSTRAINTS_BASIC_CONSTRAINT_HPP + +#include <valijson/constraints/constraint.hpp> +#include <valijson/constraints/constraint_visitor.hpp> + +#include <valijson/internal/custom_allocator.hpp> + +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<typename ConstraintType> +struct BasicConstraint: Constraint +{ + typedef internal::CustomAllocator<void *> Allocator; + + typedef std::basic_string<char, std::char_traits<char>, + internal::CustomAllocator<char> > String; + + BasicConstraint() + : allocator() { } + + BasicConstraint(Allocator::CustomAlloc allocFn, Allocator::CustomFree freeFn) + : allocator(allocFn, freeFn) { } + + BasicConstraint(const BasicConstraint &other) + : allocator(other.allocator) { } + + virtual ~BasicConstraint<ConstraintType>() { } + + virtual bool accept(ConstraintVisitor &visitor) const + { + return visitor.visit(*static_cast<const ConstraintType*>(this)); + } + + virtual Constraint * clone(CustomAlloc allocFn, CustomFree freeFn) const + { + void *ptr = allocFn(sizeof(ConstraintType)); + if (!ptr) { + throw std::runtime_error( + "Failed to allocate memory for cloned constraint"); + } + + try { + return new (ptr) ConstraintType( + *static_cast<const ConstraintType*>(this)); + } catch (...) { + freeFn(ptr); + throw; + } + } + +protected: + + Allocator allocator; +}; + +} // namespace constraints +} // namespace valijson + +#endif
diff --git a/examples/validator/valijson/include/valijson/constraints/concrete_constraints.hpp b/examples/validator/valijson/include/valijson/constraints/concrete_constraints.hpp new file mode 100644 index 0000000..ba0149d --- /dev/null +++ b/examples/validator/valijson/include/valijson/constraints/concrete_constraints.hpp
@@ -0,0 +1,1097 @@ +/** + * @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 +#ifndef __VALIJSON_CONSTRAINTS_CONCRETE_CONSTRAINTS_HPP +#define __VALIJSON_CONSTRAINTS_CONCRETE_CONSTRAINTS_HPP + +#include <limits> +#include <map> +#include <set> +#include <string> +#include <vector> + +#include <valijson/adapters/frozen_value.hpp> +#include <valijson/constraints/basic_constraint.hpp> +#include <valijson/internal/custom_allocator.hpp> +#include <valijson/schema.hpp> + +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<AllOfConstraint> +{ +public: + AllOfConstraint() + : subschemas(Allocator::rebind<const Subschema *>::other(allocator)) { } + + AllOfConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + subschemas(Allocator::rebind<const Subschema *>::other(allocator)) { } + + void addSubschema(const Subschema *subschema) + { + subschemas.push_back(subschema); + } + + template<typename FunctorType> + void applyToSubschemas(const FunctorType &fn) const + { + unsigned int index = 0; + for (const Subschema *subschema : subschemas) { + if (!fn(index, subschema)) { + return; + } + + index++; + } + } + +private: + typedef std::vector<const Subschema *, + internal::CustomAllocator<const Subschema *> > Subschemas; + + /// Collection of sub-schemas, all of which must be satisfied + Subschemas 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<AnyOfConstraint> +{ +public: + AnyOfConstraint() + : subschemas(Allocator::rebind<const Subschema *>::other(allocator)) { } + + AnyOfConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + subschemas(Allocator::rebind<const Subschema *>::other(allocator)) { } + + void addSubschema(const Subschema *subschema) + { + subschemas.push_back(subschema); + } + + template<typename FunctorType> + void applyToSubschemas(const FunctorType &fn) const + { + unsigned int index = 0; + for (const Subschema *subschema : subschemas) { + if (!fn(index, subschema)) { + return; + } + + index++; + } + } + +private: + typedef std::vector<const Subschema *, + internal::CustomAllocator<const Subschema *> > Subschemas; + + /// Collection of sub-schemas, at least one of which must be satisfied + Subschemas subschemas; +}; + +/** + * @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<DependenciesConstraint> +{ +public: + DependenciesConstraint() + : propertyDependencies(std::less<String>(), allocator), + schemaDependencies(std::less<String>(), allocator) + { } + + DependenciesConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + propertyDependencies(std::less<String>(), allocator), + schemaDependencies(std::less<String>(), allocator) + { } + + template<typename StringType> + DependenciesConstraint & addPropertyDependency( + const StringType &propertyName, + const StringType &dependencyName) + { + const String key(propertyName.c_str(), allocator); + PropertyDependencies::iterator itr = propertyDependencies.find(key); + if (itr == propertyDependencies.end()) { + itr = propertyDependencies.insert(PropertyDependencies::value_type( + key, PropertySet(std::less<String>(), allocator))).first; + } + + itr->second.insert(String(dependencyName.c_str(), allocator)); + + return *this; + } + + template<typename StringType, typename ContainerType> + DependenciesConstraint & addPropertyDependencies( + const StringType &propertyName, + const ContainerType &dependencyNames) + { + const String key(propertyName.c_str(), allocator); + PropertyDependencies::iterator itr = propertyDependencies.find(key); + if (itr == propertyDependencies.end()) { + itr = propertyDependencies.insert(PropertyDependencies::value_type( + key, PropertySet(std::less<String>(), allocator))).first; + } + + typedef typename ContainerType::value_type ValueType; + for (const ValueType &dependencyName : dependencyNames) { + itr->second.insert(String(dependencyName.c_str(), allocator)); + } + + return *this; + } + + template<typename StringType> + DependenciesConstraint & addSchemaDependency( + const StringType &propertyName, + const Subschema *schemaDependency) + { + if (schemaDependencies.insert(SchemaDependencies::value_type( + String(propertyName.c_str(), allocator), + schemaDependency)).second) { + return *this; + } + + throw std::runtime_error( + "Dependencies constraint already contains a dependent " + "schema for the property '" + propertyName + "'"); + } + + template<typename FunctorType> + void applyToPropertyDependencies(const FunctorType &fn) const + { + for (const PropertyDependencies::value_type &v : propertyDependencies) { + if (!fn(v.first, v.second)) { + return; + } + } + } + + template<typename FunctorType> + void applyToSchemaDependencies(const FunctorType &fn) const + { + for (const SchemaDependencies::value_type &v : schemaDependencies) { + if (!fn(v.first, v.second)) { + return; + } + } + } + +private: + typedef std::set<String, std::less<String>, internal::CustomAllocator<String> > PropertySet; + + typedef std::map<String, PropertySet, std::less<String>, + internal::CustomAllocator<std::pair<const String, PropertySet> > > PropertyDependencies; + + typedef std::map<String, const Subschema *, std::less<String>, + internal::CustomAllocator<std::pair<const String, const Subschema *> > > + SchemaDependencies; + + /// Mapping from property names to their property-based dependencies + PropertyDependencies propertyDependencies; + + /// Mapping from property names to their schema-based dependencies + SchemaDependencies 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<EnumConstraint> +{ +public: + EnumConstraint() + : enumValues(Allocator::rebind<const EnumValue *>::other(allocator)) { } + + EnumConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + enumValues(Allocator::rebind<const EnumValue *>::other(allocator)) { } + + EnumConstraint(const EnumConstraint &other) + : BasicConstraint(other), + enumValues(Allocator::rebind<const EnumValue *>::other(allocator)) + { + try { + // Clone individual enum values + for (const EnumValue *otherValue : other.enumValues) { + const EnumValue *value = otherValue->clone(); + try { + enumValues.push_back(value); + } catch (...) { + delete value; + throw; + } + } + + } catch (...) { + // Delete values already added to constraint + for (const EnumValue *value : enumValues) { + delete value; + } + throw; + } + } + + virtual ~EnumConstraint() + { + for (const EnumValue *value : enumValues) { + delete value; + } + } + + void addValue(const adapters::Adapter &value) + { + // TODO: Freeze value using custom alloc/free functions + enumValues.push_back(value.freeze()); + } + + void addValue(const adapters::FrozenValue &value) + { + // TODO: Clone using custom alloc/free functions + enumValues.push_back(value.clone()); + } + + template<typename FunctorType> + void applyToValues(const FunctorType &fn) const + { + for (const EnumValue *value : enumValues) { + if (!fn(*value)) { + return; + } + } + } + +private: + typedef adapters::FrozenValue EnumValue; + + typedef std::vector<const EnumValue *, + internal::CustomAllocator<const EnumValue *> > EnumValues; + + EnumValues 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<LinearItemsConstraint> +{ +public: + LinearItemsConstraint() + : itemSubschemas(Allocator::rebind<const Subschema *>::other(allocator)), + additionalItemsSubschema(NULL) { } + + LinearItemsConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + itemSubschemas(Allocator::rebind<const Subschema *>::other(allocator)), + additionalItemsSubschema(NULL) { } + + void addItemSubschema(const Subschema *subschema) + { + itemSubschemas.push_back(subschema); + } + + template<typename FunctorType> + void applyToItemSubschemas(const FunctorType &fn) const + { + unsigned int index = 0; + for( const Subschema *subschema : itemSubschemas ) { + if (!fn(index, subschema)) { + return; + } + + index++; + } + } + + const Subschema * getAdditionalItemsSubschema() const + { + return additionalItemsSubschema; + } + + size_t getItemSubschemaCount() const + { + return itemSubschemas.size(); + } + + void setAdditionalItemsSubschema(const Subschema *subschema) + { + additionalItemsSubschema = subschema; + } + +private: + typedef std::vector<const Subschema *, + internal::CustomAllocator<const Subschema *> > Subschemas; + + Subschemas itemSubschemas; + + const Subschema* additionalItemsSubschema; +}; + +/** + * @brief Represents 'maximum' and 'exclusiveMaximum' constraints + */ +class MaximumConstraint: public BasicConstraint<MaximumConstraint> +{ +public: + MaximumConstraint() + : maximum(std::numeric_limits<double>::infinity()), + exclusiveMaximum(false) { } + + MaximumConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + maximum(std::numeric_limits<double>::infinity()), + exclusiveMaximum(false) { } + + bool getExclusiveMaximum() const + { + return exclusiveMaximum; + } + + void setExclusiveMaximum(bool newExclusiveMaximum) + { + exclusiveMaximum = newExclusiveMaximum; + } + + double getMaximum() const + { + return maximum; + } + + void setMaximum(double newMaximum) + { + maximum = newMaximum; + } + +private: + double maximum; + bool exclusiveMaximum; +}; + +/** + * @brief Represents a 'maxItems' constraint + */ +class MaxItemsConstraint: public BasicConstraint<MaxItemsConstraint> +{ +public: + MaxItemsConstraint() + : maxItems(std::numeric_limits<uint64_t>::max()) { } + + MaxItemsConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + maxItems(std::numeric_limits<uint64_t>::max()) { } + + uint64_t getMaxItems() const + { + return maxItems; + } + + void setMaxItems(uint64_t newMaxItems) + { + maxItems = newMaxItems; + } + +private: + uint64_t maxItems; +}; + +/** + * @brief Represents a 'maxLength' constraint + */ +class MaxLengthConstraint: public BasicConstraint<MaxLengthConstraint> +{ +public: + MaxLengthConstraint() + : maxLength(std::numeric_limits<uint64_t>::max()) { } + + MaxLengthConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + maxLength(std::numeric_limits<uint64_t>::max()) { } + + uint64_t getMaxLength() const + { + return maxLength; + } + + void setMaxLength(uint64_t newMaxLength) + { + maxLength = newMaxLength; + } + +private: + uint64_t maxLength; +}; + +/** + * @brief Represents a 'maxProperties' constraint + */ +class MaxPropertiesConstraint: public BasicConstraint<MaxPropertiesConstraint> +{ +public: + MaxPropertiesConstraint() + : maxProperties(std::numeric_limits<uint64_t>::max()) { } + + MaxPropertiesConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + maxProperties(std::numeric_limits<uint64_t>::max()) { } + + uint64_t getMaxProperties() const + { + return maxProperties; + } + + void setMaxProperties(uint64_t newMaxProperties) + { + maxProperties = newMaxProperties; + } + +private: + uint64_t maxProperties; +}; + +/** + * @brief Represents 'minimum' and 'exclusiveMinimum' constraints + */ +class MinimumConstraint: public BasicConstraint<MinimumConstraint> +{ +public: + MinimumConstraint() + : minimum(-std::numeric_limits<double>::infinity()), + exclusiveMinimum(false) { } + + MinimumConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + minimum(-std::numeric_limits<double>::infinity()), + exclusiveMinimum(false) { } + + bool getExclusiveMinimum() const + { + return exclusiveMinimum; + } + + void setExclusiveMinimum(bool newExclusiveMinimum) + { + exclusiveMinimum = newExclusiveMinimum; + } + + double getMinimum() const + { + return minimum; + } + + void setMinimum(double newMinimum) + { + minimum = newMinimum; + } + +private: + double minimum; + bool exclusiveMinimum; +}; + +/** + * @brief Represents a 'minItems' constraint + */ +class MinItemsConstraint: public BasicConstraint<MinItemsConstraint> +{ +public: + MinItemsConstraint() + : minItems(0) { } + + MinItemsConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + minItems(0) { } + + uint64_t getMinItems() const + { + return minItems; + } + + void setMinItems(uint64_t newMinItems) + { + minItems = newMinItems; + } + +private: + size_t minItems; +}; + +/** + * @brief Represents a 'minLength' constraint + */ +class MinLengthConstraint: public BasicConstraint<MinLengthConstraint> +{ +public: + MinLengthConstraint() + : minLength(0) { } + + MinLengthConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + minLength(0) { } + + int64_t getMinLength() const + { + return minLength; + } + + void setMinLength(uint64_t newMinLength) + { + minLength = newMinLength; + } + +private: + uint64_t minLength; +}; + +/** + * @brief Represents a 'minProperties' constraint + */ +class MinPropertiesConstraint: public BasicConstraint<MinPropertiesConstraint> +{ +public: + MinPropertiesConstraint() + : minProperties(0) { } + + MinPropertiesConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + minProperties(0) { } + + uint64_t getMinProperties() const + { + return minProperties; + } + + void setMinProperties(uint64_t newMinProperties) + { + minProperties = newMinProperties; + } + +private: + size_t minProperties; +}; + +/** + * @brief Represents either 'multipleOf' or 'divisibleBy' constraints where + * the divisor is a floating point number + */ +class MultipleOfDoubleConstraint: + public BasicConstraint<MultipleOfDoubleConstraint> +{ +public: + MultipleOfDoubleConstraint() + : value(1.) { } + + MultipleOfDoubleConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + value(1.) { } + + double getDivisor() const + { + return value; + } + + void setDivisor(double newValue) + { + value = newValue; + } + +private: + double value; +}; + +/** + * @brief Represents either 'multipleOf' or 'divisibleBy' constraints where + * the divisor is of integer type + */ +class MultipleOfIntConstraint: + public BasicConstraint<MultipleOfIntConstraint> +{ +public: + MultipleOfIntConstraint() + : value(1) { } + + MultipleOfIntConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + value(1) { } + + int64_t getDivisor() const + { + return value; + } + + void setDivisor(int64_t newValue) + { + value = newValue; + } + +private: + int64_t value; +}; + +/** + * @brief Represents a 'not' constraint + */ +class NotConstraint: public BasicConstraint<NotConstraint> +{ +public: + NotConstraint() + : subschema(NULL) { } + + NotConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + subschema(NULL) { } + + const Subschema * getSubschema() const + { + return subschema; + } + + void setSubschema(const Subschema *subschema) + { + this->subschema = subschema; + } + +private: + const Subschema *subschema; +}; + +/** + * @brief Represents a 'oneOf' constraint. + */ +class OneOfConstraint: public BasicConstraint<OneOfConstraint> +{ +public: + OneOfConstraint() + : subschemas(Allocator::rebind<const Subschema *>::other(allocator)) { } + + OneOfConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + subschemas(Allocator::rebind<const Subschema *>::other(allocator)) { } + + void addSubschema(const Subschema *subschema) + { + subschemas.push_back(subschema); + } + + template<typename FunctorType> + void applyToSubschemas(const FunctorType &fn) const + { + unsigned int index = 0; + for( const Subschema *subschema : subschemas ) { + if (!fn(index, subschema)) { + return; + } + + index++; + } + } + +private: + typedef std::vector<const Subschema *, + internal::CustomAllocator<const Subschema *> > Subschemas; + + /// Collection of sub-schemas, exactly one of which must be satisfied + Subschemas subschemas; +}; + +/** + * @brief Represents a 'pattern' constraint + */ +class PatternConstraint: public BasicConstraint<PatternConstraint> +{ +public: + PatternConstraint() + : pattern(Allocator::rebind<char>::other(allocator)) { } + + PatternConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + pattern(Allocator::rebind<char>::other(allocator)) { } + + template<typename AllocatorType> + bool getPattern(std::basic_string<char, std::char_traits<char>, + AllocatorType> &result) const + { + result.assign(this->pattern.c_str()); + return true; + } + + template<typename AllocatorType> + std::basic_string<char, std::char_traits<char>, AllocatorType> getPattern( + const AllocatorType &alloc = AllocatorType()) const + { + return std::basic_string<char, std::char_traits<char>, AllocatorType>( + pattern.c_str(), alloc); + } + + template<typename AllocatorType> + void setPattern(const std::basic_string<char, std::char_traits<char>, + AllocatorType> &pattern) + { + this->pattern.assign(pattern.c_str()); + } + +private: + String pattern; +}; + +class PolyConstraint : public Constraint +{ +public: + virtual bool accept(ConstraintVisitor &visitor) const + { + return visitor.visit(*static_cast<const PolyConstraint*>(this)); + } + + virtual Constraint * clone(CustomAlloc allocFn, CustomFree freeFn) const + { + void *ptr = allocFn(sizeOf()); + if (!ptr) { + throw std::runtime_error( + "Failed to allocate memory for cloned constraint"); + } + + try { + return cloneInto(ptr); + } catch (...) { + freeFn(ptr); + throw; + } + } + + virtual bool validate(const adapters::Adapter &target, + const std::vector<std::string>& 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<PropertiesConstraint> +{ +public: + PropertiesConstraint() + : properties(std::less<String>(), allocator), + patternProperties(std::less<String>(), allocator), + additionalProperties(NULL) { } + + PropertiesConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + properties(std::less<String>(), allocator), + patternProperties(std::less<String>(), allocator), + additionalProperties(NULL) { } + + bool addPatternPropertySubschema(const char *patternProperty, + const Subschema *subschema) + { + return patternProperties.insert(PropertySchemaMap::value_type( + String(patternProperty, allocator), subschema)).second; + } + + template<typename AllocatorType> + bool addPatternPropertySubschema(const std::basic_string<char, + std::char_traits<char>, AllocatorType> &patternProperty, + const Subschema *subschema) + { + return addPatternPropertySubschema(patternProperty.c_str(), subschema); + } + + bool addPropertySubschema(const char *propertyName, + const Subschema *subschema) + { + return properties.insert(PropertySchemaMap::value_type( + String(propertyName, allocator), subschema)).second; + } + + template<typename AllocatorType> + bool addPropertySubschema(const std::basic_string<char, + std::char_traits<char>, AllocatorType> &propertyName, + const Subschema *subschema) + { + return addPropertySubschema(propertyName.c_str(), subschema); + } + + template<typename FunctorType> + void applyToPatternProperties(const FunctorType &fn) const + { + typedef typename PropertySchemaMap::value_type ValueType; + for( const ValueType &value : patternProperties ) { + if (!fn(value.first, value.second)) { + return; + } + } + } + + template<typename FunctorType> + void applyToProperties(const FunctorType &fn) const + { + typedef typename PropertySchemaMap::value_type ValueType; + for( const ValueType &value : properties ) { + if (!fn(value.first, value.second)) { + return; + } + } + } + + const Subschema * getAdditionalPropertiesSubschema() const + { + return additionalProperties; + } + + void setAdditionalPropertiesSubschema(const Subschema *subschema) + { + additionalProperties = subschema; + } + +private: + typedef std::map<String, const Subschema *, std::less<String>, internal::CustomAllocator<std::pair<const String, const Subschema *> > > + PropertySchemaMap; + + PropertySchemaMap properties; + PropertySchemaMap patternProperties; + + const Subschema *additionalProperties; +}; + +/** + * @brief Represents a 'required' constraint + */ +class RequiredConstraint: public BasicConstraint<RequiredConstraint> +{ +public: + RequiredConstraint() + : requiredProperties(std::less<String>(), allocator) { } + + RequiredConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + requiredProperties(std::less<String>(), allocator) { } + + bool addRequiredProperty(const char *propertyName) + { + return requiredProperties.insert(String(propertyName, + Allocator::rebind<char>::other(allocator))).second; + } + + template<typename AllocatorType> + bool addRequiredProperty(const std::basic_string<char, + std::char_traits<char>, AllocatorType> &propertyName) + { + return addRequiredProperty(propertyName.c_str()); + } + + template<typename FunctorType> + void applyToRequiredProperties(const FunctorType &fn) const + { + for( const String &propertyName : requiredProperties ) { + if (!fn(propertyName)) { + return; + } + } + } + +private: + typedef std::set<String, std::less<String>, + internal::CustomAllocator<String> > RequiredProperties; + + RequiredProperties 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<SingularItemsConstraint> +{ +public: + SingularItemsConstraint() + : itemsSubschema(NULL) { } + + SingularItemsConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + itemsSubschema(NULL) { } + + const Subschema * getItemsSubschema() const + { + return itemsSubschema; + } + + void setItemsSubschema(const Subschema *subschema) + { + itemsSubschema = subschema; + } + +private: + const Subschema *itemsSubschema; +}; + +/** + * @brief Represents a 'type' constraint. + */ +class TypeConstraint: public BasicConstraint<TypeConstraint> +{ +public: + enum JsonType { + kAny, + kArray, + kBoolean, + kInteger, + kNull, + kNumber, + kObject, + kString + }; + + TypeConstraint() + : namedTypes(std::less<JsonType>(), allocator), + schemaTypes(Allocator::rebind<const Subschema *>::other(allocator)) { } + + TypeConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn), + namedTypes(std::less<JsonType>(), allocator), + schemaTypes(Allocator::rebind<const Subschema *>::other(allocator)) { } + + void addNamedType(JsonType type) + { + namedTypes.insert(type); + } + + void addSchemaType(const Subschema *subschema) + { + schemaTypes.push_back(subschema); + } + + template<typename FunctorType> + void applyToNamedTypes(const FunctorType &fn) const + { + for( const JsonType namedType : namedTypes ) { + if (!fn(namedType)) { + return; + } + } + } + + template<typename FunctorType> + void applyToSchemaTypes(const FunctorType &fn) const + { + unsigned int index = 0; + for( const Subschema *subschema : schemaTypes ) { + if (!fn(index, subschema)) { + return; + } + + index++; + } + } + + template<typename AllocatorType> + static JsonType jsonTypeFromString(const std::basic_string<char, + std::char_traits<char>, 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; + } + + throw std::runtime_error("Unrecognised JSON type name '" + + std::string(typeName.c_str()) + "'"); + } + +private: + typedef std::set<JsonType, std::less<JsonType>, internal::CustomAllocator<JsonType> > NamedTypes; + + typedef std::vector<const Subschema *, + Allocator::rebind<const Subschema *>::other> SchemaTypes; + + /// Set of named JSON types that serve as valid types + NamedTypes namedTypes; + + /// Set of sub-schemas that serve as valid types + SchemaTypes schemaTypes; +}; + +/** + * @brief Represents a 'uniqueItems' constraint + */ +class UniqueItemsConstraint: public BasicConstraint<UniqueItemsConstraint> +{ +public: + UniqueItemsConstraint() { } + + UniqueItemsConstraint(CustomAlloc allocFn, CustomFree freeFn) + : BasicConstraint(allocFn, freeFn) { } +}; + +} // namespace constraints +} // namespace valijson + +#endif
diff --git a/examples/validator/valijson/include/valijson/constraints/constraint.hpp b/examples/validator/valijson/include/valijson/constraints/constraint.hpp new file mode 100644 index 0000000..c053dbb --- /dev/null +++ b/examples/validator/valijson/include/valijson/constraints/constraint.hpp
@@ -0,0 +1,54 @@ +#pragma once +#ifndef __VALIJSON_CONSTRAINTS_CONSTRAINT_HPP +#define __VALIJSON_CONSTRAINTS_CONSTRAINT_HPP + +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 *); + + /** + * @brief Virtual destructor. + */ + virtual ~Constraint() { } + + /** + * @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 Constraint * clone(CustomAlloc, CustomFree) const = 0; + +}; + +} // namespace constraints +} // namespace valijson + +#endif
diff --git a/examples/validator/valijson/include/valijson/constraints/constraint_visitor.hpp b/examples/validator/valijson/include/valijson/constraints/constraint_visitor.hpp new file mode 100644 index 0000000..a45dab0 --- /dev/null +++ b/examples/validator/valijson/include/valijson/constraints/constraint_visitor.hpp
@@ -0,0 +1,96 @@ +#pragma once +#ifndef __VALIJSON_CONSTRAINTS_CONSTRAINT_VISITOR_HPP +#define __VALIJSON_CONSTRAINTS_CONSTRAINT_VISITOR_HPP + +namespace valijson { +namespace constraints { + +class AllOfConstraint; +class AnyOfConstraint; +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 RequiredConstraint; +class SingularItemsConstraint; +class TypeConstraint; +class UniqueItemsConstraint; + +/// Interface to allow usage of the visitor pattern with Constraints +class ConstraintVisitor +{ +protected: + virtual ~ConstraintVisitor() {} + + // Shorten type names for derived classes outside of this namespace + typedef constraints::AllOfConstraint AllOfConstraint; + typedef constraints::AnyOfConstraint AnyOfConstraint; + 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::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 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 RequiredConstraint &) = 0; + virtual bool visit(const SingularItemsConstraint &) = 0; + virtual bool visit(const TypeConstraint &) = 0; + virtual bool visit(const UniqueItemsConstraint &) = 0; +}; + +} // namespace constraints +} // namespace valijson + +#endif
diff --git a/examples/validator/valijson/include/valijson/internal/custom_allocator.hpp b/examples/validator/valijson/include/valijson/internal/custom_allocator.hpp new file mode 100644 index 0000000..062f0dd --- /dev/null +++ b/examples/validator/valijson/include/valijson/internal/custom_allocator.hpp
@@ -0,0 +1,110 @@ +#ifndef __VALIJSON_CUSTOM_ALLOCATOR_HPP +#define __VALIJSON_CUSTOM_ALLOCATOR_HPP + +namespace valijson { +namespace internal { + +template<class T> +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<typename U> + struct rebind + { + typedef CustomAllocator<U> other; + }; + + CustomAllocator() + : allocFn(::operator new), + freeFn(::operator delete) { } + + CustomAllocator(CustomAlloc allocFn, CustomFree freeFn) + : allocFn(allocFn), + freeFn(freeFn) { } + + CustomAllocator(const CustomAllocator &other) + : allocFn(other.allocFn), + freeFn(other.freeFn) { } + + template<typename U> + CustomAllocator(CustomAllocator<U> const &other) + : allocFn(other.allocFn), + freeFn(other.freeFn) { } + + CustomAllocator & operator=(const CustomAllocator &other) + { + allocFn = other.allocFn; + freeFn = other.freeFn; + + return *this; + } + + pointer address(reference r) + { + return &r; + } + + const_pointer address(const_reference r) + { + return &r; + } + + pointer allocate(size_type cnt, const void * = 0) + { + return reinterpret_cast<pointer>(allocFn(cnt * sizeof(T))); + } + + void deallocate(pointer p, size_type) + { + freeFn(p); + } + + size_type max_size() const + { + return std::numeric_limits<size_type>::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.allocFn == allocFn && other.freeFn == freeFn; + } + + bool operator!=(const CustomAllocator &other) const + { + return !operator==(other); + } + + CustomAlloc allocFn; + + CustomFree freeFn; +}; + +} // end namespace internal +} // end namespace valijson + +#endif
diff --git a/examples/validator/valijson/include/valijson/internal/debug.hpp b/examples/validator/valijson/include/valijson/internal/debug.hpp new file mode 100644 index 0000000..0be7f4a --- /dev/null +++ b/examples/validator/valijson/include/valijson/internal/debug.hpp
@@ -0,0 +1,33 @@ +#ifndef __VALIJSON_DEBUG_HPP +#define __VALIJSON_DEBUG_HPP + +#include <string> + +namespace valijson { +namespace internal { + +template<typename AdapterType> +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 + +#endif \ No newline at end of file
diff --git a/examples/validator/valijson/include/valijson/internal/json_pointer.hpp b/examples/validator/valijson/include/valijson/internal/json_pointer.hpp new file mode 100644 index 0000000..2a3cb0f --- /dev/null +++ b/examples/validator/valijson/include/valijson/internal/json_pointer.hpp
@@ -0,0 +1,257 @@ +#pragma once +#ifndef __VALIJSON_INTERNAL_JSON_POINTER_HPP +#define __VALIJSON_INTERNAL_JSON_POINTER_HPP + +#include <algorithm> +#include <cerrno> +#include <cstddef> +#include <cstring> +#include <stdexcept> +#include <string> + +#include <valijson/adapters/adapter.hpp> +#include <valijson/internal/optional.hpp> + +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 replace_all_inplace(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) { + throw std::runtime_error("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 = NULL; + const unsigned long value = strtoul(begin, &end, 16); + if (end != begin && *end != '\0') { + throw std::runtime_error("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 + replace_all_inplace(token, "~1", "/"); + replace_all_inplace(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)) { + + try { + const char c = decodePercentEncodedChar(token.substr(n + 1, 2)); + token.replace(n, 3, &c, 1); + + } catch (const std::runtime_error &e) { + throw std::runtime_error( + std::string(e.what()) + "; in token: " + token); + } + } + + 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<typename AdapterType> +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 != '/') { + throw std::runtime_error("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.compare("-") == 0) { + throw std::runtime_error("Hyphens cannot be used as array indices " + "since the requested array element does not yet exist"); + } + + try { + // Fragment must be non-negative integer + const uint64_t index = std::stoul(referenceToken); + typedef typename AdapterType::Array Array; + typename Array::const_iterator itr = node.asArray().begin(); + + if (index > node.asArray().size() - 1) { + throw std::runtime_error("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<uint64_t>(std::numeric_limits<std::ptrdiff_t>::max())) { + throw std::runtime_error("Array index out of bounds; hard " + "limit is " + std::to_string( + std::numeric_limits<std::ptrdiff_t>::max())); + } + + itr.advance(static_cast<std::ptrdiff_t>(index)); + + // Recursively process the remaining tokens + return resolveJsonPointer(*itr, jsonPointer, jsonPointerNext); + + } catch (std::invalid_argument &) { + throw std::runtime_error("Expected reference token to contain a " + "non-negative integer to identify an element in the " + "current array; actual token: " + referenceToken); + } + + } else if (node.maybeObject()) { + // Fragment must identify a member of the candidate object + typedef typename AdapterType::Object Object; + typename Object::const_iterator itr = node.asObject().find( + referenceToken); + if (itr == node.asObject().end()) { + throw std::runtime_error("Expected reference token to identify an " + "element in the current object; " + "actual token: " + referenceToken); + } + + // Recursively process the remaining tokens + return resolveJsonPointer(itr->second, jsonPointer, jsonPointerNext); + } + + throw std::runtime_error("Expected end of JSON Pointer, but at least " + "one reference token has not been processed; remaining tokens: " + + std::string(jsonPointerNext, jsonPointerEnd)); +} + +/** + * @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<typename AdapterType> +inline AdapterType resolveJsonPointer( + const AdapterType &rootNode, + const std::string &jsonPointer) +{ + return resolveJsonPointer(rootNode, jsonPointer, jsonPointer.begin()); +} + +} // namespace json_pointer +} // namespace internal +} // namespace valijson + +#endif
diff --git a/examples/validator/valijson/include/valijson/internal/json_reference.hpp b/examples/validator/valijson/include/valijson/internal/json_reference.hpp new file mode 100644 index 0000000..592fe6b --- /dev/null +++ b/examples/validator/valijson/include/valijson/internal/json_reference.hpp
@@ -0,0 +1,65 @@ +#pragma once +#ifndef __VALIJSON_INTERNAL_JSON_REFERENCE_HPP +#define __VALIJSON_INTERNAL_JSON_REFERENCE_HPP + +#include <stdexcept> +#include <string> + +#include <valijson/internal/optional.hpp> + +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<std::string> 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<std::string>(); + } 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<std::string> 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<std::string>(); + } + + +} // namespace json_reference +} // namespace internal +} // namespace valijson + +#endif
diff --git a/examples/validator/valijson/include/valijson/internal/optional.hpp b/examples/validator/valijson/include/valijson/internal/optional.hpp new file mode 100644 index 0000000..8ff285c --- /dev/null +++ b/examples/validator/valijson/include/valijson/internal/optional.hpp
@@ -0,0 +1,18 @@ +#pragma once +#ifndef __VALIJSON_OPTIONAL_HPP +#define __VALIJSON_OPTIONAL_HPP + +#if __cplusplus >= 201703 +// Visual C++ only supports __has_include in versions 14.12 and greater +# if !defined(_MSC_VER) || _MSC_VER >= 1912 +# if __has_include(<optional>) +# include <optional> +namespace opt = std; +# endif +# endif +#else +# include <compat/optional.hpp> +namespace opt = std::experimental; +#endif + +#endif
diff --git a/examples/validator/valijson/include/valijson/internal/uri.hpp b/examples/validator/valijson/include/valijson/internal/uri.hpp new file mode 100644 index 0000000..c1fe6d2 --- /dev/null +++ b/examples/validator/valijson/include/valijson/internal/uri.hpp
@@ -0,0 +1,37 @@ +#pragma once +#ifndef __VALIJSON_INTERNAL_URI_HPP +#define __VALIJSON_INTERNAL_URI_HPP + +#include <string> + +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; + } + + /** + * 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 + +#endif // __VALIJSON_INTERNAL_URI_HPP
diff --git a/examples/validator/valijson/include/valijson/schema.hpp b/examples/validator/valijson/include/valijson/schema.hpp new file mode 100644 index 0000000..2308e36 --- /dev/null +++ b/examples/validator/valijson/include/valijson/schema.hpp
@@ -0,0 +1,213 @@ +#pragma once +#ifndef __VALIJSON_SCHEMA_HPP +#define __VALIJSON_SCHEMA_HPP + +#include <cstdio> +#include <set> + +#include <valijson/subschema.hpp> + +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()) { } + + /** + * @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. + */ + virtual ~Schema() + { + sharedEmptySubschema->~Subschema(); + freeFn(const_cast<Subschema *>(sharedEmptySubschema)); + sharedEmptySubschema = NULL; + + try { + for (std::set<Subschema *>::iterator itr = subschemaSet.begin(); + itr != subschemaSet.end(); ++itr) { + Subschema *subschema = *itr; + subschema->~Subschema(); + freeFn(subschema); + } + } catch (const std::exception &e) { + fprintf(stderr, "Caught an exception while destroying Schema: %s", + e.what()); + } + } + + /** + * @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(); + + try { + if (!subschemaSet.insert(subschema).second) { + throw std::runtime_error( + "Failed to store pointer for new sub-schema"); + } + } catch (...) { + subschema->~Subschema(); + freeFn(subschema); + throw; + } + + 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; + } + + /** + * @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: + + // Disable copy construction + Schema(const Schema &); + + // Disable copy assignment + Schema & operator=(const Schema &); + + Subschema *newSubschema() + { + void *ptr = allocFn(sizeof(Subschema)); + if (!ptr) { + throw std::runtime_error( + "Failed to allocate memory for shared empty sub-schema"); + } + + try { + return new (ptr) Subschema(); + } catch (...) { + freeFn(ptr); + throw; + } + } + + Subschema * mutableSubschema(const Subschema *subschema) + { + if (subschema == this) { + return this; + } + + if (subschema == sharedEmptySubschema) { + throw std::runtime_error( + "Cannot modify the shared empty sub-schema"); + } + + Subschema *noConst = const_cast<Subschema*>(subschema); + if (subschemaSet.find(noConst) == subschemaSet.end()) { + throw std::runtime_error( + "Subschema pointer is not owned by this Schema instance"); + } + + return noConst; + } + + /// Set of Subschema instances owned by this schema + std::set<Subschema*> subschemaSet; + + /// Empty schema that can be reused by multiple constraints + const Subschema *sharedEmptySubschema; +}; + +} // namespace valijson + +#endif
diff --git a/examples/validator/valijson/include/valijson/schema_parser.hpp b/examples/validator/valijson/include/valijson/schema_parser.hpp new file mode 100644 index 0000000..c08dce2 --- /dev/null +++ b/examples/validator/valijson/include/valijson/schema_parser.hpp
@@ -0,0 +1,2120 @@ +#pragma once +#ifndef __VALIJSON_SCHEMA_PARSER_HPP +#define __VALIJSON_SCHEMA_PARSER_HPP + +#include <stdexcept> +#include <iostream> +#include <vector> +#include <memory> +#include <functional> + +#include <valijson/adapters/adapter.hpp> +#include <valijson/constraints/concrete_constraints.hpp> +#include <valijson/internal/debug.hpp> +#include <valijson/internal/json_pointer.hpp> +#include <valijson/internal/json_reference.hpp> +#include <valijson/internal/uri.hpp> +#include <valijson/constraint_builder.hpp> +#include <valijson/schema.hpp> + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-local-typedef" +#endif + +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 + }; + + /// Version of JSON Schema that should be expected when parsing + const Version version; + + /** + * @brief Construct a new SchemaParser for a given version of JSON Schema + * + * @param version Version of JSON Schema that will be expected + */ + SchemaParser(const Version version = kDraft4) + : version(version) { } + + /** + * @brief Release memory associated with custom ConstraintBuilders + */ + ~SchemaParser() + { + for (ConstraintBuilders::iterator itr = constraintBuilders.begin(); + itr != constraintBuilders.end(); ++itr) { + delete itr->second; + } + } + + /** + * @brief Struct to contain templated function type for fetching documents + */ + template<typename AdapterType> + struct FunctionPtrs + { + typedef typename adapters::AdapterTraits<AdapterType>::DocumentType + DocumentType; + + /// Templated function pointer type for fetching remote documents + typedef std::function< const DocumentType* (const std::string &uri) > FetchDoc ; + + /// Templated function pointer type for freeing fetched documents + typedef std::function< void (const DocumentType *)> 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<typename AdapterType> + void populateSchema( + const AdapterType &node, + Schema &schema, + typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc = nullptr , + typename FunctionPtrs<AdapterType>::FreeDoc freeDoc = nullptr ) + { + if ((fetchDoc == nullptr ) ^ (freeDoc == nullptr)) { + throw std::runtime_error( + "Remote document fetching cannot be enabled without both " + "fetch and free functions"); + } + + typename DocumentCache<AdapterType>::Type docCache; + SchemaCache schemaCache; + try { + resolveThenPopulateSchema(schema, node, node, schema, + opt::optional<std::string>(), "", + fetchDoc, NULL, NULL, docCache, schemaCache); + } catch (...) { + freeDocumentCache<AdapterType>(docCache, freeDoc); + throw; + } + + freeDocumentCache<AdapterType>(docCache, freeDoc); + } + +private: + + typedef std::vector<std::pair<std::string, const ConstraintBuilder *> > + ConstraintBuilders; + + ConstraintBuilders constraintBuilders; + + template<typename AdapterType> + struct DocumentCache + { + typedef typename adapters::AdapterTraits<AdapterType>::DocumentType + DocumentType; + + typedef std::map<std::string, const DocumentType*> Type; + }; + + typedef std::map<std::string, const Subschema *> 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<typename AdapterType> + void freeDocumentCache(const typename DocumentCache<AdapterType>::Type + &docCache, typename FunctionPtrs<AdapterType>::FreeDoc freeDoc) + { + typedef typename DocumentCache<AdapterType>::Type DocCacheType; + + for (const typename DocCacheType::value_type &v : docCache) { + freeDoc(v.second); + } + } + + /** + * @brief Find the absolute 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: + * + * - resolution scope not present, but absolute document URI is + * => document URI as-is + * - resolution scope not present, and document URI is relative or absent + * => no result + * - resolution scope is present, and document URI is a relative path + * => resolve document URI relative to resolution scope + * - resolution scope is present, and document URI is absolute + * => document URI as-is + * - 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<std::string> findAbsoluteDocumentUri( + const opt::optional<std::string> resolutionScope, + const opt::optional<std::string> documentUri) + { + if (resolutionScope) { + if (documentUri) { + if (internal::uri::isUriAbsolute(*documentUri)) { + return *documentUri; + } else { + return internal::uri::resolveRelativeUri( + *resolutionScope, *documentUri); + } + } else { + return *resolutionScope; + } + } else if (documentUri && internal::uri::isUriAbsolute(*documentUri)) { + return *documentUri; + } else { + return opt::optional<std::string>(); + } + } + + /** + * @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<typename AdapterType> + 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.asString(result)) { + throw std::invalid_argument( + "$ref property expected to contain string value."); + } + + return true; + } + + /** + * Sanitise an optional JSON Pointer, trimming trailing slashes + */ + std::string sanitiseJsonPointer(const opt::optional<std::string> 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 NULL pointer 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 NULL pointer to the cache. + * + * @param schemaCache schema cache to query + * @param queryKey key to search for + * + * @return shared pointer to Schema if found, NULL pointer otherwise + */ + static const Subschema * querySchemaCache(SchemaCache &schemaCache, + const std::string &queryKey) + { + const SchemaCache::iterator itr = schemaCache.find(queryKey); + if (itr == schemaCache.end()) { + return NULL; + } + + 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. + */ + void updateSchemaCache(SchemaCache &schemaCache, + const std::vector<std::string> &keysToCreate, + const Subschema *schema) + { + for (const std::string &keyToCreate : keysToCreate) { + const SchemaCache::value_type value(keyToCreate, schema); + if (!schemaCache.insert(value).second) { + throw std::logic_error( + "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<typename AdapterType> + const Subschema * makeOrReuseSchema( + Schema &rootSchema, + const AdapterType &rootNode, + const AdapterType &node, + const opt::optional<std::string> currentScope, + const std::string &nodePath, + const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc, + const Subschema *parentSubschema, + const std::string *ownName, + typename DocumentCache<AdapterType>::Type &docCache, + SchemaCache &schemaCache, + std::vector<std::string> &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<std::string> 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<std::string> actualDocumentUri = + findAbsoluteDocumentUri(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<AdapterType>::DocumentType *newDoc = NULL; + + // Have we seen this document before? + typename DocumentCache<AdapterType>::Type::iterator docCacheItr = + docCache.find(*actualDocumentUri); + if (docCacheItr == docCache.end()) { + // Resolve reference against remote document + if (!fetchDoc) { + throw std::runtime_error( + "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) { + throw std::runtime_error( + "Failed to fetch referenced schema document: " + + *actualDocumentUri); + } + + typedef typename DocumentCache<AdapterType>::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<typename AdapterType> + const Subschema * makeOrReuseSchema( + Schema &rootSchema, + const AdapterType &rootNode, + const AdapterType &node, + const opt::optional<std::string> currentScope, + const std::string &nodePath, + const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc, + const Subschema *parentSubschema, + const std::string *ownName, + typename DocumentCache<AdapterType>::Type &docCache, + SchemaCache &schemaCache) + { + std::vector<std::string> 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<typename AdapterType> + void populateSchema( + Schema &rootSchema, + const AdapterType &rootNode, + const AdapterType &node, + const Subschema &subschema, + const opt::optional<std::string> currentScope, + const std::string &nodePath, + const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc, + const Subschema *parentSubschema, + const std::string *ownName, + typename DocumentCache<AdapterType>::Type &docCache, + SchemaCache &schemaCache) + { + static_assert((std::is_convertible<AdapterType, + const valijson::adapters::Adapter &>::value), + "SchemaParser::populateSchema must be invoked with an " + "appropriate Adapter implementation"); + + if (!node.isObject()) { + std::string s; + s += "Expected node at "; + s += nodePath; + s += " to contain schema object; actual node type is: "; + s += internal::nodeTypeAsString(node); + throw std::runtime_error(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<std::string> 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)) { + 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("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 { + throw std::runtime_error( + "'description' attribute should have a string value"); + } + } + + if ((itr = object.find("divisibleBy")) != object.end()) { + if (version == kDraft3) { + if (itr->second.maybeInteger()) { + rootSchema.addConstraintToSubschema( + makeMultipleOfIntConstraint(itr->second), + &subschema); + } else if (itr->second.maybeDouble()) { + rootSchema.addConstraintToSubschema( + makeMultipleOfDoubleConstraint(itr->second), + &subschema); + } else { + throw std::runtime_error("Expected an numeric value for " + " 'divisibleBy' constraint."); + } + } else { + throw std::runtime_error( + "'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 : NULL, + additionalItemsItr != object.end() ? + &additionalItemsItr->second : NULL, + updatedScope, nodePath + "/items", + nodePath + "/additionalItems", fetchDoc, + docCache, schemaCache), + &subschema); + } + } + } + + if ((itr = object.find("maximum")) != object.end()) { + typename AdapterType::Object::const_iterator exclusiveMaximumItr = + object.find("exclusiveMaximum"); + if (exclusiveMaximumItr == object.end()) { + rootSchema.addConstraintToSubschema( + makeMaximumConstraint<AdapterType>(itr->second, NULL), + &subschema); + } else { + rootSchema.addConstraintToSubschema( + makeMaximumConstraint(itr->second, + &exclusiveMaximumItr->second), + &subschema); + } + } else if (object.find("exclusiveMaximum") != object.end()) { + throw std::runtime_error( + "'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 ((itr = object.find("minimum")) != object.end()) { + typename AdapterType::Object::const_iterator exclusiveMinimumItr = + object.find("exclusiveMinimum"); + if (exclusiveMinimumItr == object.end()) { + rootSchema.addConstraintToSubschema( + makeMinimumConstraint<AdapterType>(itr->second, NULL), + &subschema); + } else { + rootSchema.addConstraintToSubschema( + makeMinimumConstraint(itr->second, + &exclusiveMinimumItr->second), + &subschema); + } + } else if (object.find("exclusiveMinimum") != object.end()) { + throw std::runtime_error( + "'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 (version == kDraft3) { + throw std::runtime_error( + "'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 { + throw std::runtime_error("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 : NULL, + patternPropertiesItr != object.end() ? + &patternPropertiesItr->second : NULL, + additionalPropertiesItr != object.end() ? + &additionalPropertiesItr->second : NULL, + updatedScope, nodePath + "/properties", + nodePath + "/patternProperties", + nodePath + "/additionalProperties", + fetchDoc, &subschema, docCache, schemaCache), + &subschema); + } + } + + if ((itr = object.find("required")) != object.end()) { + if (version == kDraft3) { + if (parentSubschema && ownName) { + opt::optional<constraints::RequiredConstraint> + constraint = makeRequiredConstraintForSelf( + itr->second, *ownName); + if (constraint) { + rootSchema.addConstraintToSubschema(*constraint, + parentSubschema); + } + } else { + throw std::runtime_error( + "'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 { + throw std::runtime_error( + "'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<constraints::UniqueItemsConstraint> constraint = + makeUniqueItemsConstraint(itr->second); + if (constraint) { + rootSchema.addConstraintToSubschema(*constraint, &subschema); + } + } + + for (ConstraintBuilders::const_iterator + builderItr = constraintBuilders.begin(); + builderItr != constraintBuilders.end(); ++builderItr) { + if ((itr = object.find(builderItr->first)) != object.end()) { + constraints::Constraint *constraint = NULL; + try { + constraint = builderItr->second->make(itr->second); + rootSchema.addConstraintToSubschema(*constraint, + &subschema); + delete constraint; + } catch (...) { + delete constraint; + throw; + } + } + } + } + + /** + * @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<typename AdapterType> + void resolveThenPopulateSchema( + Schema &rootSchema, + const AdapterType &rootNode, + const AdapterType &node, + const Subschema &subschema, + const opt::optional<std::string> currentScope, + const std::string &nodePath, + const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc, + const Subschema *parentSchema, + const std::string *ownName, + typename DocumentCache<AdapterType>::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<std::string> 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)) { + // Resolve reference against remote document + if (!fetchDoc) { + throw std::runtime_error( + "Fetching of remote JSON References not enabled."); + } + + const typename DocumentCache<AdapterType>::DocumentType *newDoc = + fetchDoc(*documentUri); + + // Can't proceed without the remote document + if (!newDoc) { + throw std::runtime_error( + "Failed to fetch referenced schema document: " + + *documentUri); + } + + // Add to document cache + typedef typename DocumentCache<AdapterType>::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, opt::optional<std::string>(), + 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, opt::optional<std::string>(), + 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<typename AdapterType> + constraints::AllOfConstraint makeAllOfConstraint( + Schema &rootSchema, + const AdapterType &rootNode, + const AdapterType &node, + const opt::optional<std::string> currentScope, + const std::string &nodePath, + const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc, + typename DocumentCache<AdapterType>::Type &docCache, + SchemaCache &schemaCache) + { + if (!node.maybeArray()) { + throw std::runtime_error( + "Expected array value for 'allOf' constraint."); + } + + constraints::AllOfConstraint constraint; + + int index = 0; + for ( const AdapterType schemaNode : node.asArray() ) { + if (schemaNode.maybeObject()) { + const std::string childPath = nodePath + "/" + + std::to_string(index); + const Subschema *subschema = makeOrReuseSchema<AdapterType>( + rootSchema, rootNode, schemaNode, currentScope, + childPath, fetchDoc, NULL, NULL, docCache, schemaCache); + constraint.addSubschema(subschema); + index++; + } else { + throw std::runtime_error( + "Expected array element to be an object value 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<typename AdapterType> + constraints::AnyOfConstraint makeAnyOfConstraint( + Schema &rootSchema, + const AdapterType &rootNode, + const AdapterType &node, + const opt::optional<std::string> currentScope, + const std::string &nodePath, + const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc, + typename DocumentCache<AdapterType>::Type &docCache, + SchemaCache &schemaCache) + { + if (!node.maybeArray()) { + throw std::runtime_error( + "Expected array value for 'anyOf' constraint."); + } + + constraints::AnyOfConstraint constraint; + + int index = 0; + for ( const AdapterType schemaNode : node.asArray() ) { + if (schemaNode.maybeObject()) { + const std::string childPath = nodePath + "/" + + std::to_string(index); + const Subschema *subschema = makeOrReuseSchema<AdapterType>( + rootSchema, rootNode, schemaNode, currentScope, + childPath, fetchDoc, NULL, NULL, docCache, schemaCache); + constraint.addSubschema(subschema); + index++; + } else { + throw std::runtime_error( + "Expected array element to be an object value in " + "'anyOf' 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<typename AdapterType> + constraints::DependenciesConstraint makeDependenciesConstraint( + Schema &rootSchema, + const AdapterType &rootNode, + const AdapterType &node, + const opt::optional<std::string> currentScope, + const std::string &nodePath, + const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc, + typename DocumentCache<AdapterType>::Type &docCache, + SchemaCache &schemaCache) + { + if (!node.maybeObject()) { + throw std::runtime_error("Expected object value 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<std::string> dependentPropertyNames; + for (const AdapterType dependencyName : member.second.asArray()) { + if (dependencyName.maybeString()) { + dependentPropertyNames.push_back(dependencyName.getString()); + } else { + throw std::runtime_error("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()) { + // Parse dependent subschema + const Subschema *childSubschema = + makeOrReuseSchema<AdapterType>(rootSchema, rootNode, + member.second, currentScope, nodePath, fetchDoc, + NULL, NULL, 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 (version == kDraft3 && member.second.isString()) { + dependenciesConstraint.addPropertyDependency(member.first, + member.second.getString()); + + // All other types result in an exception being thrown. + } else { + throw std::runtime_error("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<typename AdapterType> + 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<typename AdapterType> + constraints::LinearItemsConstraint makeLinearItemsConstraint( + Schema &rootSchema, + const AdapterType &rootNode, + const AdapterType *items, + const AdapterType *additionalItems, + const opt::optional<std::string> currentScope, + const std::string &itemsPath, + const std::string &additionalItemsPath, + const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc, + typename DocumentCache<AdapterType>::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<AdapterType>( + rootSchema, rootNode, *additionalItems, currentScope, + additionalItemsPath, fetchDoc, NULL, NULL, docCache, + schemaCache); + constraint.setAdditionalItemsSubschema(subschema); + } else { + // Any other format for the additionalItems property will result + // in an exception being thrown. + throw std::runtime_error( + "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<AdapterType>( + rootSchema, rootNode, v, currentScope, childPath, + fetchDoc, NULL, NULL, docCache, schemaCache); + constraint.addItemSubschema(subschema); + index++; + } + } else { + throw std::runtime_error( + "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<typename AdapterType> + constraints::SingularItemsConstraint makeSingularItemsConstraint( + Schema &rootSchema, + const AdapterType &rootNode, + const AdapterType &items, + const opt::optional<std::string> currentScope, + const std::string &itemsPath, + const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc, + typename DocumentCache<AdapterType>::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()) { + // 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<AdapterType>( + rootSchema, rootNode, items, currentScope, itemsPath, + fetchDoc, NULL, NULL, 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. + throw std::runtime_error( + "Expected object value for singular 'items' " + "constraint."); + } + + return constraint; + } + + /** + * @brief Make a new MaximumConstraint 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 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<typename AdapterType> + constraints::MaximumConstraint makeMaximumConstraint( + const AdapterType &node, + const AdapterType *exclusiveMaximum) + { + if (!node.maybeDouble()) { + throw std::runtime_error( + "Expected numeric value for maximum constraint."); + } + + constraints::MaximumConstraint constraint; + constraint.setMaximum(node.asDouble()); + + if (exclusiveMaximum) { + if (!exclusiveMaximum->maybeBool()) { + throw std::runtime_error( + "Expected boolean value for exclusiveMaximum " + "constraint."); + } + + constraint.setExclusiveMaximum(exclusiveMaximum->asBool()); + } + + 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<typename AdapterType> + 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; + } + } + + throw std::runtime_error( + "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<typename AdapterType> + 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; + } + } + + throw std::runtime_error( + "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<typename AdapterType> + 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; + } + } + + throw std::runtime_error( + "Expected a non-negative integer for 'maxProperties' " + "constraint."); + } + + /** + * @brief Make a new MinimumConstraint object. + * + * @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<typename AdapterType> + constraints::MinimumConstraint makeMinimumConstraint( + const AdapterType &node, + const AdapterType *exclusiveMinimum) + { + if (!node.maybeDouble()) { + throw std::runtime_error( + "Expected numeric value for minimum constraint."); + } + + constraints::MinimumConstraint constraint; + constraint.setMinimum(node.asDouble()); + + if (exclusiveMinimum) { + if (!exclusiveMinimum->maybeBool()) { + throw std::runtime_error( + "Expected boolean value for 'exclusiveMinimum' " + "constraint."); + } + + constraint.setExclusiveMinimum(exclusiveMinimum->asBool()); + } + + 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<typename AdapterType> + 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; + } + } + + throw std::runtime_error( + "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<typename AdapterType> + 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; + } + } + + throw std::runtime_error( + "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<typename AdapterType> + 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; + } + } + + throw std::runtime_error( + "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<typename AdapterType> + 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<typename AdapterType> + 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<typename AdapterType> + constraints::NotConstraint makeNotConstraint( + Schema &rootSchema, + const AdapterType &rootNode, + const AdapterType &node, + const opt::optional<std::string> currentScope, + const std::string &nodePath, + const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc, + typename DocumentCache<AdapterType>::Type &docCache, + SchemaCache &schemaCache) + { + if (node.maybeObject()) { + const Subschema *subschema = makeOrReuseSchema<AdapterType>( + rootSchema, rootNode, node, currentScope, nodePath, + fetchDoc, NULL, NULL, docCache, schemaCache); + constraints::NotConstraint constraint; + constraint.setSubschema(subschema); + return constraint; + } + + throw std::runtime_error("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<typename AdapterType> + constraints::OneOfConstraint makeOneOfConstraint( + Schema &rootSchema, + const AdapterType &rootNode, + const AdapterType &node, + const opt::optional<std::string> currentScope, + const std::string &nodePath, + const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc, + typename DocumentCache<AdapterType>::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<AdapterType>( + rootSchema, rootNode, schemaNode, currentScope, childPath, + fetchDoc, NULL, NULL, 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<typename AdapterType> + 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<typename AdapterType> + constraints::PropertiesConstraint makePropertiesConstraint( + Schema &rootSchema, + const AdapterType &rootNode, + const AdapterType *properties, + const AdapterType *patternProperties, + const AdapterType *additionalProperties, + const opt::optional<std::string> currentScope, + const std::string &propertiesPath, + const std::string &patternPropertiesPath, + const std::string &additionalPropertiesPath, + const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc, + const Subschema *parentSubschema, + typename DocumentCache<AdapterType>::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<AdapterType>( + 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<AdapterType>( + 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<AdapterType>( + rootSchema, rootNode, *additionalProperties, + currentScope, additionalPropertiesPath, fetchDoc, NULL, + NULL, docCache, schemaCache); + constraint.setAdditionalPropertiesSubschema(subschema); + } else { + // All other types are invalid + throw std::runtime_error( + "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; + } + + /** + * @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<typename AdapterType> + opt::optional<constraints::RequiredConstraint> + makeRequiredConstraintForSelf(const AdapterType &node, + const std::string &name) + { + if (!node.maybeBool()) { + throw std::runtime_error("Expected boolean value for 'required' attribute."); + } + + if (node.asBool()) { + constraints::RequiredConstraint constraint; + constraint.addRequiredProperty(name); + return constraint; + } + + return opt::optional<constraints::RequiredConstraint>(); + } + + /** + * @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<typename AdapterType> + constraints::RequiredConstraint makeRequiredConstraint( + const AdapterType &node) + { + constraints::RequiredConstraint constraint; + + for (const AdapterType v : node.getArray()) { + if (!v.isString()) { + throw std::runtime_error("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<typename AdapterType> + constraints::TypeConstraint makeTypeConstraint( + Schema &rootSchema, + const AdapterType &rootNode, + const AdapterType &node, + const opt::optional<std::string> currentScope, + const std::string &nodePath, + const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc, + typename DocumentCache<AdapterType>::Type &docCache, + SchemaCache &schemaCache) + { + typedef constraints::TypeConstraint TypeConstraint; + + TypeConstraint constraint; + + if (node.isString()) { + const TypeConstraint::JsonType type = + TypeConstraint::jsonTypeFromString(node.getString()); + + if (type == TypeConstraint::kAny && version == kDraft4) { + throw std::runtime_error( + "'any' type is not supported in version 4 schemas."); + } + + constraint.addNamedType(type); + + } else if (node.isArray()) { + int index = 0; + for (const AdapterType v : node.getArray()) { + if (v.isString()) { + const TypeConstraint::JsonType type = + TypeConstraint::jsonTypeFromString(v.getString()); + + if (type == TypeConstraint::kAny && version == kDraft4) { + throw std::runtime_error( + "'any' type is not supported in version 4 " + "schemas."); + } + + constraint.addNamedType(type); + + } else if (v.isObject() && version == kDraft3) { + const std::string childPath = nodePath + "/" + + std::to_string(index); + const Subschema *subschema = makeOrReuseSchema<AdapterType>( + rootSchema, rootNode, v, currentScope, childPath, + fetchDoc, NULL, NULL, docCache, schemaCache); + constraint.addSchemaType(subschema); + + } else { + throw std::runtime_error("Type name should be a string."); + } + + index++; + } + + } else if (node.isObject() && version == kDraft3) { + const Subschema *subschema = makeOrReuseSchema<AdapterType>( + rootSchema, rootNode, node, currentScope, nodePath, + fetchDoc, NULL, NULL, docCache, schemaCache); + constraint.addSchemaType(subschema); + + } else { + throw std::runtime_error("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 NULL if the boolean value is false. + */ + template<typename AdapterType> + opt::optional<constraints::UniqueItemsConstraint> + 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 NULL is returned instead. + if (node.asBool()) { + return constraints::UniqueItemsConstraint(); + } else { + return opt::optional<constraints::UniqueItemsConstraint>(); + } + } + + throw std::runtime_error( + "Expected boolean value for 'uniqueItems' constraint."); + } + +}; + +} // namespace valijson + +#ifdef __clang__ +# pragma clang diagnostic pop +#endif + +#endif
diff --git a/examples/validator/valijson/include/valijson/subschema.hpp b/examples/validator/valijson/include/valijson/subschema.hpp new file mode 100644 index 0000000..f6c4068 --- /dev/null +++ b/examples/validator/valijson/include/valijson/subschema.hpp
@@ -0,0 +1,286 @@ +#pragma once +#ifndef __VALIJSON_SUBSCHEMA_HPP +#define __VALIJSON_SUBSCHEMA_HPP + +#include <vector> + +#include <memory> + +#include <valijson/constraints/constraint.hpp> +#include <valijson/internal/optional.hpp> + +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<bool (const Constraint &)> ApplyFunction; + + /** + * @brief Construct a new Subschema object + */ + Subschema() + : allocFn(::operator new) + , freeFn(::operator delete) { } + + /** + * @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) + : allocFn(allocFn) + , freeFn(freeFn) { } + + /** + * @brief Clean up and free all memory managed by the Subschema + */ + virtual ~Subschema() + { + try { + for (std::vector<const Constraint *>::iterator itr = + constraints.begin(); itr != constraints.end(); ++itr) { + Constraint *constraint = const_cast<Constraint *>(*itr); + constraint->~Constraint(); + freeFn(constraint); + } + constraints.clear(); + } catch (const std::exception &e) { + fprintf(stderr, "Caught an exception in Subschema destructor: %s", + e.what()); + } + } + + /** + * @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) + { + Constraint *newConstraint = constraint.clone(allocFn, freeFn); + try { + constraints.push_back(newConstraint); + } catch (...) { + newConstraint->~Constraint(); + freeFn(newConstraint); + throw; + } + } + + /** + * @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, + * this function will also return \c false. + * + * @returns \c true if all invokations of the callback function are + * successful, \c false otherwise + */ + bool apply(ApplyFunction &applyFunction) const + { + bool allTrue = true; + for (const Constraint *constraint : constraints) { + allTrue = allTrue && applyFunction(*constraint); + } + + 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 invokations of the callback function return + * \c false. + * + * @returns \c true if all invokations of the callback function are + * successful, \c false otherwise + */ + bool applyStrict(ApplyFunction &applyFunction) const + { + for (const Constraint *constraint : constraints) { + if (!applyFunction(*constraint)) { + return false; + } + } + + return true; + } + + /** + * @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 (description) { + return *description; + } + + throw std::runtime_error("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 (id) { + return *id; + } + + throw std::runtime_error("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 (title) { + return *title; + } + + throw std::runtime_error("Schema does not have a title"); + } + + /** + * @brief Check whether this sub-schema has a description + * + * @return boolean value + */ + bool hasDescription() const + { + return static_cast<bool>(description); + } + + /** + * @brief Check whether this sub-schema has an ID + * + * @return boolean value + */ + bool hasId() const + { + return static_cast<bool>(id); + } + + /** + * @brief Check whether this sub-schema has a title + * + * @return boolean value + */ + bool hasTitle() const + { + return static_cast<bool>(title); + } + + /** + * @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) + { + this->description = description; + } + + void setId(const std::string &id) + { + this->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) + { + this->title = title; + } + +protected: + + CustomAlloc allocFn; + + CustomFree freeFn; + +private: + + // Disable copy construction + Subschema(const Subschema &); + + // Disable copy assignment + Subschema & operator=(const Subschema &); + + /// List of pointers to constraints that apply to this schema. + std::vector<const Constraint *> constraints; + + /// Schema description (optional) + opt::optional<std::string> description; + + /// Id to apply when resolving the schema URI + opt::optional<std::string> id; + + /// Title string associated with the schema (optional) + opt::optional<std::string> title; +}; + +} // namespace valijson + +#endif
diff --git a/examples/validator/valijson/include/valijson/utils/file_utils.hpp b/examples/validator/valijson/include/valijson/utils/file_utils.hpp new file mode 100644 index 0000000..2a9f724 --- /dev/null +++ b/examples/validator/valijson/include/valijson/utils/file_utils.hpp
@@ -0,0 +1,48 @@ +#pragma once +#ifndef __VALIJSON_FILE_UTILS_HPP +#define __VALIJSON_FILE_UTILS_HPP + +#include <fstream> +#include <limits> + +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<unsigned int>::max()) { + return false; + } + + dest.clear(); + dest.reserve(static_cast<unsigned int>(offset)); + + // Assign file contents to destination string + file.seekg(0, std::ios::beg); + dest.assign(std::istreambuf_iterator<char>(file), + std::istreambuf_iterator<char>()); + + return true; +} + +} // namespace utils +} // namespace valijson + +#endif
diff --git a/examples/validator/valijson/include/valijson/utils/json11_utils.hpp b/examples/validator/valijson/include/valijson/utils/json11_utils.hpp new file mode 100644 index 0000000..32e4f98 --- /dev/null +++ b/examples/validator/valijson/include/valijson/utils/json11_utils.hpp
@@ -0,0 +1,39 @@ +#pragma once +#ifndef __VALIJSON_UTILS_JSON11_UTILS_HPP +#define __VALIJSON_UTILS_JSON11_UTILS_HPP + +#include <iostream> + +#include <json11.hpp> + +#include <valijson/utils/file_utils.hpp> + +namespace valijson { +namespace utils { + +inline bool loadDocument(const std::string &path, json11::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 + std::string err; + document = json11::Json::parse(file, err); + if (!err.empty()) { + std::cerr << "json11 failed to parse the document:" << std::endl + << "Parse error: " << err << std::endl; + return false; + } + + return true; +} + +} // namespace utils +} // namespace valijson + +#endif +
diff --git a/examples/validator/valijson/include/valijson/utils/jsoncpp_utils.hpp b/examples/validator/valijson/include/valijson/utils/jsoncpp_utils.hpp new file mode 100644 index 0000000..ea60184 --- /dev/null +++ b/examples/validator/valijson/include/valijson/utils/jsoncpp_utils.hpp
@@ -0,0 +1,37 @@ +#pragma once +#ifndef __VALIJSON_UTILS_JSONCPP_UTILS_HPP +#define __VALIJSON_UTILS_JSONCPP_UTILS_HPP + +#include <iostream> + +#include <json/json.h> + +#include <valijson/utils/file_utils.hpp> + +namespace valijson { +namespace utils { + +inline bool loadDocument(const std::string &path, Json::Value &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; + } + + Json::Reader reader; + bool parsingSuccessful = reader.parse(file, document); + if (!parsingSuccessful) { + std::cerr << "Jsoncpp parser failed to parse the document:" << std::endl + << reader.getFormattedErrorMessages(); + return false; + } + + return true; +} + +} // namespace utils +} // namespace valijson + +#endif
diff --git a/examples/validator/valijson/include/valijson/utils/nlohmann_json_utils.hpp b/examples/validator/valijson/include/valijson/utils/nlohmann_json_utils.hpp new file mode 100644 index 0000000..01838d8 --- /dev/null +++ b/examples/validator/valijson/include/valijson/utils/nlohmann_json_utils.hpp
@@ -0,0 +1,37 @@ +#pragma once +#ifndef VALIJSON_NLOHMANN_JSON_UTILS_HPP +#define VALIJSON_NLOHMANN_JSON_UTILS_HPP + +#include <iostream> + +#include <json.hpp> +#include <valijson/utils/file_utils.hpp> + +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 + 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; + } + + return true; +} + +} // namespace utils +} // namespace valijson + +#endif //VALIJSON_NLOHMANN_JSON_UTILS_HPP
diff --git a/examples/validator/valijson/include/valijson/utils/picojson_utils.hpp b/examples/validator/valijson/include/valijson/utils/picojson_utils.hpp new file mode 100644 index 0000000..2ca4d46 --- /dev/null +++ b/examples/validator/valijson/include/valijson/utils/picojson_utils.hpp
@@ -0,0 +1,37 @@ +#pragma once +#ifndef __VALIJSON_UTILS_PICOJSON_UTILS_HPP +#define __VALIJSON_UTILS_PICOJSON_UTILS_HPP + +#include <iostream> + +#include <picojson.h> + +#include <valijson/utils/file_utils.hpp> + +namespace valijson { +namespace utils { + +inline bool loadDocument(const std::string &path, picojson::value &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 + std::string err = picojson::parse(document, file); + if (!err.empty()) { + std::cerr << "PicoJson failed to parse the document:" << std::endl + << "Parse error: " << err << std::endl; + return false; + } + + return true; +} + +} // namespace utils +} // namespace valijson + +#endif
diff --git a/examples/validator/valijson/include/valijson/utils/poco_json_utils.hpp b/examples/validator/valijson/include/valijson/utils/poco_json_utils.hpp new file mode 100644 index 0000000..747a7b4 --- /dev/null +++ b/examples/validator/valijson/include/valijson/utils/poco_json_utils.hpp
@@ -0,0 +1,40 @@ +#pragma once +#ifndef VALIJSON_POCO_JSON_UTILS_HPP +#define VALIJSON_POCO_JSON_UTILS_HPP + +#include <iostream> + +#include <Poco/JSON/JSONException.h> +#include <Poco/JSON/Object.h> +#include <Poco/JSON/Parser.h> + +#include <valijson/utils/file_utils.hpp> + +namespace valijson { +namespace utils { + +inline bool loadDocument(const std::string &path, Poco::Dynamic::Var &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 + try { + document = Poco::JSON::Parser().parse(file); + } catch (Poco::Exception const& exception) { + std::cerr << "Poco::JSON failed to parse the document\n" + << "Parse error:" << exception.what() << "\n"; + return false; + } + + return true; +} + +} // namespace utils +} // namespace valijson + +#endif //VALIJSON_POCO_JSON_UTILS_HPP
diff --git a/examples/validator/valijson/include/valijson/utils/property_tree_utils.hpp b/examples/validator/valijson/include/valijson/utils/property_tree_utils.hpp new file mode 100644 index 0000000..8b987e0 --- /dev/null +++ b/examples/validator/valijson/include/valijson/utils/property_tree_utils.hpp
@@ -0,0 +1,40 @@ +#pragma once +#ifndef __VALIJSON_UTILS_PROPERTY_TREE_UTILS_HPP +#define __VALIJSON_UTILS_PROPERTY_TREE_UTILS_HPP + +#include <iostream> +#include <sstream> + +#include <boost/property_tree/ptree.hpp> + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wshorten-64-to-32" +# include <boost/property_tree/json_parser.hpp> +# pragma clang diagnostic pop +#else +# include <boost/property_tree/json_parser.hpp> +#endif + +#include <valijson/utils/file_utils.hpp> + +namespace valijson { +namespace utils { + +inline bool loadDocument(const std::string &path, boost::property_tree::ptree &document) +{ + try { + boost::property_tree::read_json(path, document); + } catch (std::exception &e) { + std::cerr << "Boost Property Tree JSON parser failed to parse the document:" << std::endl; + std::cerr << e.what() << std::endl; + return false; + } + + return true; +} + +} // namespace utils +} // namespace valijson + +#endif
diff --git a/examples/validator/valijson/include/valijson/utils/qtjson_utils.hpp b/examples/validator/valijson/include/valijson/utils/qtjson_utils.hpp new file mode 100644 index 0000000..e130aa6 --- /dev/null +++ b/examples/validator/valijson/include/valijson/utils/qtjson_utils.hpp
@@ -0,0 +1,48 @@ +#pragma once +#ifndef __VALIJSON_UTILS_QTJSON_UTILS_HPP +#define __VALIJSON_UTILS_QTJSON_UTILS_HPP + +#include <QFile> + +#include <QJsonDocument> +#include <QJsonObject> + + +#include <valijson/utils/file_utils.hpp> + +namespace valijson { +namespace utils { + +inline bool loadDocument(const std::string &path, QJsonValue &root) +{ + // Load schema JSON from file + QFile file(QString::fromStdString(path)); + if (!file.open(QFile::ReadOnly)) { + std::cerr << "Failed to load json from file '" << path << "'." << std::endl; + return false; + } + + QByteArray data = file.readAll(); + + // Parse schema + QJsonParseError parseError; + QJsonDocument doc = QJsonDocument::fromJson(data); + if (doc.isNull()) { + std::cerr << "qt failed to parse the document:" << std::endl + << parseError.errorString().toStdString() << std::endl; + return false; + } else if (doc.isObject()) { + root = QJsonValue(doc.object()); + } else if (doc.isArray()) { + root = QJsonValue(doc.array()); + } else if (doc.isEmpty()) { + root = QJsonValue(); + } + + return true; +} + +} // namespace utils +} // namespace valijson + +#endif
diff --git a/examples/validator/valijson/include/valijson/utils/rapidjson_utils.hpp b/examples/validator/valijson/include/valijson/utils/rapidjson_utils.hpp new file mode 100644 index 0000000..cf7a895 --- /dev/null +++ b/examples/validator/valijson/include/valijson/utils/rapidjson_utils.hpp
@@ -0,0 +1,39 @@ +#pragma once +#ifndef __VALIJSON_UTILS_RAPIDJSON_UTILS_HPP +#define __VALIJSON_UTILS_RAPIDJSON_UTILS_HPP + +#include <iostream> + +#include <rapidjson/document.h> + +#include <valijson/utils/file_utils.hpp> + +namespace valijson { +namespace utils { + +template<typename Encoding, typename Allocator> +inline bool loadDocument(const std::string &path, rapidjson::GenericDocument<Encoding, Allocator> &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 + document.template Parse<0>(file.c_str()); + if (document.HasParseError()) { + std::cerr << "RapidJson failed to parse the document:" << std::endl; + std::cerr << "Parse error: " << document.GetParseError() << std::endl; + std::cerr << "Near: " << file.substr((std::max)(size_t(0), document.GetErrorOffset() - 20), 40) << std::endl; + return false; + } + + return true; +} + +} // namespace utils +} // namespace valijson + +#endif
diff --git a/examples/validator/valijson/include/valijson/utils/utf8_utils.hpp b/examples/validator/valijson/include/valijson/utils/utf8_utils.hpp new file mode 100644 index 0000000..4c3bc48 --- /dev/null +++ b/examples/validator/valijson/include/valijson/utils/utf8_utils.hpp
@@ -0,0 +1,64 @@ +#pragma once +#ifndef __VALIJSON_UTILS_UTF8_UTILS_HPP +#define __VALIJSON_UTILS_UTF8_UTILS_HPP + +#include <stdexcept> + +/* + 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 uint32_t u8_nextchar(const char *s, int *i) +{ + uint32_t ch = 0; + int sz = 0; + + do { + ch <<= 6; + ch += (unsigned char)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) +{ + static const int maxLength = std::numeric_limits<int>::max(); + + uint64_t count = 0; + int i = 0; + + while (s[i] != 0 && u8_nextchar(s, &i) != 0) { + if (i == maxLength) { + throw std::runtime_error( + "String exceeded maximum size of " + + std::to_string(maxLength) + " bytes."); + } + count++; + } + + return count; +} + +} // namespace utils +} // namespace valijson + +#endif
diff --git a/examples/validator/valijson/include/valijson/validation_results.hpp b/examples/validator/valijson/include/valijson/validation_results.hpp new file mode 100644 index 0000000..3fdc6dd --- /dev/null +++ b/examples/validator/valijson/include/valijson/validation_results.hpp
@@ -0,0 +1,126 @@ +#pragma once +#ifndef __VALIJSON_VALIDATION_RESULTS_HPP +#define __VALIJSON_VALIDATION_RESULTS_HPP + +#include <deque> +#include <string> +#include <vector> + +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 + { + /** + * @brief Construct an Error object with no context or description. + */ + Error() { } + + /** + * @brief Construct an Error object using a context and description. + * + * @param context Context string to use + * @param description Description string to use + */ + Error(const std::vector<std::string> &context, const std::string &description) + : context(context), + description(description) { } + + /// Path to the node that failed validation. + std::vector<std::string> context; + + /// A detailed description of the validation error. + std::string description; + }; + + /** + * @brief Return begin iterator for results in the queue. + */ + std::deque<Error>::const_iterator begin() const + { + return errors.begin(); + } + + /** + * @brief Return end iterator for results in the queue. + */ + std::deque<Error>::const_iterator end() const + { + return errors.end(); + } + + /** + * @brief Return the number of errors in the queue. + */ + size_t numErrors() const + { + return 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) + { + 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<std::string> &context, const std::string &description) + { + errors.push_back(Error(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 (errors.empty()) { + return false; + } + + error = errors.front(); + errors.pop_front(); + return true; + } + +private: + + /// FIFO queue of validation errors that have been reported + std::deque<Error> errors; + +}; + +} // namespace valijson + +#endif // __VALIJSON_VALIDATION_RESULTS_HPP
diff --git a/examples/validator/valijson/include/valijson/validation_visitor.hpp b/examples/validator/valijson/include/valijson/validation_visitor.hpp new file mode 100644 index 0000000..548bd28 --- /dev/null +++ b/examples/validator/valijson/include/valijson/validation_visitor.hpp
@@ -0,0 +1,1706 @@ +#pragma once +#ifndef __VALIJSON_VALIDATION_VISITOR_HPP +#define __VALIJSON_VALIDATION_VISITOR_HPP + +#include <cmath> +#include <string> +#include <regex> + +#include <valijson/constraints/concrete_constraints.hpp> +#include <valijson/constraints/constraint_visitor.hpp> +#include <valijson/validation_results.hpp> + +#include <valijson/utils/utf8_utils.hpp> + +namespace valijson { + +class ValidationResults; + +/** + * @brief Implementation of the ConstraintVisitor interface that validates a + * target document + * + * @tparam AdapterType Adapter type for the target document. + */ +template<typename AdapterType> +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 NULL, validation errors will caused validation to + * stop immediately. + */ + ValidationVisitor(const AdapterType &target, + const std::vector<std::string> &context, + const bool strictTypes, + ValidationResults *results) + : target(target), + context(context), + results(results), + strictTypes(strictTypes) { } + + /** + * @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) + { + // 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, *this)); + + // Perform validation against each constraint defined in the schema + if (results == NULL) { + // 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 + */ + virtual bool visit(const AllOfConstraint &constraint) + { + bool validated = true; + + constraint.applyToSubschemas(ValidateSubschemas(target, context, + true, false, *this, results, NULL, &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 + */ + virtual bool visit(const AnyOfConstraint &constraint) + { + unsigned int numValidated = 0; + + ValidationResults newResults; + ValidationResults *childResults = (results) ? &newResults : NULL; + + ValidationVisitor<AdapterType> v(target, context, strictTypes, childResults); + constraint.applyToSubschemas(ValidateSubschemas(target, context, false, + true, v, childResults, &numValidated, NULL)); + + if (numValidated == 0 && results) { + ValidationResults::Error childError; + while (childResults->popError(childError)) { + results->pushError( + childError.context, + childError.description); + } + results->pushError(context, "Failed to validate against any child " + "schemas allowed by anyOf constraint."); + } + + return numValidated > 0; + } + + /** + * @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 + */ + virtual bool visit(const DependenciesConstraint &constraint) + { + // Ignore non-objects + if ((strictTypes && !target.isObject()) || (!target.maybeObject())) { + return true; + } + + // Object to be validated + const typename AdapterType::Object object = 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, context, results, &validated)); + if (!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, context, *this, results, &validated)); + if (!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 + */ + virtual bool visit(const EnumConstraint &constraint) + { + unsigned int numValidated = 0; + constraint.applyToValues(ValidateEquality(target, context, false, true, + strictTypes, NULL, &numValidated)); + + if (numValidated == 0) { + if (results) { + results->pushError(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 + */ + virtual bool visit(const LinearItemsConstraint &constraint) + { + // Ignore values that are not arrays + if ((strictTypes && !target.isArray()) || (!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 = 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 (results) { + results->pushError(context, + "Array contains more items than allowed by " + "items constraint."); + validated = false; + } else { + return false; + } + } + } + + constraint.applyToItemSubschemas(ValidateItems(arr, context, true, + results != NULL, strictTypes, results, &numValidated, + &validated)); + + if (!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<std::string> newContext = context; + newContext.push_back("[" + + std::to_string(index) + "]"); + + ValidationVisitor<AdapterType> validator(*itr, newContext, + strictTypes, results); + + if (!validator.validateSchema(*additionalItemsSubschema)) { + if (results) { + results->pushError(context, + "Failed to validate item #" + + std::to_string(index) + + " against additional items schema."); + validated = false; + } else { + return false; + } + } + + index++; + } + + } else if (results) { + results->pushError(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 + */ + virtual bool visit(const MaximumConstraint &constraint) + { + if ((strictTypes && !target.isNumber()) || !target.maybeDouble()) { + // Ignore values that are not numbers + return true; + } + + const double maximum = constraint.getMaximum(); + + if (constraint.getExclusiveMaximum()) { + if (target.asDouble() >= maximum) { + if (results) { + results->pushError(context, "Expected number less than " + + std::to_string(maximum)); + } + + return false; + } + + } else if (target.asDouble() > maximum) { + if (results) { + results->pushError(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 + */ + virtual bool visit(const MaxItemsConstraint &constraint) + { + if ((strictTypes && !target.isArray()) || !target.maybeArray()) { + return true; + } + + const uint64_t maxItems = constraint.getMaxItems(); + if (target.asArray().size() <= maxItems) { + return true; + } + + if (results) { + results->pushError(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 + */ + virtual bool visit(const MaxLengthConstraint &constraint) + { + if ((strictTypes && !target.isString()) || !target.maybeString()) { + return true; + } + + const std::string s = target.asString(); + const uint64_t len = utils::u8_strlen(s.c_str()); + const uint64_t maxLength = constraint.getMaxLength(); + if (len <= maxLength) { + return true; + } + + if (results) { + results->pushError(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 + */ + virtual bool visit(const MaxPropertiesConstraint &constraint) + { + if ((strictTypes && !target.isObject()) || !target.maybeObject()) { + return true; + } + + const uint64_t maxProperties = constraint.getMaxProperties(); + + if (target.asObject().size() <= maxProperties) { + return true; + } + + if (results) { + results->pushError(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 + */ + virtual bool visit(const MinimumConstraint &constraint) + { + if ((strictTypes && !target.isNumber()) || !target.maybeDouble()) { + // Ignore values that are not numbers + return true; + } + + const double minimum = constraint.getMinimum(); + + if (constraint.getExclusiveMinimum()) { + if (target.asDouble() <= minimum) { + if (results) { + results->pushError(context, + "Expected number greater than " + + std::to_string(minimum)); + } + + return false; + } + } else if (target.asDouble() < minimum) { + if (results) { + results->pushError(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 + */ + virtual bool visit(const MinItemsConstraint &constraint) + { + if ((strictTypes && !target.isArray()) || !target.maybeArray()) { + return true; + } + + const uint64_t minItems = constraint.getMinItems(); + if (target.asArray().size() >= minItems) { + return true; + } + + if (results) { + results->pushError(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 + */ + virtual bool visit(const MinLengthConstraint &constraint) + { + if ((strictTypes && !target.isString()) || !target.maybeString()) { + return true; + } + + const std::string s = target.asString(); + const uint64_t len = utils::u8_strlen(s.c_str()); + const uint64_t minLength = constraint.getMinLength(); + if (len >= minLength) { + return true; + } + + if (results) { + results->pushError(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 + */ + virtual bool visit(const MinPropertiesConstraint &constraint) + { + if ((strictTypes && !target.isObject()) || !target.maybeObject()) { + return true; + } + + const uint64_t minProperties = constraint.getMinProperties(); + + if (target.asObject().size() >= minProperties) { + return true; + } + + if (results) { + results->pushError(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 + */ + virtual bool visit(const MultipleOfDoubleConstraint &constraint) + { + const double divisor = constraint.getDivisor(); + + double d = 0.; + if (target.maybeDouble()) { + if (!target.asDouble(d)) { + if (results) { + results->pushError(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 (target.maybeInteger()) { + int64_t i = 0; + if (!target.asInteger(i)) { + if (results) { + results->pushError(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<double>(i); + } else { + return true; + } + + if (d == 0) { + return true; + } + + const double r = remainder(d, divisor); + + if (fabs(r) > std::numeric_limits<double>::epsilon()) { + if (results) { + results->pushError(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 + */ + virtual bool visit(const MultipleOfIntConstraint &constraint) + { + const int64_t divisor = constraint.getDivisor(); + + int64_t i = 0; + if (target.maybeInteger()) { + if (!target.asInteger(i)) { + if (results) { + results->pushError(context, "Value could not be converted " + "to an integer for multipleOf check"); + } + return false; + } + } else if (target.maybeDouble()) { + double d; + if (!target.asDouble(d)) { + if (results) { + results->pushError(context, "Value could not be converted " + "to a double for multipleOf check"); + } + return false; + } + i = static_cast<int64_t>(d); + } else { + return true; + } + + if (i == 0) { + return true; + } + + if (i % divisor != 0) { + if (results) { + results->pushError(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 NULL pointer, 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 + */ + virtual bool visit(const NotConstraint &constraint) + { + const Subschema *subschema = constraint.getSubschema(); + if (!subschema) { + // Treat NULL pointer like empty schema + return false; + } + + ValidationVisitor<AdapterType> v(target, context, strictTypes, NULL); + if (v.validateSchema(*subschema)) { + if (results) { + results->pushError(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 + */ + virtual bool visit(const OneOfConstraint &constraint) + { + unsigned int numValidated = 0; + + ValidationResults newResults; + ValidationResults *childResults = (results) ? &newResults : NULL; + + ValidationVisitor<AdapterType> v(target, context, strictTypes, childResults); + constraint.applyToSubschemas(ValidateSubschemas(target, context, + true, true, v, childResults, &numValidated, NULL)); + + if (numValidated == 0) { + if (results) { + ValidationResults::Error childError; + while (childResults->popError(childError)) { + results->pushError( + childError.context, + childError.description); + } + results->pushError(context, "Failed to validate against any " + "child schemas allowed by oneOf constraint."); + } + return false; + } else if (numValidated != 1) { + if (results) { + results->pushError(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 + */ + virtual bool visit(const PatternConstraint &constraint) + { + if ((strictTypes && !target.isString()) || !target.maybeString()) { + return true; + } + + const std::regex patternRegex( + constraint.getPattern<std::string::allocator_type>()); + + if (!std::regex_search(target.asString(), patternRegex)) { + if (results) { + results->pushError(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 + */ + virtual bool visit(const constraints::PolyConstraint &constraint) + { + return constraint.validate(target, context, 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 + */ + virtual bool visit(const PropertiesConstraint &constraint) + { + if ((strictTypes && !target.isObject()) || !target.maybeObject()) { + return true; + } + + bool validated = true; + + // Track which properties have already been validated + std::set<std::string> propertiesMatched; + + // Validate properties against subschemas for matching 'properties' + // constraints + const typename AdapterType::Object object = target.asObject(); + constraint.applyToProperties(ValidatePropertySubschemas(object, context, + true, results != NULL, true, strictTypes, results, + &propertiesMatched, &validated)); + + // Exit early if validation failed, and we're not collecting exhaustive + // validation results + if (!validated && !results) { + return false; + } + + // Validate properties against subschemas for matching patternProperties + // constraints + constraint.applyToPatternProperties(ValidatePatternPropertySubschemas( + object, context, true, false, true, strictTypes, results, + &propertiesMatched, &validated)); + + // Validate against additionalProperties subschema for any properties + // that have not yet been matched + const Subschema *additionalPropertiesSubschema = + constraint.getAdditionalPropertiesSubschema(); + if (!additionalPropertiesSubschema) { + if (propertiesMatched.size() != target.getObjectSize()) { + if (results) { + results->pushError(context, "Object contains properties " + "that could not be validated using 'properties' " + "or 'additionalProperties' constraints"); + } + + return false; + } + + return validated; + } + + for (const typename AdapterType::ObjectMember m : object) { + if (propertiesMatched.find(m.first) == propertiesMatched.end()) { + // Update context + std::vector<std::string> newContext = context; + newContext.push_back("[" + m.first + "]"); + + // Create a validator to validate the property's value + ValidationVisitor validator(m.second, newContext, strictTypes, + results); + if (!validator.validateSchema(*additionalPropertiesSubschema)) { + if (results) { + results->pushError(context, "Failed to validate " + "against additional properties schema"); + } + + validated = false; + } + } + } + + return validated; + } + + /** + * @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 + */ + virtual bool visit(const RequiredConstraint &constraint) + { + if ((strictTypes && !target.isObject()) || !target.maybeObject()) { + if (results) { + results->pushError(context, + "Object required to validate 'required' properties."); + } + return false; + } + + bool validated = true; + const typename AdapterType::Object object = target.asObject(); + constraint.applyToRequiredProperties(ValidateProperties(object, context, + true, results != NULL, 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 + */ + virtual bool visit(const SingularItemsConstraint &constraint) + { + // Ignore values that are not arrays + if (!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 : target.getArray()) { + // Update context for current array item + std::vector<std::string> newContext = context; + newContext.push_back("[" + + std::to_string(index) + "]"); + + // Create a validator for the current array item + ValidationVisitor<AdapterType> validationVisitor(item, + newContext, strictTypes, results); + + // Perform validation + if (!validationVisitor.validateSchema(*itemsSubschema)) { + if (results) { + results->pushError(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 + */ + virtual bool visit(const TypeConstraint &constraint) + { + // Check named types + { + // ValidateNamedTypes functor assumes target is invalid + bool validated = false; + constraint.applyToNamedTypes(ValidateNamedTypes(target, false, + true, strictTypes, &validated)); + if (validated) { + return true; + } + } + + // Check schema-based types + { + unsigned int numValidated = 0; + constraint.applyToSchemaTypes(ValidateSubschemas(target, context, + false, true, *this, NULL, &numValidated, NULL)); + if (numValidated > 0) { + return true; + } else if (results) { + results->pushError(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 + */ + virtual bool visit(const UniqueItemsConstraint &) + { + if ((strictTypes && !target.isArray()) || !target.maybeArray()) { + return true; + } + + // Empty arrays are always valid + if (target.getArraySize() == 0) { + return true; + } + + const typename AdapterType::Array targetArray = target.asArray(); + const typename AdapterType::Array::const_iterator end = targetArray.end(); + + bool validated = true; + 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 (results) { + results->pushError(context, "Elements at indexes #" + + std::to_string(outerIndex) + " and #" + + std::to_string(innerIndex) + " violate uniqueness constraint."); + validated = false; + } else { + return 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<std::string> &context, + bool continueOnSuccess, + bool continueOnFailure, + bool strictTypes, + ValidationResults *results, + unsigned int *numValidated) + : target(target), + context(context), + continueOnSuccess(continueOnSuccess), + continueOnFailure(continueOnFailure), + strictTypes(strictTypes), + results(results), + numValidated(numValidated) { } + + template<typename OtherValue> + bool operator()(const OtherValue &value) const + { + if (value.equalTo(target, strictTypes)) { + if (numValidated) { + (*numValidated)++; + } + + return continueOnSuccess; + } + + if (results) { + results->pushError(context, + "Target value and comparison value are not equal"); + } + + return continueOnFailure; + } + + private: + const AdapterType ⌖ + const std::vector<std::string> &context; + bool continueOnSuccess; + bool continueOnFailure; + bool strictTypes; + ValidationResults * const results; + unsigned int * const numValidated; + }; + + /** + * @brief Functor to validate the presence of a set of properties + */ + struct ValidateProperties + { + ValidateProperties( + const typename AdapterType::Object &object, + const std::vector<std::string> &context, + bool continueOnSuccess, + bool continueOnFailure, + ValidationResults *results, + bool *validated) + : object(object), + context(context), + continueOnSuccess(continueOnSuccess), + continueOnFailure(continueOnFailure), + results(results), + validated(validated) { } + + template<typename StringType> + bool operator()(const StringType &property) const + { + if (object.find(property.c_str()) == object.end()) { + if (validated) { + *validated = false; + } + + if (results) { + results->pushError(context, "Missing required property '" + + std::string(property.c_str()) + "'."); + } + + return continueOnFailure; + } + + return continueOnSuccess; + } + + private: + const typename AdapterType::Object &object; + const std::vector<std::string> &context; + bool continueOnSuccess; + bool continueOnFailure; + ValidationResults * const results; + bool * const validated; + }; + + /** + * @brief Functor to validate property-based dependencies + */ + struct ValidatePropertyDependencies + { + ValidatePropertyDependencies( + const typename AdapterType::Object &object, + const std::vector<std::string> &context, + ValidationResults *results, + bool *validated) + : object(object), + context(context), + results(results), + validated(validated) { } + + template<typename StringType, typename ContainerType> + bool operator()( + const StringType &propertyName, + const ContainerType &dependencyNames) const + { + const std::string propertyNameKey(propertyName.c_str()); + if (object.find(propertyNameKey) == object.end()) { + return true; + } + + typedef typename ContainerType::value_type ValueType; + for (const ValueType &dependencyName : dependencyNames) { + const std::string dependencyNameKey(dependencyName.c_str()); + if (object.find(dependencyNameKey) == object.end()) { + if (validated) { + *validated = false; + } + if (results) { + results->pushError(context, "Missing dependency '" + + dependencyNameKey + "'."); + } else { + return false; + } + } + } + + return true; + } + + private: + const typename AdapterType::Object &object; + const std::vector<std::string> &context; + ValidationResults * const results; + bool * const validated; + }; + + /** + * @brief Functor to validate against sub-schemas in 'items' constraint + */ + struct ValidateItems + { + ValidateItems( + const typename AdapterType::Array &arr, + const std::vector<std::string> &context, + bool continueOnSuccess, + bool continueOnFailure, + bool strictTypes, + ValidationResults *results, + unsigned int *numValidated, + bool *validated) + : arr(arr), + context(context), + continueOnSuccess(continueOnSuccess), + continueOnFailure(continueOnFailure), + strictTypes(strictTypes), + results(results), + numValidated(numValidated), + validated(validated) { } + + bool operator()(unsigned int index, const Subschema *subschema) const + { + // Check that there are more elements to validate + if (index >= arr.size()) { + return false; + } + + // Update context + std::vector<std::string> newContext = context; + newContext.push_back( + "[" + std::to_string(index) + "]"); + + // Find array item + typename AdapterType::Array::const_iterator itr = arr.begin(); + itr.advance(index); + + // Validate current array item + ValidationVisitor validator(*itr, newContext, strictTypes, results); + if (validator.validateSchema(*subschema)) { + if (numValidated) { + (*numValidated)++; + } + + return continueOnSuccess; + } + + if (validated) { + *validated = false; + } + + if (results) { + results->pushError(newContext, + "Failed to validate item #" + + std::to_string(index) + + " against corresponding item schema."); + } + + return continueOnFailure; + } + + private: + const typename AdapterType::Array &arr; + const std::vector<std::string> &context; + bool continueOnSuccess; + bool continueOnFailure; + bool strictTypes; + ValidationResults * const results; + unsigned int * const numValidated; + bool * const validated; + + }; + + /** + * @brief Functor to validate value against named JSON types + */ + struct ValidateNamedTypes + { + ValidateNamedTypes( + const AdapterType &target, + bool continueOnSuccess, + bool continueOnFailure, + bool strictTypes, + bool *validated) + : target(target), + continueOnSuccess(continueOnSuccess), + continueOnFailure(continueOnFailure), + strictTypes(strictTypes), + 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 = target.isArray(); + break; + case TypeConstraint::kBoolean: + valid = target.isBool() || (!strictTypes && target.maybeBool()); + break; + case TypeConstraint::kInteger: + valid = target.isInteger() || + (!strictTypes && target.maybeInteger()); + break; + case TypeConstraint::kNull: + valid = target.isNull() || + (!strictTypes && target.maybeNull()); + break; + case TypeConstraint::kNumber: + valid = target.isNumber() || + (!strictTypes && target.maybeDouble()); + break; + case TypeConstraint::kObject: + valid = target.isObject(); + break; + case TypeConstraint::kString: + valid = target.isString(); + break; + default: + break; + } + + if (valid && validated) { + *validated = true; + } + + return (valid && continueOnSuccess) || continueOnFailure; + } + + private: + const AdapterType target; + const bool continueOnSuccess; + const bool continueOnFailure; + const bool strictTypes; + bool * const 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<std::string> &context, + bool continueOnSuccess, + bool continueOnFailure, + bool continueIfUnmatched, + bool strictTypes, + ValidationResults *results, + std::set<std::string> *propertiesMatched, + bool *validated) + : object(object), + context(context), + continueOnSuccess(continueOnSuccess), + continueOnFailure(continueOnFailure), + continueIfUnmatched(continueIfUnmatched), + strictTypes(strictTypes), + results(results), + propertiesMatched(propertiesMatched), + validated(validated) { } + + template<typename StringType> + 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 : object) { + if (std::regex_search(m.first, r)) { + matchFound = true; + if (propertiesMatched) { + propertiesMatched->insert(m.first); + } + + // Update context + std::vector<std::string> newContext = context; + newContext.push_back("[" + m.first + "]"); + + // Recursively validate property's value + ValidationVisitor validator(m.second, newContext, + strictTypes, results); + if (validator.validateSchema(*subschema)) { + continue; + } + + if (results) { + results->pushError(context, "Failed to validate " + "against schema associated with pattern '" + + patternPropertyStr + "'."); + } + + if (validated) { + *validated = false; + } + + if (!continueOnFailure) { + return false; + } + } + } + + // Allow iteration to terminate if there was not at least one match + if (!matchFound && !continueIfUnmatched) { + return false; + } + + return continueOnSuccess; + } + + private: + const typename AdapterType::Object &object; + const std::vector<std::string> &context; + const bool continueOnSuccess; + const bool continueOnFailure; + const bool continueIfUnmatched; + const bool strictTypes; + ValidationResults * const results; + std::set<std::string> * const propertiesMatched; + bool * const validated; + }; + + /** + * @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<std::string> &context, + bool continueOnSuccess, + bool continueOnFailure, + bool continueIfUnmatched, + bool strictTypes, + ValidationResults *results, + std::set<std::string> *propertiesMatched, + bool *validated) + : object(object), + context(context), + continueOnSuccess(continueOnSuccess), + continueOnFailure(continueOnFailure), + continueIfUnmatched(continueIfUnmatched), + strictTypes(strictTypes), + results(results), + propertiesMatched(propertiesMatched), + validated(validated) { } + + template<typename StringType> + bool operator()(const StringType &propertyName, + const Subschema *subschema) const + { + const std::string propertyNameKey(propertyName.c_str()); + const typename AdapterType::Object::const_iterator itr = + object.find(propertyNameKey); + if (itr == object.end()) { + return continueIfUnmatched; + } + + if (propertiesMatched) { + propertiesMatched->insert(propertyNameKey); + } + + // Update context + std::vector<std::string> newContext = context; + newContext.push_back("[" + propertyNameKey + "]"); + + // Recursively validate property's value + ValidationVisitor validator(itr->second, newContext, strictTypes, + results); + if (validator.validateSchema(*subschema)) { + return continueOnSuccess; + } + + if (results) { + results->pushError(context, "Failed to validate against " + "schema associated with property name '" + + propertyNameKey + "'."); + } + + if (validated) { + *validated = false; + } + + return continueOnFailure; + } + + private: + const typename AdapterType::Object &object; + const std::vector<std::string> &context; + const bool continueOnSuccess; + const bool continueOnFailure; + const bool continueIfUnmatched; + const bool strictTypes; + ValidationResults * const results; + std::set<std::string> * const propertiesMatched; + bool * const validated; + }; + + /** + * @brief Functor to validate schema-based dependencies + */ + struct ValidateSchemaDependencies + { + ValidateSchemaDependencies( + const typename AdapterType::Object &object, + const std::vector<std::string> &context, + ValidationVisitor &validationVisitor, + ValidationResults *results, + bool *validated) + : object(object), + context(context), + validationVisitor(validationVisitor), + results(results), + validated(validated) { } + + template<typename StringType> + bool operator()( + const StringType &propertyName, + const Subschema *schemaDependency) const + { + const std::string propertyNameKey(propertyName.c_str()); + if (object.find(propertyNameKey) == object.end()) { + return true; + } + + if (!validationVisitor.validateSchema(*schemaDependency)) { + if (validated) { + *validated = false; + } + if (results) { + results->pushError(context, + "Failed to validate against dependent schema."); + } else { + return false; + } + } + + return true; + } + + private: + const typename AdapterType::Object &object; + const std::vector<std::string> &context; + ValidationVisitor &validationVisitor; + ValidationResults * const results; + bool * const 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<std::string> &context, + bool continueOnSuccess, + bool continueOnFailure, + ValidationVisitor &validationVisitor, + ValidationResults *results, + unsigned int *numValidated, + bool *validated) + : adapter(adapter), + context(context), + continueOnSuccess(continueOnSuccess), + continueOnFailure(continueOnFailure), + validationVisitor(validationVisitor), + results(results), + numValidated(numValidated), + validated(validated) { } + + bool operator()(unsigned int index, const Subschema *subschema) const + { + if (validationVisitor.validateSchema(*subschema)) { + if (numValidated) { + (*numValidated)++; + } + + return continueOnSuccess; + } + + if (validated) { + *validated = false; + } + + if (results) { + results->pushError(context, + "Failed to validate against child schema #" + + std::to_string(index) + "."); + } + + return continueOnFailure; + } + + private: + const AdapterType &adapter; + const std::vector<std::string> &context; + bool continueOnSuccess; + bool continueOnFailure; + ValidationVisitor &validationVisitor; + ValidationResults * const results; + unsigned int * const numValidated; + bool * const 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<AdapterType> &visitor) + { + return constraint.accept(visitor); + } + + /// The JSON value being validated + const AdapterType target; + + /// Vector of strings describing the current object context + const std::vector<std::string> context; + + /// Optional pointer to a ValidationResults object to be populated + ValidationResults *results; + + /// Option to use strict type comparison + const bool strictTypes; + +}; + +} // namespace valijson + +#endif
diff --git a/examples/validator/valijson/include/valijson/validator.hpp b/examples/validator/valijson/include/valijson/validator.hpp new file mode 100644 index 0000000..4e4a586 --- /dev/null +++ b/examples/validator/valijson/include/valijson/validator.hpp
@@ -0,0 +1,78 @@ +#pragma once +#ifndef __VALIJSON_VALIDATOR_HPP +#define __VALIJSON_VALIDATOR_HPP + +#include <valijson/schema.hpp> +#include <valijson/validation_visitor.hpp> + +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<typename AdapterType> + bool validate(const Subschema &schema, const AdapterType &target, + ValidationResults *results) + { + // Construct a ValidationVisitor to perform validation at the root level + ValidationVisitor<AdapterType> v(target, + std::vector<std::string>(1, "<root>"), strictTypes, results); + + return v.validateSchema(schema); + } + +private: + + /// Flag indicating that strict type comparisons should be used + const bool strictTypes; + +}; + +} // namespace valijson + +#endif