aboutsummaryrefslogtreecommitdiff
path: root/include/jsoncons_ext/jsonschema
diff options
context:
space:
mode:
Diffstat (limited to 'include/jsoncons_ext/jsonschema')
-rw-r--r--include/jsoncons_ext/jsonschema/format_validator.hpp968
-rw-r--r--include/jsoncons_ext/jsonschema/json_validator.hpp120
-rw-r--r--include/jsoncons_ext/jsonschema/jsonschema.hpp13
-rw-r--r--include/jsoncons_ext/jsonschema/jsonschema_error.hpp105
-rw-r--r--include/jsoncons_ext/jsonschema/jsonschema_version.hpp18
-rw-r--r--include/jsoncons_ext/jsonschema/keyword_validator.hpp1745
-rw-r--r--include/jsoncons_ext/jsonschema/keyword_validator_factory.hpp556
-rw-r--r--include/jsoncons_ext/jsonschema/schema_draft7.hpp198
-rw-r--r--include/jsoncons_ext/jsonschema/schema_location.hpp200
-rw-r--r--include/jsoncons_ext/jsonschema/schema_version.hpp35
-rw-r--r--include/jsoncons_ext/jsonschema/subschema.hpp144
11 files changed, 4102 insertions, 0 deletions
diff --git a/include/jsoncons_ext/jsonschema/format_validator.hpp b/include/jsoncons_ext/jsonschema/format_validator.hpp
new file mode 100644
index 0000000..312bf41
--- /dev/null
+++ b/include/jsoncons_ext/jsonschema/format_validator.hpp
@@ -0,0 +1,968 @@
+// Copyright 2020 Daniel Parker
+// Distributed under the Boost license, Version 1.0.
+// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+// See https://github.com/danielaparker/jsoncons for latest version
+
+#ifndef JSONCONS_JSONSCHEMA_FORMAT_VALIDATOR_HPP
+#define JSONCONS_JSONSCHEMA_FORMAT_VALIDATOR_HPP
+
+#include <jsoncons/config/jsoncons_config.hpp>
+#include <jsoncons/uri.hpp>
+#include <jsoncons/json.hpp>
+#include <jsoncons_ext/jsonpointer/jsonpointer.hpp>
+#include <jsoncons_ext/jsonschema/subschema.hpp>
+#include <cassert>
+#include <set>
+#include <sstream>
+#include <iostream>
+#include <cassert>
+#if defined(JSONCONS_HAS_STD_REGEX)
+#include <regex>
+#endif
+
+namespace jsoncons {
+namespace jsonschema {
+
+ inline
+ bool is_atext( char c)
+ {
+ switch (c)
+ {
+ case '!':
+ case '#':
+ case '$':
+ case '%':
+ case '&':
+ case '\'':
+ case '*':
+ case '+':
+ case '-':
+ case '/':
+ case '=':
+ case '?':
+ case '^':
+ case '_':
+ case '`':
+ case '{':
+ case '|':
+ case '}':
+ case '~':
+ return true;
+ default:
+ return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
+ }
+ }
+
+ inline
+ bool is_dtext( char c)
+ {
+ return (c >= 33 && c <= 90) || (c >= 94 && c <= 126);
+ }
+
+ // RFC 5322, section 3.4.1
+ inline
+ bool validate_email_rfc5322(const std::string& s)
+ {
+ enum class state_t {local_part,atom,dot_atom,quoted_string,amp,domain};
+
+ state_t state = state_t::local_part;
+ std::size_t part_length = 0;
+
+ for (char c : s)
+ {
+ switch (state)
+ {
+ case state_t::local_part:
+ {
+ if (is_atext(c))
+ {
+ state = state_t::atom;
+ }
+ else if (c == '"')
+ {
+ state = state_t::quoted_string;
+ }
+ else
+ {
+ return false;
+ }
+ break;
+ }
+ case state_t::dot_atom:
+ {
+ if (is_atext(c))
+ {
+ ++part_length;
+ state = state_t::atom;
+ }
+ else
+ return false;
+ break;
+ }
+ case state_t::atom:
+ {
+ switch (c)
+ {
+ case '@':
+ state = state_t::domain;
+ part_length = 0;
+ break;
+ case '.':
+ state = state_t::dot_atom;
+ ++part_length;
+ break;
+ default:
+ if (is_atext(c))
+ ++part_length;
+ else
+ return false;
+ break;
+ }
+ break;
+ }
+ case state_t::quoted_string:
+ {
+ if (c == '\"')
+ {
+ state = state_t::amp;
+ }
+ else
+ {
+ ++part_length;
+ }
+ break;
+ }
+ case state_t::amp:
+ {
+ if (c == '@')
+ {
+ state = state_t::domain;
+ part_length = 0;
+ }
+ else
+ {
+ return false;
+ }
+ break;
+ }
+ case state_t::domain:
+ {
+ if (is_dtext(c))
+ {
+ ++part_length;
+ }
+ else
+ {
+ return false;
+ }
+ break;
+ }
+ }
+ }
+
+ return state == state_t::domain && part_length > 0;
+ }
+
+ // RFC 2673, Section 3.2
+
+ inline
+ bool validate_ipv6_rfc2373(const std::string& s)
+ {
+ enum class state_t{start,expect_hexdig_or_unspecified,
+ hexdig, decdig,expect_unspecified, unspecified};
+
+ state_t state = state_t::start;
+
+ std::size_t digit_count = 0;
+ std::size_t piece_count = 0;
+ std::size_t piece_count2 = 0;
+ bool has_unspecified = false;
+ std::size_t dec_value = 0;
+
+ for (std::size_t i = 0; i < s.length(); ++i)
+ {
+ char c = s[i];
+ switch (state)
+ {
+ case state_t::start:
+ {
+ switch (c)
+ {
+ case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9':
+ case 'A':case 'B':case 'C':case 'D':case 'E':case 'F':
+ case 'a':case 'b':case 'c':case 'd':case 'e':case 'f':
+ state = state_t::hexdig;
+ ++digit_count;
+ piece_count = 0;
+ break;
+ case ':':
+ if (!has_unspecified)
+ {
+ state = state_t::expect_unspecified;
+ }
+ else
+ {
+ return false;
+ }
+ break;
+ default:
+ return false;
+ }
+ break;
+ }
+ case state_t::expect_hexdig_or_unspecified:
+ {
+ switch (c)
+ {
+ case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9':
+ dec_value = dec_value*10 + static_cast<std::size_t>(c - '0'); // just in case this piece is followed by a dot
+ state = state_t::hexdig;
+ ++digit_count;
+ break;
+ case 'A':case 'B':case 'C':case 'D':case 'E':case 'F':
+ case 'a':case 'b':case 'c':case 'd':case 'e':case 'f':
+ state = state_t::hexdig;
+ ++digit_count;
+ break;
+ case ':':
+ if (!has_unspecified)
+ {
+ has_unspecified = true;
+ state = state_t::unspecified;
+ }
+ else
+ {
+ return false;
+ }
+ break;
+ default:
+ return false;
+ }
+ break;
+ }
+ case state_t::expect_unspecified:
+ {
+ if (c == ':')
+ {
+ has_unspecified = true;
+ state = state_t::unspecified;
+ }
+ else
+ {
+ return false;
+ }
+ break;
+ }
+ case state_t::hexdig:
+ {
+ switch (c)
+ {
+ case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9':
+ case 'A':case 'B':case 'C':case 'D':case 'E':case 'F':
+ case 'a':case 'b':case 'c':case 'd':case 'e':case 'f':
+ ++digit_count;
+ break;
+ case ':':
+ if (digit_count <= 4)
+ {
+ ++piece_count;
+ digit_count = 0;
+ dec_value = 0;
+ state = state_t::expect_hexdig_or_unspecified;
+ }
+ else
+ {
+ return false;
+ }
+ break;
+ case '.':
+ if (piece_count == 6 || has_unspecified)
+ {
+ ++piece_count2;
+ state = state_t::decdig;
+ dec_value = 0;
+ }
+ else
+ {
+ return false;
+ }
+ break;
+ default:
+ return false;
+ }
+ break;
+ }
+ case state_t::decdig:
+ {
+ switch (c)
+ {
+ case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9':
+ dec_value = dec_value*10 + static_cast<std::size_t>(c - '0');
+ ++digit_count;
+ break;
+ case '.':
+ if (dec_value > 0xff)
+ {
+ return false;
+ }
+ digit_count = 0;
+ dec_value = 0;
+ ++piece_count2;
+ break;
+ default:
+ return false;
+ }
+ break;
+ }
+ case state_t::unspecified:
+ {
+ switch (c)
+ {
+ case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9':
+ case 'A':case 'B':case 'C':case 'D':case 'E':case 'F':
+ case 'a':case 'b':case 'c':case 'd':case 'e':case 'f':
+ state = state_t::hexdig;
+ ++digit_count;
+ break;
+ default:
+ return false;
+ }
+ break;
+ }
+ default:
+ return false;
+ }
+ }
+
+ switch (state)
+ {
+ case state_t::unspecified:
+ return piece_count <= 8;
+ case state_t::hexdig:
+ if (digit_count <= 4)
+ {
+ ++piece_count;
+ return digit_count > 0 && (piece_count == 8 || (has_unspecified && piece_count <= 8));
+ }
+ else
+ {
+ return false;
+ }
+ case state_t::decdig:
+ ++piece_count2;
+ if (dec_value > 0xff)
+ {
+ return false;
+ }
+ return digit_count > 0 && piece_count2 == 4;
+ default:
+ return false;
+ }
+ }
+
+ // RFC 2673, Section 3.2
+
+ inline
+ bool validate_ipv4_rfc2673(const std::string& s)
+ {
+ enum class state_t {expect_indicator_or_dotted_quad,decbyte,
+ bindig, octdig, hexdig};
+
+ state_t state = state_t::expect_indicator_or_dotted_quad;
+
+ std::size_t digit_count = 0;
+ std::size_t decbyte_count = 0;
+ std::size_t value = 0;
+
+ for (std::size_t i = 0; i < s.length(); ++i)
+ {
+ char c = s[i];
+ switch (state)
+ {
+ case state_t::expect_indicator_or_dotted_quad:
+ {
+ switch (c)
+ {
+ case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9':
+ state = state_t::decbyte;
+ decbyte_count = 0;
+ digit_count = 1;
+ value = 0;
+ break;
+ case 'b':
+ state = state_t::bindig;
+ digit_count = 0;
+ break;
+ case 'o':
+ state = state_t::octdig;
+ digit_count = 0;
+ break;
+ case 'x':
+ state = state_t::hexdig;
+ digit_count = 0;
+ break;
+ default:
+ return false;
+ }
+ break;
+ }
+ case state_t::bindig:
+ {
+ if (digit_count >= 256)
+ {
+ return false;
+ }
+ switch (c)
+ {
+ case '0':case '1':
+ ++digit_count;
+ break;
+ default:
+ return false;
+ }
+ break;
+ }
+ case state_t::octdig:
+ {
+ if (digit_count >= 86)
+ {
+ return false;
+ }
+ switch (c)
+ {
+ case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':
+ ++digit_count;
+ break;
+ default:
+ return false;
+ }
+ break;
+ }
+ case state_t::hexdig:
+ {
+ if (digit_count >= 64)
+ {
+ return false;
+ }
+ switch (c)
+ {
+ case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9':
+ case 'A':case 'B':case 'C':case 'D':case 'E':case 'F':
+ case 'a':case 'b':case 'c':case 'd':case 'e':case 'f':
+ ++digit_count;
+ break;
+ default:
+ return false;
+ }
+ break;
+ }
+ case state_t::decbyte:
+ {
+ if (decbyte_count >= 4)
+ {
+ return false;
+ }
+ switch (c)
+ {
+ case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9':
+ {
+ if (digit_count >= 3)
+ {
+ return false;
+ }
+ ++digit_count;
+ value = value*10 + static_cast<std::size_t>(c - '0');
+ if (value > 255)
+ {
+ return false;
+ }
+ break;
+ }
+ case '.':
+ if (decbyte_count > 3)
+ {
+ return false;
+ }
+ ++decbyte_count;
+ digit_count = 0;
+ value = 0;
+ break;
+ default:
+ return false;
+ }
+ break;
+ }
+ default:
+ return false;
+ }
+ }
+
+ switch (state)
+ {
+ case state_t::decbyte:
+ if (digit_count > 0)
+ {
+ ++decbyte_count;
+ }
+ else
+ {
+ return false;
+ }
+ return (decbyte_count == 4) ? true : false;
+ case state_t::bindig:
+ return digit_count > 0 ? true : false;
+ case state_t::octdig:
+ return digit_count > 0 ? true : false;
+ case state_t::hexdig:
+ return digit_count > 0 ? true : false;
+ default:
+ return false;
+ }
+ }
+
+ // RFC 1034, Section 3.1
+ inline
+ bool validate_hostname_rfc1034(const std::string& hostname)
+ {
+ enum class state_t {start_label,expect_letter_or_digit_or_hyphen_or_dot};
+
+ state_t state = state_t::start_label;
+ std::size_t length = hostname.length() - 1;
+ std::size_t label_length = 0;
+
+ for (std::size_t i = 0; i < length; ++i)
+ {
+ char c = hostname[i];
+ switch (state)
+ {
+ case state_t::start_label:
+ {
+ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
+ {
+ ++label_length;
+ state = state_t::expect_letter_or_digit_or_hyphen_or_dot;
+ }
+ else
+ {
+ return false;
+ }
+ break;
+ }
+ case state_t::expect_letter_or_digit_or_hyphen_or_dot:
+ {
+ if (c == '.')
+ {
+ label_length = 0;
+ state = state_t::start_label;
+ }
+ else if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
+ (c >= '0' && c < '9') || c == '-'))
+ {
+ return false;
+ }
+ if (++label_length > 63)
+ {
+ return false;
+ }
+ break;
+ }
+ }
+ }
+
+ char last = hostname.back();
+ if (!((last >= 'a' && last <= 'z') || (last >= 'A' && last <= 'Z') || (last >= '0' && last < '9')))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ inline
+ bool is_leap_year(std::size_t year)
+ {
+ return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
+ }
+
+ inline
+ std::size_t days_in_month(std::size_t year, std::size_t month)
+ {
+ switch (month)
+ {
+ case 1: return 31;
+ case 2: return is_leap_year(year) ? 29 : 28;
+ case 3: return 31;
+ case 4: return 30;
+ case 5: return 31;
+ case 6: return 30;
+ case 7: return 31;
+ case 8: return 31;
+ case 9: return 30;
+ case 10: return 31;
+ case 11: return 30;
+ case 12: return 31;
+ default:
+ JSONCONS_UNREACHABLE();
+ break;
+ }
+ }
+
+ enum class date_time_type {date_time,date,time};
+ // RFC 3339, Section 5.6
+ inline
+ bool validate_date_time_rfc3339(const std::string& s, date_time_type type)
+ {
+ enum class state_t {fullyear,month,mday,hour,minute,second,secfrac,z,offset_hour,offset_minute};
+
+ std::size_t piece_length = 0;
+ std::size_t year = 0;
+ std::size_t month = 0;
+ std::size_t mday = 0;
+ std::size_t value = 0;
+ state_t state = (type == date_time_type::time) ? state_t::hour : state_t::fullyear;
+
+ for (char c : s)
+ {
+ switch (state)
+ {
+ case state_t::fullyear:
+ {
+ if (piece_length < 4 && (c >= '0' && c <= '9'))
+ {
+ piece_length++;
+ year = year*10 + static_cast<std::size_t>(c - '0');
+ }
+ else if (c == '-' && piece_length == 4)
+ {
+ state = state_t::month;
+ piece_length = 0;
+ }
+ else
+ {
+ return false;
+ }
+ break;
+ }
+ case state_t::month:
+ {
+ if (piece_length < 2 && (c >= '0' && c <= '9'))
+ {
+ piece_length++;
+ month = month*10 + static_cast<std::size_t>(c - '0');
+ }
+ else if (c == '-' && piece_length == 2 && (month >=1 && month <= 12))
+ {
+ state = state_t::mday;
+ piece_length = 0;
+ }
+ else
+ {
+ return false;
+ }
+ break;
+ }
+ case state_t::mday:
+ {
+ if (piece_length < 2 && (c >= '0' && c <= '9'))
+ {
+ piece_length++;
+ mday = mday *10 + static_cast<std::size_t>(c - '0');
+ }
+ else if ((c == 'T' || c == 't') && piece_length == 2 && (mday <= days_in_month(year, month)))
+ {
+ piece_length = 0;
+ state = state_t::hour;
+ }
+ else
+ {
+ return false;
+ }
+ break;
+ }
+ case state_t::hour:
+ {
+ if (piece_length < 2 && (c >= '0' && c <= '9'))
+ {
+ piece_length++;
+ value = value*10 + static_cast<std::size_t>(c - '0');
+ }
+ else if (c == ':' && piece_length == 2 && (/*value >=0 && */ value <= 23))
+ {
+ state = state_t::minute;
+ value = 0;
+ piece_length = 0;
+ }
+ else
+ {
+ return false;
+ }
+ break;
+ }
+ case state_t::minute:
+ {
+ if (piece_length < 2 && (c >= '0' && c <= '9'))
+ {
+ piece_length++;
+ value = value*10 + static_cast<std::size_t>(c - '0');
+ }
+ else if (c == ':' && piece_length == 2 && (/*value >=0 && */value <= 59))
+ {
+ state = state_t::second;
+ value = 0;
+ piece_length = 0;
+ }
+ else
+ {
+ return false;
+ }
+ break;
+ }
+ case state_t::second:
+ {
+ if (piece_length < 2 && (c >= '0' && c <= '9'))
+ {
+ piece_length++;
+ value = value*10 + static_cast<std::size_t>(c - '0');
+ }
+ else if (piece_length == 2 && (/*value >=0 && */value <= 60)) // 00-58, 00-59, 00-60 based on leap second rules
+ {
+ switch (c)
+ {
+ case '.':
+ value = 0;
+ state = state_t::secfrac;
+ break;
+ case '+':
+ case '-':
+ value = 0;
+ piece_length = 0;
+ state = state_t::offset_hour;
+ break;
+ case 'Z':
+ case 'z':
+ state = state_t::z;
+ break;
+ default:
+ return false;
+ }
+ }
+ else
+ {
+ return false;
+ }
+ break;
+ }
+ case state_t::secfrac:
+ {
+ if (c >= '0' && c <= '9')
+ {
+ value = value*10 + static_cast<std::size_t>(c - '0');
+ }
+ else
+ {
+ switch (c)
+ {
+ case '+':
+ case '-':
+ value = 0;
+ piece_length = 0;
+ state = state_t::offset_hour;
+ break;
+ case 'Z':
+ case 'z':
+ state = state_t::z;
+ break;
+ default:
+ return false;
+ }
+ }
+ break;
+ }
+ case state_t::offset_hour:
+ {
+ if (piece_length < 2 && (c >= '0' && c <= '9'))
+ {
+ piece_length++;
+ value = value*10 + static_cast<std::size_t>(c - '0');
+ }
+ else if (c == ':' && piece_length == 2 && (/*value >=0 && */value <= 23))
+ {
+ value = 0;
+ piece_length = 0;
+ state = state_t::offset_minute;
+ }
+ else
+ {
+ return false;
+ }
+ break;
+ }
+ case state_t::offset_minute:
+ {
+ if (piece_length < 2 && (c >= '0' && c <= '9'))
+ {
+ piece_length++;
+ value = value*10 + static_cast<std::size_t>(c - '0');
+ }
+ else if (c == ':' && piece_length == 2 && (/*value >=0 && */value <= 59))
+ {
+ value = 0;
+ piece_length = 0;
+ }
+ else
+ {
+ return false;
+ }
+ break;
+ }
+ case state_t::z:
+ return false;
+ }
+ }
+
+ if (type == date_time_type::date)
+ {
+ return state == state_t::mday && piece_length == 2 && (mday >= 1 && mday <= days_in_month(year, month));
+ }
+ else
+ {
+ return state == state_t::offset_minute || state == state_t::z || state == state_t::secfrac;
+ }
+ }
+
+ // format checkers
+ using format_checker = std::function<void(const std::string& absolute_keyword_location,
+ const jsonpointer::json_pointer& instance_location,
+ const std::string&,
+ error_reporter& reporter)>;
+
+ inline
+ void rfc3339_date_check(const std::string& absolute_keyword_location,
+ const jsonpointer::json_pointer& instance_location,
+ const std::string& value,
+ error_reporter& reporter)
+ {
+ if (!validate_date_time_rfc3339(value,date_time_type::date))
+ {
+ reporter.error(validation_output("date",
+ absolute_keyword_location,
+ instance_location.to_uri_fragment(),
+ "\"" + value + "\" is not a RFC 3339 date string"));
+ }
+ }
+
+ inline
+ void rfc3339_time_check(const std::string& absolute_keyword_location,
+ const jsonpointer::json_pointer& instance_location,
+ const std::string &value,
+ error_reporter& reporter)
+ {
+ if (!validate_date_time_rfc3339(value, date_time_type::time))
+ {
+ reporter.error(validation_output("time",
+ absolute_keyword_location,
+ instance_location.to_uri_fragment(),
+ "\"" + value + "\" is not a RFC 3339 time string"));
+ }
+ }
+
+ inline
+ void rfc3339_date_time_check(const std::string& absolute_keyword_location,
+ const jsonpointer::json_pointer& instance_location,
+ const std::string &value,
+ error_reporter& reporter)
+ {
+ if (!validate_date_time_rfc3339(value, date_time_type::date_time))
+ {
+ reporter.error(validation_output("date-time",
+ absolute_keyword_location,
+ instance_location.to_uri_fragment(),
+ "\"" + value + "\" is not a RFC 3339 date-time string"));
+ }
+ }
+
+ inline
+ void email_check(const std::string& absolute_keyword_location,
+ const jsonpointer::json_pointer& instance_location,
+ const std::string& value,
+ error_reporter& reporter)
+ {
+ if (!validate_email_rfc5322(value))
+ {
+ reporter.error(validation_output("email",
+ absolute_keyword_location,
+ instance_location.to_uri_fragment(),
+ "\"" + value + "\" is not a valid email address as defined by RFC 5322"));
+ }
+ }
+
+ inline
+ void hostname_check(const std::string& absolute_keyword_location,
+ const jsonpointer::json_pointer& instance_location,
+ const std::string& value,
+ error_reporter& reporter)
+ {
+ if (!validate_hostname_rfc1034(value))
+ {
+ reporter.error(validation_output("hostname",
+ absolute_keyword_location,
+ instance_location.to_uri_fragment(),
+ "\"" + value + "\" is not a valid hostname as defined by RFC 3986 Appendix A"));
+ }
+ }
+
+ inline
+ void ipv4_check(const std::string& absolute_keyword_location,
+ const jsonpointer::json_pointer& instance_location,
+ const std::string& value,
+ error_reporter& reporter)
+ {
+ if (!validate_ipv4_rfc2673(value))
+ {
+ reporter.error(validation_output("ipv4",
+ absolute_keyword_location,
+ instance_location.to_uri_fragment(),
+ "\"" + value + "\" is not a valid IPv4 address as defined by RFC 2673"));
+ }
+ }
+
+ inline
+ void ipv6_check(const std::string& absolute_keyword_location,
+ const jsonpointer::json_pointer& instance_location,
+ const std::string& value,
+ error_reporter& reporter)
+ {
+ if (!validate_ipv6_rfc2373(value))
+ {
+ reporter.error(validation_output("ipv6",
+ absolute_keyword_location,
+ instance_location.to_uri_fragment(),
+ "\"" + value + "\" is not a valid IPv6 address as defined by RFC 2373"));
+ }
+ }
+
+ inline
+ void regex_check(const std::string& absolute_keyword_location,
+ const jsonpointer::json_pointer& instance_location,
+ const std::string& value,
+ error_reporter& reporter)
+ {
+#if defined(JSONCONS_HAS_STD_REGEX)
+ try
+ {
+ std::regex re(value, std::regex::ECMAScript);
+ }
+ catch (const std::exception& e)
+ {
+ reporter.error(validation_output("pattern",
+ absolute_keyword_location,
+ instance_location.to_uri_fragment(),
+ "\"" + value + "\" is not a valid ECMAScript regular expression. " + e.what()));
+ }
+#endif
+ }
+
+} // namespace jsonschema
+} // namespace jsoncons
+
+#endif // JSONCONS_JSONSCHEMA_FORMAT_CHECKERS_HPP
diff --git a/include/jsoncons_ext/jsonschema/json_validator.hpp b/include/jsoncons_ext/jsonschema/json_validator.hpp
new file mode 100644
index 0000000..87bec58
--- /dev/null
+++ b/include/jsoncons_ext/jsonschema/json_validator.hpp
@@ -0,0 +1,120 @@
+// Copyright 2020 Daniel Parker
+// Distributed under the Boost license, Version 1.0.
+// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+// See https://github.com/danielaparker/jsoncons for latest version
+
+#ifndef JSONCONS_JSONSCHEMA_JSON_VALIDATOR_HPP
+#define JSONCONS_JSONSCHEMA_JSON_VALIDATOR_HPP
+
+#include <jsoncons/config/jsoncons_config.hpp>
+#include <jsoncons/uri.hpp>
+#include <jsoncons/json.hpp>
+#include <jsoncons_ext/jsonpointer/jsonpointer.hpp>
+#include <jsoncons_ext/jsonschema/keyword_validator_factory.hpp>
+#include <cassert>
+#include <set>
+#include <sstream>
+#include <iostream>
+#include <cassert>
+#include <functional>
+
+namespace jsoncons {
+namespace jsonschema {
+
+ class throwing_error_reporter : public error_reporter
+ {
+ void do_error(const validation_output& o) override
+ {
+ JSONCONS_THROW(validation_error(o.message()));
+ }
+ };
+
+ class fail_early_reporter : public error_reporter
+ {
+ void do_error(const validation_output&) override
+ {
+ }
+ public:
+ fail_early_reporter()
+ : error_reporter(true)
+ {
+ }
+ };
+
+ using error_reporter_t = std::function<void(const validation_output& o)>;
+
+ struct error_reporter_adaptor : public error_reporter
+ {
+ error_reporter_t reporter_;
+
+ error_reporter_adaptor(const error_reporter_t& reporter)
+ : reporter_(reporter)
+ {
+ }
+ private:
+ void do_error(const validation_output& e) override
+ {
+ reporter_(e);
+ }
+ };
+
+ template <class Json>
+ class json_validator
+ {
+ std::shared_ptr<json_schema<Json>> root_;
+
+ public:
+ json_validator(std::shared_ptr<json_schema<Json>> root)
+ : root_(root)
+ {
+ }
+
+ json_validator(json_validator &&) = default;
+ json_validator &operator=(json_validator &&) = default;
+
+ json_validator(json_validator const &) = delete;
+ json_validator &operator=(json_validator const &) = delete;
+
+ ~json_validator() = default;
+
+ // Validate input JSON against a JSON Schema with a default throwing error reporter
+ Json validate(const Json& instance) const
+ {
+ throwing_error_reporter reporter;
+ jsonpointer::json_pointer instance_location("#");
+ Json patch(json_array_arg);
+
+ root_->validate(instance, instance_location, reporter, patch);
+ return patch;
+ }
+
+ // Validate input JSON against a JSON Schema
+ bool is_valid(const Json& instance) const
+ {
+ fail_early_reporter reporter;
+ jsonpointer::json_pointer instance_location("#");
+ Json patch(json_array_arg);
+
+ root_->validate(instance, instance_location, reporter, patch);
+ return reporter.error_count() == 0;
+ }
+
+ // Validate input JSON against a JSON Schema with a provided error reporter
+ template <class Reporter>
+ typename std::enable_if<type_traits::is_unary_function_object_exact<Reporter,void,validation_output>::value,Json>::type
+ validate(const Json& instance, const Reporter& reporter) const
+ {
+ jsonpointer::json_pointer instance_location("#");
+ Json patch(json_array_arg);
+
+ error_reporter_adaptor adaptor(reporter);
+ root_->validate(instance, instance_location, adaptor, patch);
+ return patch;
+ }
+ };
+
+} // namespace jsonschema
+} // namespace jsoncons
+
+#endif // JSONCONS_JSONSCHEMA_JSON_VALIDATOR_HPP
diff --git a/include/jsoncons_ext/jsonschema/jsonschema.hpp b/include/jsoncons_ext/jsonschema/jsonschema.hpp
new file mode 100644
index 0000000..e2c4210
--- /dev/null
+++ b/include/jsoncons_ext/jsonschema/jsonschema.hpp
@@ -0,0 +1,13 @@
+// Copyright 2020 Daniel Parker
+// Distributed under the Boost license, Version 1.0.
+// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+// See https://github.com/danielaparker/jsoncons for latest version
+
+#ifndef JSONCONS_JSONSCHEMA_JSONSCHEMA_HPP
+#define JSONCONS_JSONSCHEMA_JSONSCHEMA_HPP
+
+#include <jsoncons_ext/jsonschema/keyword_validator.hpp>
+#include <jsoncons_ext/jsonschema/json_validator.hpp>
+
+#endif // JSONCONS_JSONSCHEMA_JSONSCHEMA_HPP
diff --git a/include/jsoncons_ext/jsonschema/jsonschema_error.hpp b/include/jsoncons_ext/jsonschema/jsonschema_error.hpp
new file mode 100644
index 0000000..7cb1061
--- /dev/null
+++ b/include/jsoncons_ext/jsonschema/jsonschema_error.hpp
@@ -0,0 +1,105 @@
+/// Copyright 2020 Daniel Parker
+// Distributed under the Boost license, Version 1.0.
+// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+// See https://github.com/danielaparker/jsoncons for latest version
+
+#ifndef JSONCONS_JSONSCHEMA_JSONSCHEMA_ERROR_HPP
+#define JSONCONS_JSONSCHEMA_JSONSCHEMA_ERROR_HPP
+
+#include <jsoncons/json_exception.hpp>
+#include <system_error>
+
+namespace jsoncons {
+namespace jsonschema {
+
+ class schema_error : public std::runtime_error, public virtual json_exception
+ {
+ public:
+ schema_error(const std::string& message)
+ : std::runtime_error(message)
+ {
+ }
+
+ const char* what() const noexcept override
+ {
+ return std::runtime_error::what();
+ }
+ };
+
+ class validation_error : public std::runtime_error, public virtual json_exception
+ {
+ public:
+ validation_error(const std::string& message)
+ : std::runtime_error(message)
+ {
+ }
+
+ const char* what() const noexcept override
+ {
+ return std::runtime_error::what();
+ }
+ };
+
+ class validation_output
+ {
+ std::string keyword_;
+ std::string absolute_keyword_location_;
+ std::string instance_location_;
+ std::string message_;
+ std::vector<validation_output> nested_errors_;
+ public:
+ validation_output(std::string keyword,
+ std::string absolute_keyword_location,
+ std::string instance_location,
+ std::string message)
+ : keyword_(std::move(keyword)),
+ absolute_keyword_location_(std::move(absolute_keyword_location)),
+ instance_location_(std::move(instance_location)),
+ message_(std::move(message))
+ {
+ }
+
+ validation_output(const std::string& keyword,
+ const std::string& absolute_keyword_location,
+ const std::string& instance_location,
+ const std::string& message,
+ const std::vector<validation_output>& nested_errors)
+ : keyword_(keyword),
+ absolute_keyword_location_(absolute_keyword_location),
+ instance_location_(instance_location),
+ message_(message),
+ nested_errors_(nested_errors)
+ {
+ }
+
+ const std::string& instance_location() const
+ {
+ return instance_location_;
+ }
+
+ const std::string& message() const
+ {
+ return message_;
+ }
+
+ const std::string& absolute_keyword_location() const
+ {
+ return absolute_keyword_location_;
+ }
+
+ const std::string& keyword() const
+ {
+ return keyword_;
+ }
+
+ const std::vector<validation_output>& nested_errors() const
+ {
+ return nested_errors_;
+ }
+ };
+
+} // namespace jsonschema
+} // namespace jsoncons
+
+#endif // JSONCONS_JSONSCHEMA_JSONSCHEMA_ERROR_HPP
diff --git a/include/jsoncons_ext/jsonschema/jsonschema_version.hpp b/include/jsoncons_ext/jsonschema/jsonschema_version.hpp
new file mode 100644
index 0000000..bf0afff
--- /dev/null
+++ b/include/jsoncons_ext/jsonschema/jsonschema_version.hpp
@@ -0,0 +1,18 @@
+// Copyright 2021 Daniel Parker
+// Distributed under the Boost license, Version 1.0.
+// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+// See https://github.com/danielaparker/jsoncons for latest version
+
+#ifndef JSONCONS_JSONSCHEMA_JSONSCHEMA_VERSION_HPP
+#define JSONCONS_JSONSCHEMA_JSONSCHEMA_VERSION_HPP
+
+#include <jsoncons/json.hpp>
+
+namespace jsoncons {
+namespace jsonschema {
+
+} // namespace jsonschema
+} // namespace jsoncons
+
+#endif // JSONCONS_JSONSCHEMA_JSONSCHEMA_VERSION_HPP
diff --git a/include/jsoncons_ext/jsonschema/keyword_validator.hpp b/include/jsoncons_ext/jsonschema/keyword_validator.hpp
new file mode 100644
index 0000000..249c7d0
--- /dev/null
+++ b/include/jsoncons_ext/jsonschema/keyword_validator.hpp
@@ -0,0 +1,1745 @@
+// Copyright 2020 Daniel Parker
+// Distributed under the Boost license, Version 1.0.
+// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+// See https://github.com/danielaparker/jsoncons for latest version
+
+#ifndef JSONCONS_JSONSCHEMA_KEYWORD_VALIDATOR_HPP
+#define JSONCONS_JSONSCHEMA_KEYWORD_VALIDATOR_HPP
+
+#include <jsoncons/config/jsoncons_config.hpp>
+#include <jsoncons/uri.hpp>
+#include <jsoncons/json.hpp>
+#include <jsoncons_ext/jsonpointer/jsonpointer.hpp>
+#include <jsoncons_ext/jsonschema/subschema.hpp>
+#include <jsoncons_ext/jsonschema/format_validator.hpp>
+#include <cassert>
+#include <set>
+#include <sstream>
+#include <iostream>
+#include <cassert>
+#if defined(JSONCONS_HAS_STD_REGEX)
+#include <regex>
+#endif
+
+namespace jsoncons {
+namespace jsonschema {
+
+ template <class Json>
+ class abstract_keyword_validator_factory
+ {
+ public:
+ using validator_pointer = typename keyword_validator<Json>::self_pointer;
+
+ virtual ~abstract_keyword_validator_factory() = default;
+
+ virtual validator_pointer make_keyword_validator(const Json& schema,
+ const std::vector<schema_location>& uris,
+ const std::vector<std::string>& keys) = 0;
+ virtual validator_pointer make_required_validator(const std::vector<schema_location>& uris,
+ const std::vector<std::string>& items) = 0;
+
+ virtual validator_pointer make_null_validator(const std::vector<schema_location>& uris) = 0;
+
+ virtual validator_pointer make_true_validator(const std::vector<schema_location>& uris) = 0;
+
+ virtual validator_pointer make_false_validator(const std::vector<schema_location>& uris) = 0;
+
+ virtual validator_pointer make_object_validator(const Json& sch,
+ const std::vector<schema_location>& uris) = 0;
+
+ virtual validator_pointer make_array_validator(const Json& sch,
+ const std::vector<schema_location>& uris) = 0;
+
+ virtual validator_pointer make_string_validator(const Json& sch,
+ const std::vector<schema_location>& uris) = 0;
+
+ virtual validator_pointer make_boolean_validator(const std::vector<schema_location>& uris) = 0;
+
+ virtual validator_pointer make_integer_validator(const Json& sch,
+ const std::vector<schema_location>& uris,
+ std::set<std::string>& keywords) = 0;
+
+ virtual validator_pointer make_number_validator(const Json& sch,
+ const std::vector<schema_location>& uris,
+ std::set<std::string>& keywords) = 0;
+
+ virtual validator_pointer make_not_validator(const Json& schema,
+ const std::vector<schema_location>& uris) = 0;
+
+ virtual validator_pointer make_all_of_validator(const Json& schema,
+ const std::vector<schema_location>& uris) = 0;
+
+ virtual validator_pointer make_any_of_validator(const Json& schema,
+ const std::vector<schema_location>& uris) = 0;
+
+ virtual validator_pointer make_one_of_validator(const Json& schema,
+ const std::vector<schema_location>& uris) = 0;
+
+ virtual validator_pointer make_type_validator(const Json& schema,
+ const std::vector<schema_location>& uris) = 0;
+ };
+
+ struct collecting_error_reporter : public error_reporter
+ {
+ std::vector<validation_output> errors;
+
+ private:
+ void do_error(const validation_output& o) override
+ {
+ errors.push_back(o);
+ }
+ };
+
+ // string keyword_validator
+
+ inline
+ std::string make_absolute_keyword_location(const std::vector<schema_location>& uris,
+ const std::string& keyword)
+ {
+ for (auto it = uris.rbegin(); it != uris.rend(); ++it)
+ {
+ if (!it->has_identifier() && it->is_absolute())
+ {
+ return it->append(keyword).string();
+ }
+ }
+ return "";
+ }
+
+ template <class Json>
+ class string_validator : public keyword_validator<Json>
+ {
+ jsoncons::optional<std::size_t> max_length_;
+ std::string max_length_location_;
+ jsoncons::optional<std::size_t> min_length_;
+ std::string min_length_location_;
+
+ #if defined(JSONCONS_HAS_STD_REGEX)
+ jsoncons::optional<std::regex> pattern_;
+ std::string pattern_string_;
+ std::string pattern_location_;
+ #endif
+
+ format_checker format_check_;
+ std::string format_location_;
+
+ jsoncons::optional<std::string> content_encoding_;
+ std::string content_encoding_location_;
+ jsoncons::optional<std::string> content_media_type_;
+ std::string content_media_type_location_;
+
+ public:
+ string_validator(const Json& sch, const std::vector<schema_location>& uris)
+ : keyword_validator<Json>((!uris.empty() && uris.back().is_absolute()) ? uris.back().string() : ""), max_length_(), min_length_(),
+ #if defined(JSONCONS_HAS_STD_REGEX)
+ pattern_(),
+ #endif
+ content_encoding_(), content_media_type_()
+ {
+ auto it = sch.find("maxLength");
+ if (it != sch.object_range().end())
+ {
+ max_length_ = it->value().template as<std::size_t>();
+ max_length_location_ = make_absolute_keyword_location(uris, "maxLength");
+ }
+
+ it = sch.find("minLength");
+ if (it != sch.object_range().end())
+ {
+ min_length_ = it->value().template as<std::size_t>();
+ min_length_location_ = make_absolute_keyword_location(uris, "minLength");
+ }
+
+ it = sch.find("contentEncoding");
+ if (it != sch.object_range().end())
+ {
+ content_encoding_ = it->value().template as<std::string>();
+ content_encoding_location_ = make_absolute_keyword_location(uris, "contentEncoding");
+ // If "contentEncoding" is set to "binary", a Json value
+ // of type json_type::byte_string_value is accepted.
+ }
+
+ it = sch.find("contentMediaType");
+ if (it != sch.object_range().end())
+ {
+ content_media_type_ = it->value().template as<std::string>();
+ content_media_type_location_ = make_absolute_keyword_location(uris, "contentMediaType");
+ }
+
+ #if defined(JSONCONS_HAS_STD_REGEX)
+ it = sch.find("pattern");
+ if (it != sch.object_range().end())
+ {
+ pattern_string_ = it->value().template as<std::string>();
+ pattern_ = std::regex(it->value().template as<std::string>(),std::regex::ECMAScript);
+ pattern_location_ = make_absolute_keyword_location(uris, "pattern");
+ }
+ #endif
+
+ it = sch.find("format");
+ if (it != sch.object_range().end())
+ {
+ format_location_ = make_absolute_keyword_location(uris, "format");
+ std::string format = it->value().template as<std::string>();
+ if (format == "date-time")
+ {
+ format_check_ = rfc3339_date_time_check;
+ }
+ else if (format == "date")
+ {
+ format_check_ = rfc3339_date_check;
+ }
+ else if (format == "time")
+ {
+ format_check_ = rfc3339_time_check;
+ }
+ else if (format == "email")
+ {
+ format_check_ = email_check;
+ }
+ else if (format == "hostname")
+ {
+ format_check_ = hostname_check;
+ }
+ else if (format == "ipv4")
+ {
+ format_check_ = ipv4_check;
+ }
+ else if (format == "ipv6")
+ {
+ format_check_ = ipv6_check;
+ }
+ else if (format == "regex")
+ {
+ format_check_ = regex_check;
+ }
+ else
+ {
+ // Not supported - ignore
+ }
+ }
+ }
+
+ private:
+
+ void do_validate(const Json& instance,
+ const jsonpointer::json_pointer& instance_location,
+ error_reporter& reporter,
+ Json&) const override
+ {
+ std::string content;
+ if (content_encoding_)
+ {
+ if (*content_encoding_ == "base64")
+ {
+ auto s = instance.template as<jsoncons::string_view>();
+ auto retval = jsoncons::decode_base64(s.begin(), s.end(), content);
+ if (retval.ec != jsoncons::conv_errc::success)
+ {
+ reporter.error(validation_output("contentEncoding",
+ content_encoding_location_,
+ instance_location.to_uri_fragment(),
+ "Content is not a base64 string"));
+ if (reporter.fail_early())
+ {
+ return;
+ }
+ }
+ }
+ else if (!content_encoding_->empty())
+ {
+ reporter.error(validation_output("contentEncoding",
+ content_encoding_location_,
+ instance_location.to_uri_fragment(),
+ "unable to check for contentEncoding '" + *content_encoding_ + "'"));
+ if (reporter.fail_early())
+ {
+ return;
+ }
+ }
+ }
+ else
+ {
+ content = instance.template as<std::string>();
+ }
+
+ if (content_media_type_)
+ {
+ if (content_media_type_ == "application/Json")
+ {
+ json_string_reader reader(content);
+ std::error_code ec;
+ reader.read(ec);
+
+ if (ec)
+ {
+ reporter.error(validation_output("contentMediaType",
+ content_media_type_location_,
+ instance_location.to_uri_fragment(),
+ std::string("Content is not JSON: ") + ec.message()));
+ }
+ }
+ }
+ else if (instance.type() == json_type::byte_string_value)
+ {
+ reporter.error(validation_output("contentMediaType",
+ content_media_type_location_,
+ instance_location.to_uri_fragment(),
+ "Expected string, but is byte string"));
+ if (reporter.fail_early())
+ {
+ return;
+ }
+ }
+
+ if (instance.type() != json_type::string_value)
+ {
+ return;
+ }
+
+ if (min_length_)
+ {
+ std::size_t length = unicode_traits::count_codepoints(content.data(), content.size());
+ if (length < *min_length_)
+ {
+ reporter.error(validation_output("minLength",
+ min_length_location_,
+ instance_location.to_uri_fragment(),
+ std::string("Expected minLength: ") + std::to_string(*min_length_)
+ + ", actual: " + std::to_string(length)));
+ if (reporter.fail_early())
+ {
+ return;
+ }
+ }
+ }
+
+ if (max_length_)
+ {
+ std::size_t length = unicode_traits::count_codepoints(content.data(), content.size());
+ if (length > *max_length_)
+ {
+ reporter.error(validation_output("maxLength",
+ max_length_location_,
+ instance_location.to_uri_fragment(),
+ std::string("Expected maxLength: ") + std::to_string(*max_length_)
+ + ", actual: " + std::to_string(length)));
+ if (reporter.fail_early())
+ {
+ return;
+ }
+ }
+ }
+
+ #if defined(JSONCONS_HAS_STD_REGEX)
+ if (pattern_)
+ {
+ if (!std::regex_search(content, *pattern_))
+ {
+ std::string message("String \"");
+ message.append(instance.template as<std::string>());
+ message.append("\" does not match pattern \"");
+ message.append(pattern_string_);
+ message.append("\"");
+ reporter.error(validation_output("pattern",
+ pattern_location_,
+ instance_location.to_uri_fragment(),
+ std::move(message)));
+ if (reporter.fail_early())
+ {
+ return;
+ }
+ }
+ }
+
+ #endif
+
+ if (format_check_ != nullptr)
+ {
+ format_check_(format_location_, instance_location, content, reporter);
+ if (reporter.error_count() > 0 && reporter.fail_early())
+ {
+ return;
+ }
+ }
+ }
+ };
+
+ // not_validator
+
+ template <class Json>
+ class not_validator : public keyword_validator<Json>
+ {
+ using validator_pointer = typename keyword_validator<Json>::self_pointer;
+
+ validator_pointer rule_;
+
+ public:
+ not_validator(abstract_keyword_validator_factory<Json>* builder,
+ const Json& sch,
+ const std::vector<schema_location>& uris)
+ : keyword_validator<Json>((!uris.empty() && uris.back().is_absolute()) ? uris.back().string() : "")
+ {
+ rule_ = builder->make_keyword_validator(sch, uris, {"not"});
+ }
+
+ private:
+
+ void do_validate(const Json& instance,
+ const jsonpointer::json_pointer& instance_location,
+ error_reporter& reporter,
+ Json& patch) const final
+ {
+ collecting_error_reporter local_reporter;
+ rule_->validate(instance, instance_location, local_reporter, patch);
+
+ if (local_reporter.errors.empty())
+ {
+ reporter.error(validation_output("not",
+ this->absolute_keyword_location(),
+ instance_location.to_uri_fragment(),
+ "Instance must not be valid against schema"));
+ }
+ }
+
+ jsoncons::optional<Json> get_default_value(const jsonpointer::json_pointer& instance_location,
+ const Json& instance,
+ error_reporter& reporter) const override
+ {
+ return rule_->get_default_value(instance_location, instance, reporter);
+ }
+ };
+
+ template <class Json>
+ struct all_of_criterion
+ {
+ static const std::string& key()
+ {
+ static const std::string k("allOf");
+ return k;
+ }
+
+ static bool is_complete(const Json&,
+ const jsonpointer::json_pointer& instance_location,
+ error_reporter& reporter,
+ const collecting_error_reporter& local_reporter,
+ std::size_t)
+ {
+ if (!local_reporter.errors.empty())
+ reporter.error(validation_output("allOf",
+ "",
+ instance_location.to_uri_fragment(),
+ "At least one schema failed to match, but all are required to match. ",
+ local_reporter.errors));
+ return !local_reporter.errors.empty();
+ }
+ };
+
+ template <class Json>
+ struct any_of_criterion
+ {
+ static const std::string& key()
+ {
+ static const std::string k("anyOf");
+ return k;
+ }
+
+ static bool is_complete(const Json&,
+ const jsonpointer::json_pointer&,
+ error_reporter&,
+ const collecting_error_reporter&,
+ std::size_t count)
+ {
+ return count == 1;
+ }
+ };
+
+ template <class Json>
+ struct one_of_criterion
+ {
+ static const std::string& key()
+ {
+ static const std::string k("oneOf");
+ return k;
+ }
+
+ static bool is_complete(const Json&,
+ const jsonpointer::json_pointer& instance_location,
+ error_reporter& reporter,
+ const collecting_error_reporter&,
+ std::size_t count)
+ {
+ if (count > 1)
+ {
+ std::string message(std::to_string(count));
+ message.append(" subschemas matched, but exactly one is required to match");
+ reporter.error(validation_output("oneOf",
+ "",
+ instance_location.to_uri_fragment(),
+ std::move(message)));
+ }
+ return count > 1;
+ }
+ };
+
+ template <class Json,class Criterion>
+ class combining_validator : public keyword_validator<Json>
+ {
+ using validator_pointer = typename keyword_validator<Json>::self_pointer;
+
+ std::vector<validator_pointer> subschemas_;
+
+ public:
+ combining_validator(abstract_keyword_validator_factory<Json>* builder,
+ const Json& sch,
+ const std::vector<schema_location>& uris)
+ : keyword_validator<Json>((!uris.empty() && uris.back().is_absolute()) ? uris.back().string() : "")
+ {
+ size_t c = 0;
+ for (const auto& subsch : sch.array_range())
+ {
+ subschemas_.push_back(builder->make_keyword_validator(subsch, uris, {Criterion::key(), std::to_string(c++)}));
+ }
+
+ // Validate value of allOf, anyOf, and oneOf "MUST be a non-empty array"
+ }
+
+ private:
+
+ void do_validate(const Json& instance,
+ const jsonpointer::json_pointer& instance_location,
+ error_reporter& reporter,
+ Json& patch) const final
+ {
+ size_t count = 0;
+
+ collecting_error_reporter local_reporter;
+ for (auto& s : subschemas_)
+ {
+ std::size_t mark = local_reporter.errors.size();
+ s->validate(instance, instance_location, local_reporter, patch);
+ if (mark == local_reporter.errors.size())
+ count++;
+
+ if (Criterion::is_complete(instance, instance_location, reporter, local_reporter, count))
+ return;
+ }
+
+ if (count == 0)
+ {
+ reporter.error(validation_output("combined",
+ this->absolute_keyword_location(),
+ instance_location.to_uri_fragment(),
+ "No schema matched, but one of them is required to match",
+ local_reporter.errors));
+ }
+ }
+ };
+
+ template <class T, class Json>
+ T get_number(const Json& val, const string_view& keyword)
+ {
+ if (!val.is_number())
+ {
+ std::string message(keyword);
+ message.append(" must be a number value");
+ JSONCONS_THROW(schema_error(message));
+ }
+ return val.template as<T>();
+ }
+
+ template <class Json,class T>
+ class numeric_validator_base : public keyword_validator<Json>
+ {
+ jsoncons::optional<T> maximum_;
+ std::string absolute_maximum_location_;
+ jsoncons::optional<T> minimum_;
+ std::string absolute_minimum_location_;
+ jsoncons::optional<T> exclusive_maximum_;
+ std::string absolute_exclusive_maximum_location_;
+ jsoncons::optional<T> exclusive_minimum_;
+ std::string absolute_exclusive_minimum_location_;
+ jsoncons::optional<double> multiple_of_;
+ std::string absolute_multiple_of_location_;
+
+ public:
+ numeric_validator_base(const Json& sch,
+ const std::vector<schema_location>& uris,
+ std::set<std::string>& keywords)
+ : keyword_validator<Json>((!uris.empty() && uris.back().is_absolute()) ? uris.back().string() : ""),
+ maximum_(), minimum_(),exclusive_maximum_(), exclusive_minimum_(), multiple_of_()
+ {
+ auto it = sch.find("maximum");
+ if (it != sch.object_range().end())
+ {
+ maximum_ = get_number<T>(it->value(), "maximum");
+ absolute_maximum_location_ = make_absolute_keyword_location(uris,"maximum");
+ keywords.insert("maximum");
+ }
+
+ it = sch.find("minimum");
+ if (it != sch.object_range().end())
+ {
+ minimum_ = get_number<T>(it->value(), "minimum");
+ absolute_minimum_location_ = make_absolute_keyword_location(uris,"minimum");
+ keywords.insert("minimum");
+ }
+
+ it = sch.find("exclusiveMaximum");
+ if (it != sch.object_range().end())
+ {
+ exclusive_maximum_ = get_number<T>(it->value(), "exclusiveMaximum");
+ absolute_exclusive_maximum_location_ = make_absolute_keyword_location(uris,"exclusiveMaximum");
+ keywords.insert("exclusiveMaximum");
+ }
+
+ it = sch.find("exclusiveMinimum");
+ if (it != sch.object_range().end())
+ {
+ exclusive_minimum_ = get_number<T>(it->value(), "exclusiveMinimum");
+ absolute_exclusive_minimum_location_ = make_absolute_keyword_location(uris,"exclusiveMinimum");
+ keywords.insert("exclusiveMinimum");
+ }
+
+ it = sch.find("multipleOf");
+ if (it != sch.object_range().end())
+ {
+ multiple_of_ = get_number<double>(it->value(), "multipleOf");
+ absolute_multiple_of_location_ = make_absolute_keyword_location(uris,"multipleOf");
+ keywords.insert("multipleOf");
+ }
+ }
+
+ protected:
+
+ void apply_kewords(T value,
+ const jsonpointer::json_pointer& instance_location,
+ const Json& instance,
+ error_reporter& reporter) const
+ {
+ if (multiple_of_ && value != 0) // exclude zero
+ {
+ if (!is_multiple_of(value, *multiple_of_))
+ {
+ reporter.error(validation_output("multipleOf",
+ absolute_multiple_of_location_,
+ instance_location.to_uri_fragment(),
+ instance.template as<std::string>() + " is not a multiple of " + std::to_string(*multiple_of_)));
+ if (reporter.fail_early())
+ {
+ return;
+ }
+ }
+ }
+
+ if (maximum_)
+ {
+ if (value > *maximum_)
+ {
+ reporter.error(validation_output("maximum",
+ absolute_maximum_location_,
+ instance_location.to_uri_fragment(),
+ instance.template as<std::string>() + " exceeds maximum of " + std::to_string(*maximum_)));
+ if (reporter.fail_early())
+ {
+ return;
+ }
+ }
+ }
+
+ if (minimum_)
+ {
+ if (value < *minimum_)
+ {
+ reporter.error(validation_output("minimum",
+ absolute_minimum_location_,
+ instance_location.to_uri_fragment(),
+ instance.template as<std::string>() + " is below minimum of " + std::to_string(*minimum_)));
+ if (reporter.fail_early())
+ {
+ return;
+ }
+ }
+ }
+
+ if (exclusive_maximum_)
+ {
+ if (value >= *exclusive_maximum_)
+ {
+ reporter.error(validation_output("exclusiveMaximum",
+ absolute_exclusive_maximum_location_,
+ instance_location.to_uri_fragment(),
+ instance.template as<std::string>() + " exceeds maximum of " + std::to_string(*exclusive_maximum_)));
+ if (reporter.fail_early())
+ {
+ return;
+ }
+ }
+ }
+
+ if (exclusive_minimum_)
+ {
+ if (value <= *exclusive_minimum_)
+ {
+ reporter.error(validation_output("exclusiveMinimum",
+ absolute_exclusive_minimum_location_,
+ instance_location.to_uri_fragment(),
+ instance.template as<std::string>() + " is below minimum of " + std::to_string(*exclusive_minimum_)));
+ if (reporter.fail_early())
+ {
+ return;
+ }
+ }
+ }
+ }
+ private:
+ static bool is_multiple_of(T x, double multiple_of)
+ {
+ double rem = std::remainder(x, multiple_of);
+ double eps = std::nextafter(x, 0) - x;
+ return std::fabs(rem) < std::fabs(eps);
+ }
+ };
+
+ template <class Json>
+ class integer_validator : public numeric_validator_base<Json,int64_t>
+ {
+ public:
+ integer_validator(const Json& sch,
+ const std::vector<schema_location>& uris,
+ std::set<std::string>& keywords)
+ : numeric_validator_base<Json, int64_t>(sch, uris, keywords)
+ {
+ }
+ private:
+ void do_validate(const Json& instance,
+ const jsonpointer::json_pointer& instance_location,
+ error_reporter& reporter,
+ Json&) const
+ {
+ if (!(instance.template is_integer<int64_t>() || (instance.is_double() && static_cast<double>(instance.template as<int64_t>()) == instance.template as<double>())))
+ {
+ reporter.error(validation_output("integer",
+ this->absolute_keyword_location(),
+ instance_location.to_uri_fragment(),
+ "Instance is not an integer"));
+ if (reporter.fail_early())
+ {
+ return;
+ }
+ }
+ int64_t value = instance.template as<int64_t>();
+ this->apply_kewords(value, instance_location, instance, reporter);
+ }
+ };
+
+ template <class Json>
+ class number_validator : public numeric_validator_base<Json,double>
+ {
+ public:
+ number_validator(const Json& sch,
+ const std::vector<schema_location>& uris,
+ std::set<std::string>& keywords)
+ : numeric_validator_base<Json, double>(sch, uris, keywords)
+ {
+ }
+ private:
+ void do_validate(const Json& instance,
+ const jsonpointer::json_pointer& instance_location,
+ error_reporter& reporter,
+ Json&) const
+ {
+ if (!(instance.template is_integer<int64_t>() || instance.is_double()))
+ {
+ reporter.error(validation_output("number",
+ this->absolute_keyword_location(),
+ instance_location.to_uri_fragment(),
+ "Instance is not a number"));
+ if (reporter.fail_early())
+ {
+ return;
+ }
+ }
+ double value = instance.template as<double>();
+ this->apply_kewords(value, instance_location, instance, reporter);
+ }
+ };
+
+ // null_validator
+
+ template <class Json>
+ class null_validator : public keyword_validator<Json>
+ {
+ public:
+ null_validator(const std::vector<schema_location>& uris)
+ : keyword_validator<Json>((!uris.empty() && uris.back().is_absolute()) ? uris.back().string() : "")
+ {
+ }
+ private:
+ void do_validate(const Json& instance,
+ const jsonpointer::json_pointer& instance_location,
+ error_reporter& reporter,
+ Json&) const override
+ {
+ if (!instance.is_null())
+ {
+ reporter.error(validation_output("null",
+ this->absolute_keyword_location(),
+ instance_location.to_uri_fragment(),
+ "Expected to be null"));
+ }
+ }
+ };
+
+ template <class Json>
+ class boolean_validator : public keyword_validator<Json>
+ {
+ public:
+ boolean_validator(const std::vector<schema_location>& uris)
+ : keyword_validator<Json>((!uris.empty() && uris.back().is_absolute()) ? uris.back().string() : "")
+ {
+ }
+ private:
+ void do_validate(const Json&,
+ const jsonpointer::json_pointer&,
+ error_reporter&,
+ Json&) const override
+ {
+ }
+
+ };
+
+ template <class Json>
+ class true_validator : public keyword_validator<Json>
+ {
+ public:
+ true_validator(const std::vector<schema_location>& uris)
+ : keyword_validator<Json>((!uris.empty() && uris.back().is_absolute()) ? uris.back().string() : "")
+ {
+ }
+ private:
+ void do_validate(const Json&,
+ const jsonpointer::json_pointer&,
+ error_reporter&,
+ Json&) const override
+ {
+ }
+ };
+
+ template <class Json>
+ class false_validator : public keyword_validator<Json>
+ {
+ public:
+ false_validator(const std::vector<schema_location>& uris)
+ : keyword_validator<Json>((!uris.empty() && uris.back().is_absolute()) ? uris.back().string() : "")
+ {
+ }
+ private:
+ void do_validate(const Json&,
+ const jsonpointer::json_pointer& instance_location,
+ error_reporter& reporter,
+ Json&) const override
+ {
+ reporter.error(validation_output("false",
+ this->absolute_keyword_location(),
+ instance_location.to_uri_fragment(),
+ "False schema always fails"));
+ }
+ };
+
+ template <class Json>
+ class required_validator : public keyword_validator<Json>
+ {
+ using validator_pointer = typename keyword_validator<Json>::self_pointer;
+
+ std::vector<std::string> items_;
+
+ public:
+ required_validator(const std::vector<schema_location>& uris,
+ const std::vector<std::string>& items)
+ : keyword_validator<Json>((!uris.empty() && uris.back().is_absolute()) ? uris.back().string() : ""), items_(items) {}
+ required_validator(const std::string& absolute_keyword_location, const std::vector<std::string>& items)
+ : keyword_validator<Json>(absolute_keyword_location), items_(items) {}
+
+ required_validator(const required_validator&) = delete;
+ required_validator(required_validator&&) = default;
+ required_validator& operator=(const required_validator&) = delete;
+ required_validator& operator=(required_validator&&) = default;
+ private:
+
+ void do_validate(const Json& instance,
+ const jsonpointer::json_pointer& instance_location,
+ error_reporter& reporter,
+ Json&) const override final
+ {
+ for (const auto& key : items_)
+ {
+ if (instance.find(key) == instance.object_range().end())
+ {
+ reporter.error(validation_output("required",
+ this->absolute_keyword_location(),
+ instance_location.to_uri_fragment(),
+ "Required property \"" + key + "\" not found"));
+ if (reporter.fail_early())
+ {
+ return;
+ }
+ }
+ }
+ }
+ };
+
+ template <class Json>
+ class object_validator : public keyword_validator<Json>
+ {
+ using validator_pointer = typename keyword_validator<Json>::self_pointer;
+
+ jsoncons::optional<std::size_t> max_properties_;
+ std::string absolute_max_properties_location_;
+ jsoncons::optional<std::size_t> min_properties_;
+ std::string absolute_min_properties_location_;
+ jsoncons::optional<required_validator<Json>> required_;
+
+ std::map<std::string, validator_pointer> properties_;
+ #if defined(JSONCONS_HAS_STD_REGEX)
+ std::vector<std::pair<std::regex, validator_pointer>> pattern_properties_;
+ #endif
+ validator_pointer additional_properties_;
+
+ std::map<std::string, validator_pointer> dependencies_;
+
+ validator_pointer property_name_validator_;
+
+ public:
+ object_validator(abstract_keyword_validator_factory<Json>* builder,
+ const Json& sch,
+ const std::vector<schema_location>& uris)
+ : keyword_validator<Json>((!uris.empty() && uris.back().is_absolute()) ? uris.back().string() : ""),
+ max_properties_(), min_properties_(),
+ additional_properties_(nullptr),
+ property_name_validator_(nullptr)
+ {
+ auto it = sch.find("maxProperties");
+ if (it != sch.object_range().end())
+ {
+ max_properties_ = it->value().template as<std::size_t>();
+ absolute_max_properties_location_ = make_absolute_keyword_location(uris, "maxProperties");
+ }
+
+ it = sch.find("minProperties");
+ if (it != sch.object_range().end())
+ {
+ min_properties_ = it->value().template as<std::size_t>();
+ absolute_min_properties_location_ = make_absolute_keyword_location(uris, "minProperties");
+ }
+
+ it = sch.find("required");
+ if (it != sch.object_range().end())
+ {
+ auto location = make_absolute_keyword_location(uris, "required");
+ required_ = required_validator<Json>(location,
+ it->value().template as<std::vector<std::string>>());
+ }
+
+ it = sch.find("properties");
+ if (it != sch.object_range().end())
+ {
+ for (const auto& prop : it->value().object_range())
+ properties_.emplace(
+ std::make_pair(
+ prop.key(),
+ builder->make_keyword_validator(prop.value(), uris, {"properties", prop.key()})));
+ }
+
+ #if defined(JSONCONS_HAS_STD_REGEX)
+ it = sch.find("patternProperties");
+ if (it != sch.object_range().end())
+ {
+ for (const auto& prop : it->value().object_range())
+ pattern_properties_.emplace_back(
+ std::make_pair(
+ std::regex(prop.key(), std::regex::ECMAScript),
+ builder->make_keyword_validator(prop.value(), uris, {prop.key()})));
+ }
+ #endif
+
+ it = sch.find("additionalProperties");
+ if (it != sch.object_range().end())
+ {
+ additional_properties_ = builder->make_keyword_validator(it->value(), uris, {"additionalProperties"});
+ }
+
+ it = sch.find("dependencies");
+ if (it != sch.object_range().end())
+ {
+ for (const auto& dep : it->value().object_range())
+ {
+ switch (dep.value().type())
+ {
+ case json_type::array_value:
+ {
+ auto location = make_absolute_keyword_location(uris, "dependencies");
+ dependencies_.emplace(dep.key(),
+ builder->make_required_validator({location},
+ dep.value().template as<std::vector<std::string>>()));
+ break;
+ }
+ default:
+ {
+ dependencies_.emplace(dep.key(),
+ builder->make_keyword_validator(dep.value(), uris, {"dependencies", dep.key()}));
+ break;
+ }
+ }
+ }
+ }
+
+ auto property_names_it = sch.find("propertyNames");
+ if (property_names_it != sch.object_range().end())
+ {
+ property_name_validator_ = builder->make_keyword_validator(property_names_it->value(), uris, {"propertyNames"});
+ }
+ }
+ private:
+
+ void do_validate(const Json& instance,
+ const jsonpointer::json_pointer& instance_location,
+ error_reporter& reporter,
+ Json& patch) const override
+ {
+ if (max_properties_ && instance.size() > *max_properties_)
+ {
+ std::string message("Maximum properties: " + std::to_string(*max_properties_));
+ message.append(", found: " + std::to_string(instance.size()));
+ reporter.error(validation_output("maxProperties",
+ absolute_max_properties_location_,
+ instance_location.to_uri_fragment(),
+ std::move(message)));
+ if (reporter.fail_early())
+ {
+ return;
+ }
+ }
+
+ if (min_properties_ && instance.size() < *min_properties_)
+ {
+ std::string message("Minimum properties: " + std::to_string(*min_properties_));
+ message.append(", found: " + std::to_string(instance.size()));
+ reporter.error(validation_output("minProperties",
+ absolute_min_properties_location_,
+ instance_location.to_uri_fragment(),
+ std::move(message)));
+ if (reporter.fail_early())
+ {
+ return;
+ }
+ }
+
+ if (required_)
+ required_->validate(instance, instance_location, reporter, patch);
+
+ for (const auto& property : instance.object_range())
+ {
+ if (property_name_validator_)
+ property_name_validator_->validate(property.key(), instance_location, reporter, patch);
+
+ bool a_prop_or_pattern_matched = false;
+ auto properties_it = properties_.find(property.key());
+
+ // check if it is in "properties"
+ if (properties_it != properties_.end())
+ {
+ a_prop_or_pattern_matched = true;
+ jsonpointer::json_pointer pointer(instance_location);
+ pointer /= property.key();
+ properties_it->second->validate(property.value(), pointer, reporter, patch);
+ }
+
+ #if defined(JSONCONS_HAS_STD_REGEX)
+
+ // check all matching "patternProperties"
+ for (auto& schema_pp : pattern_properties_)
+ if (std::regex_search(property.key(), schema_pp.first))
+ {
+ a_prop_or_pattern_matched = true;
+ jsonpointer::json_pointer pointer(instance_location);
+ pointer /= property.key();
+ schema_pp.second->validate(property.value(), pointer, reporter, patch);
+ }
+ #endif
+
+ // finally, check "additionalProperties"
+ if (!a_prop_or_pattern_matched && additional_properties_)
+ {
+ collecting_error_reporter local_reporter;
+
+ jsonpointer::json_pointer pointer(instance_location);
+ pointer /= property.key();
+ additional_properties_->validate(property.value(), pointer, local_reporter, patch);
+ if (!local_reporter.errors.empty())
+ {
+ reporter.error(validation_output("additionalProperties",
+ additional_properties_->absolute_keyword_location(),
+ instance_location.to_uri_fragment(),
+ "Additional property \"" + property.key() + "\" found but was invalid."));
+ if (reporter.fail_early())
+ {
+ return;
+ }
+ }
+ }
+ }
+
+ // reverse search
+ for (auto const& prop : properties_)
+ {
+ const auto finding = instance.find(prop.first);
+ if (finding == instance.object_range().end())
+ {
+ // If property is not in instance
+ auto default_value = prop.second->get_default_value(instance_location, instance, reporter);
+ if (default_value)
+ {
+ // If default value is available, update patch
+ jsonpointer::json_pointer pointer(instance_location);
+ pointer /= prop.first;
+
+ update_patch(patch, pointer, std::move(*default_value));
+ }
+ }
+ }
+
+ for (const auto& dep : dependencies_)
+ {
+ auto prop = instance.find(dep.first);
+ if (prop != instance.object_range().end())
+ {
+ // if dependency-property is present in instance
+ jsonpointer::json_pointer pointer(instance_location);
+ pointer /= dep.first;
+ dep.second->validate(instance, pointer, reporter, patch); // validate
+ }
+ }
+ }
+
+ void update_patch(Json& patch, const jsonpointer::json_pointer& instance_location, Json&& default_value) const
+ {
+ Json j;
+ j.try_emplace("op", "add");
+ j.try_emplace("path", instance_location.to_uri_fragment());
+ j.try_emplace("value", std::forward<Json>(default_value));
+ patch.push_back(std::move(j));
+ }
+ };
+
+ // array_validator
+
+ template <class Json>
+ class array_validator : public keyword_validator<Json>
+ {
+ using validator_pointer = typename keyword_validator<Json>::self_pointer;
+
+ jsoncons::optional<std::size_t> max_items_;
+ std::string absolute_max_items_location_;
+ jsoncons::optional<std::size_t> min_items_;
+ std::string absolute_min_items_location_;
+ bool unique_items_ = false;
+ validator_pointer items_validator_;
+ std::vector<validator_pointer> item_validators_;
+ validator_pointer additional_items_validator_;
+ validator_pointer contains_validator_;
+
+ public:
+ array_validator(abstract_keyword_validator_factory<Json>* builder,
+ const Json& sch,
+ const std::vector<schema_location>& uris)
+ : keyword_validator<Json>((!uris.empty() && uris.back().is_absolute()) ? uris.back().string() : ""),
+ max_items_(), min_items_(), items_validator_(nullptr), additional_items_validator_(nullptr), contains_validator_(nullptr)
+ {
+ {
+ auto it = sch.find("maxItems");
+ if (it != sch.object_range().end())
+ {
+ max_items_ = it->value().template as<std::size_t>();
+ absolute_max_items_location_ = make_absolute_keyword_location(uris, "maxItems");
+ }
+ }
+
+ {
+ auto it = sch.find("minItems");
+ if (it != sch.object_range().end())
+ {
+ min_items_ = it->value().template as<std::size_t>();
+ absolute_min_items_location_ = make_absolute_keyword_location(uris, "minItems");
+ }
+ }
+
+ {
+ auto it = sch.find("uniqueItems");
+ if (it != sch.object_range().end())
+ {
+ unique_items_ = it->value().template as<bool>();
+ }
+ }
+
+ {
+ auto it = sch.find("items");
+ if (it != sch.object_range().end())
+ {
+
+ if (it->value().type() == json_type::array_value)
+ {
+ size_t c = 0;
+ for (const auto& subsch : it->value().array_range())
+ item_validators_.push_back(builder->make_keyword_validator(subsch, uris, {"items", std::to_string(c++)}));
+
+ auto attr_add = sch.find("additionalItems");
+ if (attr_add != sch.object_range().end())
+ {
+ additional_items_validator_ = builder->make_keyword_validator(attr_add->value(), uris, {"additionalItems"});
+ }
+
+ }
+ else if (it->value().type() == json_type::object_value ||
+ it->value().type() == json_type::bool_value)
+ {
+ items_validator_ = builder->make_keyword_validator(it->value(), uris, {"items"});
+ }
+
+ }
+ }
+
+ {
+ auto it = sch.find("contains");
+ if (it != sch.object_range().end())
+ {
+ contains_validator_ = builder->make_keyword_validator(it->value(), uris, {"contains"});
+ }
+ }
+ }
+ private:
+
+ void do_validate(const Json& instance,
+ const jsonpointer::json_pointer& instance_location,
+ error_reporter& reporter,
+ Json& patch) const override
+ {
+ if (max_items_)
+ {
+ if (instance.size() > *max_items_)
+ {
+ std::string message("Expected maximum item count: " + std::to_string(*max_items_));
+ message.append(", found: " + std::to_string(instance.size()));
+ reporter.error(validation_output("maxItems",
+ absolute_max_items_location_,
+ instance_location.to_uri_fragment(),
+ std::move(message)));
+ if (reporter.fail_early())
+ {
+ return;
+ }
+ }
+ }
+
+ if (min_items_)
+ {
+ if (instance.size() < *min_items_)
+ {
+ std::string message("Expected minimum item count: " + std::to_string(*min_items_));
+ message.append(", found: " + std::to_string(instance.size()));
+ reporter.error(validation_output("minItems",
+ absolute_min_items_location_,
+ instance_location.to_uri_fragment(),
+ std::move(message)));
+ if (reporter.fail_early())
+ {
+ return;
+ }
+ }
+ }
+
+ if (unique_items_)
+ {
+ if (!array_has_unique_items(instance))
+ {
+ reporter.error(validation_output("uniqueItems",
+ this->absolute_keyword_location(),
+ instance_location.to_uri_fragment(),
+ "Array items are not unique"));
+ if (reporter.fail_early())
+ {
+ return;
+ }
+ }
+ }
+
+ size_t index = 0;
+ if (items_validator_)
+ {
+ for (const auto& i : instance.array_range())
+ {
+ jsonpointer::json_pointer pointer(instance_location);
+ pointer /= index;
+ items_validator_->validate(i, pointer, reporter, patch);
+ index++;
+ }
+ }
+ else
+ {
+ auto validator_it = item_validators_.cbegin();
+ for (const auto& item : instance.array_range())
+ {
+ validator_pointer item_validator = nullptr;
+ if (validator_it != item_validators_.cend())
+ {
+ item_validator = *validator_it;
+ ++validator_it;
+ }
+ else if (additional_items_validator_ != nullptr)
+ {
+ item_validator = additional_items_validator_;
+ }
+ else
+ break;
+
+ jsonpointer::json_pointer pointer(instance_location);
+ pointer /= index;
+ item_validator->validate(item, pointer, reporter, patch);
+ }
+ }
+
+ if (contains_validator_)
+ {
+ bool contained = false;
+ collecting_error_reporter local_reporter;
+ for (const auto& item : instance.array_range())
+ {
+ std::size_t mark = local_reporter.errors.size();
+ contains_validator_->validate(item, instance_location, local_reporter, patch);
+ if (mark == local_reporter.errors.size())
+ {
+ contained = true;
+ break;
+ }
+ }
+ if (!contained)
+ {
+ reporter.error(validation_output("contains",
+ this->absolute_keyword_location(),
+ instance_location.to_uri_fragment(),
+ "Expected at least one array item to match \"contains\" schema",
+ local_reporter.errors));
+ if (reporter.fail_early())
+ {
+ return;
+ }
+ }
+ }
+ }
+
+ static bool array_has_unique_items(const Json& a)
+ {
+ for (auto it = a.array_range().begin(); it != a.array_range().end(); ++it)
+ {
+ for (auto jt = it+1; jt != a.array_range().end(); ++jt)
+ {
+ if (*it == *jt)
+ {
+ return false; // contains duplicates
+ }
+ }
+ }
+ return true; // elements are unique
+ }
+ };
+
+ template <class Json>
+ class conditional_validator : public keyword_validator<Json>
+ {
+ using validator_pointer = typename keyword_validator<Json>::self_pointer;
+
+ validator_pointer if_validator_;
+ validator_pointer then_validator_;
+ validator_pointer else_validator_;
+
+ public:
+ conditional_validator(abstract_keyword_validator_factory<Json>* builder,
+ const Json& sch_if,
+ const Json& sch,
+ const std::vector<schema_location>& uris)
+ : keyword_validator<Json>((!uris.empty() && uris.back().is_absolute()) ? uris.back().string() : ""), if_validator_(nullptr), then_validator_(nullptr), else_validator_(nullptr)
+ {
+ auto then_it = sch.find("then");
+ auto else_it = sch.find("else");
+
+ if (then_it != sch.object_range().end() || else_it != sch.object_range().end())
+ {
+ if_validator_ = builder->make_keyword_validator(sch_if, uris, {"if"});
+
+ if (then_it != sch.object_range().end())
+ {
+ then_validator_ = builder->make_keyword_validator(then_it->value(), uris, {"then"});
+ }
+
+ if (else_it != sch.object_range().end())
+ {
+ else_validator_ = builder->make_keyword_validator(else_it->value(), uris, {"else"});
+ }
+ }
+ }
+ private:
+ void do_validate(const Json& instance,
+ const jsonpointer::json_pointer& instance_location,
+ error_reporter& reporter,
+ Json& patch) const final
+ {
+ if (if_validator_)
+ {
+ collecting_error_reporter local_reporter;
+
+ if_validator_->validate(instance, instance_location, local_reporter, patch);
+ if (local_reporter.errors.empty())
+ {
+ if (then_validator_)
+ then_validator_->validate(instance, instance_location, reporter, patch);
+ }
+ else
+ {
+ if (else_validator_)
+ else_validator_->validate(instance, instance_location, reporter, patch);
+ }
+ }
+ }
+ };
+
+ // enum_validator
+
+ template <class Json>
+ class enum_validator : public keyword_validator<Json>
+ {
+ Json enum_validator_;
+
+ public:
+ enum_validator(const Json& sch,
+ const std::vector<schema_location>& uris)
+ : keyword_validator<Json>((!uris.empty() && uris.back().is_absolute()) ? uris.back().string() : ""), enum_validator_(sch)
+ {
+ }
+ private:
+ void do_validate(const Json& instance,
+ const jsonpointer::json_pointer& instance_location,
+ error_reporter& reporter,
+ Json&) const final
+ {
+ bool in_range = false;
+ for (const auto& item : enum_validator_.array_range())
+ {
+ if (item == instance)
+ {
+ in_range = true;
+ break;
+ }
+ }
+
+ if (!in_range)
+ {
+ reporter.error(validation_output("enum",
+ this->absolute_keyword_location(),
+ instance_location.to_uri_fragment(),
+ instance.template as<std::string>() + " is not a valid enum value"));
+ if (reporter.fail_early())
+ {
+ return;
+ }
+ }
+ }
+ };
+
+ // const_keyword
+
+ template <class Json>
+ class const_keyword : public keyword_validator<Json>
+ {
+ Json const_validator_;
+
+ public:
+ const_keyword(const Json& sch, const std::vector<schema_location>& uris)
+ : keyword_validator<Json>((!uris.empty() && uris.back().is_absolute()) ? uris.back().string() : ""), const_validator_(sch)
+ {
+ }
+ private:
+ void do_validate(const Json& instance,
+ const jsonpointer::json_pointer& instance_location,
+ error_reporter& reporter,
+ Json&) const final
+ {
+ if (const_validator_ != instance)
+ reporter.error(validation_output("const",
+ this->absolute_keyword_location(),
+ instance_location.to_uri_fragment(),
+ "Instance is not const"));
+ }
+ };
+
+ template <class Json>
+ class type_validator : public keyword_validator<Json>
+ {
+ using validator_pointer = typename keyword_validator<Json>::self_pointer;
+
+ Json default_value_;
+ std::vector<validator_pointer> type_mapping_;
+ jsoncons::optional<enum_validator<Json>> enum_validator_;
+ jsoncons::optional<const_keyword<Json>> const_validator_;
+ std::vector<validator_pointer> combined_validators_;
+ jsoncons::optional<conditional_validator<Json>> conditional_validator_;
+ std::vector<std::string> expected_types_;
+
+ public:
+ type_validator(const type_validator&) = delete;
+ type_validator& operator=(const type_validator&) = delete;
+ type_validator(type_validator&&) = default;
+ type_validator& operator=(type_validator&&) = default;
+
+ type_validator(abstract_keyword_validator_factory<Json>* builder,
+ const Json& sch,
+ const std::vector<schema_location>& uris)
+ : keyword_validator<Json>((!uris.empty() && uris.back().is_absolute()) ? uris.back().string() : ""), default_value_(jsoncons::null_type()),
+ type_mapping_((uint8_t)(json_type::object_value)+1),
+ enum_validator_(), const_validator_()
+ {
+ //std::cout << uris.size() << " uris: ";
+ //for (const auto& uri : uris)
+ //{
+ // std::cout << uri.string() << ", ";
+ //}
+ //std::cout << "\n";
+ std::set<std::string> known_keywords;
+
+ auto it = sch.find("type");
+ if (it == sch.object_range().end())
+ {
+ initialize_type_mapping(builder, "", sch, uris, known_keywords);
+ }
+ else
+ {
+ switch (it->value().type())
+ {
+ case json_type::string_value:
+ {
+ auto type = it->value().template as<std::string>();
+ initialize_type_mapping(builder, type, sch, uris, known_keywords);
+ expected_types_.emplace_back(std::move(type));
+ break;
+ }
+
+ case json_type::array_value: // "type": ["type1", "type2"]
+ {
+ for (const auto& item : it->value().array_range())
+ {
+ auto type = item.template as<std::string>();
+ initialize_type_mapping(builder, type, sch, uris, known_keywords);
+ expected_types_.emplace_back(std::move(type));
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ const auto default_it = sch.find("default");
+ if (default_it != sch.object_range().end())
+ {
+ default_value_ = default_it->value();
+ }
+
+ it = sch.find("enum");
+ if (it != sch.object_range().end())
+ {
+ enum_validator_ = enum_validator<Json >(it->value(), uris);
+ }
+
+ it = sch.find("const");
+ if (it != sch.object_range().end())
+ {
+ const_validator_ = const_keyword<Json>(it->value(), uris);
+ }
+
+ it = sch.find("not");
+ if (it != sch.object_range().end())
+ {
+ combined_validators_.push_back(builder->make_not_validator(it->value(), uris));
+ }
+
+ it = sch.find("allOf");
+ if (it != sch.object_range().end())
+ {
+ combined_validators_.push_back(builder->make_all_of_validator(it->value(), uris));
+ }
+
+ it = sch.find("anyOf");
+ if (it != sch.object_range().end())
+ {
+ combined_validators_.push_back(builder->make_any_of_validator(it->value(), uris));
+ }
+
+ it = sch.find("oneOf");
+ if (it != sch.object_range().end())
+ {
+ combined_validators_.push_back(builder->make_one_of_validator(it->value(), uris));
+ }
+
+ it = sch.find("if");
+ if (it != sch.object_range().end())
+ {
+ conditional_validator_ = conditional_validator<Json>(builder, it->value(), sch, uris);
+ }
+ }
+ private:
+
+ void do_validate(const Json& instance,
+ const jsonpointer::json_pointer& instance_location,
+ error_reporter& reporter,
+ Json& patch) const override final
+ {
+ auto type = type_mapping_[(uint8_t) instance.type()];
+
+ if (type)
+ type->validate(instance, instance_location, reporter, patch);
+ else
+ {
+ std::ostringstream ss;
+ ss << "Expected ";
+ for (std::size_t i = 0; i < expected_types_.size(); ++i)
+ {
+ if (i > 0)
+ {
+ ss << ", ";
+ if (i+1 == expected_types_.size())
+ {
+ ss << "or ";
+ }
+ }
+ ss << expected_types_[i];
+ }
+ ss << ", found " << instance.type();
+
+ reporter.error(validation_output("type",
+ this->absolute_keyword_location(),
+ instance_location.to_uri_fragment(),
+ ss.str()));
+ if (reporter.fail_early())
+ {
+ return;
+ }
+ }
+
+ if (enum_validator_)
+ {
+ enum_validator_->validate(instance, instance_location, reporter, patch);
+ if (reporter.error_count() > 0 && reporter.fail_early())
+ {
+ return;
+ }
+ }
+
+ if (const_validator_)
+ {
+ const_validator_->validate(instance, instance_location, reporter, patch);
+ if (reporter.error_count() > 0 && reporter.fail_early())
+ {
+ return;
+ }
+ }
+
+ for (const auto& validator : combined_validators_)
+ {
+ validator->validate(instance, instance_location, reporter, patch);
+ if (reporter.error_count() > 0 && reporter.fail_early())
+ {
+ return;
+ }
+ }
+
+
+ if (conditional_validator_)
+ {
+ conditional_validator_->validate(instance, instance_location, reporter, patch);
+ if (reporter.error_count() > 0 && reporter.fail_early())
+ {
+ return;
+ }
+ }
+ }
+
+ jsoncons::optional<Json> get_default_value(const jsonpointer::json_pointer&,
+ const Json&,
+ error_reporter&) const override
+ {
+ return default_value_;
+ }
+
+ void initialize_type_mapping(abstract_keyword_validator_factory<Json>* builder,
+ const std::string& type,
+ const Json& sch,
+ const std::vector<schema_location>& uris,
+ std::set<std::string>& keywords)
+ {
+ if (type == "null")
+ {
+ type_mapping_[(uint8_t)json_type::null_value] = builder->make_null_validator(uris);
+ }
+ else if (type == "object")
+ {
+ type_mapping_[(uint8_t)json_type::object_value] = builder->make_object_validator(sch, uris);
+ }
+ else if (type == "array")
+ {
+ type_mapping_[(uint8_t)json_type::array_value] = builder->make_array_validator(sch, uris);
+ }
+ else if (type == "string")
+ {
+ type_mapping_[(uint8_t)json_type::string_value] = builder->make_string_validator(sch, uris);
+ // For binary types
+ type_mapping_[(uint8_t) json_type::byte_string_value] = type_mapping_[(uint8_t) json_type::string_value];
+ }
+ else if (type == "boolean")
+ {
+ type_mapping_[(uint8_t)json_type::bool_value] = builder->make_boolean_validator(uris);
+ }
+ else if (type == "integer")
+ {
+ type_mapping_[(uint8_t)json_type::int64_value] = builder->make_integer_validator(sch, uris, keywords);
+ type_mapping_[(uint8_t)json_type::uint64_value] = type_mapping_[(uint8_t)json_type::int64_value];
+ type_mapping_[(uint8_t)json_type::double_value] = type_mapping_[(uint8_t)json_type::int64_value];
+ }
+ else if (type == "number")
+ {
+ type_mapping_[(uint8_t)json_type::double_value] = builder->make_number_validator(sch, uris, keywords);
+ type_mapping_[(uint8_t)json_type::int64_value] = type_mapping_[(uint8_t)json_type::double_value];
+ type_mapping_[(uint8_t)json_type::uint64_value] = type_mapping_[(uint8_t)json_type::double_value];
+ }
+ else if (type.empty())
+ {
+ type_mapping_[(uint8_t)json_type::null_value] = builder->make_null_validator(uris);
+ type_mapping_[(uint8_t)json_type::object_value] = builder->make_object_validator(sch, uris);
+ type_mapping_[(uint8_t)json_type::array_value] = builder->make_array_validator(sch, uris);
+ type_mapping_[(uint8_t)json_type::string_value] = builder->make_string_validator(sch, uris);
+ // For binary types
+ type_mapping_[(uint8_t) json_type::byte_string_value] = type_mapping_[(uint8_t) json_type::string_value];
+ type_mapping_[(uint8_t)json_type::bool_value] = builder->make_boolean_validator(uris);
+ type_mapping_[(uint8_t)json_type::int64_value] = builder->make_integer_validator(sch, uris, keywords);
+ type_mapping_[(uint8_t)json_type::uint64_value] = type_mapping_[(uint8_t)json_type::int64_value];
+ type_mapping_[(uint8_t)json_type::double_value] = type_mapping_[(uint8_t)json_type::int64_value];
+ type_mapping_[(uint8_t)json_type::double_value] = builder->make_number_validator(sch, uris, keywords);
+ type_mapping_[(uint8_t)json_type::int64_value] = type_mapping_[(uint8_t)json_type::double_value];
+ type_mapping_[(uint8_t)json_type::uint64_value] = type_mapping_[(uint8_t)json_type::double_value];
+ }
+ }
+ };
+
+} // namespace jsonschema
+} // namespace jsoncons
+
+#endif // JSONCONS_JSONSCHEMA_VALUE_RULES_HPP
diff --git a/include/jsoncons_ext/jsonschema/keyword_validator_factory.hpp b/include/jsoncons_ext/jsonschema/keyword_validator_factory.hpp
new file mode 100644
index 0000000..f538105
--- /dev/null
+++ b/include/jsoncons_ext/jsonschema/keyword_validator_factory.hpp
@@ -0,0 +1,556 @@
+// Copyright 2020 Daniel Parker
+// Distributed under the Boost license, Version 1.0.
+// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+// See https://github.com/danielaparker/jsoncons for latest version
+
+#ifndef JSONCONS_JSONSCHEMA_KEYWORD_VALIDATOR_FACTORY_HPP
+#define JSONCONS_JSONSCHEMA_KEYWORD_VALIDATOR_FACTORY_HPP
+
+#include <jsoncons/config/jsoncons_config.hpp>
+#include <jsoncons/uri.hpp>
+#include <jsoncons/json.hpp>
+#include <jsoncons_ext/jsonpointer/jsonpointer.hpp>
+#include <jsoncons_ext/jsonschema/subschema.hpp>
+#include <jsoncons_ext/jsonschema/keyword_validator.hpp>
+#include <jsoncons_ext/jsonschema/schema_draft7.hpp>
+#include <jsoncons_ext/jsonschema/schema_version.hpp>
+#include <cassert>
+#include <set>
+#include <sstream>
+#include <iostream>
+#include <cassert>
+#if defined(JSONCONS_HAS_STD_REGEX)
+#include <regex>
+#endif
+
+namespace jsoncons {
+namespace jsonschema {
+
+ template <class Json>
+ using uri_resolver = std::function<Json(const jsoncons::uri & /*id*/)>;
+
+ template <class Json>
+ class reference_schema : public keyword_validator<Json>
+ {
+ using validator_pointer = typename keyword_validator<Json>::self_pointer;
+
+ validator_pointer referred_schema_;
+
+ public:
+ reference_schema(const std::string& id)
+ : keyword_validator<Json>(id), referred_schema_(nullptr) {}
+
+ void set_referred_schema(validator_pointer target) { referred_schema_ = target; }
+
+ private:
+
+ void do_validate(const Json& instance,
+ const jsonpointer::json_pointer& instance_location,
+ error_reporter& reporter,
+ Json& patch) const override
+ {
+ if (!referred_schema_)
+ {
+ reporter.error(validation_output("",
+ this->absolute_keyword_location(),
+ instance_location.to_uri_fragment(),
+ "Unresolved schema reference " + this->absolute_keyword_location()));
+ return;
+ }
+
+ referred_schema_->validate(instance, instance_location, reporter, patch);
+ }
+
+ jsoncons::optional<Json> get_default_value(const jsonpointer::json_pointer& instance_location,
+ const Json& instance,
+ error_reporter& reporter) const override
+ {
+ if (!referred_schema_)
+ {
+ reporter.error(validation_output("",
+ this->absolute_keyword_location(),
+ instance_location.to_uri_fragment(),
+ "Unresolved schema reference " + this->absolute_keyword_location()));
+ return jsoncons::optional<Json>();
+ }
+
+ return referred_schema_->get_default_value(instance_location, instance, reporter);
+ }
+ };
+
+ template <class Json>
+ class keyword_validator_factory;
+
+ template <class Json>
+ class json_schema
+ {
+ using validator_pointer = typename keyword_validator<Json>::self_pointer;
+
+ friend class keyword_validator_factory<Json>;
+
+ std::vector<std::unique_ptr<keyword_validator<Json>>> subschemas_;
+ validator_pointer root_;
+ public:
+ json_schema(std::vector<std::unique_ptr<keyword_validator<Json>>>&& subschemas,
+ validator_pointer root)
+ : subschemas_(std::move(subschemas)), root_(root)
+ {
+ if (root_ == nullptr)
+ JSONCONS_THROW(schema_error("There is no root schema to validate an instance against"));
+ }
+
+ json_schema(const json_schema&) = delete;
+ json_schema(json_schema&&) = default;
+ json_schema& operator=(const json_schema&) = delete;
+ json_schema& operator=(json_schema&&) = default;
+
+ void validate(const Json& instance,
+ const jsonpointer::json_pointer& instance_location,
+ error_reporter& reporter,
+ Json& patch) const
+ {
+ JSONCONS_ASSERT(root_ != nullptr);
+ root_->validate(instance, instance_location, reporter, patch);
+ }
+ };
+
+ template <class Json>
+ struct default_uri_resolver
+ {
+ Json operator()(const jsoncons::uri& uri)
+ {
+ if (uri.path() == "/draft-07/schema")
+ {
+ return jsoncons::jsonschema::schema_draft7<Json>::get_schema();
+ }
+
+ JSONCONS_THROW(jsonschema::schema_error("Don't know how to load JSON Schema " + std::string(uri.base())));
+ }
+ };
+
+ template <class Json>
+ class keyword_validator_factory : public abstract_keyword_validator_factory<Json>
+ {
+ using validator_pointer = typename keyword_validator<Json>::self_pointer;
+
+ struct subschema_registry
+ {
+ std::map<std::string, validator_pointer> schemas; // schemas
+ std::map<std::string, reference_schema<Json>*> unresolved; // unresolved references
+ std::map<std::string, Json> unprocessed_keywords;
+ };
+
+ uri_resolver<Json> resolver_;
+ validator_pointer root_;
+
+ // Owns all schemas
+ std::vector<std::unique_ptr<keyword_validator<Json>>> subschemas_;
+
+ // Map location to subschema_registry
+ std::map<std::string, subschema_registry> subschema_registries_;
+
+ public:
+ keyword_validator_factory(uri_resolver<Json>&& resolver) noexcept
+
+ : resolver_(std::move(resolver))
+ {
+ }
+
+ keyword_validator_factory(const keyword_validator_factory&) = delete;
+ keyword_validator_factory& operator=(const keyword_validator_factory&) = delete;
+ keyword_validator_factory(keyword_validator_factory&&) = default;
+ keyword_validator_factory& operator=(keyword_validator_factory&&) = default;
+
+ std::shared_ptr<json_schema<Json>> get_schema()
+ {
+ return std::make_shared<json_schema<Json>>(std::move(subschemas_), root_);
+ }
+
+ validator_pointer make_required_validator(const std::vector<schema_location>& uris,
+ const std::vector<std::string>& r) override
+ {
+ auto sch_orig = jsoncons::make_unique<required_validator<Json>>(uris, r);
+ auto sch = sch_orig.get();
+ subschemas_.emplace_back(std::move(sch_orig));
+ return sch;
+ }
+
+ validator_pointer make_null_validator(const std::vector<schema_location>& uris) override
+ {
+ auto sch_orig = jsoncons::make_unique<null_validator<Json>>(uris);
+ auto sch = sch_orig.get();
+ subschemas_.emplace_back(std::move(sch_orig));
+ return sch;
+ }
+
+ validator_pointer make_true_validator(const std::vector<schema_location>& uris) override
+ {
+ auto sch_orig = jsoncons::make_unique<true_validator<Json>>(uris);
+ auto sch = sch_orig.get();
+ subschemas_.emplace_back(std::move(sch_orig));
+ return sch;
+ }
+
+ validator_pointer make_false_validator(const std::vector<schema_location>& uris) override
+ {
+ auto sch_orig = jsoncons::make_unique<false_validator<Json>>(uris);
+ auto sch = sch_orig.get();
+ subschemas_.emplace_back(std::move(sch_orig));
+ return sch;
+ }
+
+ validator_pointer make_object_validator(const Json& schema,
+ const std::vector<schema_location>& uris) override
+ {
+ auto sch_orig = jsoncons::make_unique<object_validator<Json>>(this, schema, uris);
+ auto sch = sch_orig.get();
+ subschemas_.emplace_back(std::move(sch_orig));
+ return sch;
+ }
+
+ validator_pointer make_array_validator(const Json& schema,
+ const std::vector<schema_location>& uris) override
+ {
+ auto sch_orig = jsoncons::make_unique<array_validator<Json>>(this, schema, uris);
+ auto sch = sch_orig.get();
+ subschemas_.emplace_back(std::move(sch_orig));
+ return sch;
+ }
+
+ validator_pointer make_string_validator(const Json& schema,
+ const std::vector<schema_location>& uris) override
+ {
+ auto sch_orig = jsoncons::make_unique<string_validator<Json>>(schema, uris);
+ auto sch = sch_orig.get();
+ subschemas_.emplace_back(std::move(sch_orig));
+ return sch;
+ }
+
+ validator_pointer make_boolean_validator(const std::vector<schema_location>& uris) override
+ {
+ auto sch_orig = jsoncons::make_unique<boolean_validator<Json>>(uris);
+ auto sch = sch_orig.get();
+ subschemas_.emplace_back(std::move(sch_orig));
+ return sch;
+ }
+
+ validator_pointer make_integer_validator(const Json& schema,
+ const std::vector<schema_location>& uris,
+ std::set<std::string>& keywords) override
+ {
+ auto sch_orig = jsoncons::make_unique<integer_validator<Json>>(schema, uris, keywords);
+ auto sch = sch_orig.get();
+ subschemas_.emplace_back(std::move(sch_orig));
+ return sch;
+ }
+
+ validator_pointer make_number_validator(const Json& schema,
+ const std::vector<schema_location>& uris,
+ std::set<std::string>& keywords) override
+ {
+ auto sch_orig = jsoncons::make_unique<number_validator<Json>>(schema, uris, keywords);
+ auto sch = sch_orig.get();
+ subschemas_.emplace_back(std::move(sch_orig));
+ return sch;
+ }
+
+ validator_pointer make_not_validator(const Json& schema,
+ const std::vector<schema_location>& uris) override
+ {
+ auto sch_orig = jsoncons::make_unique<not_validator<Json>>(this, schema, uris);
+ auto sch = sch_orig.get();
+ subschemas_.emplace_back(std::move(sch_orig));
+ return sch;
+ }
+
+ validator_pointer make_all_of_validator(const Json& schema,
+ const std::vector<schema_location>& uris) override
+ {
+ auto sch_orig = jsoncons::make_unique<combining_validator<Json,all_of_criterion<Json>>>(this, schema, uris);
+ auto sch = sch_orig.get();
+ subschemas_.emplace_back(std::move(sch_orig));
+ return sch;
+ }
+
+ validator_pointer make_any_of_validator(const Json& schema,
+ const std::vector<schema_location>& uris) override
+ {
+ auto sch_orig = jsoncons::make_unique<combining_validator<Json,any_of_criterion<Json>>>(this, schema, uris);
+ auto sch = sch_orig.get();
+ subschemas_.emplace_back(std::move(sch_orig));
+ return sch;
+ }
+
+ validator_pointer make_one_of_validator(const Json& schema,
+ const std::vector<schema_location>& uris) override
+ {
+ auto sch_orig = jsoncons::make_unique<combining_validator<Json,one_of_criterion<Json>>>(this, schema, uris);
+ auto sch = sch_orig.get();
+ subschemas_.emplace_back(std::move(sch_orig));
+ return sch;
+ }
+
+ validator_pointer make_type_validator(const Json& schema,
+ const std::vector<schema_location>& uris) override
+ {
+ auto sch_orig = jsoncons::make_unique<type_validator<Json>>(this, schema, uris);
+ auto sch = sch_orig.get();
+ subschemas_.emplace_back(std::move(sch_orig));
+ return sch;
+ }
+
+ validator_pointer make_keyword_validator(const Json& schema,
+ const std::vector<schema_location>& uris,
+ const std::vector<std::string>& keys) override
+ {
+ std::vector<schema_location> new_uris = update_uris(schema, uris, keys);
+
+ validator_pointer sch = nullptr;
+
+ switch (schema.type())
+ {
+ case json_type::bool_value:
+ if (schema.template as<bool>())
+ {
+ sch = make_true_validator(new_uris);
+ }
+ else
+ {
+ sch = make_false_validator(new_uris);
+ }
+ break;
+ case json_type::object_value:
+ {
+ auto it = schema.find("definitions");
+ if (it != schema.object_range().end())
+ {
+ for (const auto& def : it->value().object_range())
+ make_keyword_validator(def.value(), new_uris, {"definitions", def.key()});
+ }
+
+ it = schema.find("$ref");
+ if (it != schema.object_range().end()) // this schema is a reference
+ {
+ schema_location relative(it->value().template as<std::string>());
+ schema_location id = relative.resolve(new_uris.back());
+ sch = get_or_create_reference(id);
+ }
+ else
+ {
+ sch = make_type_validator(schema, new_uris);
+ }
+ break;
+ }
+ default:
+ JSONCONS_THROW(schema_error("invalid JSON-type for a schema for " + new_uris[0].string() + ", expected: boolean or object"));
+ break;
+ }
+
+ for (const auto& uri : new_uris)
+ {
+ insert(uri, sch);
+
+ if (schema.type() == json_type::object_value)
+ {
+ for (const auto& item : schema.object_range())
+ insert_unknown_keyword(uri, item.key(), item.value()); // save unknown keywords for later reference
+ }
+ }
+ return sch;
+ }
+
+ void load_root(const Json& sch)
+ {
+ if (sch.is_object())
+ {
+ auto it = sch.find("$schema");
+ if (it != sch.object_range().end())
+ {
+ auto sv = it->value().as_string_view();
+ if (!schema_version::contains(sv))
+ {
+ std::string message("Unsupported schema version ");
+ message.append(sv.data(), sv.size());
+ JSONCONS_THROW(schema_error(message));
+ }
+ }
+ }
+ load(sch);
+ }
+
+ void load(const Json& sch)
+ {
+ subschema_registries_.clear();
+ root_ = make_keyword_validator(sch, {{"#"}}, {});
+
+ // load all external schemas that have not already been loaded
+
+ std::size_t loaded_count = 0;
+ do
+ {
+ loaded_count = 0;
+
+ std::vector<std::string> locations;
+ for (const auto& item : subschema_registries_)
+ locations.push_back(item.first);
+
+ for (const auto& loc : locations)
+ {
+ if (subschema_registries_[loc].schemas.empty()) // registry for this file is empty
+ {
+ if (resolver_)
+ {
+ Json external_schema = resolver_(loc);
+ make_keyword_validator(external_schema, {{loc}}, {});
+ ++loaded_count;
+ }
+ else
+ {
+ JSONCONS_THROW(schema_error("External schema reference '" + loc + "' needs to be loaded, but no resolver provided"));
+ }
+ }
+ }
+ }
+ while (loaded_count > 0);
+
+ for (const auto &file : subschema_registries_)
+ {
+ if (!file.second.unresolved.empty())
+ {
+ JSONCONS_THROW(schema_error("after all files have been parsed, '" +
+ (file.first == "" ? "<root>" : file.first) +
+ "' has still undefined references."));
+ }
+ }
+ }
+
+ private:
+
+ void insert(const schema_location& uri, validator_pointer s)
+ {
+ auto& file = get_or_create_file(std::string(uri.base()));
+ auto schemas_it = file.schemas.find(std::string(uri.fragment()));
+ if (schemas_it != file.schemas.end())
+ {
+ JSONCONS_THROW(schema_error("schema with " + uri.string() + " already inserted"));
+ return;
+ }
+
+ file.schemas.insert({std::string(uri.fragment()), s});
+
+ // is there an unresolved reference to this newly inserted schema?
+ auto unresolved_it = file.unresolved.find(std::string(uri.fragment()));
+ if (unresolved_it != file.unresolved.end())
+ {
+ unresolved_it->second->set_referred_schema(s);
+ file.unresolved.erase(unresolved_it);
+
+ }
+ }
+
+ void insert_unknown_keyword(const schema_location& uri,
+ const std::string& key,
+ const Json& value)
+ {
+ auto &file = get_or_create_file(std::string(uri.base()));
+ auto new_u = uri.append(key);
+ schema_location new_uri(new_u);
+
+ if (new_uri.has_fragment() && !new_uri.has_identifier())
+ {
+ auto fragment = std::string(new_uri.fragment());
+ // is there a reference looking for this unknown-keyword, which is thus no longer a unknown keyword but a schema
+ auto unresolved = file.unresolved.find(fragment);
+ if (unresolved != file.unresolved.end())
+ make_keyword_validator(value, {{new_uri}}, {});
+ else // no, nothing ref'd it, keep for later
+ file.unprocessed_keywords[fragment] = value;
+
+ // recursively add possible subschemas of unknown keywords
+ if (value.type() == json_type::object_value)
+ for (const auto& subsch : value.object_range())
+ {
+ insert_unknown_keyword(new_uri, subsch.key(), subsch.value());
+ }
+ }
+ }
+
+ validator_pointer get_or_create_reference(const schema_location& uri)
+ {
+ auto &file = get_or_create_file(std::string(uri.base()));
+
+ // a schema already exists
+ auto sch = file.schemas.find(std::string(uri.fragment()));
+ if (sch != file.schemas.end())
+ return sch->second;
+
+ // referencing an unknown keyword, turn it into schema
+ //
+ // an unknown keyword can only be referenced by a JSONPointer,
+ // not by a plain name identifier
+ if (uri.has_fragment() && !uri.has_identifier())
+ {
+ std::string fragment = std::string(uri.fragment());
+ auto unprocessed_keywords_it = file.unprocessed_keywords.find(fragment);
+ if (unprocessed_keywords_it != file.unprocessed_keywords.end())
+ {
+ auto &subsch = unprocessed_keywords_it->second;
+ auto s = make_keyword_validator(subsch, {{uri}}, {}); // A JSON Schema MUST be an object or a boolean.
+ file.unprocessed_keywords.erase(unprocessed_keywords_it);
+ return s;
+ }
+ }
+
+ // get or create a reference_schema
+ auto ref = file.unresolved.find(std::string(uri.fragment()));
+ if (ref != file.unresolved.end())
+ {
+ return ref->second; // unresolved, use existing reference
+ }
+ else
+ {
+ auto orig = jsoncons::make_unique<reference_schema<Json>>(uri.string());
+ auto p = file.unresolved.insert(ref,
+ {std::string(uri.fragment()), orig.get()})
+ ->second; // unresolved, create new reference
+
+ subschemas_.emplace_back(std::move(orig));
+ return p;
+ }
+ }
+
+ subschema_registry& get_or_create_file(const std::string& loc)
+ {
+ auto file = subschema_registries_.find(loc);
+ if (file != subschema_registries_.end())
+ return file->second;
+ else
+ return subschema_registries_.insert(file, {loc, {}})->second;
+ }
+
+ };
+
+ template <class Json>
+ std::shared_ptr<json_schema<Json>> make_schema(const Json& schema)
+ {
+ keyword_validator_factory<Json> loader{default_uri_resolver<Json>()};
+ loader.load_root(schema);
+
+ return loader.get_schema();
+ }
+
+ template <class Json,class URIResolver>
+ typename std::enable_if<type_traits::is_unary_function_object_exact<URIResolver,Json,std::string>::value,std::shared_ptr<json_schema<Json>>>::type
+ make_schema(const Json& schema, const URIResolver& resolver)
+ {
+ keyword_validator_factory<Json> loader(resolver);
+ loader.load_root(schema);
+
+ return loader.get_schema();
+ }
+
+} // namespace jsonschema
+} // namespace jsoncons
+
+#endif // JSONCONS_JSONSCHEMA_SCHEMA_LOADER_HPP
diff --git a/include/jsoncons_ext/jsonschema/schema_draft7.hpp b/include/jsoncons_ext/jsonschema/schema_draft7.hpp
new file mode 100644
index 0000000..c6f6fbc
--- /dev/null
+++ b/include/jsoncons_ext/jsonschema/schema_draft7.hpp
@@ -0,0 +1,198 @@
+// Copyright 2020 Daniel Parker
+// Distributed under the Boost license, Version 1.0.
+// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+// See https://github.com/danielaparker/jsoncons for latest version
+
+#ifndef JSONCONS_JSONSCHEMA_SCHEMA_DRAFT7_HPP
+#define JSONCONS_JSONSCHEMA_SCHEMA_DRAFT7_HPP
+
+#include <jsoncons/json.hpp>
+
+namespace jsoncons {
+namespace jsonschema {
+
+ template <class Json>
+ struct schema_draft7
+ {
+ static Json get_schema()
+ {
+ static Json schema = Json::parse(R"(
+ {
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "http://json-schema.org/draft-07/schema#",
+ "title": "Core schema meta-schema",
+ "definitions": {
+ "schemaArray": {
+ "type": "array",
+ "minItems": 1,
+ "items": { "$ref": "#" }
+ },
+ "nonNegativeInteger": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "nonNegativeIntegerDefault0": {
+ "allOf": [
+ { "$ref": "#/definitions/nonNegativeInteger" },
+ { "default": 0 }
+ ]
+ },
+ "simpleTypes": {
+ "enum": [
+ "array",
+ "boolean",
+ "integer",
+ "null",
+ "number",
+ "object",
+ "string"
+ ]
+ },
+ "stringArray": {
+ "type": "array",
+ "items": { "type": "string" },
+ "uniqueItems": true,
+ "default": []
+ }
+ },
+ "type": ["object", "boolean"],
+ "properties": {
+ "$id": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "$schema": {
+ "type": "string",
+ "format": "uri"
+ },
+ "$ref": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "$comment": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "default": true,
+ "readOnly": {
+ "type": "boolean",
+ "default": false
+ },
+ "examples": {
+ "type": "array",
+ "items": true
+ },
+ "multipleOf": {
+ "type": "number",
+ "exclusiveMinimum": 0
+ },
+ "maximum": {
+ "type": "number"
+ },
+ "exclusiveMaximum": {
+ "type": "number"
+ },
+ "minimum": {
+ "type": "number"
+ },
+ "exclusiveMinimum": {
+ "type": "number"
+ },
+ "maxLength": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "pattern": {
+ "type": "string",
+ "format": "regex"
+ },
+ "additionalItems": { "$ref": "#" },
+ "items": {
+ "anyOf": [
+ { "$ref": "#" },
+ { "$ref": "#/definitions/schemaArray" }
+ ],
+ "default": true
+ },
+ "maxItems": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "uniqueItems": {
+ "type": "boolean",
+ "default": false
+ },
+ "contains": { "$ref": "#" },
+ "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "required": { "$ref": "#/definitions/stringArray" },
+ "additionalProperties": { "$ref": "#" },
+ "definitions": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "properties": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "patternProperties": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "propertyNames": { "format": "regex" },
+ "default": {}
+ },
+ "dependencies": {
+ "type": "object",
+ "additionalProperties": {
+ "anyOf": [
+ { "$ref": "#" },
+ { "$ref": "#/definitions/stringArray" }
+ ]
+ }
+ },
+ "propertyNames": { "$ref": "#" },
+ "const": true,
+ "enum": {
+ "type": "array",
+ "items": true,
+ "minItems": 1,
+ "uniqueItems": true
+ },
+ "type": {
+ "anyOf": [
+ { "$ref": "#/definitions/simpleTypes" },
+ {
+ "type": "array",
+ "items": { "$ref": "#/definitions/simpleTypes" },
+ "minItems": 1,
+ "uniqueItems": true
+ }
+ ]
+ },
+ "format": { "type": "string" },
+ "contentMediaType": { "type": "string" },
+ "contentEncoding": { "type": "string" },
+ "if": { "$ref": "#" },
+ "then": { "$ref": "#" },
+ "else": { "$ref": "#" },
+ "allOf": { "$ref": "#/definitions/schemaArray" },
+ "anyOf": { "$ref": "#/definitions/schemaArray" },
+ "oneOf": { "$ref": "#/definitions/schemaArray" },
+ "not": { "$ref": "#" }
+ },
+ "default": true
+ }
+ )");
+
+ return schema;
+ }
+ };
+
+} // namespace jsonschema
+} // namespace jsoncons
+
+#endif // JSONCONS_JSONSCHEMA_SCHEMA_DRAFT7_HPP
diff --git a/include/jsoncons_ext/jsonschema/schema_location.hpp b/include/jsoncons_ext/jsonschema/schema_location.hpp
new file mode 100644
index 0000000..fd1a743
--- /dev/null
+++ b/include/jsoncons_ext/jsonschema/schema_location.hpp
@@ -0,0 +1,200 @@
+// Copyright 2020 Daniel Parker
+// Distributed under the Boost license, Version 1.0.
+// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+// See https://github.com/danielaparker/jsoncons for latest version
+
+#ifndef JSONCONS_JSONSCHEMA_SCHEMA_LOCATION_HPP
+#define JSONCONS_JSONSCHEMA_SCHEMA_LOCATION_HPP
+
+#include <jsoncons/config/jsoncons_config.hpp>
+#include <jsoncons/uri.hpp>
+#include <jsoncons/json.hpp>
+#include <jsoncons_ext/jsonpointer/jsonpointer.hpp>
+#include <jsoncons_ext/jsonschema/jsonschema_error.hpp>
+
+namespace jsoncons {
+namespace jsonschema {
+
+ class schema_location
+ {
+ jsoncons::uri uri_;
+ std::string identifier_;
+ public:
+ schema_location()
+ {
+ }
+
+ schema_location(const std::string& uri)
+ {
+ auto pos = uri.find('#');
+ if (pos != std::string::npos)
+ {
+ identifier_ = uri.substr(pos + 1);
+ unescape_percent(identifier_);
+ }
+ uri_ = jsoncons::uri(uri);
+ }
+
+ jsoncons::uri uri() const
+ {
+ return uri_;
+ }
+
+ bool has_fragment() const
+ {
+ return !identifier_.empty();
+ }
+
+ bool has_identifier() const
+ {
+ return !identifier_.empty() && identifier_.front() != '/';
+ }
+
+ jsoncons::string_view base() const
+ {
+ return uri_.base();
+ }
+
+ jsoncons::string_view path() const
+ {
+ return uri_.path();
+ }
+
+ bool is_absolute() const
+ {
+ return uri_.is_absolute();
+ }
+
+ std::string identifier() const
+ {
+ return identifier_;
+ }
+
+ std::string fragment() const
+ {
+ return identifier_;
+ }
+
+ schema_location resolve(const schema_location& uri) const
+ {
+ schema_location new_uri;
+ new_uri.identifier_ = identifier_;
+ new_uri.uri_ = uri_.resolve(uri.uri_);
+ return new_uri;
+ }
+
+ int compare(const schema_location& other) const
+ {
+ int result = uri_.compare(other.uri_);
+ if (result != 0)
+ {
+ return result;
+ }
+ return result;
+ }
+
+ schema_location append(const std::string& field) const
+ {
+ if (has_identifier())
+ return *this;
+
+ jsoncons::jsonpointer::json_pointer pointer(std::string(uri_.fragment()));
+ pointer /= field;
+
+ jsoncons::uri new_uri(uri_.scheme(),
+ uri_.userinfo(),
+ uri_.host(),
+ uri_.port(),
+ uri_.path(),
+ uri_.query(),
+ pointer.to_string());
+
+ schema_location wrapper;
+ wrapper.uri_ = new_uri;
+ wrapper.identifier_ = pointer.to_string();
+
+ return wrapper;
+ }
+
+ schema_location append(std::size_t index) const
+ {
+ if (has_identifier())
+ return *this;
+
+ jsoncons::jsonpointer::json_pointer pointer(std::string(uri_.fragment()));
+ pointer /= index;
+
+ jsoncons::uri new_uri(uri_.scheme(),
+ uri_.userinfo(),
+ uri_.host(),
+ uri_.port(),
+ uri_.path(),
+ uri_.query(),
+ pointer.to_string());
+
+ schema_location wrapper;
+ wrapper.uri_ = new_uri;
+ wrapper.identifier_ = pointer.to_string();
+
+ return wrapper;
+ }
+
+ std::string string() const
+ {
+ std::string s = uri_.string();
+ return s;
+ }
+
+ friend bool operator==(const schema_location& lhs, const schema_location& rhs)
+ {
+ return lhs.compare(rhs) == 0;
+ }
+
+ friend bool operator!=(const schema_location& lhs, const schema_location& rhs)
+ {
+ return lhs.compare(rhs) != 0;
+ }
+
+ friend bool operator<(const schema_location& lhs, const schema_location& rhs)
+ {
+ return lhs.compare(rhs) < 0;
+ }
+
+ friend bool operator<=(const schema_location& lhs, const schema_location& rhs)
+ {
+ return lhs.compare(rhs) <= 0;
+ }
+
+ friend bool operator>(const schema_location& lhs, const schema_location& rhs)
+ {
+ return lhs.compare(rhs) > 0;
+ }
+
+ friend bool operator>=(const schema_location& lhs, const schema_location& rhs)
+ {
+ return lhs.compare(rhs) >= 0;
+ }
+ private:
+ static void unescape_percent(std::string& s)
+ {
+ if (s.size() >= 3)
+ {
+ std::size_t pos = s.size() - 2;
+ while (pos-- >= 1)
+ {
+ if (s[pos] == '%')
+ {
+ std::string hex = s.substr(pos + 1, 2);
+ char ch = (char) std::strtoul(hex.c_str(), nullptr, 16);
+ s.replace(pos, 3, 1, ch);
+ }
+ }
+ }
+ }
+ };
+
+} // namespace jsonschema
+} // namespace jsoncons
+
+#endif // JSONCONS_JSONSCHEMA_RULE_HPP
diff --git a/include/jsoncons_ext/jsonschema/schema_version.hpp b/include/jsoncons_ext/jsonschema/schema_version.hpp
new file mode 100644
index 0000000..b804712
--- /dev/null
+++ b/include/jsoncons_ext/jsonschema/schema_version.hpp
@@ -0,0 +1,35 @@
+// Copyright 2021 Daniel Parker
+// Distributed under the Boost license, Version 1.0.
+// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+// See https://github.com/danielaparker/jsoncons for latest version
+
+#ifndef JSONCONS_JSONSCHEMA_SCHEMA_VERSION_HPP
+#define JSONCONS_JSONSCHEMA_SCHEMA_VERSION_HPP
+
+#include <jsoncons/json.hpp>
+
+namespace jsoncons {
+namespace jsonschema {
+
+ class schema_version
+ {
+ public:
+ static bool contains(const string_view& url)
+ {
+ if (url.find("json-schema.org/draft-07/schema#") != string_view::npos)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ };
+
+
+} // namespace jsonschema
+} // namespace jsoncons
+
+#endif // JSONCONS_JSONSCHEMA_JSONSCHEMA_VERSION_HPP
diff --git a/include/jsoncons_ext/jsonschema/subschema.hpp b/include/jsoncons_ext/jsonschema/subschema.hpp
new file mode 100644
index 0000000..cbe0af4
--- /dev/null
+++ b/include/jsoncons_ext/jsonschema/subschema.hpp
@@ -0,0 +1,144 @@
+// Copyright 2020 Daniel Parker
+// Distributed under the Boost license, Version 1.0.
+// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+// See https://github.com/danielaparker/jsoncons for latest version
+
+#ifndef JSONCONS_JSONSCHEMA_SUBSCHEMA_HPP
+#define JSONCONS_JSONSCHEMA_SUBSCHEMA_HPP
+
+#include <jsoncons/config/jsoncons_config.hpp>
+#include <jsoncons/uri.hpp>
+#include <jsoncons/json.hpp>
+#include <jsoncons_ext/jsonpointer/jsonpointer.hpp>
+#include <jsoncons_ext/jsonschema/jsonschema_error.hpp>
+#include <jsoncons_ext/jsonschema/schema_location.hpp>
+
+namespace jsoncons {
+namespace jsonschema {
+
+ // Interface for validation error handlers
+ class error_reporter
+ {
+ bool fail_early_;
+ std::size_t error_count_;
+ public:
+ error_reporter(bool fail_early = false)
+ : fail_early_(fail_early), error_count_(0)
+ {
+ }
+
+ virtual ~error_reporter() = default;
+
+ void error(const validation_output& o)
+ {
+ ++error_count_;
+ do_error(o);
+ }
+
+ std::size_t error_count() const
+ {
+ return error_count_;
+ }
+
+ bool fail_early() const
+ {
+ return fail_early_;
+ }
+
+ private:
+ virtual void do_error(const validation_output& /* e */) = 0;
+ };
+
+ template <class Json>
+ class keyword_validator
+ {
+ std::string absolute_keyword_location_;
+ public:
+ using self_pointer = keyword_validator<Json>*;
+
+ keyword_validator(const std::string& absolute_keyword_location)
+ : absolute_keyword_location_(absolute_keyword_location)
+ {
+ }
+
+ keyword_validator(const keyword_validator&) = delete;
+ keyword_validator(keyword_validator&&) = default;
+ keyword_validator& operator=(const keyword_validator&) = delete;
+ keyword_validator& operator=(keyword_validator&&) = default;
+
+ virtual ~keyword_validator() = default;
+
+ const std::string& absolute_keyword_location() const
+ {
+ return absolute_keyword_location_;
+ }
+
+ void validate(const Json& instance,
+ const jsonpointer::json_pointer& instance_location,
+ error_reporter& reporter,
+ Json& patch) const
+ {
+ do_validate(instance,
+ instance_location,
+ reporter,
+ patch);
+ }
+
+ virtual jsoncons::optional<Json> get_default_value(const jsonpointer::json_pointer&, const Json&, error_reporter&) const
+ {
+ return jsoncons::optional<Json>();
+ }
+
+ private:
+ virtual void do_validate(const Json& instance,
+ const jsonpointer::json_pointer& instance_location,
+ error_reporter& reporter,
+ Json& patch) const = 0;
+ };
+
+ template <class Json>
+ std::vector<schema_location> update_uris(const Json& schema,
+ const std::vector<schema_location>& uris,
+ const std::vector<std::string>& keys)
+ {
+ // Exclude uri's that are not plain name identifiers
+ std::vector<schema_location> new_uris;
+ for (const auto& uri : uris)
+ {
+ if (!uri.has_identifier())
+ new_uris.push_back(uri);
+ }
+
+ // Append the keys for this sub-schema to the uri's
+ for (const auto& key : keys)
+ {
+ for (auto& uri : new_uris)
+ {
+ auto new_u = uri.append(key);
+ uri = schema_location(new_u);
+ }
+ }
+ if (schema.type() == json_type::object_value)
+ {
+ auto it = schema.find("$id"); // If $id is found, this schema can be referenced by the id
+ if (it != schema.object_range().end())
+ {
+ std::string id = it->value().template as<std::string>();
+ // Add it to the list if it is not already there
+ if (std::find(new_uris.begin(), new_uris.end(), id) == new_uris.end())
+ {
+ schema_location relative(id);
+ schema_location new_uri = relative.resolve(new_uris.back());
+ new_uris.emplace_back(new_uri);
+ }
+ }
+ }
+
+ return new_uris;
+ }
+
+} // namespace jsonschema
+} // namespace jsoncons
+
+#endif // JSONCONS_JSONSCHEMA_RULE_HPP