valijson/examples/valijson_nlohmann_bundled.hpp
2024-01-06 22:33:22 +01:00

10773 lines
351 KiB
C++

#pragma once
#include <iostream>
#include <exception>
namespace valijson {
#if defined(_MSC_VER) && _MSC_VER == 1800
#define VALIJSON_NORETURN __declspec(noreturn)
#else
#define VALIJSON_NORETURN [[noreturn]]
#endif
#if VALIJSON_USE_EXCEPTIONS
#include <stdexcept>
VALIJSON_NORETURN inline void throwRuntimeError(const std::string& msg) {
throw std::runtime_error(msg);
}
VALIJSON_NORETURN inline void throwLogicError(const std::string& msg) {
throw std::logic_error(msg);
}
#else
VALIJSON_NORETURN inline void throwRuntimeError(const std::string& msg) {
std::cerr << msg << std::endl;
abort();
}
VALIJSON_NORETURN inline void throwLogicError(const std::string& msg) {
std::cerr << msg << std::endl;
abort();
}
#endif
VALIJSON_NORETURN inline void throwNotSupported() {
throwRuntimeError("Not supported");
}
} // namespace valijson
// Copyright (C) 2011 - 2012 Andrzej Krzemienski.
//
// Use, modification, and distribution is subject to the Boost Software
// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
// The idea and interface is based on Boost.Optional library
// authored by Fernando Luis Cacciola Carballal
# ifndef OPTIONAL_HPP
# define OPTIONAL_HPP
# include <utility>
# include <type_traits>
# include <initializer_list>
# include <cassert>
# include <functional>
# include <string>
# include <stdexcept>
# define TR2_OPTIONAL_REQUIRES(...) typename enable_if<__VA_ARGS__::value, bool>::type = false
# if defined __GNUC__ // NOTE: GNUC is also defined for Clang
# if (__GNUC__ == 4) && (__GNUC_MINOR__ >= 8)
# define TR2_OPTIONAL_GCC_4_8_AND_HIGHER___
# elif (__GNUC__ > 4)
# define TR2_OPTIONAL_GCC_4_8_AND_HIGHER___
# endif
#
# if (__GNUC__ == 4) && (__GNUC_MINOR__ >= 7)
# define TR2_OPTIONAL_GCC_4_7_AND_HIGHER___
# elif (__GNUC__ > 4)
# define TR2_OPTIONAL_GCC_4_7_AND_HIGHER___
# endif
#
# if (__GNUC__ == 4) && (__GNUC_MINOR__ == 8) && (__GNUC_PATCHLEVEL__ >= 1)
# define TR2_OPTIONAL_GCC_4_8_1_AND_HIGHER___
# elif (__GNUC__ == 4) && (__GNUC_MINOR__ >= 9)
# define TR2_OPTIONAL_GCC_4_8_1_AND_HIGHER___
# elif (__GNUC__ > 4)
# define TR2_OPTIONAL_GCC_4_8_1_AND_HIGHER___
# endif
# endif
#
# if defined __clang_major__
# if (__clang_major__ == 3 && __clang_minor__ >= 5)
# define TR2_OPTIONAL_CLANG_3_5_AND_HIGHTER_
# elif (__clang_major__ > 3)
# define TR2_OPTIONAL_CLANG_3_5_AND_HIGHTER_
# endif
# if defined TR2_OPTIONAL_CLANG_3_5_AND_HIGHTER_
# define TR2_OPTIONAL_CLANG_3_4_2_AND_HIGHER_
# elif (__clang_major__ == 3 && __clang_minor__ == 4 && __clang_patchlevel__ >= 2)
# define TR2_OPTIONAL_CLANG_3_4_2_AND_HIGHER_
# endif
# endif
#
# if defined _MSC_VER
# if (_MSC_VER >= 1900)
# define TR2_OPTIONAL_MSVC_2015_AND_HIGHER___
# endif
# endif
# if defined __clang__
# if (__clang_major__ > 2) || (__clang_major__ == 2) && (__clang_minor__ >= 9)
# define OPTIONAL_HAS_THIS_RVALUE_REFS 1
# else
# define OPTIONAL_HAS_THIS_RVALUE_REFS 0
# endif
# elif defined TR2_OPTIONAL_GCC_4_8_1_AND_HIGHER___
# define OPTIONAL_HAS_THIS_RVALUE_REFS 1
# elif defined TR2_OPTIONAL_MSVC_2015_AND_HIGHER___
# define OPTIONAL_HAS_THIS_RVALUE_REFS 1
# else
# define OPTIONAL_HAS_THIS_RVALUE_REFS 0
# endif
# if defined TR2_OPTIONAL_GCC_4_8_1_AND_HIGHER___
# define OPTIONAL_HAS_CONSTEXPR_INIT_LIST 1
# define OPTIONAL_CONSTEXPR_INIT_LIST constexpr
# else
# define OPTIONAL_HAS_CONSTEXPR_INIT_LIST 0
# define OPTIONAL_CONSTEXPR_INIT_LIST
# endif
# if defined TR2_OPTIONAL_CLANG_3_5_AND_HIGHTER_ && (defined __cplusplus) && (__cplusplus != 201103L)
# define OPTIONAL_HAS_MOVE_ACCESSORS 1
# else
# define OPTIONAL_HAS_MOVE_ACCESSORS 0
# endif
# // In C++11 constexpr implies const, so we need to make non-const members also non-constexpr
# if (defined __cplusplus) && (__cplusplus == 201103L)
# define OPTIONAL_MUTABLE_CONSTEXPR
# else
# define OPTIONAL_MUTABLE_CONSTEXPR constexpr
# endif
namespace std{
namespace experimental{
// BEGIN workaround for missing is_trivially_destructible
# if defined TR2_OPTIONAL_GCC_4_8_AND_HIGHER___
// leave it: it is already there
# elif defined TR2_OPTIONAL_CLANG_3_4_2_AND_HIGHER_
// leave it: it is already there
# elif defined TR2_OPTIONAL_MSVC_2015_AND_HIGHER___
// leave it: it is already there
# elif defined TR2_OPTIONAL_DISABLE_EMULATION_OF_TYPE_TRAITS
// leave it: the user doesn't want it
# else
template <typename T>
using is_trivially_destructible = std::has_trivial_destructor<T>;
# endif
// END workaround for missing is_trivially_destructible
# if (defined TR2_OPTIONAL_GCC_4_7_AND_HIGHER___)
// leave it; our metafunctions are already defined.
# elif defined TR2_OPTIONAL_CLANG_3_4_2_AND_HIGHER_
// leave it; our metafunctions are already defined.
# elif defined TR2_OPTIONAL_MSVC_2015_AND_HIGHER___
// leave it: it is already there
# elif defined TR2_OPTIONAL_DISABLE_EMULATION_OF_TYPE_TRAITS
// leave it: the user doesn't want it
# else
// workaround for missing traits in GCC and CLANG
template <class T>
struct is_nothrow_move_constructible
{
constexpr static bool value = std::is_nothrow_constructible<T, T&&>::value;
};
template <class T, class U>
struct is_assignable
{
template <class X, class Y>
constexpr static bool has_assign(...) { return false; }
template <class X, class Y, size_t S = sizeof((std::declval<X>() = std::declval<Y>(), true)) >
// the comma operator is necessary for the cases where operator= returns void
constexpr static bool has_assign(bool) { return true; }
constexpr static bool value = has_assign<T, U>(true);
};
template <class T>
struct is_nothrow_move_assignable
{
template <class X, bool has_any_move_assign>
struct has_nothrow_move_assign {
constexpr static bool value = false;
};
template <class X>
struct has_nothrow_move_assign<X, true> {
constexpr static bool value = noexcept( std::declval<X&>() = std::declval<X&&>() );
};
constexpr static bool value = has_nothrow_move_assign<T, is_assignable<T&, T&&>::value>::value;
};
// end workaround
# endif
// 20.5.4, optional for object types
template <class T> class optional;
// 20.5.5, optional for lvalue reference types
template <class T> class optional<T&>;
// workaround: std utility functions aren't constexpr yet
template <class T> inline constexpr T&& constexpr_forward(typename std::remove_reference<T>::type& t) noexcept
{
return static_cast<T&&>(t);
}
template <class T> inline constexpr T&& constexpr_forward(typename std::remove_reference<T>::type&& t) noexcept
{
static_assert(!std::is_lvalue_reference<T>::value, "!!");
return static_cast<T&&>(t);
}
template <class T> inline constexpr typename std::remove_reference<T>::type&& constexpr_move(T&& t) noexcept
{
return static_cast<typename std::remove_reference<T>::type&&>(t);
}
#if defined NDEBUG
# define TR2_OPTIONAL_ASSERTED_EXPRESSION(CHECK, EXPR) (EXPR)
#else
# define TR2_OPTIONAL_ASSERTED_EXPRESSION(CHECK, EXPR) ((CHECK) ? (EXPR) : ([]{assert(!#CHECK);}(), (EXPR)))
#endif
namespace detail_
{
// static_addressof: a constexpr version of addressof
template <typename T>
struct has_overloaded_addressof
{
template <class X>
constexpr static bool has_overload(...) { return false; }
template <class X, size_t S = sizeof(std::declval<X&>().operator&()) >
constexpr static bool has_overload(bool) { return true; }
constexpr static bool value = has_overload<T>(true);
};
template <typename T, TR2_OPTIONAL_REQUIRES(!has_overloaded_addressof<T>)>
constexpr T* static_addressof(T& ref)
{
return &ref;
}
template <typename T, TR2_OPTIONAL_REQUIRES(has_overloaded_addressof<T>)>
T* static_addressof(T& ref)
{
return std::addressof(ref);
}
// the call to convert<A>(b) has return type A and converts b to type A iff b decltype(b) is implicitly convertible to A
template <class U>
constexpr U convert(U v) { return v; }
} // namespace detail
constexpr struct trivial_init_t{} trivial_init{};
// 20.5.6, In-place construction
constexpr struct in_place_t{} in_place{};
// 20.5.7, Disengaged state indicator
struct nullopt_t
{
struct init{};
constexpr explicit nullopt_t(init){}
};
constexpr nullopt_t nullopt{nullopt_t::init()};
// 20.5.8, class bad_optional_access
class bad_optional_access : public logic_error {
public:
explicit bad_optional_access(const string& what_arg) : logic_error{what_arg} {}
explicit bad_optional_access(const char* what_arg) : logic_error{what_arg} {}
};
template <class T>
union storage_t
{
unsigned char dummy_;
T value_;
constexpr storage_t( trivial_init_t ) noexcept : dummy_() {}
template <class... Args>
constexpr storage_t( Args&&... args ) : value_(constexpr_forward<Args>(args)...) {}
~storage_t(){}
};
template <class T>
union constexpr_storage_t
{
unsigned char dummy_;
T value_;
constexpr constexpr_storage_t( trivial_init_t ) noexcept : dummy_() {}
template <class... Args>
constexpr constexpr_storage_t( Args&&... args ) : value_(constexpr_forward<Args>(args)...) {}
~constexpr_storage_t() = default;
};
template <class T>
struct optional_base
{
bool init_;
storage_t<T> storage_;
constexpr optional_base() noexcept : init_(false), storage_(trivial_init) {}
explicit constexpr optional_base(const T& v) : init_(true), storage_(v) {}
explicit constexpr optional_base(T&& v) : init_(true), storage_(constexpr_move(v)) {}
template <class... Args> explicit optional_base(in_place_t, Args&&... args)
: init_(true), storage_(constexpr_forward<Args>(args)...) {}
template <class U, class... Args, TR2_OPTIONAL_REQUIRES(is_constructible<T, std::initializer_list<U>>)>
explicit optional_base(in_place_t, std::initializer_list<U> il, Args&&... args)
: init_(true), storage_(il, std::forward<Args>(args)...) {}
~optional_base() { if (init_) storage_.value_.T::~T(); }
};
template <class T>
struct constexpr_optional_base
{
bool init_;
constexpr_storage_t<T> storage_;
constexpr constexpr_optional_base() noexcept : init_(false), storage_(trivial_init) {}
explicit constexpr constexpr_optional_base(const T& v) : init_(true), storage_(v) {}
explicit constexpr constexpr_optional_base(T&& v) : init_(true), storage_(constexpr_move(v)) {}
template <class... Args> explicit constexpr constexpr_optional_base(in_place_t, Args&&... args)
: init_(true), storage_(constexpr_forward<Args>(args)...) {}
template <class U, class... Args, TR2_OPTIONAL_REQUIRES(is_constructible<T, std::initializer_list<U>>)>
OPTIONAL_CONSTEXPR_INIT_LIST explicit constexpr_optional_base(in_place_t, std::initializer_list<U> il, Args&&... args)
: init_(true), storage_(il, std::forward<Args>(args)...) {}
~constexpr_optional_base() = default;
};
template <class T>
using OptionalBase = typename std::conditional<
is_trivially_destructible<T>::value,
constexpr_optional_base<typename std::remove_const<T>::type>,
optional_base<typename std::remove_const<T>::type>
>::type;
template <class T>
class optional : private OptionalBase<T>
{
static_assert( !std::is_same<typename std::decay<T>::type, nullopt_t>::value, "bad T" );
static_assert( !std::is_same<typename std::decay<T>::type, in_place_t>::value, "bad T" );
constexpr bool initialized() const noexcept { return OptionalBase<T>::init_; }
typename std::remove_const<T>::type* dataptr() { return std::addressof(OptionalBase<T>::storage_.value_); }
constexpr const T* dataptr() const { return detail_::static_addressof(OptionalBase<T>::storage_.value_); }
# if OPTIONAL_HAS_THIS_RVALUE_REFS == 1
constexpr const T& contained_val() const& { return OptionalBase<T>::storage_.value_; }
# if OPTIONAL_HAS_MOVE_ACCESSORS == 1
OPTIONAL_MUTABLE_CONSTEXPR T&& contained_val() && { return std::move(OptionalBase<T>::storage_.value_); }
OPTIONAL_MUTABLE_CONSTEXPR T& contained_val() & { return OptionalBase<T>::storage_.value_; }
# else
T& contained_val() & { return OptionalBase<T>::storage_.value_; }
T&& contained_val() && { return std::move(OptionalBase<T>::storage_.value_); }
# endif
# else
constexpr const T& contained_val() const { return OptionalBase<T>::storage_.value_; }
T& contained_val() { return OptionalBase<T>::storage_.value_; }
# endif
void clear() noexcept {
if (initialized()) dataptr()->T::~T();
OptionalBase<T>::init_ = false;
}
template <class... Args>
void initialize(Args&&... args) noexcept(noexcept(T(std::forward<Args>(args)...)))
{
assert(!OptionalBase<T>::init_);
::new (static_cast<void*>(dataptr())) T(std::forward<Args>(args)...);
OptionalBase<T>::init_ = true;
}
template <class U, class... Args>
void initialize(std::initializer_list<U> il, Args&&... args) noexcept(noexcept(T(il, std::forward<Args>(args)...)))
{
assert(!OptionalBase<T>::init_);
::new (static_cast<void*>(dataptr())) T(il, std::forward<Args>(args)...);
OptionalBase<T>::init_ = true;
}
public:
typedef T value_type;
// 20.5.5.1, constructors
constexpr optional() noexcept : OptionalBase<T>() {}
constexpr optional(nullopt_t) noexcept : OptionalBase<T>() {}
optional(const optional& rhs)
: OptionalBase<T>()
{
if (rhs.initialized()) {
::new (static_cast<void*>(dataptr())) T(*rhs);
OptionalBase<T>::init_ = true;
}
}
optional(optional&& rhs) noexcept(is_nothrow_move_constructible<T>::value)
: OptionalBase<T>()
{
if (rhs.initialized()) {
::new (static_cast<void*>(dataptr())) T(std::move(*rhs));
OptionalBase<T>::init_ = true;
}
}
constexpr optional(const T& v) : OptionalBase<T>(v) {}
constexpr optional(T&& v) : OptionalBase<T>(constexpr_move(v)) {}
template <class... Args>
explicit constexpr optional(in_place_t, Args&&... args)
: OptionalBase<T>(in_place_t{}, constexpr_forward<Args>(args)...) {}
template <class U, class... Args, TR2_OPTIONAL_REQUIRES(is_constructible<T, std::initializer_list<U>>)>
OPTIONAL_CONSTEXPR_INIT_LIST explicit optional(in_place_t, std::initializer_list<U> il, Args&&... args)
: OptionalBase<T>(in_place_t{}, il, constexpr_forward<Args>(args)...) {}
// 20.5.4.2, Destructor
~optional() = default;
// 20.5.4.3, assignment
optional& operator=(nullopt_t) noexcept
{
clear();
return *this;
}
optional& operator=(const optional& rhs)
{
if (initialized() == true && rhs.initialized() == false) clear();
else if (initialized() == false && rhs.initialized() == true) initialize(*rhs);
else if (initialized() == true && rhs.initialized() == true) contained_val() = *rhs;
return *this;
}
optional& operator=(optional&& rhs)
noexcept(is_nothrow_move_assignable<T>::value && is_nothrow_move_constructible<T>::value)
{
if (initialized() == true && rhs.initialized() == false) clear();
else if (initialized() == false && rhs.initialized() == true) initialize(std::move(*rhs));
else if (initialized() == true && rhs.initialized() == true) contained_val() = std::move(*rhs);
return *this;
}
template <class U>
auto operator=(U&& v)
-> typename enable_if
<
is_same<typename decay<U>::type, T>::value,
optional&
>::type
{
if (initialized()) { contained_val() = std::forward<U>(v); }
else { initialize(std::forward<U>(v)); }
return *this;
}
template <class... Args>
void emplace(Args&&... args)
{
clear();
initialize(std::forward<Args>(args)...);
}
template <class U, class... Args>
void emplace(initializer_list<U> il, Args&&... args)
{
clear();
initialize<U, Args...>(il, std::forward<Args>(args)...);
}
// 20.5.4.4, Swap
void swap(optional<T>& rhs) noexcept(is_nothrow_move_constructible<T>::value && noexcept(swap(declval<T&>(), declval<T&>())))
{
if (initialized() == true && rhs.initialized() == false) { rhs.initialize(std::move(**this)); clear(); }
else if (initialized() == false && rhs.initialized() == true) { initialize(std::move(*rhs)); rhs.clear(); }
else if (initialized() == true && rhs.initialized() == true) { using std::swap; swap(**this, *rhs); }
}
// 20.5.4.5, Observers
explicit constexpr operator bool() const noexcept { return initialized(); }
constexpr T const* operator ->() const {
return TR2_OPTIONAL_ASSERTED_EXPRESSION(initialized(), dataptr());
}
# if OPTIONAL_HAS_MOVE_ACCESSORS == 1
OPTIONAL_MUTABLE_CONSTEXPR T* operator ->() {
assert (initialized());
return dataptr();
}
constexpr T const& operator *() const& {
return TR2_OPTIONAL_ASSERTED_EXPRESSION(initialized(), contained_val());
}
OPTIONAL_MUTABLE_CONSTEXPR T& operator *() & {
assert (initialized());
return contained_val();
}
OPTIONAL_MUTABLE_CONSTEXPR T&& operator *() && {
assert (initialized());
return constexpr_move(contained_val());
}
constexpr T const& value() const& {
return initialized() ? contained_val() : (valijson::throwRuntimeError("bad optional access"), contained_val());
}
OPTIONAL_MUTABLE_CONSTEXPR T& value() & {
return initialized() ? contained_val() : (valijson::throwRuntimeError("bad optional access"), contained_val());
}
OPTIONAL_MUTABLE_CONSTEXPR T&& value() && {
if (!initialized()) valijson::throwRuntimeError("bad optional access");
return std::move(contained_val());
}
# else
T* operator ->() {
assert (initialized());
return dataptr();
}
constexpr T const& operator *() const {
return TR2_OPTIONAL_ASSERTED_EXPRESSION(initialized(), contained_val());
}
T& operator *() {
assert (initialized());
return contained_val();
}
constexpr T const& value() const {
return initialized() ? contained_val() : (valijson::throwRuntimeError("bad optional access"), contained_val());
}
T& value() {
return initialized() ? contained_val() : (valijson::throwRuntimeError("bad optional access"), contained_val());
}
# endif
# if OPTIONAL_HAS_THIS_RVALUE_REFS == 1
template <class V>
constexpr T value_or(V&& v) const&
{
return *this ? **this : detail_::convert<T>(constexpr_forward<V>(v));
}
# if OPTIONAL_HAS_MOVE_ACCESSORS == 1
template <class V>
OPTIONAL_MUTABLE_CONSTEXPR T value_or(V&& v) &&
{
return *this ? constexpr_move(const_cast<optional<T>&>(*this).contained_val()) : detail_::convert<T>(constexpr_forward<V>(v));
}
# else
template <class V>
T value_or(V&& v) &&
{
return *this ? constexpr_move(const_cast<optional<T>&>(*this).contained_val()) : detail_::convert<T>(constexpr_forward<V>(v));
}
# endif
# else
template <class V>
constexpr T value_or(V&& v) const
{
return *this ? **this : detail_::convert<T>(constexpr_forward<V>(v));
}
# endif
};
template <class T>
class optional<T&>
{
static_assert( !std::is_same<T, nullopt_t>::value, "bad T" );
static_assert( !std::is_same<T, in_place_t>::value, "bad T" );
T* ref;
public:
// 20.5.5.1, construction/destruction
constexpr optional() noexcept : ref(nullptr) {}
constexpr optional(nullopt_t) noexcept : ref(nullptr) {}
constexpr optional(T& v) noexcept : ref(detail_::static_addressof(v)) {}
optional(T&&) = delete;
constexpr optional(const optional& rhs) noexcept : ref(rhs.ref) {}
explicit constexpr optional(in_place_t, T& v) noexcept : ref(detail_::static_addressof(v)) {}
explicit optional(in_place_t, T&&) = delete;
~optional() = default;
// 20.5.5.2, mutation
optional& operator=(nullopt_t) noexcept {
ref = nullptr;
return *this;
}
// optional& operator=(const optional& rhs) noexcept {
// ref = rhs.ref;
// return *this;
// }
// optional& operator=(optional&& rhs) noexcept {
// ref = rhs.ref;
// return *this;
// }
template <typename U>
auto operator=(U&& rhs) noexcept
-> typename enable_if
<
is_same<typename decay<U>::type, optional<T&>>::value,
optional&
>::type
{
ref = rhs.ref;
return *this;
}
template <typename U>
auto operator=(U&& rhs) noexcept
-> typename enable_if
<
!is_same<typename decay<U>::type, optional<T&>>::value,
optional&
>::type
= delete;
void emplace(T& v) noexcept {
ref = detail_::static_addressof(v);
}
void emplace(T&&) = delete;
void swap(optional<T&>& rhs) noexcept
{
std::swap(ref, rhs.ref);
}
// 20.5.5.3, observers
constexpr T* operator->() const {
return TR2_OPTIONAL_ASSERTED_EXPRESSION(ref, ref);
}
constexpr T& operator*() const {
return TR2_OPTIONAL_ASSERTED_EXPRESSION(ref, *ref);
}
constexpr T& value() const {
return ref ? *ref : (valijson::throwRuntimeError("bad optional access"), *ref);
}
explicit constexpr operator bool() const noexcept {
return ref != nullptr;
}
template <class V>
constexpr typename decay<T>::type value_or(V&& v) const
{
return *this ? **this : detail_::convert<typename decay<T>::type>(constexpr_forward<V>(v));
}
};
template <class T>
class optional<T&&>
{
static_assert( sizeof(T) == 0, "optional rvalue references disallowed" );
};
// 20.5.8, Relational operators
template <class T> constexpr bool operator==(const optional<T>& x, const optional<T>& y)
{
return bool(x) != bool(y) ? false : bool(x) == false ? true : *x == *y;
}
template <class T> constexpr bool operator!=(const optional<T>& x, const optional<T>& y)
{
return !(x == y);
}
template <class T> constexpr bool operator<(const optional<T>& x, const optional<T>& y)
{
return (!y) ? false : (!x) ? true : *x < *y;
}
template <class T> constexpr bool operator>(const optional<T>& x, const optional<T>& y)
{
return (y < x);
}
template <class T> constexpr bool operator<=(const optional<T>& x, const optional<T>& y)
{
return !(y < x);
}
template <class T> constexpr bool operator>=(const optional<T>& x, const optional<T>& y)
{
return !(x < y);
}
// 20.5.9, Comparison with nullopt
template <class T> constexpr bool operator==(const optional<T>& x, nullopt_t) noexcept
{
return (!x);
}
template <class T> constexpr bool operator==(nullopt_t, const optional<T>& x) noexcept
{
return (!x);
}
template <class T> constexpr bool operator!=(const optional<T>& x, nullopt_t) noexcept
{
return bool(x);
}
template <class T> constexpr bool operator!=(nullopt_t, const optional<T>& x) noexcept
{
return bool(x);
}
template <class T> constexpr bool operator<(const optional<T>&, nullopt_t) noexcept
{
return false;
}
template <class T> constexpr bool operator<(nullopt_t, const optional<T>& x) noexcept
{
return bool(x);
}
template <class T> constexpr bool operator<=(const optional<T>& x, nullopt_t) noexcept
{
return (!x);
}
template <class T> constexpr bool operator<=(nullopt_t, const optional<T>&) noexcept
{
return true;
}
template <class T> constexpr bool operator>(const optional<T>& x, nullopt_t) noexcept
{
return bool(x);
}
template <class T> constexpr bool operator>(nullopt_t, const optional<T>&) noexcept
{
return false;
}
template <class T> constexpr bool operator>=(const optional<T>&, nullopt_t) noexcept
{
return true;
}
template <class T> constexpr bool operator>=(nullopt_t, const optional<T>& x) noexcept
{
return (!x);
}
// 20.5.10, Comparison with T
template <class T> constexpr bool operator==(const optional<T>& x, const T& v)
{
return bool(x) ? *x == v : false;
}
template <class T> constexpr bool operator==(const T& v, const optional<T>& x)
{
return bool(x) ? v == *x : false;
}
template <class T> constexpr bool operator!=(const optional<T>& x, const T& v)
{
return bool(x) ? *x != v : true;
}
template <class T> constexpr bool operator!=(const T& v, const optional<T>& x)
{
return bool(x) ? v != *x : true;
}
template <class T> constexpr bool operator<(const optional<T>& x, const T& v)
{
return bool(x) ? *x < v : true;
}
template <class T> constexpr bool operator>(const T& v, const optional<T>& x)
{
return bool(x) ? v > *x : true;
}
template <class T> constexpr bool operator>(const optional<T>& x, const T& v)
{
return bool(x) ? *x > v : false;
}
template <class T> constexpr bool operator<(const T& v, const optional<T>& x)
{
return bool(x) ? v < *x : false;
}
template <class T> constexpr bool operator>=(const optional<T>& x, const T& v)
{
return bool(x) ? *x >= v : false;
}
template <class T> constexpr bool operator<=(const T& v, const optional<T>& x)
{
return bool(x) ? v <= *x : false;
}
template <class T> constexpr bool operator<=(const optional<T>& x, const T& v)
{
return bool(x) ? *x <= v : true;
}
template <class T> constexpr bool operator>=(const T& v, const optional<T>& x)
{
return bool(x) ? v >= *x : true;
}
// Comparison of optional<T&> with T
template <class T> constexpr bool operator==(const optional<T&>& x, const T& v)
{
return bool(x) ? *x == v : false;
}
template <class T> constexpr bool operator==(const T& v, const optional<T&>& x)
{
return bool(x) ? v == *x : false;
}
template <class T> constexpr bool operator!=(const optional<T&>& x, const T& v)
{
return bool(x) ? *x != v : true;
}
template <class T> constexpr bool operator!=(const T& v, const optional<T&>& x)
{
return bool(x) ? v != *x : true;
}
template <class T> constexpr bool operator<(const optional<T&>& x, const T& v)
{
return bool(x) ? *x < v : true;
}
template <class T> constexpr bool operator>(const T& v, const optional<T&>& x)
{
return bool(x) ? v > *x : true;
}
template <class T> constexpr bool operator>(const optional<T&>& x, const T& v)
{
return bool(x) ? *x > v : false;
}
template <class T> constexpr bool operator<(const T& v, const optional<T&>& x)
{
return bool(x) ? v < *x : false;
}
template <class T> constexpr bool operator>=(const optional<T&>& x, const T& v)
{
return bool(x) ? *x >= v : false;
}
template <class T> constexpr bool operator<=(const T& v, const optional<T&>& x)
{
return bool(x) ? v <= *x : false;
}
template <class T> constexpr bool operator<=(const optional<T&>& x, const T& v)
{
return bool(x) ? *x <= v : true;
}
template <class T> constexpr bool operator>=(const T& v, const optional<T&>& x)
{
return bool(x) ? v >= *x : true;
}
// Comparison of optional<T const&> with T
template <class T> constexpr bool operator==(const optional<const T&>& x, const T& v)
{
return bool(x) ? *x == v : false;
}
template <class T> constexpr bool operator==(const T& v, const optional<const T&>& x)
{
return bool(x) ? v == *x : false;
}
template <class T> constexpr bool operator!=(const optional<const T&>& x, const T& v)
{
return bool(x) ? *x != v : true;
}
template <class T> constexpr bool operator!=(const T& v, const optional<const T&>& x)
{
return bool(x) ? v != *x : true;
}
template <class T> constexpr bool operator<(const optional<const T&>& x, const T& v)
{
return bool(x) ? *x < v : true;
}
template <class T> constexpr bool operator>(const T& v, const optional<const T&>& x)
{
return bool(x) ? v > *x : true;
}
template <class T> constexpr bool operator>(const optional<const T&>& x, const T& v)
{
return bool(x) ? *x > v : false;
}
template <class T> constexpr bool operator<(const T& v, const optional<const T&>& x)
{
return bool(x) ? v < *x : false;
}
template <class T> constexpr bool operator>=(const optional<const T&>& x, const T& v)
{
return bool(x) ? *x >= v : false;
}
template <class T> constexpr bool operator<=(const T& v, const optional<const T&>& x)
{
return bool(x) ? v <= *x : false;
}
template <class T> constexpr bool operator<=(const optional<const T&>& x, const T& v)
{
return bool(x) ? *x <= v : true;
}
template <class T> constexpr bool operator>=(const T& v, const optional<const T&>& x)
{
return bool(x) ? v >= *x : true;
}
// 20.5.12, Specialized algorithms
template <class T>
void swap(optional<T>& x, optional<T>& y) noexcept(noexcept(x.swap(y)))
{
x.swap(y);
}
template <class T>
constexpr optional<typename decay<T>::type> make_optional(T&& v)
{
return optional<typename decay<T>::type>(constexpr_forward<T>(v));
}
template <class X>
constexpr optional<X&> make_optional(reference_wrapper<X> v)
{
return optional<X&>(v.get());
}
} // namespace experimental
} // namespace std
namespace std
{
template <typename T>
struct hash<std::experimental::optional<T>>
{
typedef typename hash<T>::result_type result_type;
typedef std::experimental::optional<T> argument_type;
constexpr result_type operator()(argument_type const& arg) const {
return arg ? std::hash<T>{}(*arg) : result_type{};
}
};
template <typename T>
struct hash<std::experimental::optional<T&>>
{
typedef typename hash<T>::result_type result_type;
typedef std::experimental::optional<T&> argument_type;
constexpr result_type operator()(argument_type const& arg) const {
return arg ? std::hash<T>{}(*arg) : result_type{};
}
};
}
# undef TR2_OPTIONAL_REQUIRES
# undef TR2_OPTIONAL_ASSERTED_EXPRESSION
# endif //OPTIONAL_HPP
#pragma once
namespace opt = std::experimental;
#pragma once
#include <functional>
namespace valijson {
namespace adapters {
class FrozenValue;
/**
* @brief An interface that encapsulates access to the JSON values provided
* by a JSON parser implementation.
*
* This interface allows JSON processing code to be parser-agnostic. It provides
* functions to access the plain old datatypes (PODs) that are described in the
* JSON specification, and callback-based access to the contents of arrays and
* objects.
*
* The interface also defines a set of functions that allow for type-casting and
* type-comparison based on value rather than on type.
*/
class Adapter
{
public:
/// Typedef for callback function supplied to applyToArray.
typedef std::function<bool (const Adapter &)>
ArrayValueCallback;
/// Typedef for callback function supplied to applyToObject.
typedef std::function<bool (const std::string &, const Adapter &)>
ObjectMemberCallback;
/**
* @brief Virtual destructor defined to ensure deletion via base-class
* pointers is safe.
*/
virtual ~Adapter() = default;
/**
* @brief Apply a callback function to each value in an array.
*
* The callback function is invoked for each element in the array, until
* it has been applied to all values, or it returns false.
*
* @param fn Callback function to invoke
*
* @returns true if Adapter contains an array and all values are equal,
* false otherwise.
*/
virtual bool applyToArray(ArrayValueCallback fn) const = 0;
/**
* @brief Apply a callback function to each member in an object.
*
* The callback function shall be invoked for each member in the object,
* until it has been applied to all values, or it returns false.
*
* @param fn Callback function to invoke
*
* @returns true if Adapter contains an object, and callback function
* returns true for each member in the object, false otherwise.
*/
virtual bool applyToObject(ObjectMemberCallback fn) const = 0;
/**
* @brief Return the boolean representation of the contained value.
*
* This function shall return a boolean value if the Adapter contains either
* an actual boolean value, or one of the strings 'true' or 'false'.
* The string comparison is case sensitive.
*
* An exception shall be thrown if the value cannot be cast to a boolean.
*
* @returns Boolean representation of contained value.
*/
virtual bool asBool() const = 0;
/**
* @brief Retrieve the boolean representation of the contained value.
*
* This function shall retrieve a boolean value if the Adapter contains
* either an actual boolean value, or one of the strings 'true' or 'false'.
* The string comparison is case sensitive.
*
* The retrieved value is returned via reference.
*
* @param result reference to a bool to set with retrieved value.
*
* @returns true if the value could be retrieved, false otherwise
*/
virtual bool asBool(bool &result) const = 0;
/**
* @brief Return the double representation of the contained value.
*
* This function shall return a double value if the Adapter contains either
* an actual double, an integer, or a string that contains a valid
* representation of a numeric value (according to the C++ Std Library).
*
* An exception shall be thrown if the value cannot be cast to a double.
*
* @returns Double representation of contained value.
*/
virtual double asDouble() const = 0;
/**
* @brief Retrieve the double representation of the contained value.
*
* This function shall retrieve a double value if the Adapter contains either
* an actual double, an integer, or a string that contains a valid
* representation of a numeric value (according to the C++ Std Library).
*
* The retrieved value is returned via reference.
*
* @param result reference to a double to set with retrieved value.
*
* @returns true if the value could be retrieved, false otherwise
*/
virtual bool asDouble(double &result) const = 0;
/**
* @brief Return the int64_t representation of the contained value.
*
* This function shall return an int64_t value if the Adapter contains either
* an actual integer, or a string that contains a valid representation of an
* integer value (according to the C++ Std Library).
*
* An exception shall be thrown if the value cannot be cast to an int64_t.
*
* @returns int64_t representation of contained value.
*/
virtual int64_t asInteger() const = 0;
/**
* @brief Retrieve the int64_t representation of the contained value.
*
* This function shall retrieve an int64_t value if the Adapter contains
* either an actual integer, or a string that contains a valid
* representation of an integer value (according to the C++ Std Library).
*
* The retrieved value is returned via reference.
*
* @param result reference to a int64_t to set with retrieved value.
*
* @returns true if the value could be retrieved, false otherwise
*/
virtual bool asInteger(int64_t &result) const = 0;
/**
* @brief Return the string representation of the contained value.
*
* This function shall return a string value if the Adapter contains either
* an actual string, a literal value of another POD type, an empty array,
* an empty object, or null.
*
* An exception shall be thrown if the value cannot be cast to a string.
*
* @returns string representation of contained value.
*/
virtual std::string asString() const = 0;
/**
* @brief Retrieve the string representation of the contained value.
*
* This function shall retrieve a string value if the Adapter contains either
* an actual string, a literal value of another POD type, an empty array,
* an empty object, or null.
*
* The retrieved value is returned via reference.
*
* @param result reference to a string to set with retrieved value.
*
* @returns true if the value could be retrieved, false otherwise
*/
virtual bool asString(std::string &result) const = 0;
/**
* @brief Compare the value held by this Adapter instance with the value
* held by another Adapter instance.
*
* @param other the other adapter instance
* @param strict flag to use strict type comparison
*
* @returns true if values are equal, false otherwise
*/
virtual bool equalTo(const Adapter &other, bool strict) const = 0;
/**
* @brief Create a new FrozenValue instance that is equivalent to the
* value contained by the Adapter.
*
* @returns pointer to a new FrozenValue instance, belonging to the caller.
*/
virtual FrozenValue* freeze() const = 0;
/**
* @brief Return the number of elements in the array.
*
* Throws an exception if the value is not an array.
*
* @return number of elements if value is an array
*/
virtual size_t getArraySize() const = 0;
/**
* @brief Retrieve the number of elements in the array.
*
* This function shall return true or false to indicate whether or not the
* result value was set. If the contained value is not an array, the
* result value shall not be set. This applies even if the value could be
* cast to an empty array. The calling code is expected to handles those
* cases manually.
*
* @param result reference to size_t variable to set with result.
*
* @return true if value retrieved successfully, false otherwise.
*/
virtual bool getArraySize(size_t &result) const = 0;
/**
* @brief Return the contained boolean value.
*
* This function shall throw an exception if the contained value is not a
* boolean.
*
* @returns contained boolean value.
*/
virtual bool getBool() const = 0;
/**
* @brief Retrieve the contained boolean value.
*
* This function shall retrieve the boolean value contained by this Adapter,
* and store it in the result variable that was passed by reference.
*
* @param result reference to boolean variable to set with result.
*
* @returns true if the value was retrieved, false otherwise.
*/
virtual bool getBool(bool &result) const = 0;
/**
* @brief Return the contained double value.
*
* This function shall throw an exception if the contained value is not a
* double.
*
* @returns contained double value.
*/
virtual double getDouble() const = 0;
/**
* @brief Retrieve the contained double value.
*
* This function shall retrieve the double value contained by this Adapter,
* and store it in the result variable that was passed by reference.
*
* @param result reference to double variable to set with result.
*
* @returns true if the value was retrieved, false otherwise.
*/
virtual bool getDouble(double &result) const = 0;
/**
* @brief Return the contained integer value.
*
* This function shall throw an exception if the contained value is not a
* integer.
*
* @returns contained integer value.
*/
virtual int64_t getInteger() const = 0;
/**
* @brief Retrieve the contained integer value.
*
* This function shall retrieve the integer value contained by this Adapter,
* and store it in the result variable that was passed by reference.
*
* @param result reference to integer variable to set with result.
*
* @returns true if the value was retrieved, false otherwise.
*/
virtual bool getInteger(int64_t &result) const = 0;
/**
* @brief Return the contained numeric value as a double.
*
* This function shall throw an exception if the contained value is not a
* integer or a double.
*
* @returns contained double or integral value.
*/
virtual double getNumber() const = 0;
/**
* @brief Retrieve the contained numeric value as a double.
*
* This function shall retrieve the double or integral value contained by
* this Adapter, and store it in the result variable that was passed by
* reference.
*
* @param result reference to double variable to set with result.
*
* @returns true if the value was retrieved, false otherwise.
*/
virtual bool getNumber(double &result) const = 0;
/**
* @brief Return the number of members in the object.
*
* Throws an exception if the value is not an object.
*
* @return number of members if value is an object
*/
virtual size_t getObjectSize() const = 0;
/**
* @brief Retrieve the number of members in the object.
*
* This function shall return true or false to indicate whether or not the
* result value was set. If the contained value is not an object, the
* result value shall not be set. This applies even if the value could be
* cast to an empty object. The calling code is expected to handles those
* cases manually.
*
* @param result reference to size_t variable to set with result.
*
* @return true if value retrieved successfully, false otherwise.
*/
virtual bool getObjectSize(size_t &result) const = 0;
/**
* @brief Return the contained string value.
*
* This function shall throw an exception if the contained value is not a
* string - even if the value could be cast to a string. The asString()
* function should be used when casting is allowed.
*
* @returns string contained by this Adapter
*/
virtual std::string getString() const = 0;
/**
* @brief Retrieve the contained string value.
*
* This function shall retrieve the string value contained by this Adapter,
* and store it in result variable that is passed by reference.
*
* @param result reference to string to set with result
*
* @returns true if string was retrieved, false otherwise
*/
virtual bool getString(std::string &result) const = 0;
/**
* @brief Returns whether or not this Adapter supports strict types.
*
* This function shall return true if the Adapter implementation supports
* strict types, or false if the Adapter fails to store any part of the
* type information supported by the Adapter interface.
*
* For example, the PropertyTreeAdapter implementation stores POD values as
* strings, effectively discarding any other type information. If you were
* to call isDouble() on a double stored by this Adapter, the result would
* be false. The maybeDouble(), asDouble() and various related functions
* are provided to perform type checking based on value rather than on type.
*
* The BasicAdapter template class provides implementations for the type-
* casting functions so that Adapter implementations are semantically
* equivalent in their type-casting behaviour.
*
* @returns true if Adapter supports strict types, false otherwise
*/
virtual bool hasStrictTypes() const = 0;
/// Returns true if the contained value is definitely an array.
virtual bool isArray() const = 0;
/// Returns true if the contained value is definitely a boolean.
virtual bool isBool() const = 0;
/// Returns true if the contained value is definitely a double.
virtual bool isDouble() const = 0;
/// Returns true if the contained value is definitely an integer.
virtual bool isInteger() const = 0;
/// Returns true if the contained value is definitely a null.
virtual bool isNull() const = 0;
/// Returns true if the contained value is either a double or an integer.
virtual bool isNumber() const = 0;
/// Returns true if the contained value is definitely an object.
virtual bool isObject() const = 0;
/// Returns true if the contained value is definitely a string.
virtual bool isString() const = 0;
/**
* @brief Returns true if the contained value can be cast to an array.
*
* @returns true if the contained value is an array, an empty string, or an
* empty object.
*/
virtual bool maybeArray() const = 0;
/**
* @brief Returns true if the contained value can be cast to a boolean.
*
* @returns true if the contained value is a boolean, or one of the strings
* 'true' or 'false'. Note that numeric values are not to be cast
* to boolean values.
*/
virtual bool maybeBool() const = 0;
/**
* @brief Returns true if the contained value can be cast to a double.
*
* @returns true if the contained value is a double, an integer, or a string
* containing a double or integral value.
*/
virtual bool maybeDouble() const = 0;
/**
* @brief Returns true if the contained value can be cast to an integer.
*
* @returns true if the contained value is an integer, or a string
* containing an integral value.
*/
virtual bool maybeInteger() const = 0;
/**
* @brief Returns true if the contained value can be cast to a null.
*
* @returns true if the contained value is null or an empty string.
*/
virtual bool maybeNull() const = 0;
/**
* @brief Returns true if the contained value can be cast to an object.
*
* @returns true if the contained value is an object, an empty array or
* an empty string.
*/
virtual bool maybeObject() const = 0;
/**
* @brief Returns true if the contained value can be cast to a string.
*
* @returns true if the contained value is a non-null POD type, an empty
* array, or an empty object.
*/
virtual bool maybeString() const = 0;
};
/**
* @brief Template struct that should be specialised for each concrete Adapter
* class.
*
* @deprecated This is a bit of a hack, and I'd like to remove it.
*/
template<typename T>
struct AdapterTraits
{
};
} // namespace adapters
} // namespace valijson
#pragma once
#include <cstdint>
#include <sstream>
namespace valijson {
namespace adapters {
/**
* @brief A helper for the array and object member iterators.
*
* See http://www.stlsoft.org/doc-1.9/group__group____pattern____dereference__proxy.html
* for motivation
*
* @tparam Value Name of the value type
*/
template<class Value>
struct DerefProxy
{
explicit DerefProxy(const Value& x)
: m_ref(x) { }
Value* operator->()
{
return std::addressof(m_ref);
}
explicit operator Value*()
{
return std::addressof(m_ref);
}
private:
Value m_ref;
};
/**
* @brief Template class that implements the expected semantics of an Adapter.
*
* Implementing all of the type-casting functionality for each Adapter is error
* prone and tedious, so this template class aims to minimise the duplication
* of code between various Adapter implementations. This template doesn't quite
* succeed in removing all duplication, but it has greatly simplified the
* implementation of a new Adapter by encapsulating the type-casting semantics
* and a lot of the trivial functionality associated with the Adapter interface.
*
* By inheriting from this template class, Adapter implementations will inherit
* the exception throwing behaviour that is expected by other parts of the
* Valijson library.
*
* @tparam AdapterType Self-referential name of the Adapter being
* specialised.
* @tparam ArrayType Name of the type that will be returned by the
* getArray() function. Instances of this type should
* provide begin(), end() and size() functions so
* that it is possible to iterate over the values in
* the array.
* @tparam ObjectMemberType Name of the type exposed when iterating over the
* contents of an object returned by getObject().
* @tparam ObjectType Name of the type that will be returned by the
* getObject() function. Instances of this type
* should provide begin(), end(), find() and size()
* functions so that it is possible to iterate over
* the members of the object.
* @tparam ValueType Name of the type that provides a consistent
* interface to a JSON value for a parser. For
* example, this type should provide the getDouble()
* and isDouble() functions. But it does not need to
* know how to cast values from one type to another -
* that functionality is provided by this template
* class.
*/
template<
typename AdapterType,
typename ArrayType,
typename ObjectMemberType,
typename ObjectType,
typename ValueType>
class BasicAdapter: public Adapter
{
protected:
/**
* @brief Functor for comparing two arrays.
*
* This functor is used to compare the elements in an array of the type
* ArrayType with individual values provided as generic Adapter objects.
* Comparison is performed by the () operator.
*
* The functor works by maintaining an iterator for the current position
* in an array. Each time the () operator is called, the value at this
* position is compared with the value passed as an argument to ().
* Immediately after the comparison, the iterator will be incremented.
*
* This functor is designed to be passed to the applyToArray() function
* of an Adapter object.
*/
class ArrayComparisonFunctor
{
public:
/**
* @brief Construct an ArrayComparisonFunctor for an array.
*
* @param array Array to compare values against
* @param strict Flag to use strict type comparison
*/
ArrayComparisonFunctor(const ArrayType &array, bool strict)
: m_itr(array.begin()),
m_end(array.end()),
m_strict(strict) { }
/**
* @brief Compare a value against the current element in the array.
*
* @param adapter Value to be compared with current element
*
* @returns true if values are equal, false otherwise.
*/
bool operator()(const Adapter &adapter)
{
if (m_itr == m_end) {
return false;
}
return AdapterType(*m_itr++).equalTo(adapter, m_strict);
}
private:
/// Iterator for current element in the array
typename ArrayType::const_iterator m_itr;
/// Iterator for one-past the last element of the array
typename ArrayType::const_iterator m_end;
/// Flag to use strict type comparison
const bool m_strict;
};
/**
* @brief Functor for comparing two objects
*
* This functor is used to compare the members of an object of the type
* ObjectType with key-value pairs belonging to another object.
*
* The functor works by maintaining a reference to an object provided via
* the constructor. When time the () operator is called with a key-value
* pair as arguments, the function will attempt to find the key in the
* base object. If found, the associated value will be compared with the
* value provided to the () operator.
*
* This functor is designed to be passed to the applyToObject() function
* of an Adapter object.
*/
class ObjectComparisonFunctor
{
public:
/**
* @brief Construct a new ObjectComparisonFunctor for an object.
*
* @param object object to use as comparison baseline
* @param strict flag to use strict type-checking
*/
ObjectComparisonFunctor(const ObjectType &object, bool strict)
: m_object(object),
m_strict(strict) { }
/**
* @brief Find a key in the object and compare its value.
*
* @param key Key to find
* @param value Value to be compared against
*
* @returns true if key is found and values are equal, false otherwise.
*/
bool operator()(const std::string &key, const Adapter &value)
{
const typename ObjectType::const_iterator itr = m_object.find(key);
if (itr == m_object.end()) {
return false;
}
return (*itr).second.equalTo(value, m_strict);
}
private:
/// Object to be used as a comparison baseline
const ObjectType &m_object;
/// Flag to use strict type-checking
bool m_strict;
};
public:
/// Alias for ArrayType template parameter
typedef ArrayType Array;
/// Alias for ObjectMemberType template parameter
typedef ObjectMemberType ObjectMember;
/// Alias for ObjectType template parameter
typedef ObjectType Object;
/**
* @brief Construct an Adapter using the default value.
*
* This constructor relies on the default constructor of the ValueType
* class provided as a template argument.
*/
BasicAdapter() = default;
/**
* @brief Construct an Adapter using a specified ValueType object.
*
* This constructor relies on the copy constructor of the ValueType
* class provided as template argument.
*/
explicit BasicAdapter(const ValueType &value)
: m_value(value) { }
bool applyToArray(ArrayValueCallback fn) const override
{
if (!maybeArray()) {
return false;
}
// Due to the fact that the only way a value can be 'maybe an array' is
// if it is an empty string or empty object, we only need to go to
// effort of constructing an ArrayType instance if the value is
// definitely an array.
if (m_value.isArray()) {
const opt::optional<Array> array = m_value.getArrayOptional();
for (const AdapterType element : *array) {
if (!fn(element)) {
return false;
}
}
}
return true;
}
bool applyToObject(ObjectMemberCallback fn) const override
{
if (!maybeObject()) {
return false;
}
if (m_value.isObject()) {
const opt::optional<Object> object = m_value.getObjectOptional();
for (const ObjectMemberType member : *object) {
if (!fn(member.first, AdapterType(member.second))) {
return false;
}
}
}
return true;
}
/**
* @brief Return an ArrayType instance containing an array representation
* of the value held by this Adapter.
*
* This is a convenience function that is not actually declared in the
* Adapter interface, but allows for useful techniques such as procedural
* iteration over the elements in an array. The ArrayType instance that is
* returned by this function is compatible with the BOOST_FOREACH macro.
*
* If the contained value is either an empty object, or an empty string,
* then this function will cast the value to an empty array.
*
* @returns ArrayType instance containing an array representation of the
* value held by this Adapter.
*/
ArrayType asArray() const
{
if (m_value.isArray()) {
return *m_value.getArrayOptional();
} else if (m_value.isObject()) {
size_t objectSize;
if (m_value.getObjectSize(objectSize) && objectSize == 0) {
return ArrayType();
}
} else if (m_value.isString()) {
std::string stringValue;
if (m_value.getString(stringValue) && stringValue.empty()) {
return ArrayType();
}
}
throwRuntimeError("JSON value cannot be cast to an array.");
}
bool asBool() const override
{
bool result;
if (asBool(result)) {
return result;
}
throwRuntimeError("JSON value cannot be cast to a boolean.");
}
bool asBool(bool &result) const override
{
if (m_value.isBool()) {
return m_value.getBool(result);
} else if (m_value.isString()) {
std::string s;
if (m_value.getString(s)) {
if (s == "true") {
result = true;
return true;
} else if (s == "false") {
result = false;
return true;
}
}
}
return false;
}
double asDouble() const override
{
double result;
if (asDouble(result)) {
return result;
}
throwRuntimeError("JSON value cannot be cast to a double.");
}
bool asDouble(double &result) const override
{
if (m_value.isDouble()) {
return m_value.getDouble(result);
} else if (m_value.isInteger()) {
int64_t i;
if (m_value.getInteger(i)) {
result = double(i);
return true;
}
} else if (m_value.isString()) {
std::string s;
if (m_value.getString(s)) {
const char *b = s.c_str();
char *e = nullptr;
double x = strtod(b, &e);
if (e == b || e != b + s.length()) {
return false;
}
result = x;
return true;
}
}
return false;
}
int64_t asInteger() const override
{
int64_t result;
if (asInteger(result)) {
return result;
}
throwRuntimeError("JSON value cannot be cast as an integer.");
}
bool asInteger(int64_t &result) const override
{
if (m_value.isInteger()) {
return m_value.getInteger(result);
} else if (m_value.isString()) {
std::string s;
if (m_value.getString(s)) {
std::istringstream i(s);
int64_t x;
char c;
if (!(!(i >> x) || i.get(c))) {
result = x;
return true;
}
}
}
return false;
}
/**
* @brief Return an ObjectType instance containing an array representation
* of the value held by this Adapter.
*
* This is a convenience function that is not actually declared in the
* Adapter interface, but allows for useful techniques such as procedural
* iteration over the members of the object. The ObjectType instance that is
* returned by this function is compatible with the BOOST_FOREACH macro.
*
* @returns ObjectType instance containing an object representation of the
* value held by this Adapter.
*/
ObjectType asObject() const
{
if (m_value.isObject()) {
return *m_value.getObjectOptional();
} else if (m_value.isArray()) {
size_t arraySize;
if (m_value.getArraySize(arraySize) && arraySize == 0) {
return ObjectType();
}
} else if (m_value.isString()) {
std::string stringValue;
if (m_value.getString(stringValue) && stringValue.empty()) {
return ObjectType();
}
}
throwRuntimeError("JSON value cannot be cast to an object.");
}
std::string asString() const override
{
std::string result;
if (asString(result)) {
return result;
}
throwRuntimeError("JSON value cannot be cast to a string.");
}
bool asString(std::string &result) const override
{
if (m_value.isString()) {
return m_value.getString(result);
} else if (m_value.isNull()) {
result.clear();
return true;
} else if (m_value.isArray()) {
size_t arraySize;
if (m_value.getArraySize(arraySize) && arraySize == 0) {
result.clear();
return true;
}
} else if (m_value.isObject()) {
size_t objectSize;
if (m_value.getObjectSize(objectSize) && objectSize == 0) {
result.clear();
return true;
}
} else if (m_value.isBool()) {
bool boolValue;
if (m_value.getBool(boolValue)) {
result = boolValue ? "true" : "false";
return true;
}
} else if (m_value.isInteger()) {
int64_t integerValue;
if (m_value.getInteger(integerValue)) {
result = std::to_string(integerValue);
return true;
}
} else if (m_value.isDouble()) {
double doubleValue;
if (m_value.getDouble(doubleValue)) {
result = std::to_string(doubleValue);
return true;
}
}
return false;
}
bool equalTo(const Adapter &other, bool strict) const override
{
if (isNull() || (!strict && maybeNull())) {
return other.isNull() || (!strict && other.maybeNull());
} else if (isBool() || (!strict && maybeBool())) {
return (other.isBool() || (!strict && other.maybeBool())) && other.asBool() == asBool();
} else if (isNumber() && strict) {
return other.isNumber() && other.getNumber() == getNumber();
} else if (!strict && maybeDouble()) {
return (other.maybeDouble() && other.asDouble() == asDouble());
} else if (!strict && maybeInteger()) {
return (other.maybeInteger() && other.asInteger() == asInteger());
} else if (isString() || (!strict && maybeString())) {
return (other.isString() || (!strict && other.maybeString())) &&
other.asString() == asString();
} else if (isArray()) {
if (other.isArray() && getArraySize() == other.getArraySize()) {
const opt::optional<ArrayType> array = m_value.getArrayOptional();
if (array) {
ArrayComparisonFunctor fn(*array, strict);
return other.applyToArray(fn);
}
} else if (!strict && other.maybeArray() && getArraySize() == 0) {
return true;
}
} else if (isObject()) {
if (other.isObject() && other.getObjectSize() == getObjectSize()) {
const opt::optional<ObjectType> object = m_value.getObjectOptional();
if (object) {
ObjectComparisonFunctor fn(*object, strict);
return other.applyToObject(fn);
}
} else if (!strict && other.maybeObject() && getObjectSize() == 0) {
return true;
}
}
return false;
}
/**
* @brief Return an ArrayType instance representing the array contained
* by this Adapter instance.
*
* This is a convenience function that is not actually declared in the
* Adapter interface, but allows for useful techniques such as procedural
* iteration over the elements in an array. The ArrayType instance that is
* returned by this function is compatible with the BOOST_FOREACH macro.
*
* If the contained is not an array, this function will throw an exception.
*
* @returns ArrayType instance containing an array representation of the
* value held by this Adapter.
*/
ArrayType getArray() const
{
opt::optional<ArrayType> arrayValue = m_value.getArrayOptional();
if (arrayValue) {
return *arrayValue;
}
throwRuntimeError("JSON value is not an array.");
}
size_t getArraySize() const override
{
size_t result;
if (m_value.getArraySize(result)) {
return result;
}
throwRuntimeError("JSON value is not an array.");
}
bool getArraySize(size_t &result) const override
{
return m_value.getArraySize(result);
}
bool getBool() const override
{
bool result;
if (getBool(result)) {
return result;
}
throwRuntimeError("JSON value is not a boolean.");
}
bool getBool(bool &result) const override
{
return m_value.getBool(result);
}
double getDouble() const override
{
double result;
if (getDouble(result)) {
return result;
}
throwRuntimeError("JSON value is not a double.");
}
bool getDouble(double &result) const override
{
return m_value.getDouble(result);
}
int64_t getInteger() const override
{
int64_t result;
if (getInteger(result)) {
return result;
}
throwRuntimeError("JSON value is not an integer.");
}
bool getInteger(int64_t &result) const override
{
return m_value.getInteger(result);
}
double getNumber() const override
{
double result;
if (getNumber(result)) {
return result;
}
throwRuntimeError("JSON value is not a number.");
}
bool getNumber(double &result) const override
{
if (isDouble()) {
return getDouble(result);
} else if (isInteger()) {
int64_t integerResult;
if (getInteger(integerResult)) {
result = static_cast<double>(integerResult);
return true;
}
}
return false;
}
/**
* @brief Return an ObjectType instance representing the object contained
* by this Adapter instance.
*
* This is a convenience function that is not actually declared in the
* Adapter interface, but allows for useful techniques such as procedural
* iteration over the members of an object. The ObjectType instance that is
* returned by this function is compatible with the BOOST_FOREACH macro.
*
* If the contained is not an object, this function will throw an exception.
*
* @returns ObjectType instance containing an array representation of the
* value held by this Adapter.
*/
ObjectType getObject() const
{
opt::optional<ObjectType> objectValue = m_value.getObjectOptional();
if (objectValue) {
return *objectValue;
}
throwRuntimeError("JSON value is not an object.");
}
size_t getObjectSize() const override
{
size_t result;
if (getObjectSize(result)) {
return result;
}
throwRuntimeError("JSON value is not an object.");
}
bool getObjectSize(size_t &result) const override
{
return m_value.getObjectSize(result);
}
std::string getString() const override
{
std::string result;
if (getString(result)) {
return result;
}
throwRuntimeError("JSON value is not a string.");
}
bool getString(std::string &result) const override
{
return m_value.getString(result);
}
FrozenValue * freeze() const override
{
return m_value.freeze();
}
bool hasStrictTypes() const override
{
return ValueType::hasStrictTypes();
}
bool isArray() const override
{
return m_value.isArray();
}
bool isBool() const override
{
return m_value.isBool();
}
bool isDouble() const override
{
return m_value.isDouble();
}
bool isInteger() const override
{
return m_value.isInteger();
}
bool isNull() const override
{
return m_value.isNull();
}
bool isNumber() const override
{
return m_value.isInteger() || m_value.isDouble();
}
bool isObject() const override
{
return m_value.isObject();
}
bool isString() const override
{
return m_value.isString();
}
bool maybeArray() const override
{
if (m_value.isArray()) {
return true;
} else if (m_value.isObject()) {
size_t objectSize;
if (m_value.getObjectSize(objectSize) && objectSize == 0) {
return true;
}
}
return false;
}
bool maybeBool() const override
{
if (m_value.isBool()) {
return true;
} else if (maybeString()) {
std::string stringValue;
if (m_value.getString(stringValue)) {
if (stringValue == "true" || stringValue == "false") {
return true;
}
}
}
return false;
}
bool maybeDouble() const override
{
if (m_value.isNumber()) {
return true;
} else if (maybeString()) {
std::string s;
if (m_value.getString(s)) {
const char *b = s.c_str();
char *e = nullptr;
strtod(b, &e);
return e != b && e == b + s.length();
}
}
return false;
}
bool maybeInteger() const override
{
if (m_value.isInteger()) {
return true;
} else if (maybeString()) {
std::string s;
if (m_value.getString(s)) {
std::istringstream i(s);
int64_t x;
char c;
if (!(i >> x) || i.get(c)) {
return false;
}
return true;
}
}
return false;
}
bool maybeNull() const override
{
if (m_value.isNull()) {
return true;
} else if (maybeString()) {
std::string stringValue;
if (m_value.getString(stringValue)) {
if (stringValue.empty()) {
return true;
}
}
}
return false;
}
bool maybeObject() const override
{
if (m_value.isObject()) {
return true;
} else if (maybeArray()) {
size_t arraySize;
if (m_value.getArraySize(arraySize) && arraySize == 0) {
return true;
}
}
return false;
}
bool maybeString() const override
{
if (m_value.isString() || m_value.isBool() || m_value.isInteger() || m_value.isDouble()) {
return true;
} else if (m_value.isObject()) {
size_t objectSize;
if (m_value.getObjectSize(objectSize) && objectSize == 0) {
return true;
}
} else if (m_value.isArray()) {
size_t arraySize;
if (m_value.getArraySize(arraySize) && arraySize == 0) {
return true;
}
}
return false;
}
private:
const ValueType m_value;
};
} // namespace adapters
} // namespace valijson
#pragma once
#include <limits>
namespace valijson {
namespace internal {
template<class T>
class CustomAllocator
{
public:
/// Typedef for custom new-/malloc-like function
typedef void * (*CustomAlloc)(size_t size);
/// Typedef for custom free-like function
typedef void (*CustomFree)(void *);
// Standard allocator typedefs
typedef T value_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
template<typename U>
struct rebind
{
typedef CustomAllocator<U> other;
};
CustomAllocator()
: m_allocFn([](size_t size) { return ::operator new(size, std::nothrow); }),
m_freeFn(::operator delete) { }
CustomAllocator(CustomAlloc allocFn, CustomFree freeFn)
: m_allocFn(allocFn),
m_freeFn(freeFn) { }
CustomAllocator(const CustomAllocator &other)
: m_allocFn(other.m_allocFn),
m_freeFn(other.m_freeFn) { }
template<typename U>
CustomAllocator(CustomAllocator<U> const &other)
: m_allocFn(other.m_allocFn),
m_freeFn(other.m_freeFn) { }
CustomAllocator & operator=(const CustomAllocator &other)
{
m_allocFn = other.m_allocFn;
m_freeFn = other.m_freeFn;
return *this;
}
pointer address(reference r)
{
return &r;
}
const_pointer address(const_reference r)
{
return &r;
}
pointer allocate(size_type cnt, const void * = nullptr)
{
return reinterpret_cast<pointer>(m_allocFn(cnt * sizeof(T)));
}
void deallocate(pointer p, size_type)
{
m_freeFn(p);
}
size_type max_size() const
{
return std::numeric_limits<size_type>::max() / sizeof(T);
}
void construct(pointer p, const T& t)
{
new(p) T(t);
}
void destroy(pointer p)
{
p->~T();
}
bool operator==(const CustomAllocator &other) const
{
return other.m_allocFn == m_allocFn && other.m_freeFn == m_freeFn;
}
bool operator!=(const CustomAllocator &other) const
{
return !operator==(other);
}
CustomAlloc m_allocFn;
CustomFree m_freeFn;
};
} // end namespace internal
} // end namespace valijson
#pragma once
#include <string>
namespace valijson {
namespace internal {
template<typename AdapterType>
std::string nodeTypeAsString(const AdapterType &node) {
if (node.isArray()) {
return "array";
} else if (node.isObject()) {
return "object";
} else if (node.isString()) {
return "string";
} else if (node.isNull()) {
return "null";
} else if (node.isInteger()) {
return "integer";
} else if (node.isDouble()) {
return "double";
} else if (node.isBool()) {
return "bool";
}
return "unknown";
}
} // end namespace internal
} // end namespace valijson
#pragma once
namespace valijson {
namespace adapters {
/**
* @brief An interface that provides minimal access to a stored JSON value.
*
* The main reason that this interface exists is to support the 'enum'
* constraint. Each Adapter type is expected to provide an implementation of
* this interface. That class should be able to maintain its own copy of a
* JSON value, independent of the original document.
*
* This interface currently provides just the clone and equalTo functions, but
* could be expanded to include other functions declared in the Adapter
* interface.
*
* @todo it would be nice to better integrate this with the Adapter interface
*/
class FrozenValue
{
public:
/**
* @brief Virtual destructor defined to ensure deletion via base-class
* pointers is safe.
*/
virtual ~FrozenValue() { }
/**
* @brief Clone the stored value and return a pointer to a new FrozenValue
* object containing the value.
*/
virtual FrozenValue *clone() const = 0;
/**
* @brief Return true if the stored value is equal to the value contained
* by an Adapter instance.
*
* @param adapter Adapter to compare value against
* @param strict Flag to use strict type comparison
*
* @returns true if values are equal, false otherwise
*/
virtual bool equalTo(const Adapter &adapter, bool strict) const = 0;
};
} // namespace adapters
} // namespace valijson
#pragma once
#include <algorithm>
#include <cerrno>
#include <cstddef>
#include <cstring>
#include <stdexcept>
#include <string>
#ifdef _MSC_VER
#pragma warning( push )
#pragma warning( disable : 4702 )
#endif
namespace valijson {
namespace internal {
namespace json_pointer {
/**
* @brief Replace all occurrences of `search` with `replace`. Modifies `subject` in place.
*
* @param subject string to operate on
* @param search string to search
* @param replace replacement string
*/
inline void replaceAllInPlace(std::string& subject, const char* search,
const char* replace)
{
size_t pos = 0;
while((pos = subject.find(search, pos)) != std::string::npos) {
subject.replace(pos, strlen(search), replace);
pos += strlen(replace);
}
}
/**
* @brief Return the char value corresponding to a 2-digit hexadecimal string
*
* @throws std::runtime_error for strings that are not exactly two characters
* in length and for strings that contain non-hexadecimal characters
*
* @return decoded char value corresponding to the hexadecimal string
*/
inline char decodePercentEncodedChar(const std::string &digits)
{
if (digits.length() != 2) {
throwRuntimeError("Failed to decode %-encoded character '" +
digits + "' due to unexpected number of characters; "
"expected two characters");
}
errno = 0;
const char *begin = digits.c_str();
char *end = nullptr;
const unsigned long value = strtoul(begin, &end, 16);
if (end != begin && *end != '\0') {
throwRuntimeError("Failed to decode %-encoded character '" +
digits + "'");
}
return char(value);
}
/**
* @brief Extract and transform the token between two iterators
*
* This function is responsible for extracting a JSON Reference token from
* between two iterators, and performing any necessary transformations, before
* returning the resulting string. Its main purpose is to replace the escaped
* character sequences defined in the RFC-6901 (JSON Pointer), and to decode
* %-encoded character sequences defined in RFC-3986 (URI).
*
* The encoding used in RFC-3986 should be familiar to many developers, but
* the escaped character sequences used in JSON Pointers may be less so. From
* the JSON Pointer specification (RFC 6901, April 2013):
*
* Evaluation of each reference token begins by decoding any escaped
* character sequence. This is performed by first transforming any
* occurrence of the sequence '~1' to '/', and then transforming any
* occurrence of the sequence '~0' to '~'. By performing the
* substitutions in this order, an implementation avoids the error of
* turning '~01' first into '~1' and then into '/', which would be
* incorrect (the string '~01' correctly becomes '~1' after
* transformation).
*
* @param begin iterator pointing to beginning of a token
* @param end iterator pointing to one character past the end of the token
*
* @return string with escaped character sequences replaced
*
*/
inline std::string extractReferenceToken(std::string::const_iterator begin,
std::string::const_iterator end)
{
std::string token(begin, end);
// Replace JSON Pointer-specific escaped character sequences
replaceAllInPlace(token, "~1", "/");
replaceAllInPlace(token, "~0", "~");
// Replace %-encoded character sequences with their actual characters
for (size_t n = token.find('%'); n != std::string::npos;
n = token.find('%', n + 1)) {
#if VALIJSON_USE_EXCEPTIONS
try {
#endif
const char c = decodePercentEncodedChar(token.substr(n + 1, 2));
token.replace(n, 3, 1, c);
#if VALIJSON_USE_EXCEPTIONS
} catch (const std::runtime_error &e) {
throwRuntimeError(
std::string(e.what()) + "; in token: " + token);
}
#endif
}
return token;
}
/**
* @brief Recursively locate the value referenced by a JSON Pointer
*
* This function takes both a string reference and an iterator to the beginning
* of the substring that is being resolved. This iterator is expected to point
* to the beginning of a reference token, whose length will be determined by
* searching for the next delimiter ('/' or '\0'). A reference token must be
* at least one character in length to be considered valid.
*
* Once the next reference token has been identified, it will be used either as
* an array index or as an the name an object member. The validity of a
* reference token depends on the type of the node currently being traversed,
* and the applicability of the token to that node. For example, an array can
* only be dereferenced by a non-negative integral index.
*
* Once the next node has been identified, the length of the remaining portion
* of the JSON Pointer will be used to determine whether recursion should
* terminate.
*
* @param node current node in recursive evaluation of JSON Pointer
* @param jsonPointer string containing complete JSON Pointer
* @param jsonPointerItr string iterator pointing the beginning of the next
* reference token
*
* @return an instance of AdapterType that wraps the dereferenced node
*/
template<typename AdapterType>
inline AdapterType resolveJsonPointer(
const AdapterType &node,
const std::string &jsonPointer,
const std::string::const_iterator jsonPointerItr)
{
// TODO: This function will probably need to implement support for
// fetching documents referenced by JSON Pointers, similar to the
// populateSchema function.
const std::string::const_iterator jsonPointerEnd = jsonPointer.end();
// Terminate recursion if all reference tokens have been consumed
if (jsonPointerItr == jsonPointerEnd) {
return node;
}
// Reference tokens must begin with a leading slash
if (*jsonPointerItr != '/') {
throwRuntimeError("Expected reference token to begin with "
"leading slash; remaining tokens: " +
std::string(jsonPointerItr, jsonPointerEnd));
}
// Find iterator that points to next slash or newline character; this is
// one character past the end of the current reference token
std::string::const_iterator jsonPointerNext =
std::find(jsonPointerItr + 1, jsonPointerEnd, '/');
// Extract the next reference token
const std::string referenceToken = extractReferenceToken(
jsonPointerItr + 1, jsonPointerNext);
// Empty reference tokens should be ignored
if (referenceToken.empty()) {
return resolveJsonPointer(node, jsonPointer, jsonPointerNext);
} else if (node.isArray()) {
if (referenceToken == "-") {
throwRuntimeError("Hyphens cannot be used as array indices "
"since the requested array element does not yet exist");
}
#if VALIJSON_USE_EXCEPTIONS
try {
#endif
// Fragment must be non-negative integer
const uint64_t index = std::stoul(referenceToken);
typedef typename AdapterType::Array Array;
const Array arr = node.asArray();
typename Array::const_iterator itr = arr.begin();
const uint64_t arrSize = arr.size();
if (arrSize == 0 || index > arrSize - 1) {
throwRuntimeError("Expected reference token to identify "
"an element in the current array, but array index is "
"out of bounds; actual token: " + referenceToken);
}
if (index > static_cast<uint64_t>(std::numeric_limits<std::ptrdiff_t>::max())) {
throwRuntimeError("Array index out of bounds; hard "
"limit is " + std::to_string(
std::numeric_limits<std::ptrdiff_t>::max()));
}
itr.advance(static_cast<std::ptrdiff_t>(index));
// Recursively process the remaining tokens
return resolveJsonPointer(*itr, jsonPointer, jsonPointerNext);
#if VALIJSON_USE_EXCEPTIONS
} catch (std::invalid_argument &) {
throwRuntimeError("Expected reference token to contain a "
"non-negative integer to identify an element in the "
"current array; actual token: " + referenceToken);
}
#endif
} else if (node.maybeObject()) {
// Fragment must identify a member of the candidate object
typedef typename AdapterType::Object Object;
const Object object = node.asObject();
typename Object::const_iterator itr = object.find(
referenceToken);
if (itr == object.end()) {
throwRuntimeError("Expected reference token to identify an "
"element in the current object; "
"actual token: " + referenceToken);
abort();
}
// Recursively process the remaining tokens
return resolveJsonPointer(itr->second, jsonPointer, jsonPointerNext);
}
throwRuntimeError("Expected end of JSON Pointer, but at least "
"one reference token has not been processed; remaining tokens: " +
std::string(jsonPointerNext, jsonPointerEnd));
abort();
}
/**
* @brief Return the JSON Value referenced by a JSON Pointer
*
* @param rootNode node to use as root for JSON Pointer resolution
* @param jsonPointer string containing JSON Pointer
*
* @return an instance AdapterType in the specified document
*/
template<typename AdapterType>
inline AdapterType resolveJsonPointer(
const AdapterType &rootNode,
const std::string &jsonPointer)
{
return resolveJsonPointer(rootNode, jsonPointer, jsonPointer.begin());
}
} // namespace json_pointer
} // namespace internal
} // namespace valijson
#ifdef _MSC_VER
#pragma warning( pop )
#endif
#pragma once
#include <stdexcept>
#include <string>
namespace valijson {
namespace internal {
namespace json_reference {
/**
* @brief Extract URI from JSON Reference relative to the current schema
*
* @param jsonRef JSON Reference to extract from
*
* @return Optional string containing URI
*/
inline opt::optional<std::string> getJsonReferenceUri(
const std::string &jsonRef)
{
const size_t ptrPos = jsonRef.find('#');
if (ptrPos == 0) {
// The JSON Reference does not contain a URI, but might contain a
// JSON Pointer that refers to the current document
return opt::optional<std::string>();
} else if (ptrPos != std::string::npos) {
// The JSON Reference contains a URI and possibly a JSON Pointer
return jsonRef.substr(0, ptrPos);
}
// The entire JSON Reference should be treated as a URI
return jsonRef;
}
/**
* @brief Extract JSON Pointer portion of a JSON Reference
*
* @param jsonRef JSON Reference to extract from
*
* @return Optional string containing JSON Pointer
*/
inline opt::optional<std::string> getJsonReferencePointer(
const std::string &jsonRef)
{
// Attempt to extract JSON Pointer if '#' character is present. Note
// that a valid pointer would contain at least a leading forward
// slash character.
const size_t ptrPos = jsonRef.find('#');
if (ptrPos != std::string::npos) {
return jsonRef.substr(ptrPos + 1);
}
return opt::optional<std::string>();
}
} // namespace json_reference
} // namespace internal
} // namespace valijson
#pragma once
#include <regex>
#include <string>
namespace valijson {
namespace internal {
namespace uri {
/**
* @brief Placeholder function to check whether a URI is absolute
*
* This function just checks for '://'
*/
inline bool isUriAbsolute(const std::string &documentUri)
{
static const char * placeholderMarker = "://";
return documentUri.find(placeholderMarker) != std::string::npos;
}
/**
* @brief Placeholder function to check whether a URI is a URN
*
* This function validates that the URI matches the RFC 8141 spec
*/
inline bool isUrn(const std::string &documentUri) {
static const std::regex pattern(
"^((urn)|(URN)):(?!urn:)([a-zA-Z0-9][a-zA-Z0-9-]{1,31})(:[-a-zA-Z0-9\\\\._~%!$&'()\\/*+,;=]+)+(\\?[-a-zA-Z0-9\\\\._~%!$&'()\\/*+,;:=]+){0,1}(#[-a-zA-Z0-9\\\\._~%!$&'()\\/*+,;:=]+){0,1}$");
return std::regex_match(documentUri, pattern);
}
/**
* Placeholder function to resolve a relative URI within a given scope
*/
inline std::string resolveRelativeUri(
const std::string &resolutionScope,
const std::string &relativeUri)
{
return resolutionScope + relativeUri;
}
} // namespace uri
} // namespace internal
} // namespace valijson
#pragma once
#include <fstream>
#include <limits>
namespace valijson {
namespace utils {
/**
* Load a file into a string
*
* @param path path to the file to be loaded
* @param dest string into which file should be loaded
*
* @return true if loaded, false otherwise
*/
inline bool loadFile(const std::string &path, std::string &dest)
{
// Open file for reading
std::ifstream file(path.c_str());
if (!file.is_open()) {
return false;
}
// Allocate space for file contents
file.seekg(0, std::ios::end);
const std::streamoff offset = file.tellg();
if (offset < 0 || offset > std::numeric_limits<unsigned int>::max()) {
return false;
}
dest.clear();
dest.reserve(static_cast<unsigned int>(offset));
// Assign file contents to destination string
file.seekg(0, std::ios::beg);
dest.assign(std::istreambuf_iterator<char>(file),
std::istreambuf_iterator<char>());
return true;
}
} // namespace utils
} // namespace valijson
#pragma once
#include <assert.h>
#include <stdexcept>
#include <string>
/*
Basic UTF-8 manipulation routines, adapted from code that was released into
the public domain by Jeff Bezanson.
*/
namespace valijson {
namespace utils {
static const uint32_t offsetsFromUTF8[6] = {
0x00000000UL, 0x00003080UL, 0x000E2080UL,
0x03C82080UL, 0xFA082080UL, 0x82082080UL
};
/* is c the start of a utf8 sequence? */
inline bool isutf(char c) {
return ((c & 0xC0) != 0x80);
}
/* reads the next utf-8 sequence out of a string, updating an index */
inline uint64_t u8_nextchar(const char *s, uint64_t *i)
{
uint64_t ch = 0;
int sz = 0;
do {
ch <<= 6;
ch += static_cast<unsigned char>(s[(*i)++]);
sz++;
} while (s[*i] && !isutf(s[*i]));
ch -= offsetsFromUTF8[sz-1];
return ch;
}
/* number of characters */
inline uint64_t u8_strlen(const char *s)
{
constexpr auto maxLength = std::numeric_limits<uint64_t>::max();
uint64_t count = 0;
uint64_t i = 0;
while (s[i] != 0 && u8_nextchar(s, &i) != 0) {
if (i == maxLength) {
throwRuntimeError(
"String exceeded maximum size of " +
std::to_string(maxLength) + " bytes.");
}
count++;
}
return count;
}
} // namespace utils
} // namespace valijson
#pragma once
#include <memory>
#include <type_traits>
namespace valijson {
namespace constraints {
class ConstraintVisitor;
/**
* @brief Interface that must be implemented by concrete constraint types.
*
* @todo Consider using something like the boost::cloneable concept here.
*/
struct Constraint
{
/// Typedef for custom new-/malloc-like function
typedef void * (*CustomAlloc)(size_t size);
/// Typedef for custom free-like function
typedef void (*CustomFree)(void *);
/// Deleter type to be used with std::unique_ptr / std::shared_ptr
/// @tparam T Const or non-const type (same as the one used in unique_ptr/shared_ptr)
template<typename T>
struct CustomDeleter
{
CustomDeleter(CustomFree freeFn)
: m_freeFn(freeFn) { }
void operator()(T *ptr) const
{
auto *nonconst = const_cast<typename std::remove_const<T>::type *>(ptr);
nonconst->~T();
m_freeFn(nonconst);
}
private:
CustomFree m_freeFn;
};
/// Exclusive-ownership pointer to automatically handle deallocation
typedef std::unique_ptr<const Constraint, CustomDeleter<const Constraint>> OwningPointer;
/**
* @brief Virtual destructor.
*/
virtual ~Constraint() = default;
/**
* @brief Perform an action on the constraint using the visitor pattern.
*
* Note that Constraints cannot be modified by visitors.
*
* @param visitor Reference to a ConstraintVisitor object.
*
* @returns the boolean value returned by one of the visitor's visit
* functions.
*/
virtual bool accept(ConstraintVisitor &visitor) const = 0;
/**
* @brief Make a copy of a constraint.
*
* Note that this should be a deep copy of the constraint.
*
* @returns an owning-pointer to the new constraint.
*/
virtual OwningPointer clone(CustomAlloc, CustomFree) const = 0;
};
} // namespace constraints
} // namespace valijson
#pragma once
#include <functional>
#include <memory>
#include <vector>
namespace valijson {
/**
* Represents a sub-schema within a JSON Schema
*
* While all JSON Schemas have at least one sub-schema, the root, some will
* have additional sub-schemas that are defined as part of constraints that are
* included in the schema. For example, a 'oneOf' constraint maintains a set of
* references to one or more nested sub-schemas. As per the definition of a
* oneOf constraint, a document is valid within that constraint if it validates
* against one of the nested sub-schemas.
*/
class Subschema
{
public:
/// Typedef for custom new-/malloc-like function
typedef void * (*CustomAlloc)(size_t size);
/// Typedef for custom free-like function
typedef void (*CustomFree)(void *);
/// Typedef the Constraint class into the local namespace for convenience
typedef constraints::Constraint Constraint;
/// Typedef for a function that can be applied to each of the Constraint
/// instances owned by a Schema.
typedef std::function<bool (const Constraint &)> ApplyFunction;
// Disable copy construction
Subschema(const Subschema &) = delete;
// Disable copy assignment
Subschema & operator=(const Subschema &) = delete;
// Default move construction
Subschema(Subschema &&) = default;
// Default move assignment
Subschema & operator=(Subschema &&) = default;
/**
* @brief Construct a new Subschema object
*/
Subschema()
: m_allocFn([](size_t size) { return ::operator new(size, std::nothrow); })
, m_freeFn(::operator delete)
, m_alwaysInvalid(false) { }
/**
* @brief Construct a new Subschema using custom memory management
* functions
*
* @param allocFn malloc- or new-like function to allocate memory
* within Schema, such as for Subschema instances
* @param freeFn free-like function to free memory allocated with
* the `customAlloc` function
*/
Subschema(CustomAlloc allocFn, CustomFree freeFn)
: m_allocFn(allocFn)
, m_freeFn(freeFn)
, m_alwaysInvalid(false)
{
// explicitly initialise optionals. See: https://github.com/tristanpenman/valijson/issues/124
m_description = opt::nullopt;
m_id = opt::nullopt;
m_title = opt::nullopt;
}
/**
* @brief Clean up and free all memory managed by the Subschema
*/
virtual ~Subschema()
{
#if VALIJSON_USE_EXCEPTIONS
try {
#endif
m_constraints.clear();
#if VALIJSON_USE_EXCEPTIONS
} catch (const std::exception &e) {
fprintf(stderr, "Caught an exception in Subschema destructor: %s",
e.what());
}
#endif
}
/**
* @brief Add a constraint to this sub-schema
*
* The constraint will be copied before being added to the list of
* constraints for this Subschema. Note that constraints will be copied
* only as deep as references to other Subschemas - e.g. copies of
* constraints that refer to sub-schemas, will continue to refer to the
* same Subschema instances.
*
* @param constraint Reference to the constraint to copy
*/
void addConstraint(const Constraint &constraint)
{
// the vector allocation might throw but the constraint memory will be taken care of anyways
m_constraints.push_back(constraint.clone(m_allocFn, m_freeFn));
}
/**
* @brief Invoke a function on each child Constraint
*
* This function will apply the callback function to each constraint in
* the Subschema, even if one of the invocations returns \c false. However,
* if one or more invocations of the callback function return \c false,
* this function will also return \c false.
*
* @returns \c true if all invocations of the callback function are
* successful, \c false otherwise
*/
bool apply(ApplyFunction &applyFunction) const
{
bool allTrue = true;
for (auto &&constraint : m_constraints) {
// Even if an application fails, we want to continue checking the
// schema. In that case we set allTrue to false, and then fall
// through to the next constraint
if (!applyFunction(*constraint)) {
allTrue = false;
}
}
return allTrue;
}
/**
* @brief Invoke a function on each child Constraint
*
* This is a stricter version of the apply() function that will return
* immediately if any of the invocations of the callback function return
* \c false.
*
* @returns \c true if all invocations of the callback function are
* successful, \c false otherwise
*/
bool applyStrict(ApplyFunction &applyFunction) const
{
for (auto &&constraint : m_constraints) {
if (!applyFunction(*constraint)) {
return false;
}
}
return true;
}
bool getAlwaysInvalid() const
{
return m_alwaysInvalid;
}
/**
* @brief Get the description associated with this sub-schema
*
* @throws std::runtime_error if a description has not been set
*
* @returns string containing sub-schema description
*/
std::string getDescription() const
{
if (m_description) {
return *m_description;
}
throwRuntimeError("Schema does not have a description");
}
/**
* @brief Get the ID associated with this sub-schema
*
* @throws std::runtime_error if an ID has not been set
*
* @returns string containing sub-schema ID
*/
std::string getId() const
{
if (m_id) {
return *m_id;
}
throwRuntimeError("Schema does not have an ID");
}
/**
* @brief Get the title associated with this sub-schema
*
* @throws std::runtime_error if a title has not been set
*
* @returns string containing sub-schema title
*/
std::string getTitle() const
{
if (m_title) {
return *m_title;
}
throwRuntimeError("Schema does not have a title");
}
/**
* @brief Check whether this sub-schema has a description
*
* @return boolean value
*/
bool hasDescription() const
{
return static_cast<bool>(m_description);
}
/**
* @brief Check whether this sub-schema has an ID
*
* @return boolean value
*/
bool hasId() const
{
return static_cast<bool>(m_id);
}
/**
* @brief Check whether this sub-schema has a title
*
* @return boolean value
*/
bool hasTitle() const
{
return static_cast<bool>(m_title);
}
void setAlwaysInvalid(bool value)
{
m_alwaysInvalid = value;
}
/**
* @brief Set the description for this sub-schema
*
* The description will not be used for validation, but may be used as part
* of the user interface for interacting with schemas and sub-schemas. As
* an example, it may be used as part of the validation error descriptions
* that are produced by the Validator and ValidationVisitor classes.
*
* @param description new description
*/
void setDescription(const std::string &description)
{
m_description = description;
}
void setId(const std::string &id)
{
m_id = id;
}
/**
* @brief Set the title for this sub-schema
*
* The title will not be used for validation, but may be used as part
* of the user interface for interacting with schemas and sub-schema. As an
* example, it may be used as part of the validation error descriptions
* that are produced by the Validator and ValidationVisitor classes.
*
* @param title new title
*/
void setTitle(const std::string &title)
{
m_title = title;
}
protected:
CustomAlloc m_allocFn;
CustomFree m_freeFn;
private:
bool m_alwaysInvalid;
/// List of pointers to constraints that apply to this schema.
std::vector<Constraint::OwningPointer> m_constraints;
/// Schema description (optional)
opt::optional<std::string> m_description;
/// Id to apply when resolving the schema URI
opt::optional<std::string> m_id;
/// Title string associated with the schema (optional)
opt::optional<std::string> m_title;
};
} // namespace valijson
#pragma once
#include <cstdio>
#include <set>
namespace valijson {
/**
* Represents the root of a JSON Schema
*
* The root is distinct from other sub-schemas because it is the canonical
* starting point for validation of a document against a given a JSON Schema.
*/
class Schema: public Subschema
{
public:
/**
* @brief Construct a new Schema instance with no constraints
*/
Schema()
: sharedEmptySubschema(newSubschema()) { }
/**
* @brief Construct a new Schema using custom memory management
* functions
*
* @param allocFn malloc- or new-like function to allocate memory
* within Schema, such as for Subschema instances
* @param freeFn free-like function to free memory allocated with
* the `customAlloc` function
*/
Schema(CustomAlloc allocFn, CustomFree freeFn)
: Subschema(allocFn, freeFn),
sharedEmptySubschema(newSubschema()) { }
// Disable copy construction
Schema(const Schema &) = delete;
// Disable copy assignment
Schema & operator=(const Schema &) = delete;
// Default move construction
Schema(Schema &&other) = default;
// Disable copy assignment
Schema & operator=(Schema &&) = default;
/**
* @brief Clean up and free all memory managed by the Schema
*
* Note that any Subschema pointers created and returned by this Schema
* should be considered invalid.
*/
~Schema() override
{
sharedEmptySubschema->~Subschema();
m_freeFn(const_cast<Subschema *>(sharedEmptySubschema));
sharedEmptySubschema = nullptr;
#if VALIJSON_USE_EXCEPTIONS
try {
#endif
for (auto subschema : subschemaSet) {
subschema->~Subschema();
m_freeFn(subschema);
}
#if VALIJSON_USE_EXCEPTIONS
} catch (const std::exception &e) {
fprintf(stderr, "Caught an exception while destroying Schema: %s",
e.what());
}
#endif
}
/**
* @brief Copy a constraint to a specific sub-schema
*
* @param constraint reference to a constraint that will be copied into
* the sub-schema
* @param subschema pointer to the sub-schema that will own the copied
* constraint
*
* @throws std::runtime_error if the sub-schema is not owned by this Schema
* instance
*/
void addConstraintToSubschema(const Constraint &constraint,
const Subschema *subschema)
{
// TODO: Check heirarchy for subschemas that do not belong...
mutableSubschema(subschema)->addConstraint(constraint);
}
/**
* @brief Create a new Subschema instance that is owned by this Schema
*
* @returns const pointer to the new Subschema instance
*/
const Subschema * createSubschema()
{
Subschema *subschema = newSubschema();
#if VALIJSON_USE_EXCEPTIONS
try {
#endif
if (!subschemaSet.insert(subschema).second) {
throwRuntimeError(
"Failed to store pointer for new sub-schema");
}
#if VALIJSON_USE_EXCEPTIONS
} catch (...) {
subschema->~Subschema();
m_freeFn(subschema);
throw;
}
#endif
return subschema;
}
/**
* @brief Return a pointer to the shared empty schema
*/
const Subschema * emptySubschema() const
{
return sharedEmptySubschema;
}
/**
* @brief Get a pointer to the root sub-schema of this Schema instance
*/
const Subschema * root() const
{
return this;
}
void setAlwaysInvalid(const Subschema *subschema, bool value)
{
mutableSubschema(subschema)->setAlwaysInvalid(value);
}
/**
* @brief Update the description for one of the sub-schemas owned by this
* Schema instance
*
* @param subschema sub-schema to update
* @param description new description
*/
void setSubschemaDescription(const Subschema *subschema,
const std::string &description)
{
mutableSubschema(subschema)->setDescription(description);
}
/**
* @brief Update the ID for one of the sub-schemas owned by this Schema
* instance
*
* @param subschema sub-schema to update
* @param id new ID
*/
void setSubschemaId(const Subschema *subschema, const std::string &id)
{
mutableSubschema(subschema)->setId(id);
}
/**
* @brief Update the title for one of the sub-schemas owned by this Schema
* instance
*
* @param subschema sub-schema to update
* @param title new title
*/
void setSubschemaTitle(const Subschema *subschema, const std::string &title)
{
mutableSubschema(subschema)->setTitle(title);
}
private:
Subschema *newSubschema()
{
void *ptr = m_allocFn(sizeof(Subschema));
if (!ptr) {
throwRuntimeError(
"Failed to allocate memory for shared empty sub-schema");
}
#if VALIJSON_USE_EXCEPTIONS
try {
#endif
return new (ptr) Subschema();
#if VALIJSON_USE_EXCEPTIONS
} catch (...) {
m_freeFn(ptr);
throw;
}
#endif
}
Subschema * mutableSubschema(const Subschema *subschema)
{
if (subschema == this) {
return this;
}
if (subschema == sharedEmptySubschema) {
throwRuntimeError(
"Cannot modify the shared empty sub-schema");
}
auto *noConst = const_cast<Subschema*>(subschema);
if (subschemaSet.find(noConst) == subschemaSet.end()) {
throwRuntimeError(
"Subschema pointer is not owned by this Schema instance");
}
return noConst;
}
/// Set of Subschema instances owned by this schema
std::set<Subschema*> subschemaSet;
/// Empty schema that can be reused by multiple constraints
const Subschema *sharedEmptySubschema;
};
} // namespace valijson
#pragma once
namespace valijson {
namespace constraints {
class AllOfConstraint;
class AnyOfConstraint;
class ConditionalConstraint;
class ConstConstraint;
class ContainsConstraint;
class DependenciesConstraint;
class EnumConstraint;
class FormatConstraint;
class LinearItemsConstraint;
class MaxItemsConstraint;
class MaximumConstraint;
class MaxLengthConstraint;
class MaxPropertiesConstraint;
class MinItemsConstraint;
class MinimumConstraint;
class MinLengthConstraint;
class MinPropertiesConstraint;
class MultipleOfDoubleConstraint;
class MultipleOfIntConstraint;
class NotConstraint;
class OneOfConstraint;
class PatternConstraint;
class PolyConstraint;
class PropertiesConstraint;
class PropertyNamesConstraint;
class RequiredConstraint;
class SingularItemsConstraint;
class TypeConstraint;
class UniqueItemsConstraint;
/// Interface to allow usage of the visitor pattern with Constraints
class ConstraintVisitor
{
protected:
virtual ~ConstraintVisitor() = default;
// Shorten type names for derived classes outside of this namespace
typedef constraints::AllOfConstraint AllOfConstraint;
typedef constraints::AnyOfConstraint AnyOfConstraint;
typedef constraints::ConditionalConstraint ConditionalConstraint;
typedef constraints::ConstConstraint ConstConstraint;
typedef constraints::ContainsConstraint ContainsConstraint;
typedef constraints::DependenciesConstraint DependenciesConstraint;
typedef constraints::EnumConstraint EnumConstraint;
typedef constraints::FormatConstraint FormatConstraint;
typedef constraints::LinearItemsConstraint LinearItemsConstraint;
typedef constraints::MaximumConstraint MaximumConstraint;
typedef constraints::MaxItemsConstraint MaxItemsConstraint;
typedef constraints::MaxLengthConstraint MaxLengthConstraint;
typedef constraints::MaxPropertiesConstraint MaxPropertiesConstraint;
typedef constraints::MinimumConstraint MinimumConstraint;
typedef constraints::MinItemsConstraint MinItemsConstraint;
typedef constraints::MinLengthConstraint MinLengthConstraint;
typedef constraints::MinPropertiesConstraint MinPropertiesConstraint;
typedef constraints::MultipleOfDoubleConstraint MultipleOfDoubleConstraint;
typedef constraints::MultipleOfIntConstraint MultipleOfIntConstraint;
typedef constraints::NotConstraint NotConstraint;
typedef constraints::OneOfConstraint OneOfConstraint;
typedef constraints::PatternConstraint PatternConstraint;
typedef constraints::PolyConstraint PolyConstraint;
typedef constraints::PropertiesConstraint PropertiesConstraint;
typedef constraints::PropertyNamesConstraint PropertyNamesConstraint;
typedef constraints::RequiredConstraint RequiredConstraint;
typedef constraints::SingularItemsConstraint SingularItemsConstraint;
typedef constraints::TypeConstraint TypeConstraint;
typedef constraints::UniqueItemsConstraint UniqueItemsConstraint;
public:
virtual bool visit(const AllOfConstraint &) = 0;
virtual bool visit(const AnyOfConstraint &) = 0;
virtual bool visit(const ConditionalConstraint &) = 0;
virtual bool visit(const ConstConstraint &) = 0;
virtual bool visit(const ContainsConstraint &) = 0;
virtual bool visit(const DependenciesConstraint &) = 0;
virtual bool visit(const EnumConstraint &) = 0;
virtual bool visit(const FormatConstraint &) = 0;
virtual bool visit(const LinearItemsConstraint &) = 0;
virtual bool visit(const MaximumConstraint &) = 0;
virtual bool visit(const MaxItemsConstraint &) = 0;
virtual bool visit(const MaxLengthConstraint &) = 0;
virtual bool visit(const MaxPropertiesConstraint &) = 0;
virtual bool visit(const MinimumConstraint &) = 0;
virtual bool visit(const MinItemsConstraint &) = 0;
virtual bool visit(const MinLengthConstraint &) = 0;
virtual bool visit(const MinPropertiesConstraint &) = 0;
virtual bool visit(const MultipleOfDoubleConstraint &) = 0;
virtual bool visit(const MultipleOfIntConstraint &) = 0;
virtual bool visit(const NotConstraint &) = 0;
virtual bool visit(const OneOfConstraint &) = 0;
virtual bool visit(const PatternConstraint &) = 0;
virtual bool visit(const PolyConstraint &) = 0;
virtual bool visit(const PropertiesConstraint &) = 0;
virtual bool visit(const PropertyNamesConstraint &) = 0;
virtual bool visit(const RequiredConstraint &) = 0;
virtual bool visit(const SingularItemsConstraint &) = 0;
virtual bool visit(const TypeConstraint &) = 0;
virtual bool visit(const UniqueItemsConstraint &) = 0;
};
} // namespace constraints
} // namespace valijson
#pragma once
namespace valijson {
namespace constraints {
/**
* @brief Template class that implements the accept() and clone() functions of the Constraint interface.
*
* @tparam ConstraintType name of the concrete constraint type, which must provide a copy constructor.
*/
template<typename ConstraintType>
struct BasicConstraint: Constraint
{
typedef internal::CustomAllocator<void *> Allocator;
typedef std::basic_string<char, std::char_traits<char>, internal::CustomAllocator<char>> String;
BasicConstraint()
: m_allocator() { }
BasicConstraint(Allocator::CustomAlloc allocFn, Allocator::CustomFree freeFn)
: m_allocator(allocFn, freeFn) { }
BasicConstraint(const BasicConstraint &other)
: m_allocator(other.m_allocator) { }
~BasicConstraint() override = default;
bool accept(ConstraintVisitor &visitor) const override
{
return visitor.visit(*static_cast<const ConstraintType*>(this));
}
OwningPointer clone(CustomAlloc allocFn, CustomFree freeFn) const override
{
// smart pointer to automatically free raw memory on exception
typedef std::unique_ptr<Constraint, CustomFree> RawOwningPointer;
auto ptr = RawOwningPointer(static_cast<Constraint*>(allocFn(sizeof(ConstraintType))), freeFn);
if (!ptr) {
throwRuntimeError("Failed to allocate memory for cloned constraint");
}
// constructor might throw but the memory will be taken care of anyways
(void)new (ptr.get()) ConstraintType(*static_cast<const ConstraintType*>(this));
// implicitly convert to smart pointer that will also destroy object instance
return ptr;
}
protected:
Allocator m_allocator;
};
} // namespace constraints
} // namespace valijson
/**
* @file
*
* @brief Class definitions to support JSON Schema constraints
*
* This file contains class definitions for all of the constraints required to
* support JSON Schema. These classes all inherit from the BasicConstraint
* template class, which implements the common parts of the Constraint
* interface.
*
* @see BasicConstraint
* @see Constraint
*/
#pragma once
#include <limits>
#include <map>
#include <set>
#include <string>
#include <vector>
#ifdef _MSC_VER
#pragma warning( push )
#pragma warning( disable : 4702 )
#endif
namespace valijson {
class ValidationResults;
namespace constraints {
/**
* @brief Represents an 'allOf' constraint.
*
* An allOf constraint provides a collection of sub-schemas that a value must
* validate against. If a value fails to validate against any of these sub-
* schemas, then validation fails.
*/
class AllOfConstraint: public BasicConstraint<AllOfConstraint>
{
public:
AllOfConstraint()
: m_subschemas(Allocator::rebind<const Subschema *>::other(m_allocator)) { }
AllOfConstraint(CustomAlloc allocFn, CustomFree freeFn)
: BasicConstraint(allocFn, freeFn),
m_subschemas(Allocator::rebind<const Subschema *>::other(m_allocator)) { }
void addSubschema(const Subschema *subschema)
{
m_subschemas.push_back(subschema);
}
template<typename FunctorType>
void applyToSubschemas(const FunctorType &fn) const
{
unsigned int index = 0;
for (const Subschema *subschema : m_subschemas) {
if (!fn(index, subschema)) {
return;
}
index++;
}
}
private:
typedef std::vector<const Subschema *, internal::CustomAllocator<const Subschema *>> Subschemas;
/// Collection of sub-schemas, all of which must be satisfied
Subschemas m_subschemas;
};
/**
* @brief Represents an 'anyOf' constraint
*
* An anyOf constraint provides a collection of sub-schemas that a value can
* validate against. If a value validates against one of these sub-schemas,
* then the validation passes.
*/
class AnyOfConstraint: public BasicConstraint<AnyOfConstraint>
{
public:
AnyOfConstraint()
: m_subschemas(Allocator::rebind<const Subschema *>::other(m_allocator)) { }
AnyOfConstraint(CustomAlloc allocFn, CustomFree freeFn)
: BasicConstraint(allocFn, freeFn),
m_subschemas(Allocator::rebind<const Subschema *>::other(m_allocator)) { }
void addSubschema(const Subschema *subschema)
{
m_subschemas.push_back(subschema);
}
template<typename FunctorType>
void applyToSubschemas(const FunctorType &fn) const
{
unsigned int index = 0;
for (const Subschema *subschema : m_subschemas) {
if (!fn(index, subschema)) {
return;
}
index++;
}
}
private:
typedef std::vector<const Subschema *, internal::CustomAllocator<const Subschema *>> Subschemas;
/// Collection of sub-schemas, at least one of which must be satisfied
Subschemas m_subschemas;
};
/**
* @brief Represents a combination 'if', 'then' and 'else' constraints
*
* The schema provided by an 'if' constraint is used as the expression for a conditional. When the
* target validates against that schema, the 'then' subschema will be also be tested. Otherwise,
* the 'else' subschema will be tested.
*/
class ConditionalConstraint: public BasicConstraint<ConditionalConstraint>
{
public:
ConditionalConstraint()
: m_ifSubschema(nullptr),
m_thenSubschema(nullptr),
m_elseSubschema(nullptr) { }
ConditionalConstraint(CustomAlloc allocFn, CustomFree freeFn)
: BasicConstraint(allocFn, freeFn),
m_ifSubschema(nullptr),
m_thenSubschema(nullptr),
m_elseSubschema(nullptr) { }
const Subschema * getIfSubschema() const
{
return m_ifSubschema;
}
const Subschema * getThenSubschema() const
{
return m_thenSubschema;
}
const Subschema * getElseSubschema() const
{
return m_elseSubschema;
}
void setIfSubschema(const Subschema *subschema)
{
m_ifSubschema = subschema;
}
void setThenSubschema(const Subschema *subschema)
{
m_thenSubschema = subschema;
}
void setElseSubschema(const Subschema *subschema)
{
m_elseSubschema = subschema;
}
private:
const Subschema *m_ifSubschema;
const Subschema *m_thenSubschema;
const Subschema *m_elseSubschema;
};
class ConstConstraint: public BasicConstraint<ConstConstraint>
{
public:
ConstConstraint()
: m_value(nullptr) { }
ConstConstraint(CustomAlloc allocFn, CustomFree freeFn)
: BasicConstraint(allocFn, freeFn),
m_value(nullptr) { }
ConstConstraint(const ConstConstraint &other)
: BasicConstraint(other),
m_value(other.m_value->clone()) { }
adapters::FrozenValue * getValue() const
{
return m_value.get();
}
void setValue(const adapters::Adapter &value)
{
m_value = std::unique_ptr<adapters::FrozenValue>(value.freeze());
}
private:
std::unique_ptr<adapters::FrozenValue> m_value;
};
/**
* @brief Represents a 'contains' constraint
*
* A 'contains' constraint specifies a schema that must be satisfied by at least one
* of the values in an array.
*/
class ContainsConstraint: public BasicConstraint<ContainsConstraint>
{
public:
ContainsConstraint()
: m_subschema(nullptr) { }
ContainsConstraint(CustomAlloc allocFn, CustomFree freeFn)
: BasicConstraint(allocFn, freeFn),
m_subschema(nullptr) { }
const Subschema * getSubschema() const
{
return m_subschema;
}
void setSubschema(const Subschema *subschema)
{
m_subschema = subschema;
}
private:
const Subschema *m_subschema;
};
/**
* @brief Represents a 'dependencies' constraint.
*
* A dependency constraint ensures that a given property is valid only if the
* properties that it depends on are present.
*/
class DependenciesConstraint: public BasicConstraint<DependenciesConstraint>
{
public:
DependenciesConstraint()
: m_propertyDependencies(std::less<String>(), m_allocator),
m_schemaDependencies(std::less<String>(), m_allocator)
{ }
DependenciesConstraint(CustomAlloc allocFn, CustomFree freeFn)
: BasicConstraint(allocFn, freeFn),
m_propertyDependencies(std::less<String>(), m_allocator),
m_schemaDependencies(std::less<String>(), m_allocator)
{ }
template<typename StringType>
DependenciesConstraint & addPropertyDependency(
const StringType &propertyName,
const StringType &dependencyName)
{
const String key(propertyName.c_str(), m_allocator);
auto itr = m_propertyDependencies.find(key);
if (itr == m_propertyDependencies.end()) {
itr = m_propertyDependencies.insert(PropertyDependencies::value_type(
key, PropertySet(std::less<String>(), m_allocator))).first;
}
itr->second.insert(String(dependencyName.c_str(), m_allocator));
return *this;
}
template<typename StringType, typename ContainerType>
DependenciesConstraint & addPropertyDependencies(
const StringType &propertyName,
const ContainerType &dependencyNames)
{
const String key(propertyName.c_str(), m_allocator);
auto itr = m_propertyDependencies.find(key);
if (itr == m_propertyDependencies.end()) {
itr = m_propertyDependencies.insert(PropertyDependencies::value_type(
key, PropertySet(std::less<String>(), m_allocator))).first;
}
typedef typename ContainerType::value_type ValueType;
for (const ValueType &dependencyName : dependencyNames) {
itr->second.insert(String(dependencyName.c_str(), m_allocator));
}
return *this;
}
template<typename StringType>
DependenciesConstraint & addSchemaDependency(const StringType &propertyName, const Subschema *schemaDependency)
{
if (m_schemaDependencies.insert(SchemaDependencies::value_type(
String(propertyName.c_str(), m_allocator),
schemaDependency)).second) {
return *this;
}
throwRuntimeError("Dependencies constraint already contains a dependent "
"schema for the property '" + propertyName + "'");
}
template<typename FunctorType>
void applyToPropertyDependencies(const FunctorType &fn) const
{
for (const PropertyDependencies::value_type &v : m_propertyDependencies) {
if (!fn(v.first, v.second)) {
return;
}
}
}
template<typename FunctorType>
void applyToSchemaDependencies(const FunctorType &fn) const
{
for (const SchemaDependencies::value_type &v : m_schemaDependencies) {
if (!fn(v.first, v.second)) {
return;
}
}
}
private:
typedef std::set<String, std::less<String>, internal::CustomAllocator<String>> PropertySet;
typedef std::map<String, PropertySet, std::less<String>,
internal::CustomAllocator<std::pair<const String, PropertySet>>> PropertyDependencies;
typedef std::map<String, const Subschema *, std::less<String>,
internal::CustomAllocator<std::pair<const String, const Subschema *>>> SchemaDependencies;
/// Mapping from property names to their property-based dependencies
PropertyDependencies m_propertyDependencies;
/// Mapping from property names to their schema-based dependencies
SchemaDependencies m_schemaDependencies;
};
/**
* @brief Represents an 'enum' constraint
*
* An enum constraint provides a collection of permissible values for a JSON
* node. The node will only validate against this constraint if it matches one
* or more of the values in the collection.
*/
class EnumConstraint: public BasicConstraint<EnumConstraint>
{
public:
EnumConstraint()
: m_enumValues(Allocator::rebind<const EnumValue *>::other(m_allocator)) { }
EnumConstraint(CustomAlloc allocFn, CustomFree freeFn)
: BasicConstraint(allocFn, freeFn),
m_enumValues(Allocator::rebind<const EnumValue *>::other(m_allocator)) { }
EnumConstraint(const EnumConstraint &other)
: BasicConstraint(other),
m_enumValues(Allocator::rebind<const EnumValue *>::other(m_allocator))
{
#if VALIJSON_USE_EXCEPTIONS
try {
#endif
// Clone individual enum values
for (const EnumValue *otherValue : other.m_enumValues) {
const EnumValue *value = otherValue->clone();
#if VALIJSON_USE_EXCEPTIONS
try {
#endif
m_enumValues.push_back(value);
#if VALIJSON_USE_EXCEPTIONS
} catch (...) {
delete value;
value = nullptr;
throw;
}
}
} catch (...) {
// Delete values already added to constraint
for (const EnumValue *value : m_enumValues) {
delete value;
}
throw;
#endif
}
}
~EnumConstraint() override
{
for (const EnumValue *value : m_enumValues) {
delete value;
}
}
void addValue(const adapters::Adapter &value)
{
// TODO: Freeze value using custom alloc/free functions
m_enumValues.push_back(value.freeze());
}
void addValue(const adapters::FrozenValue &value)
{
// TODO: Clone using custom alloc/free functions
m_enumValues.push_back(value.clone());
}
template<typename FunctorType>
void applyToValues(const FunctorType &fn) const
{
for (const EnumValue *value : m_enumValues) {
if (!fn(*value)) {
return;
}
}
}
private:
typedef adapters::FrozenValue EnumValue;
typedef std::vector<const EnumValue *, internal::CustomAllocator<const EnumValue *>> EnumValues;
EnumValues m_enumValues;
};
/**
* @brief Represent a 'format' constraint
*
* A format constraint restricts the content of string values, as defined by a set of commonly used formats.
*
* As this is an optional feature in JSON Schema, unrecognised formats will be treated as valid for any string value.
*/
class FormatConstraint: public BasicConstraint<FormatConstraint>
{
public:
FormatConstraint()
: m_format() { }
const std::string & getFormat() const
{
return m_format;
}
void setFormat(const std::string & format)
{
m_format = format;
}
private:
std::string m_format;
};
/**
* @brief Represents non-singular 'items' and 'additionalItems' constraints
*
* Unlike the SingularItemsConstraint class, this class represents an 'items'
* constraint that specifies an array of sub-schemas, which should be used to
* validate each item in an array, in sequence. It also represents an optional
* 'additionalItems' sub-schema that should be used when an array contains
* more values than there are sub-schemas in the 'items' constraint.
*
* The prefix 'Linear' comes from the fact that this class contains a list of
* sub-schemas that corresponding array items must be validated against, and
* this validation is performed linearly (i.e. in sequence).
*/
class LinearItemsConstraint: public BasicConstraint<LinearItemsConstraint>
{
public:
LinearItemsConstraint()
: m_itemSubschemas(Allocator::rebind<const Subschema *>::other(m_allocator)),
m_additionalItemsSubschema(nullptr) { }
LinearItemsConstraint(CustomAlloc allocFn, CustomFree freeFn)
: BasicConstraint(allocFn, freeFn),
m_itemSubschemas(Allocator::rebind<const Subschema *>::other(m_allocator)),
m_additionalItemsSubschema(nullptr) { }
void addItemSubschema(const Subschema *subschema)
{
m_itemSubschemas.push_back(subschema);
}
template<typename FunctorType>
void applyToItemSubschemas(const FunctorType &fn) const
{
unsigned int index = 0;
for (const Subschema *subschema : m_itemSubschemas) {
if (!fn(index, subschema)) {
return;
}
index++;
}
}
const Subschema * getAdditionalItemsSubschema() const
{
return m_additionalItemsSubschema;
}
size_t getItemSubschemaCount() const
{
return m_itemSubschemas.size();
}
void setAdditionalItemsSubschema(const Subschema *subschema)
{
m_additionalItemsSubschema = subschema;
}
private:
typedef std::vector<const Subschema *, internal::CustomAllocator<const Subschema *>> Subschemas;
Subschemas m_itemSubschemas;
const Subschema* m_additionalItemsSubschema;
};
/**
* @brief Represents 'maximum' and 'exclusiveMaximum' constraints
*/
class MaximumConstraint: public BasicConstraint<MaximumConstraint>
{
public:
MaximumConstraint()
: m_maximum(std::numeric_limits<double>::infinity()),
m_exclusiveMaximum(false) { }
MaximumConstraint(CustomAlloc allocFn, CustomFree freeFn)
: BasicConstraint(allocFn, freeFn),
m_maximum(std::numeric_limits<double>::infinity()),
m_exclusiveMaximum(false) { }
bool getExclusiveMaximum() const
{
return m_exclusiveMaximum;
}
void setExclusiveMaximum(bool newExclusiveMaximum)
{
m_exclusiveMaximum = newExclusiveMaximum;
}
double getMaximum() const
{
return m_maximum;
}
void setMaximum(double newMaximum)
{
m_maximum = newMaximum;
}
private:
double m_maximum;
bool m_exclusiveMaximum;
};
/**
* @brief Represents a 'maxItems' constraint
*/
class MaxItemsConstraint: public BasicConstraint<MaxItemsConstraint>
{
public:
MaxItemsConstraint()
: m_maxItems(std::numeric_limits<uint64_t>::max()) { }
MaxItemsConstraint(CustomAlloc allocFn, CustomFree freeFn)
: BasicConstraint(allocFn, freeFn),
m_maxItems(std::numeric_limits<uint64_t>::max()) { }
uint64_t getMaxItems() const
{
return m_maxItems;
}
void setMaxItems(uint64_t newMaxItems)
{
m_maxItems = newMaxItems;
}
private:
uint64_t m_maxItems;
};
/**
* @brief Represents a 'maxLength' constraint
*/
class MaxLengthConstraint: public BasicConstraint<MaxLengthConstraint>
{
public:
MaxLengthConstraint()
: m_maxLength(std::numeric_limits<uint64_t>::max()) { }
MaxLengthConstraint(CustomAlloc allocFn, CustomFree freeFn)
: BasicConstraint(allocFn, freeFn),
m_maxLength(std::numeric_limits<uint64_t>::max()) { }
uint64_t getMaxLength() const
{
return m_maxLength;
}
void setMaxLength(uint64_t newMaxLength)
{
m_maxLength = newMaxLength;
}
private:
uint64_t m_maxLength;
};
/**
* @brief Represents a 'maxProperties' constraint
*/
class MaxPropertiesConstraint: public BasicConstraint<MaxPropertiesConstraint>
{
public:
MaxPropertiesConstraint()
: m_maxProperties(std::numeric_limits<uint64_t>::max()) { }
MaxPropertiesConstraint(CustomAlloc allocFn, CustomFree freeFn)
: BasicConstraint(allocFn, freeFn),
m_maxProperties(std::numeric_limits<uint64_t>::max()) { }
uint64_t getMaxProperties() const
{
return m_maxProperties;
}
void setMaxProperties(uint64_t newMaxProperties)
{
m_maxProperties = newMaxProperties;
}
private:
uint64_t m_maxProperties;
};
/**
* @brief Represents 'minimum' and 'exclusiveMinimum' constraints
*/
class MinimumConstraint: public BasicConstraint<MinimumConstraint>
{
public:
MinimumConstraint()
: m_minimum(-std::numeric_limits<double>::infinity()),
m_exclusiveMinimum(false) { }
MinimumConstraint(CustomAlloc allocFn, CustomFree freeFn)
: BasicConstraint(allocFn, freeFn),
m_minimum(-std::numeric_limits<double>::infinity()),
m_exclusiveMinimum(false) { }
bool getExclusiveMinimum() const
{
return m_exclusiveMinimum;
}
void setExclusiveMinimum(bool newExclusiveMinimum)
{
m_exclusiveMinimum = newExclusiveMinimum;
}
double getMinimum() const
{
return m_minimum;
}
void setMinimum(double newMinimum)
{
m_minimum = newMinimum;
}
private:
double m_minimum;
bool m_exclusiveMinimum;
};
/**
* @brief Represents a 'minItems' constraint
*/
class MinItemsConstraint: public BasicConstraint<MinItemsConstraint>
{
public:
MinItemsConstraint()
: m_minItems(0) { }
MinItemsConstraint(CustomAlloc allocFn, CustomFree freeFn)
: BasicConstraint(allocFn, freeFn),
m_minItems(0) { }
uint64_t getMinItems() const
{
return m_minItems;
}
void setMinItems(uint64_t newMinItems)
{
m_minItems = newMinItems;
}
private:
uint64_t m_minItems;
};
/**
* @brief Represents a 'minLength' constraint
*/
class MinLengthConstraint: public BasicConstraint<MinLengthConstraint>
{
public:
MinLengthConstraint()
: m_minLength(0) { }
MinLengthConstraint(CustomAlloc allocFn, CustomFree freeFn)
: BasicConstraint(allocFn, freeFn),
m_minLength(0) { }
uint64_t getMinLength() const
{
return m_minLength;
}
void setMinLength(uint64_t newMinLength)
{
m_minLength = newMinLength;
}
private:
uint64_t m_minLength;
};
/**
* @brief Represents a 'minProperties' constraint
*/
class MinPropertiesConstraint: public BasicConstraint<MinPropertiesConstraint>
{
public:
MinPropertiesConstraint()
: m_minProperties(0) { }
MinPropertiesConstraint(CustomAlloc allocFn, CustomFree freeFn)
: BasicConstraint(allocFn, freeFn),
m_minProperties(0) { }
uint64_t getMinProperties() const
{
return m_minProperties;
}
void setMinProperties(uint64_t newMinProperties)
{
m_minProperties = newMinProperties;
}
private:
uint64_t m_minProperties;
};
/**
* @brief Represents either 'multipleOf' or 'divisibleBy' constraints where
* the divisor is a floating point number
*/
class MultipleOfDoubleConstraint:
public BasicConstraint<MultipleOfDoubleConstraint>
{
public:
MultipleOfDoubleConstraint()
: m_value(1.) { }
MultipleOfDoubleConstraint(CustomAlloc allocFn, CustomFree freeFn)
: BasicConstraint(allocFn, freeFn),
m_value(1.) { }
double getDivisor() const
{
return m_value;
}
void setDivisor(double newValue)
{
m_value = newValue;
}
private:
double m_value;
};
/**
* @brief Represents either 'multipleOf' or 'divisibleBy' constraints where
* the divisor is of integer type
*/
class MultipleOfIntConstraint:
public BasicConstraint<MultipleOfIntConstraint>
{
public:
MultipleOfIntConstraint()
: m_value(1) { }
MultipleOfIntConstraint(CustomAlloc allocFn, CustomFree freeFn)
: BasicConstraint(allocFn, freeFn),
m_value(1) { }
int64_t getDivisor() const
{
return m_value;
}
void setDivisor(int64_t newValue)
{
m_value = newValue;
}
private:
int64_t m_value;
};
/**
* @brief Represents a 'not' constraint
*/
class NotConstraint: public BasicConstraint<NotConstraint>
{
public:
NotConstraint()
: m_subschema(nullptr) { }
NotConstraint(CustomAlloc allocFn, CustomFree freeFn)
: BasicConstraint(allocFn, freeFn),
m_subschema(nullptr) { }
const Subschema * getSubschema() const
{
return m_subschema;
}
void setSubschema(const Subschema *subschema)
{
m_subschema = subschema;
}
private:
const Subschema *m_subschema;
};
/**
* @brief Represents a 'oneOf' constraint.
*/
class OneOfConstraint: public BasicConstraint<OneOfConstraint>
{
public:
OneOfConstraint()
: m_subschemas(Allocator::rebind<const Subschema *>::other(m_allocator)) { }
OneOfConstraint(CustomAlloc allocFn, CustomFree freeFn)
: BasicConstraint(allocFn, freeFn),
m_subschemas(Allocator::rebind<const Subschema *>::other(m_allocator)) { }
void addSubschema(const Subschema *subschema)
{
m_subschemas.push_back(subschema);
}
template<typename FunctorType>
void applyToSubschemas(const FunctorType &fn) const
{
unsigned int index = 0;
for (const Subschema *subschema : m_subschemas) {
if (!fn(index, subschema)) {
return;
}
index++;
}
}
private:
typedef std::vector<const Subschema *, internal::CustomAllocator<const Subschema *>> Subschemas;
/// Collection of sub-schemas, exactly one of which must be satisfied
Subschemas m_subschemas;
};
/**
* @brief Represents a 'pattern' constraint
*/
class PatternConstraint: public BasicConstraint<PatternConstraint>
{
public:
PatternConstraint()
: m_pattern(Allocator::rebind<char>::other(m_allocator)) { }
PatternConstraint(CustomAlloc allocFn, CustomFree freeFn)
: BasicConstraint(allocFn, freeFn),
m_pattern(Allocator::rebind<char>::other(m_allocator)) { }
template<typename AllocatorType>
bool getPattern(std::basic_string<char, std::char_traits<char>, AllocatorType> &result) const
{
result.assign(m_pattern.c_str());
return true;
}
template<typename AllocatorType>
std::basic_string<char, std::char_traits<char>, AllocatorType> getPattern(
const AllocatorType &alloc = AllocatorType()) const
{
return std::basic_string<char, std::char_traits<char>, AllocatorType>(m_pattern.c_str(), alloc);
}
template<typename AllocatorType>
void setPattern(const std::basic_string<char, std::char_traits<char>, AllocatorType> &pattern)
{
m_pattern.assign(pattern.c_str());
}
private:
String m_pattern;
};
class PolyConstraint : public Constraint
{
public:
bool accept(ConstraintVisitor &visitor) const override
{
return visitor.visit(*static_cast<const PolyConstraint*>(this));
}
OwningPointer clone(CustomAlloc allocFn, CustomFree freeFn) const override
{
// smart pointer to automatically free raw memory on exception
typedef std::unique_ptr<Constraint, CustomFree> RawOwningPointer;
auto ptr = RawOwningPointer(static_cast<Constraint*>(allocFn(sizeOf())), freeFn);
if (!ptr) {
throwRuntimeError("Failed to allocate memory for cloned constraint");
}
// constructor might throw but the memory will be taken care of anyways
(void)cloneInto(ptr.get());
// implicitly convert to smart pointer that will also destroy object instance
return ptr;
}
virtual bool validate(const adapters::Adapter &target,
const std::vector<std::string>& context,
valijson::ValidationResults *results) const = 0;
private:
virtual Constraint * cloneInto(void *) const = 0;
virtual size_t sizeOf() const = 0;
};
/**
* @brief Represents a combination of 'properties', 'patternProperties' and
* 'additionalProperties' constraints
*/
class PropertiesConstraint: public BasicConstraint<PropertiesConstraint>
{
public:
PropertiesConstraint()
: m_properties(std::less<String>(), m_allocator),
m_patternProperties(std::less<String>(), m_allocator),
m_additionalProperties(nullptr) { }
PropertiesConstraint(CustomAlloc allocFn, CustomFree freeFn)
: BasicConstraint(allocFn, freeFn),
m_properties(std::less<String>(), m_allocator),
m_patternProperties(std::less<String>(), m_allocator),
m_additionalProperties(nullptr) { }
bool addPatternPropertySubschema(const char *patternProperty, const Subschema *subschema)
{
return m_patternProperties.insert(PropertySchemaMap::value_type(
String(patternProperty, m_allocator), subschema)).second;
}
template<typename AllocatorType>
bool addPatternPropertySubschema(const std::basic_string<char,
std::char_traits<char>, AllocatorType> &patternProperty,
const Subschema *subschema)
{
return addPatternPropertySubschema(patternProperty.c_str(), subschema);
}
bool addPropertySubschema(const char *propertyName,
const Subschema *subschema)
{
return m_properties.insert(PropertySchemaMap::value_type(
String(propertyName, m_allocator), subschema)).second;
}
template<typename AllocatorType>
bool addPropertySubschema(const std::basic_string<char,
std::char_traits<char>, AllocatorType> &propertyName,
const Subschema *subschema)
{
return addPropertySubschema(propertyName.c_str(), subschema);
}
template<typename FunctorType>
void applyToPatternProperties(const FunctorType &fn) const
{
typedef typename PropertySchemaMap::value_type ValueType;
for (const ValueType &value : m_patternProperties) {
if (!fn(value.first, value.second)) {
return;
}
}
}
template<typename FunctorType>
void applyToProperties(const FunctorType &fn) const
{
typedef typename PropertySchemaMap::value_type ValueType;
for (const ValueType &value : m_properties) {
if (!fn(value.first, value.second)) {
return;
}
}
}
const Subschema * getAdditionalPropertiesSubschema() const
{
return m_additionalProperties;
}
void setAdditionalPropertiesSubschema(const Subschema *subschema)
{
m_additionalProperties = subschema;
}
private:
typedef std::map<
String,
const Subschema *,
std::less<String>,
internal::CustomAllocator<std::pair<const String, const Subschema *>>
> PropertySchemaMap;
PropertySchemaMap m_properties;
PropertySchemaMap m_patternProperties;
const Subschema *m_additionalProperties;
};
class PropertyNamesConstraint: public BasicConstraint<PropertyNamesConstraint>
{
public:
PropertyNamesConstraint()
: m_subschema(nullptr) { }
PropertyNamesConstraint(CustomAlloc allocFn, CustomFree freeFn)
: BasicConstraint(allocFn, freeFn),
m_subschema(nullptr) { }
const Subschema * getSubschema() const
{
return m_subschema;
}
void setSubschema(const Subschema *subschema)
{
m_subschema = subschema;
}
private:
const Subschema *m_subschema;
};
/**
* @brief Represents a 'required' constraint
*/
class RequiredConstraint: public BasicConstraint<RequiredConstraint>
{
public:
RequiredConstraint()
: m_requiredProperties(std::less<String>(), m_allocator) { }
RequiredConstraint(CustomAlloc allocFn, CustomFree freeFn)
: BasicConstraint(allocFn, freeFn),
m_requiredProperties(std::less<String>(), m_allocator) { }
bool addRequiredProperty(const char *propertyName)
{
return m_requiredProperties.insert(String(propertyName,
Allocator::rebind<char>::other(m_allocator))).second;
}
template<typename AllocatorType>
bool addRequiredProperty(const std::basic_string<char, std::char_traits<char>, AllocatorType> &propertyName)
{
return addRequiredProperty(propertyName.c_str());
}
template<typename FunctorType>
void applyToRequiredProperties(const FunctorType &fn) const
{
for (const String &propertyName : m_requiredProperties) {
if (!fn(propertyName)) {
return;
}
}
}
private:
typedef std::set<String, std::less<String>,
internal::CustomAllocator<String>> RequiredProperties;
RequiredProperties m_requiredProperties;
};
/**
* @brief Represents an 'items' constraint that specifies one sub-schema
*
* A value is considered valid against this constraint if it is an array, and
* each item in the array validates against the sub-schema specified by this
* constraint.
*
* The prefix 'Singular' comes from the fact that array items must validate
* against exactly one sub-schema.
*/
class SingularItemsConstraint: public BasicConstraint<SingularItemsConstraint>
{
public:
SingularItemsConstraint()
: m_itemsSubschema(nullptr) { }
SingularItemsConstraint(CustomAlloc allocFn, CustomFree freeFn)
: BasicConstraint(allocFn, freeFn),
m_itemsSubschema(nullptr) { }
const Subschema * getItemsSubschema() const
{
return m_itemsSubschema;
}
void setItemsSubschema(const Subschema *subschema)
{
m_itemsSubschema = subschema;
}
private:
const Subschema *m_itemsSubschema;
};
/**
* @brief Represents a 'type' constraint.
*/
class TypeConstraint: public BasicConstraint<TypeConstraint>
{
public:
enum JsonType {
kAny,
kArray,
kBoolean,
kInteger,
kNull,
kNumber,
kObject,
kString
};
TypeConstraint()
: m_namedTypes(std::less<JsonType>(), m_allocator),
m_schemaTypes(Allocator::rebind<const Subschema *>::other(m_allocator)) { }
TypeConstraint(CustomAlloc allocFn, CustomFree freeFn)
: BasicConstraint(allocFn, freeFn),
m_namedTypes(std::less<JsonType>(), m_allocator),
m_schemaTypes(Allocator::rebind<const Subschema *>::other(m_allocator)) { }
void addNamedType(JsonType type)
{
m_namedTypes.insert(type);
}
void addSchemaType(const Subschema *subschema)
{
m_schemaTypes.push_back(subschema);
}
template<typename FunctorType>
void applyToNamedTypes(const FunctorType &fn) const
{
for (const JsonType namedType : m_namedTypes) {
if (!fn(namedType)) {
return;
}
}
}
template<typename FunctorType>
void applyToSchemaTypes(const FunctorType &fn) const
{
unsigned int index = 0;
for (const Subschema *subschema : m_schemaTypes) {
if (!fn(index, subschema)) {
return;
}
index++;
}
}
template<typename AllocatorType>
static JsonType jsonTypeFromString(const std::basic_string<char,
std::char_traits<char>, AllocatorType> &typeName)
{
if (typeName.compare("any") == 0) {
return kAny;
} else if (typeName.compare("array") == 0) {
return kArray;
} else if (typeName.compare("boolean") == 0) {
return kBoolean;
} else if (typeName.compare("integer") == 0) {
return kInteger;
} else if (typeName.compare("null") == 0) {
return kNull;
} else if (typeName.compare("number") == 0) {
return kNumber;
} else if (typeName.compare("object") == 0) {
return kObject;
} else if (typeName.compare("string") == 0) {
return kString;
}
throwRuntimeError("Unrecognised JSON type name '" +
std::string(typeName.c_str()) + "'");
abort();
}
private:
typedef std::set<JsonType, std::less<JsonType>, internal::CustomAllocator<JsonType>> NamedTypes;
typedef std::vector<const Subschema *,
Allocator::rebind<const Subschema *>::other> SchemaTypes;
/// Set of named JSON types that serve as valid types
NamedTypes m_namedTypes;
/// Set of sub-schemas that serve as valid types
SchemaTypes m_schemaTypes;
};
/**
* @brief Represents a 'uniqueItems' constraint
*/
class UniqueItemsConstraint: public BasicConstraint<UniqueItemsConstraint>
{
public:
UniqueItemsConstraint() = default;
UniqueItemsConstraint(CustomAlloc allocFn, CustomFree freeFn)
: BasicConstraint(allocFn, freeFn) { }
};
} // namespace constraints
} // namespace valijson
#ifdef _MSC_VER
#pragma warning( pop )
#endif
#pragma once
namespace valijson {
namespace adapters {
class Adapter;
}
namespace constraints {
struct Constraint;
}
class ConstraintBuilder
{
public:
virtual ~ConstraintBuilder() = default;
virtual constraints::Constraint * make(const adapters::Adapter &) const = 0;
};
} // namespace valijson
#pragma once
#include <stdexcept>
#include <iostream>
#include <vector>
#include <memory>
#include <functional>
namespace valijson {
/**
* @brief Parser for populating a Schema based on a JSON Schema document.
*
* The SchemaParser class supports Drafts 3 and 4 of JSON Schema, however
* Draft 3 support should be considered deprecated.
*
* The functions provided by this class have been templated so that they can
* be used with different Adapter types.
*/
class SchemaParser
{
public:
/// Supported versions of JSON Schema
enum Version {
kDraft3, ///< @deprecated JSON Schema v3 has been superseded by v4
kDraft4,
kDraft7
};
/**
* @brief Construct a new SchemaParser for a given version of JSON Schema
*
* @param version Version of JSON Schema that will be expected
*/
explicit SchemaParser(const Version version = kDraft7)
: m_version(version) { }
/**
* @brief Release memory associated with custom ConstraintBuilders
*/
virtual ~SchemaParser()
{
for (const auto& entry : constraintBuilders) {
delete entry.second;
}
}
/**
* @brief Struct to contain templated function type for fetching documents
*/
template<typename AdapterType>
struct FunctionPtrs
{
typedef typename adapters::AdapterTraits<AdapterType>::DocumentType DocumentType;
/// Templated function pointer type for fetching remote documents
typedef std::function<const DocumentType* (const std::string &uri)> FetchDoc;
/// Templated function pointer type for freeing fetched documents
typedef std::function<void (const DocumentType *)> FreeDoc;
};
/**
* @brief Add a custom contraint to this SchemaParser
* @param key name that will be used to identify relevant constraints
* while parsing a schema document
* @param builder pointer to a subclass of ConstraintBuilder that can
* parse custom constraints found in a schema document,
* and return an appropriate instance of Constraint; this
* class guarantees that it will take ownership of this
* pointer - unless this function throws an exception
*
* @todo consider accepting a list of custom ConstraintBuilders in
* constructor, so that this class remains immutable after
* construction
*
* @todo Add additional checks for key conflicts, empty keys, and
* potential restrictions relating to case sensitivity
*/
void addConstraintBuilder(const std::string &key, const ConstraintBuilder *builder)
{
constraintBuilders.push_back(std::make_pair(key, builder));
}
/**
* @brief Populate a Schema object from JSON Schema document
*
* When processing Draft 3 schemas, the parentSubschema and ownName pointers
* should be set in contexts where a 'required' constraint would be valid.
* These are used to add a RequiredConstraint object to the Schema that
* contains the required property.
*
* @param node Reference to node to parse
* @param schema Reference to Schema to populate
* @param fetchDoc Function to fetch remote JSON documents (optional)
*/
template<typename AdapterType>
void populateSchema(
const AdapterType &node,
Schema &schema,
typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc = nullptr ,
typename FunctionPtrs<AdapterType>::FreeDoc freeDoc = nullptr )
{
if ((fetchDoc == nullptr ) ^ (freeDoc == nullptr)) {
throwRuntimeError("Remote document fetching can't be enabled without both fetch and free functions");
}
typename DocumentCache<AdapterType>::Type docCache;
SchemaCache schemaCache;
#if VALIJSON_USE_EXCEPTIONS
try {
#endif
resolveThenPopulateSchema(schema, node, node, schema, opt::optional<std::string>(), "", fetchDoc, nullptr,
nullptr, docCache, schemaCache);
#if VALIJSON_USE_EXCEPTIONS
} catch (...) {
freeDocumentCache<AdapterType>(docCache, freeDoc);
throw;
}
#endif
freeDocumentCache<AdapterType>(docCache, freeDoc);
}
private:
typedef std::vector<std::pair<std::string, const ConstraintBuilder *>>
ConstraintBuilders;
ConstraintBuilders constraintBuilders;
template<typename AdapterType>
struct DocumentCache
{
typedef typename adapters::AdapterTraits<AdapterType>::DocumentType DocumentType;
typedef std::map<std::string, const DocumentType*> Type;
};
typedef std::map<std::string, const Subschema *> SchemaCache;
/**
* @brief Free memory used by fetched documents
*
* If a custom 'free' function has not been provided, then the default
* delete operator will be used.
*
* @param docCache collection of fetched documents to free
* @param freeDoc optional custom free function
*/
template<typename AdapterType>
void freeDocumentCache(const typename DocumentCache<AdapterType>::Type
&docCache, typename FunctionPtrs<AdapterType>::FreeDoc freeDoc)
{
typedef typename DocumentCache<AdapterType>::Type DocCacheType;
for (const typename DocCacheType::value_type &v : docCache) {
freeDoc(v.second);
}
}
/**
* @brief Find the complete URI for a document, within a resolution scope
*
* This function captures five different cases that can occur when
* attempting to resolve a document URI within a particular resolution
* scope:
*
* (1) resolution scope not present, but URN or absolute document URI is
* => document URI as-is
* (2) resolution scope not present, and document URI is relative or absent
* => document URI, if present, otherwise no result
* (3) resolution scope is present, and document URI is a relative path
* => resolve document URI relative to resolution scope
* (4) resolution scope is present, and document URI is absolute
* => document URI as-is
* (5) resolution scope is present, but document URI is not
* => resolution scope as-is
*
* This function assumes that the resolution scope is absolute.
*
* When resolving a document URI relative to the resolution scope, the
* document URI should be used to replace the path, query and fragment
* portions of URI provided by the resolution scope.
*/
virtual opt::optional<std::string> resolveDocumentUri(
const opt::optional<std::string>& resolutionScope,
const opt::optional<std::string>& documentUri)
{
if (resolutionScope) {
if (documentUri) {
if (internal::uri::isUriAbsolute(*documentUri) || internal::uri::isUrn(*documentUri)) {
// (4) resolution scope is present, and document URI is absolute
// => document URI as-is
return *documentUri;
} else {
// (3) resolution scope is present, and document URI is a relative path
// => resolve document URI relative to resolution scope
return internal::uri::resolveRelativeUri(*resolutionScope, *documentUri);
}
} else {
// (5) resolution scope is present, but document URI is not
// => resolution scope as-is
return *resolutionScope;
}
} else if (documentUri && internal::uri::isUriAbsolute(*documentUri)) {
// (1a) resolution scope not present, but absolute document URI is
// => document URI as-is
return *documentUri;
} else if (documentUri && internal::uri::isUrn(*documentUri)) {
// (1b) resolution scope not present, but URN is
// => document URI as-is
return *documentUri;
} else {
// (2) resolution scope not present, and document URI is relative or absent
// => document URI, if present, otherwise no result
// documentUri is already std::optional
return documentUri;
}
}
/**
* @brief Extract a JSON Reference string from a node
*
* @param node node to extract the JSON Reference from
* @param result reference to string to set with the result
*
* @throws std::invalid_argument if node is an object containing a `$ref`
* property but with a value that cannot be interpreted as a string
*
* @return \c true if a JSON Reference was extracted; \c false otherwise
*/
template<typename AdapterType>
bool extractJsonReference(const AdapterType &node, std::string &result)
{
if (!node.isObject()) {
return false;
}
const typename AdapterType::Object o = node.getObject();
const typename AdapterType::Object::const_iterator itr = o.find("$ref");
if (itr == o.end()) {
return false;
} else if (!itr->second.getString(result)) {
throwRuntimeError("$ref property expected to contain string value.");
}
return true;
}
/**
* Sanitise an optional JSON Pointer, trimming trailing slashes
*/
static std::string sanitiseJsonPointer(const opt::optional<std::string>& input)
{
if (input) {
// Trim trailing slash(es)
std::string sanitised = *input;
sanitised.erase(sanitised.find_last_not_of('/') + 1,
std::string::npos);
return sanitised;
}
// If the JSON Pointer is not set, assume that the URI points to
// the root of the document
return "";
}
/**
* @brief Search the schema cache for a schema matching a given key
*
* If the key is not present in the query cache, a nullptr will be
* returned, and the contents of the cache will remain unchanged. This is
* in contrast to the behaviour of the std::map [] operator, which would
* add the nullptr to the cache.
*
* @param schemaCache schema cache to query
* @param queryKey key to search for
*
* @return shared pointer to Schema if found, nullptr otherwise
*/
static const Subschema * querySchemaCache(SchemaCache &schemaCache,
const std::string &queryKey)
{
const SchemaCache::iterator itr = schemaCache.find(queryKey);
if (itr == schemaCache.end()) {
return nullptr;
}
return itr->second;
}
/**
* @brief Add entries to the schema cache for a given list of keys
*
* @param schemaCache schema cache to update
* @param keysToCreate list of keys to create entries for
* @param schema shared pointer to schema that keys will map to
*
* @throws std::logic_error if any of the keys are already present in the
* schema cache. This behaviour is intended to help detect incorrect
* usage of the schema cache during development, and is not expected
* to occur otherwise, even for malformed schemas.
*/
static void updateSchemaCache(SchemaCache &schemaCache,
const std::vector<std::string> &keysToCreate,
const Subschema *schema)
{
for (const std::string &keyToCreate : keysToCreate) {
const SchemaCache::value_type value(keyToCreate, schema);
if (!schemaCache.insert(value).second) {
throwLogicError("Key '" + keyToCreate + "' already in schema cache.");
}
}
}
/**
* @brief Recursive helper function for retrieving or creating schemas
*
* This function will be applied recursively until a concrete node is found.
* A concrete node is a node that contains actual schema constraints rather
* than a JSON Reference.
*
* This termination condition may be trigged by visiting the concrete node
* at the end of a series of $ref nodes, or by finding a schema for one of
* those $ref nodes in the schema cache. An entry will be added to the
* schema cache for each node visited on the path to the concrete node.
*
* @param rootSchema The Schema instance, and root subschema, through
* which other subschemas can be created and
* modified
* @param rootNode Reference to the node from which JSON References
* will be resolved when they refer to the current
* document
* @param node Reference to the node to parse
* @param currentScope URI for current resolution scope
* @param nodePath JSON Pointer representing path to current node
* @param fetchDoc Function to fetch remote JSON documents (optional)
* @param parentSubschema Optional pointer to the parent schema, used to
* support required keyword in Draft 3
* @param ownName Optional pointer to a node name, used to support
* the 'required' keyword in Draft 3
* @param docCache Cache of resolved and fetched remote documents
* @param schemaCache Cache of populated schemas
* @param newCacheKeys A list of keys that should be added to the cache
* when recursion terminates
*/
template<typename AdapterType>
const Subschema * makeOrReuseSchema(
Schema &rootSchema,
const AdapterType &rootNode,
const AdapterType &node,
const opt::optional<std::string> currentScope,
const std::string &nodePath,
const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc,
const Subschema *parentSubschema,
const std::string *ownName,
typename DocumentCache<AdapterType>::Type &docCache,
SchemaCache &schemaCache,
std::vector<std::string> &newCacheKeys)
{
std::string jsonRef;
// Check for the first termination condition (found a non-$ref node)
if (!extractJsonReference(node, jsonRef)) {
// Construct a key that we can use to search the schema cache for
// a schema corresponding to the current node
const std::string schemaCacheKey = currentScope ? (*currentScope + nodePath) : nodePath;
// Retrieve an existing schema from the cache if possible
const Subschema *cachedPtr = querySchemaCache(schemaCache, schemaCacheKey);
// Create a new schema otherwise
const Subschema *subschema = cachedPtr ? cachedPtr : rootSchema.createSubschema();
// Add cache entries for keys belonging to any $ref nodes that were
// visited before arriving at the current node
updateSchemaCache(schemaCache, newCacheKeys, subschema);
// Schema cache did not contain a pre-existing schema corresponding
// to the current node, so the schema that was returned will need
// to be populated
if (!cachedPtr) {
populateSchema(rootSchema, rootNode, node, *subschema,
currentScope, nodePath, fetchDoc, parentSubschema,
ownName, docCache, schemaCache);
}
return subschema;
}
// Returns a document URI if the reference points somewhere
// other than the current document
const opt::optional<std::string> documentUri = internal::json_reference::getJsonReferenceUri(jsonRef);
// Extract JSON Pointer from JSON Reference, with any trailing
// slashes removed so that keys in the schema cache end
// consistently
const std::string actualJsonPointer = sanitiseJsonPointer(
internal::json_reference::getJsonReferencePointer(jsonRef));
// Determine the actual document URI based on the resolution
// scope. An absolute document URI will take precedence when
// present, otherwise we need to resolve the URI relative to
// the current resolution scope
const opt::optional<std::string> actualDocumentUri = resolveDocumentUri(currentScope, documentUri);
// Construct a key to search the schema cache for an existing schema
const std::string queryKey = actualDocumentUri ? (*actualDocumentUri + actualJsonPointer) : actualJsonPointer;
// Check for the second termination condition (found a $ref node that
// already has an entry in the schema cache)
const Subschema *cachedPtr = querySchemaCache(schemaCache, queryKey);
if (cachedPtr) {
updateSchemaCache(schemaCache, newCacheKeys, cachedPtr);
return cachedPtr;
}
if (actualDocumentUri && (!currentScope || *actualDocumentUri != *currentScope)) {
const typename FunctionPtrs<AdapterType>::DocumentType *newDoc = nullptr;
// Have we seen this document before?
typename DocumentCache<AdapterType>::Type::iterator docCacheItr =
docCache.find(*actualDocumentUri);
if (docCacheItr == docCache.end()) {
// Resolve reference against remote document
if (!fetchDoc) {
throwRuntimeError("Fetching of remote JSON References not enabled.");
}
// Returns a pointer to the remote document that was
// retrieved, or null if retrieval failed. This class
// will take ownership of the pointer, and call freeDoc
// when it is no longer needed.
newDoc = fetchDoc(*actualDocumentUri);
// Can't proceed without the remote document
if (!newDoc) {
throwRuntimeError("Failed to fetch referenced schema document: " + *actualDocumentUri);
}
typedef typename DocumentCache<AdapterType>::Type::value_type
DocCacheValueType;
docCache.insert(DocCacheValueType(*actualDocumentUri, newDoc));
} else {
newDoc = docCacheItr->second;
}
const AdapterType newRootNode(*newDoc);
// Find where we need to be in the document
const AdapterType &referencedAdapter =
internal::json_pointer::resolveJsonPointer(newRootNode,
actualJsonPointer);
newCacheKeys.push_back(queryKey);
// Populate the schema, starting from the referenced node, with
// nested JSON References resolved relative to the new root node
return makeOrReuseSchema(rootSchema, newRootNode, referencedAdapter,
currentScope, actualJsonPointer, fetchDoc, parentSubschema,
ownName, docCache, schemaCache, newCacheKeys);
}
// JSON References in nested schema will be resolved relative to the
// current document
const AdapterType &referencedAdapter =
internal::json_pointer::resolveJsonPointer(
rootNode, actualJsonPointer);
newCacheKeys.push_back(queryKey);
// Populate the schema, starting from the referenced node, with
// nested JSON References resolved relative to the new root node
return makeOrReuseSchema(rootSchema, rootNode, referencedAdapter,
currentScope, actualJsonPointer, fetchDoc, parentSubschema,
ownName, docCache, schemaCache, newCacheKeys);
}
/**
* @brief Return pointer for the schema corresponding to a given node
*
* This function makes use of a schema cache, so that if the path to the
* current node is the same as one that has already been parsed and
* populated, a pointer to the existing Subschema will be returned.
*
* Should a series of $ref, or reference, nodes be resolved before reaching
* a concrete node, an entry will be added to the schema cache for each of
* the nodes in that path.
*
* @param rootSchema The Schema instance, and root subschema, through
* which other subschemas can be created and
* modified
* @param rootNode Reference to the node from which JSON References
* will be resolved when they refer to the current
* document
* @param node Reference to the node to parse
* @param currentScope URI for current resolution scope
* @param nodePath JSON Pointer representing path to current node
* @param fetchDoc Function to fetch remote JSON documents (optional)
* @param parentSubschema Optional pointer to the parent schema, used to
* support required keyword in Draft 3
* @param ownName Optional pointer to a node name, used to support
* the 'required' keyword in Draft 3
* @param docCache Cache of resolved and fetched remote documents
* @param schemaCache Cache of populated schemas
*/
template<typename AdapterType>
const Subschema * makeOrReuseSchema(
Schema &rootSchema,
const AdapterType &rootNode,
const AdapterType &node,
const opt::optional<std::string> currentScope,
const std::string &nodePath,
const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc,
const Subschema *parentSubschema,
const std::string *ownName,
typename DocumentCache<AdapterType>::Type &docCache,
SchemaCache &schemaCache)
{
std::vector<std::string> schemaCacheKeysToCreate;
return makeOrReuseSchema(rootSchema, rootNode, node, currentScope,
nodePath, fetchDoc, parentSubschema, ownName, docCache,
schemaCache, schemaCacheKeysToCreate);
}
/**
* @brief Populate a Schema object from JSON Schema document
*
* When processing Draft 3 schemas, the parentSubschema and ownName pointers
* should be set in contexts where a 'required' constraint would be valid.
* These are used to add a RequiredConstraint object to the Schema that
* contains the required property.
*
* @param rootSchema The Schema instance, and root subschema, through
* which other subschemas can be created and
* modified
* @param rootNode Reference to the node from which JSON References
* will be resolved when they refer to the current
* document
* @param node Reference to node to parse
* @param subschema Reference to Schema to populate
* @param currentScope URI for current resolution scope
* @param nodePath JSON Pointer representing path to current node
* @param fetchDoc Optional function to fetch remote JSON documents
* @param parentSubschema Optional pointer to the parent schema, used to
* support required keyword in Draft 3
* @param ownName Optional pointer to a node name, used to support
* the 'required' keyword in Draft 3
* @param docCache Cache of resolved and fetched remote documents
* @param schemaCache Cache of populated schemas
*/
template<typename AdapterType>
void populateSchema(
Schema &rootSchema,
const AdapterType &rootNode,
const AdapterType &node,
const Subschema &subschema,
const opt::optional<std::string>& currentScope,
const std::string &nodePath,
const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc,
const Subschema *parentSubschema,
const std::string *ownName,
typename DocumentCache<AdapterType>::Type &docCache,
SchemaCache &schemaCache)
{
static_assert((std::is_convertible<AdapterType,
const valijson::adapters::Adapter &>::value),
"SchemaParser::populateSchema must be invoked with an "
"appropriate Adapter implementation");
if (!node.isObject()) {
if (m_version == kDraft7 && node.maybeBool()) {
// Boolean schema
if (!node.asBool()) {
rootSchema.setAlwaysInvalid(&subschema, true);
}
return;
} else {
std::string s;
s += "Expected node at ";
s += nodePath;
if (m_version == kDraft7) {
s += " to contain schema object or boolean value; actual node type is: ";
} else {
s += " to contain schema object; actual node type is: ";
}
s += internal::nodeTypeAsString(node);
throwRuntimeError(s);
}
}
const typename AdapterType::Object object = node.asObject();
typename AdapterType::Object::const_iterator itr(object.end());
// Check for 'id' attribute and update current scope
opt::optional<std::string> updatedScope;
if ((itr = object.find("id")) != object.end() && itr->second.maybeString()) {
const std::string id = itr->second.asString();
rootSchema.setSubschemaId(&subschema, itr->second.asString());
if (!currentScope || internal::uri::isUriAbsolute(id) || internal::uri::isUrn(id)) {
updatedScope = id;
} else {
updatedScope = internal::uri::resolveRelativeUri(*currentScope, id);
}
} else {
updatedScope = currentScope;
}
// Add the type constraint first to be the first one to check because other constraints may rely on it
if ((itr = object.find("type")) != object.end()) {
rootSchema.addConstraintToSubschema(
makeTypeConstraint(rootSchema, rootNode, itr->second, updatedScope, nodePath + "/type", fetchDoc,
docCache, schemaCache),
&subschema);
}
if ((itr = object.find("allOf")) != object.end()) {
rootSchema.addConstraintToSubschema(
makeAllOfConstraint(rootSchema, rootNode, itr->second,
updatedScope, nodePath + "/allOf", fetchDoc,
docCache, schemaCache),
&subschema);
}
if ((itr = object.find("anyOf")) != object.end()) {
rootSchema.addConstraintToSubschema(
makeAnyOfConstraint(rootSchema, rootNode, itr->second,
updatedScope, nodePath + "/anyOf", fetchDoc,
docCache, schemaCache),
&subschema);
}
if ((itr = object.find("const")) != object.end()) {
rootSchema.addConstraintToSubschema(makeConstConstraint(itr->second), &subschema);
}
if ((itr = object.find("contains")) != object.end()) {
rootSchema.addConstraintToSubschema(
makeContainsConstraint(rootSchema, rootNode, itr->second,
updatedScope, nodePath + "/contains", fetchDoc,
docCache, schemaCache), &subschema);
}
if ((itr = object.find("dependencies")) != object.end()) {
rootSchema.addConstraintToSubschema(
makeDependenciesConstraint(rootSchema, rootNode,
itr->second, updatedScope,
nodePath + "/dependencies", fetchDoc, docCache,
schemaCache),
&subschema);
}
if ((itr = object.find("description")) != object.end()) {
if (itr->second.maybeString()) {
rootSchema.setSubschemaDescription(&subschema,
itr->second.asString());
} else {
throwRuntimeError(
"'description' attribute should have a string value");
}
}
if ((itr = object.find("divisibleBy")) != object.end()) {
if (m_version == kDraft3) {
if (itr->second.maybeInteger()) {
rootSchema.addConstraintToSubschema(
makeMultipleOfIntConstraint(itr->second),
&subschema);
} else if (itr->second.maybeDouble()) {
rootSchema.addConstraintToSubschema(
makeMultipleOfDoubleConstraint(itr->second),
&subschema);
} else {
throwRuntimeError("Expected an numeric value for "
" 'divisibleBy' constraint.");
}
} else {
throwRuntimeError(
"'divisibleBy' constraint not valid after draft 3");
}
}
if ((itr = object.find("enum")) != object.end()) {
rootSchema.addConstraintToSubschema(makeEnumConstraint(itr->second), &subschema);
}
if ((itr = object.find("format")) != object.end()) {
rootSchema.addConstraintToSubschema(makeFormatConstraint(itr->second), &subschema);
}
{
const typename AdapterType::Object::const_iterator itemsItr =
object.find("items");
if (object.end() != itemsItr) {
if (!itemsItr->second.isArray()) {
rootSchema.addConstraintToSubschema(
makeSingularItemsConstraint(rootSchema, rootNode,
itemsItr->second, updatedScope,
nodePath + "/items", fetchDoc, docCache,
schemaCache),
&subschema);
} else {
const typename AdapterType::Object::const_iterator
additionalItemsItr = object.find("additionalItems");
rootSchema.addConstraintToSubschema(
makeLinearItemsConstraint(rootSchema, rootNode,
itemsItr != object.end() ? &itemsItr->second : nullptr,
additionalItemsItr != object.end() ? &additionalItemsItr->second : nullptr,
updatedScope, nodePath + "/items",
nodePath + "/additionalItems", fetchDoc,
docCache, schemaCache),
&subschema);
}
}
}
{
const typename AdapterType::Object::const_iterator ifItr = object.find("if");
const typename AdapterType::Object::const_iterator thenItr = object.find("then");
const typename AdapterType::Object::const_iterator elseItr = object.find("else");
if (object.end() != ifItr) {
if (m_version == kDraft7) {
rootSchema.addConstraintToSubschema(
makeConditionalConstraint(rootSchema, rootNode,
ifItr->second,
thenItr == object.end() ? nullptr : &thenItr->second,
elseItr == object.end() ? nullptr : &elseItr->second,
updatedScope, nodePath, fetchDoc, docCache, schemaCache),
&subschema);
} else {
throwRuntimeError("Not supported");
}
}
}
if (m_version == kDraft7) {
if ((itr = object.find("exclusiveMaximum")) != object.end()) {
rootSchema.addConstraintToSubschema(
makeMaximumConstraintExclusive(itr->second),
&subschema);
}
if ((itr = object.find("maximum")) != object.end()) {
rootSchema.addConstraintToSubschema(
makeMaximumConstraint<AdapterType>(itr->second, nullptr),
&subschema);
}
} else if ((itr = object.find("maximum")) != object.end()) {
typename AdapterType::Object::const_iterator exclusiveMaximumItr =
object.find("exclusiveMaximum");
if (exclusiveMaximumItr == object.end()) {
rootSchema.addConstraintToSubschema(
makeMaximumConstraint<AdapterType>(itr->second, nullptr),
&subschema);
} else {
rootSchema.addConstraintToSubschema(
makeMaximumConstraint(itr->second, &exclusiveMaximumItr->second),
&subschema);
}
} else if (object.find("exclusiveMaximum") != object.end()) {
throwRuntimeError("'exclusiveMaximum' constraint only valid if a 'maximum' "
"constraint is also present");
}
if ((itr = object.find("maxItems")) != object.end()) {
rootSchema.addConstraintToSubschema(
makeMaxItemsConstraint(itr->second), &subschema);
}
if ((itr = object.find("maxLength")) != object.end()) {
rootSchema.addConstraintToSubschema(
makeMaxLengthConstraint(itr->second), &subschema);
}
if ((itr = object.find("maxProperties")) != object.end()) {
rootSchema.addConstraintToSubschema(
makeMaxPropertiesConstraint(itr->second), &subschema);
}
if (m_version == kDraft7) {
if ((itr = object.find("exclusiveMinimum")) != object.end()) {
rootSchema.addConstraintToSubschema(
makeMinimumConstraintExclusive(itr->second), &subschema);
}
if ((itr = object.find("minimum")) != object.end()) {
rootSchema.addConstraintToSubschema(
makeMinimumConstraint<AdapterType>(itr->second, nullptr),
&subschema);
}
} else if ((itr = object.find("minimum")) != object.end()) {
typename AdapterType::Object::const_iterator exclusiveMinimumItr = object.find("exclusiveMinimum");
if (exclusiveMinimumItr == object.end()) {
rootSchema.addConstraintToSubschema(
makeMinimumConstraint<AdapterType>(itr->second, nullptr),
&subschema);
} else {
rootSchema.addConstraintToSubschema(
makeMinimumConstraint<AdapterType>(itr->second, &exclusiveMinimumItr->second),
&subschema);
}
} else if (object.find("exclusiveMinimum") != object.end()) {
throwRuntimeError("'exclusiveMinimum' constraint only valid if a 'minimum' "
"constraint is also present");
}
if ((itr = object.find("minItems")) != object.end()) {
rootSchema.addConstraintToSubschema(
makeMinItemsConstraint(itr->second), &subschema);
}
if ((itr = object.find("minLength")) != object.end()) {
rootSchema.addConstraintToSubschema(
makeMinLengthConstraint(itr->second), &subschema);
}
if ((itr = object.find("minProperties")) != object.end()) {
rootSchema.addConstraintToSubschema(
makeMinPropertiesConstraint(itr->second), &subschema);
}
if ((itr = object.find("multipleOf")) != object.end()) {
if (m_version == kDraft3) {
throwRuntimeError("'multipleOf' constraint not available in draft 3");
} else if (itr->second.maybeInteger()) {
rootSchema.addConstraintToSubschema(
makeMultipleOfIntConstraint(itr->second),
&subschema);
} else if (itr->second.maybeDouble()) {
rootSchema.addConstraintToSubschema(
makeMultipleOfDoubleConstraint(itr->second),
&subschema);
} else {
throwRuntimeError("Expected an numeric value for 'divisibleBy' constraint.");
}
}
if ((itr = object.find("not")) != object.end()) {
rootSchema.addConstraintToSubschema(
makeNotConstraint(rootSchema, rootNode, itr->second, updatedScope, nodePath + "/not", fetchDoc,
docCache, schemaCache),
&subschema);
}
if ((itr = object.find("oneOf")) != object.end()) {
rootSchema.addConstraintToSubschema(
makeOneOfConstraint(rootSchema, rootNode, itr->second, updatedScope, nodePath + "/oneOf", fetchDoc,
docCache, schemaCache),
&subschema);
}
if ((itr = object.find("pattern")) != object.end()) {
rootSchema.addConstraintToSubschema(
makePatternConstraint(itr->second), &subschema);
}
{
// Check for schema keywords that require the creation of a
// PropertiesConstraint instance.
const typename AdapterType::Object::const_iterator
propertiesItr = object.find("properties"),
patternPropertiesItr = object.find("patternProperties"),
additionalPropertiesItr = object.find("additionalProperties");
if (object.end() != propertiesItr ||
object.end() != patternPropertiesItr ||
object.end() != additionalPropertiesItr) {
rootSchema.addConstraintToSubschema(
makePropertiesConstraint(rootSchema, rootNode,
propertiesItr != object.end() ? &propertiesItr->second : nullptr,
patternPropertiesItr != object.end() ? &patternPropertiesItr->second : nullptr,
additionalPropertiesItr != object.end() ? &additionalPropertiesItr->second : nullptr,
updatedScope, nodePath + "/properties",
nodePath + "/patternProperties",
nodePath + "/additionalProperties",
fetchDoc, &subschema, docCache, schemaCache),
&subschema);
}
}
if ((itr = object.find("propertyNames")) != object.end()) {
if (m_version == kDraft7) {
rootSchema.addConstraintToSubschema(
makePropertyNamesConstraint(rootSchema, rootNode, itr->second, updatedScope,
nodePath, fetchDoc, docCache, schemaCache),
&subschema);
} else {
throwRuntimeError("Not supported");
}
}
if ((itr = object.find("required")) != object.end()) {
if (m_version == kDraft3) {
if (parentSubschema && ownName) {
opt::optional<constraints::RequiredConstraint> constraint =
makeRequiredConstraintForSelf(itr->second, *ownName);
if (constraint) {
rootSchema.addConstraintToSubschema(*constraint, parentSubschema);
}
} else {
throwRuntimeError("'required' constraint not valid here");
}
} else {
rootSchema.addConstraintToSubschema(makeRequiredConstraint(itr->second), &subschema);
}
}
if ((itr = object.find("title")) != object.end()) {
if (itr->second.maybeString()) {
rootSchema.setSubschemaTitle(&subschema, itr->second.asString());
} else {
throwRuntimeError("'title' attribute should have a string value");
}
}
if ((itr = object.find("uniqueItems")) != object.end()) {
opt::optional<constraints::UniqueItemsConstraint> constraint = makeUniqueItemsConstraint(itr->second);
if (constraint) {
rootSchema.addConstraintToSubschema(*constraint, &subschema);
}
}
for (const auto & constraintBuilder : constraintBuilders) {
if ((itr = object.find(constraintBuilder.first)) != object.end()) {
constraints::Constraint *constraint = nullptr;
#if VALIJSON_USE_EXCEPTIONS
try {
#endif
constraint = constraintBuilder.second->make(itr->second);
rootSchema.addConstraintToSubschema(*constraint, &subschema);
delete constraint;
#if VALIJSON_USE_EXCEPTIONS
} catch (...) {
delete constraint;
throw;
}
#endif
}
}
}
/**
* @brief Resolves a chain of JSON References before populating a schema
*
* This helper function is used directly by the publicly visible
* populateSchema function. It ensures that the node being parsed is a
* concrete node, and not a JSON Reference. This function will call itself
* recursively to resolve references until a concrete node is found.
*
* @param rootSchema The Schema instance, and root subschema, through
* which other subschemas can be created and modified
* @param rootNode Reference to the node from which JSON References
* will be resolved when they refer to the current
* document
* @param node Reference to node to parse
* @param subschema Reference to Schema to populate
* @param currentScope URI for current resolution scope
* @param nodePath JSON Pointer representing path to current node
* @param fetchDoc Function to fetch remote JSON documents (optional)
* @param parentSchema Optional pointer to the parent schema, used to
* support required keyword in Draft 3
* @param ownName Optional pointer to a node name, used to support
* the 'required' keyword in Draft 3
* @param docCache Cache of resolved and fetched remote documents
* @param schemaCache Cache of populated schemas
*/
template<typename AdapterType>
void resolveThenPopulateSchema(
Schema &rootSchema,
const AdapterType &rootNode,
const AdapterType &node,
const Subschema &subschema,
const opt::optional<std::string> currentScope,
const std::string &nodePath,
const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc,
const Subschema *parentSchema,
const std::string *ownName,
typename DocumentCache<AdapterType>::Type &docCache,
SchemaCache &schemaCache)
{
std::string jsonRef;
if (!extractJsonReference(node, jsonRef)) {
populateSchema(rootSchema, rootNode, node, subschema, currentScope, nodePath, fetchDoc, parentSchema,
ownName, docCache, schemaCache);
return;
}
// Returns a document URI if the reference points somewhere
// other than the current document
const opt::optional<std::string> documentUri = internal::json_reference::getJsonReferenceUri(jsonRef);
// Extract JSON Pointer from JSON Reference
const std::string actualJsonPointer = sanitiseJsonPointer(
internal::json_reference::getJsonReferencePointer(jsonRef));
if (documentUri && (internal::uri::isUriAbsolute(*documentUri) || internal::uri::isUrn(*documentUri))) {
// Resolve reference against remote document
if (!fetchDoc) {
throwRuntimeError("Fetching of remote JSON References not enabled.");
}
const typename DocumentCache<AdapterType>::DocumentType *newDoc = fetchDoc(*documentUri);
// Can't proceed without the remote document
if (!newDoc) {
throwRuntimeError("Failed to fetch referenced schema document: " + *documentUri);
}
// Add to document cache
typedef typename DocumentCache<AdapterType>::Type::value_type DocCacheValueType;
docCache.insert(DocCacheValueType(*documentUri, newDoc));
const AdapterType newRootNode(*newDoc);
const AdapterType &referencedAdapter =
internal::json_pointer::resolveJsonPointer(newRootNode, actualJsonPointer);
// TODO: Need to detect degenerate circular references
resolveThenPopulateSchema(rootSchema, newRootNode, referencedAdapter, subschema, {}, actualJsonPointer,
fetchDoc, parentSchema, ownName, docCache, schemaCache);
} else {
const AdapterType &referencedAdapter =
internal::json_pointer::resolveJsonPointer(rootNode, actualJsonPointer);
// TODO: Need to detect degenerate circular references
resolveThenPopulateSchema(rootSchema, rootNode, referencedAdapter, subschema, {}, actualJsonPointer,
fetchDoc, parentSchema, ownName, docCache, schemaCache);
}
}
/**
* @brief Make a new AllOfConstraint object
*
* @param rootSchema The Schema instance, and root subschema, through
* which other subschemas can be created and modified
* @param rootNode Reference to the node from which JSON References
* will be resolved when they refer to the current
* document; used for recursive parsing of schemas
* @param node JSON node containing an array of child schemas
* @param currentScope URI for current resolution scope
* @param nodePath JSON Pointer representing path to current node
* @param fetchDoc Function to fetch remote JSON documents (optional)
* @param docCache Cache of resolved and fetched remote documents
* @param schemaCache Cache of populated schemas
*
* @return pointer to a new AllOfConstraint object that belongs to the
* caller
*/
template<typename AdapterType>
constraints::AllOfConstraint makeAllOfConstraint(
Schema &rootSchema,
const AdapterType &rootNode,
const AdapterType &node,
const opt::optional<std::string> currentScope,
const std::string &nodePath,
const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc,
typename DocumentCache<AdapterType>::Type &docCache,
SchemaCache &schemaCache)
{
if (!node.maybeArray()) {
throwRuntimeError("Expected array value for 'allOf' constraint.");
}
constraints::AllOfConstraint constraint;
int index = 0;
for (const AdapterType schemaNode : node.asArray()) {
if (schemaNode.maybeObject() || (m_version == kDraft7 && schemaNode.isBool())) {
const std::string childPath = nodePath + "/" + std::to_string(index);
const Subschema *subschema = makeOrReuseSchema<AdapterType>(
rootSchema, rootNode, schemaNode, currentScope,
childPath, fetchDoc, nullptr, nullptr, docCache, schemaCache);
constraint.addSubschema(subschema);
index++;
} else {
throwRuntimeError("Expected element to be a valid schema in 'allOf' constraint.");
}
}
return constraint;
}
/**
* @brief Make a new AnyOfConstraint object
*
* @param rootSchema The Schema instance, and root subschema, through
* which other subschemas can be created and modified
* @param rootNode Reference to the node from which JSON References
* will be resolved when they refer to the current
* document; used for recursive parsing of schemas
* @param node JSON node containing an array of child schemas
* @param currentScope URI for current resolution scope
* @param nodePath JSON Pointer representing path to current node
* @param fetchDoc Function to fetch remote JSON documents (optional)
* @param docCache Cache of resolved and fetched remote documents
* @param schemaCache Cache of populated schemas
*
* @return pointer to a new AnyOfConstraint object that belongs to the
* caller
*/
template<typename AdapterType>
constraints::AnyOfConstraint makeAnyOfConstraint(
Schema &rootSchema,
const AdapterType &rootNode,
const AdapterType &node,
const opt::optional<std::string> currentScope,
const std::string &nodePath,
const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc,
typename DocumentCache<AdapterType>::Type &docCache,
SchemaCache &schemaCache)
{
if (!node.maybeArray()) {
throwRuntimeError("Expected array value for 'anyOf' constraint.");
}
constraints::AnyOfConstraint constraint;
int index = 0;
for (const AdapterType schemaNode : node.asArray()) {
if (schemaNode.maybeObject() || (m_version == kDraft7 && schemaNode.isBool())) {
const std::string childPath = nodePath + "/" + std::to_string(index);
const Subschema *subschema = makeOrReuseSchema<AdapterType>(
rootSchema, rootNode, schemaNode, currentScope,
childPath, fetchDoc, nullptr, nullptr, docCache, schemaCache);
constraint.addSubschema(subschema);
index++;
} else {
throwRuntimeError("Expected array element to be a valid schema in 'anyOf' constraint.");
}
}
return constraint;
}
/**
* @brief Make a new ConditionalConstraint object.
*
* @param rootSchema The Schema instance, and root subschema,
* through which other subschemas can be
* created and modified
* @param rootNode Reference to the node from which JSON
* References will be resolved when they refer
* to the current document; used for recursive
* parsing of schemas
* @param ifNode Schema that will be used to evaluate the
* conditional.
* @param thenNode Optional pointer to a JSON node containing
* a schema that will be used when the conditional
* evaluates to true.
* @param elseNode Optional pointer to a JSON node containing
* a schema that will be used when the conditional
* evaluates to false.
* @param currentScope URI for current resolution scope
* @param nodePath JSON Pointer representing the path to
* the 'contains' node
* @param fetchDoc Function to fetch remote JSON documents
* (optional)
* @param docCache Cache of resolved and fetched remote
* documents
* @param schemaCache Cache of populated schemas
*
* @return pointer to a new ContainsConstraint that belongs to the caller
*/
template<typename AdapterType>
constraints::ConditionalConstraint makeConditionalConstraint(
Schema &rootSchema,
const AdapterType &rootNode,
const AdapterType &ifNode,
const AdapterType *thenNode,
const AdapterType *elseNode,
const opt::optional<std::string> currentScope,
const std::string &nodePath,
const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc,
typename DocumentCache<AdapterType>::Type &docCache,
SchemaCache &schemaCache)
{
constraints::ConditionalConstraint constraint;
const Subschema *ifSubschema = makeOrReuseSchema<AdapterType>(
rootSchema, rootNode, ifNode, currentScope,
nodePath + "/if", fetchDoc, nullptr, nullptr, docCache,
schemaCache);
constraint.setIfSubschema(ifSubschema);
if (thenNode) {
const Subschema *thenSubschema = makeOrReuseSchema<AdapterType>(
rootSchema, rootNode, *thenNode, currentScope, nodePath + "/then", fetchDoc, nullptr,
nullptr, docCache, schemaCache);
constraint.setThenSubschema(thenSubschema);
}
if (elseNode) {
const Subschema *elseSubschema = makeOrReuseSchema<AdapterType>(
rootSchema, rootNode, *elseNode, currentScope, nodePath + "/else", fetchDoc, nullptr,
nullptr, docCache, schemaCache);
constraint.setElseSubschema(elseSubschema);
}
return constraint;
}
/**
* @brief Make a new ConstConstraint object.
*
* @param node JSON node containing an arbitrary value
*
* @return pointer to a new MinimumConstraint that belongs to the caller
*/
template<typename AdapterType>
constraints::ConstConstraint makeConstConstraint(const AdapterType &node)
{
constraints::ConstConstraint constraint;
constraint.setValue(node);
return constraint;
}
/**
* @brief Make a new ContainsConstraint object.
*
* @param rootSchema The Schema instance, and root subschema,
* through which other subschemas can be
* created and modified
* @param rootNode Reference to the node from which JSON
* References will be resolved when they refer
* to the current document; used for recursive
* parsing of schemas
* @param contains Optional pointer to a JSON node containing
* an object mapping property names to
* schemas.
* @param currentScope URI for current resolution scope
* @param containsPath JSON Pointer representing the path to
* the 'contains' node
* @param fetchDoc Function to fetch remote JSON documents
* (optional)
* @param docCache Cache of resolved and fetched remote
* documents
* @param schemaCache Cache of populated schemas
*
* @return pointer to a new ContainsConstraint that belongs to the caller
*/
template<typename AdapterType>
constraints::ContainsConstraint makeContainsConstraint(
Schema &rootSchema,
const AdapterType &rootNode,
const AdapterType &contains,
const opt::optional<std::string> currentScope,
const std::string &containsPath,
const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc,
typename DocumentCache<AdapterType>::Type &docCache,
SchemaCache &schemaCache)
{
constraints::ContainsConstraint constraint;
if (contains.isObject() || (m_version == kDraft7 && contains.maybeBool())) {
const Subschema *subschema = makeOrReuseSchema<AdapterType>(
rootSchema, rootNode, contains, currentScope, containsPath,
fetchDoc, nullptr, nullptr, docCache, schemaCache);
constraint.setSubschema(subschema);
} else if (contains.maybeObject()) {
// If a loosely-typed Adapter type is being used, then we'll
// assume that an empty schema has been provided.
constraint.setSubschema(rootSchema.emptySubschema());
} else {
// All other formats will result in an exception being thrown.
throwRuntimeError("Expected valid schema for 'contains' constraint.");
}
return constraint;
}
/**
* @brief Make a new DependenciesConstraint object
*
* The dependencies for a property can be defined several ways. When parsing
* a Draft 4 schema, the following can be used:
* - an array that lists the name of each property that must be present
* if the dependent property is present
* - an object that specifies a schema which must be satisfied if the
* dependent property is present
*
* When parsing a Draft 3 schema, in addition to the formats above, the
* following format can be used:
* - a string that names a single property that must be present if the
* dependent property is presnet
*
* Multiple methods can be used in the same dependency constraint.
*
* If the format of any part of the the dependency node does not match one
* of these formats, an exception will be thrown.
*
* @param rootSchema The Schema instance, and root subschema, through
* which other subschemas can be created and modified
* @param rootNode Reference to the node from which JSON References
* will be resolved when they refer to the current
* document; used for recursive parsing of schemas
* @param node JSON node containing an object that defines a
* mapping of properties to their dependencies.
* @param currentScope URI for current resolution scope
* @param nodePath JSON Pointer representing path to current node
* @param fetchDoc Function to fetch remote JSON documents (optional)
* @param docCache Cache of resolved and fetched remote documents
* @param schemaCache Cache of populated schemas
*
* @return pointer to a new DependencyConstraint that belongs to the
* caller
*/
template<typename AdapterType>
constraints::DependenciesConstraint makeDependenciesConstraint(
Schema &rootSchema,
const AdapterType &rootNode,
const AdapterType &node,
const opt::optional<std::string> currentScope,
const std::string &nodePath,
const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc,
typename DocumentCache<AdapterType>::Type &docCache,
SchemaCache &schemaCache)
{
if (!node.maybeObject()) {
throwRuntimeError("Expected valid subschema for 'dependencies' constraint.");
}
constraints::DependenciesConstraint dependenciesConstraint;
// Process each of the dependency mappings defined by the object
for (const typename AdapterType::ObjectMember member : node.asObject()) {
// First, we attempt to parse the value of the dependency mapping
// as an array of strings. If the Adapter type does not support
// strict types, then an empty string or empty object will be cast
// to an array, and the resulting dependency list will be empty.
// This is equivalent to using an empty object, but does mean that
// if the user provides an actual string then this error will not
// be detected.
if (member.second.maybeArray()) {
// Parse an array of dependency names
std::vector<std::string> dependentPropertyNames;
for (const AdapterType dependencyName : member.second.asArray()) {
if (dependencyName.maybeString()) {
dependentPropertyNames.push_back(dependencyName.getString());
} else {
throwRuntimeError("Expected string value in dependency list of property '" +
member.first + "' in 'dependencies' constraint.");
}
}
dependenciesConstraint.addPropertyDependencies(member.first,
dependentPropertyNames);
// If the value of dependency mapping could not be processed as an
// array, we'll try to process it as an object instead. Note that
// strict type comparison is used here, since we've already
// exercised the flexibility by loosely-typed Adapter types. If the
// value of the dependency mapping is an object, then we'll try to
// process it as a dependent schema.
} else if (member.second.isObject() || (m_version == kDraft7 && member.second.maybeBool())) {
// Parse dependent subschema
const Subschema *childSubschema =
makeOrReuseSchema<AdapterType>(rootSchema, rootNode,
member.second, currentScope, nodePath, fetchDoc,
nullptr, nullptr, docCache, schemaCache);
dependenciesConstraint.addSchemaDependency(member.first,
childSubschema);
// If we're supposed to be parsing a Draft3 schema, then the value
// of the dependency mapping can also be a string containing the
// name of a single dependency.
} else if (m_version == kDraft3 && member.second.isString()) {
dependenciesConstraint.addPropertyDependency(member.first,
member.second.getString());
// All other types result in an exception being thrown.
} else {
throwRuntimeError("Invalid dependencies definition.");
}
}
return dependenciesConstraint;
}
/**
* @brief Make a new EnumConstraint object.
*
* @param node JSON node containing an array of values permitted by the
* constraint.
*
* @return pointer to a new EnumConstraint that belongs to the caller
*/
template<typename AdapterType>
constraints::EnumConstraint makeEnumConstraint(
const AdapterType &node)
{
// Make a copy of each value in the enum array
constraints::EnumConstraint constraint;
for (const AdapterType value : node.getArray()) {
constraint.addValue(value);
}
/// @todo This will make another copy of the values while constructing
/// the EnumConstraint. Move semantics in C++11 should make it possible
/// to avoid these copies without complicating the implementation of the
/// EnumConstraint class.
return constraint;
}
/**
* @brief Make a new FormatConstraint object
*
* @param node JSON node containing the configuration for this constraint
*
* @return pointer to a new FormatConstraint that belongs to the caller
*/
template<typename AdapterType>
constraints::FormatConstraint makeFormatConstraint(
const AdapterType &node)
{
if (node.isString()) {
const std::string value = node.asString();
if (!value.empty()) {
constraints::FormatConstraint constraint;
constraint.setFormat(value);
return constraint;
}
}
throwRuntimeError("Expected a string value for 'format' constraint.");
}
/**
* @brief Make a new ItemsConstraint object.
*
* @param rootSchema The Schema instance, and root subschema,
* through which other subschemas can be
* created and modified
* @param rootNode Reference to the node from which JSON
* References will be resolved when they refer
* to the current document; used for recursive
* parsing of schemas
* @param items Optional pointer to a JSON node containing
* an object mapping property names to
* schemas.
* @param additionalItems Optional pointer to a JSON node containing
* an additional properties schema or a
* boolean value.
* @param currentScope URI for current resolution scope
* @param itemsPath JSON Pointer representing the path to
* the 'items' node
* @param additionalItemsPath JSON Pointer representing the path to
* the 'additionalItems' node
* @param fetchDoc Function to fetch remote JSON documents
* (optional)
* @param docCache Cache of resolved and fetched remote
* documents
* @param schemaCache Cache of populated schemas
*
* @return pointer to a new ItemsConstraint that belongs to the caller
*/
template<typename AdapterType>
constraints::LinearItemsConstraint makeLinearItemsConstraint(
Schema &rootSchema,
const AdapterType &rootNode,
const AdapterType *items,
const AdapterType *additionalItems,
const opt::optional<std::string> currentScope,
const std::string &itemsPath,
const std::string &additionalItemsPath,
const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc,
typename DocumentCache<AdapterType>::Type &docCache,
SchemaCache &schemaCache)
{
constraints::LinearItemsConstraint constraint;
// Construct a Schema object for the the additionalItems constraint,
// if the additionalItems property is present
if (additionalItems) {
if (additionalItems->maybeBool()) {
// If the value of the additionalItems property is a boolean
// and is set to true, then additional array items do not need
// to satisfy any constraints.
if (additionalItems->asBool()) {
constraint.setAdditionalItemsSubschema(rootSchema.emptySubschema());
}
} else if (additionalItems->maybeObject()) {
// If the value of the additionalItems property is an object,
// then it should be parsed into a Schema object, which will be
// used to validate additional array items.
const Subschema *subschema = makeOrReuseSchema<AdapterType>(
rootSchema, rootNode, *additionalItems, currentScope,
additionalItemsPath, fetchDoc, nullptr, nullptr, docCache,
schemaCache);
constraint.setAdditionalItemsSubschema(subschema);
} else {
// Any other format for the additionalItems property will result
// in an exception being thrown.
throwRuntimeError("Expected bool or object value for 'additionalItems'");
}
} else {
// The default value for the additionalItems property is an empty
// object, which means that additional array items do not need to
// satisfy any constraints.
constraint.setAdditionalItemsSubschema(rootSchema.emptySubschema());
}
// Construct a Schema object for each item in the items array.
// If the items constraint is not provided, then array items
// will be validated against the additionalItems schema.
if (items) {
if (items->isArray()) {
// If the items constraint contains an array, then it should
// contain a list of child schemas which will be used to
// validate the values at the corresponding indexes in a target
// array.
int index = 0;
for (const AdapterType v : items->getArray()) {
const std::string childPath = itemsPath + "/" +
std::to_string(index);
const Subschema *subschema = makeOrReuseSchema<AdapterType>(
rootSchema, rootNode, v, currentScope, childPath,
fetchDoc, nullptr, nullptr, docCache, schemaCache);
constraint.addItemSubschema(subschema);
index++;
}
} else {
throwRuntimeError("Expected array value for non-singular 'items' constraint.");
}
}
return constraint;
}
/**
* @brief Make a new ItemsConstraint object.
*
* @param rootSchema The Schema instance, and root subschema,
* through which other subschemas can be
* created and modified
* @param rootNode Reference to the node from which JSON
* References will be resolved when they refer
* to the current document; used for recursive
* parsing of schemas
* @param items Optional pointer to a JSON node containing
* an object mapping property names to
* schemas.
* @param currentScope URI for current resolution scope
* @param itemsPath JSON Pointer representing the path to
* the 'items' node
* @param fetchDoc Function to fetch remote JSON documents
* (optional)
* @param docCache Cache of resolved and fetched remote
* documents
* @param schemaCache Cache of populated schemas
*
* @return pointer to a new ItemsConstraint that belongs to the caller
*/
template<typename AdapterType>
constraints::SingularItemsConstraint makeSingularItemsConstraint(
Schema &rootSchema,
const AdapterType &rootNode,
const AdapterType &items,
const opt::optional<std::string> currentScope,
const std::string &itemsPath,
const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc,
typename DocumentCache<AdapterType>::Type &docCache,
SchemaCache &schemaCache)
{
constraints::SingularItemsConstraint constraint;
// Construct a Schema object for each item in the items array, if an
// array is provided, or a single Schema object, in an object value is
// provided. If the items constraint is not provided, then array items
// will be validated against the additionalItems schema.
if (items.isObject() || (m_version == kDraft7 && items.maybeBool())) {
// If the items constraint contains an object value, then it
// should contain a Schema that will be used to validate all
// items in a target array. Any schema defined by the
// additionalItems constraint will be ignored.
const Subschema *subschema = makeOrReuseSchema<AdapterType>(
rootSchema, rootNode, items, currentScope, itemsPath,
fetchDoc, nullptr, nullptr, docCache, schemaCache);
constraint.setItemsSubschema(subschema);
} else if (items.maybeObject()) {
// If a loosely-typed Adapter type is being used, then we'll
// assume that an empty schema has been provided.
constraint.setItemsSubschema(rootSchema.emptySubschema());
} else {
// All other formats will result in an exception being thrown.
throwRuntimeError("Expected valid schema for singular 'items' constraint.");
}
return constraint;
}
/**
* @brief Make a new MaximumConstraint object (draft 3 and 4).
*
* @param node JSON node containing the maximum value.
* @param exclusiveMaximum Optional pointer to a JSON boolean value that
* indicates whether maximum value is excluded
* from the range of permitted values.
*
* @return pointer to a new MaximumConstraint that belongs to the caller
*/
template<typename AdapterType>
constraints::MaximumConstraint makeMaximumConstraint(
const AdapterType &node,
const AdapterType *exclusiveMaximum)
{
if (!node.maybeDouble()) {
throwRuntimeError("Expected numeric value for maximum constraint.");
}
constraints::MaximumConstraint constraint;
constraint.setMaximum(node.asDouble());
if (exclusiveMaximum) {
if (!exclusiveMaximum->maybeBool()) {
throwRuntimeError("Expected boolean value for exclusiveMaximum constraint.");
}
constraint.setExclusiveMaximum(exclusiveMaximum->asBool());
}
return constraint;
}
/**
* @brief Make a new MaximumConstraint object that is always exclusive (draft 7).
*
* @param node JSON node containing an integer, representing the maximum value.
*
* @return pointer to a new Maximum that belongs to the caller
*/
template<typename AdapterType>
constraints::MaximumConstraint makeMaximumConstraintExclusive(const AdapterType &node)
{
if (!node.maybeDouble()) {
throwRuntimeError("Expected numeric value for exclusiveMaximum constraint.");
}
constraints::MaximumConstraint constraint;
constraint.setMaximum(node.asDouble());
constraint.setExclusiveMaximum(true);
return constraint;
}
/**
* @brief Make a new MaxItemsConstraint object.
*
* @param node JSON node containing an integer value representing the
* maximum number of items that may be contaned by an array.
*
* @return pointer to a new MaxItemsConstraint that belongs to the caller.
*/
template<typename AdapterType>
constraints::MaxItemsConstraint makeMaxItemsConstraint(
const AdapterType &node)
{
if (node.maybeInteger()) {
const int64_t value = node.asInteger();
if (value >= 0) {
constraints::MaxItemsConstraint constraint;
constraint.setMaxItems(value);
return constraint;
}
}
throwRuntimeError("Expected non-negative integer value for 'maxItems' constraint.");
}
/**
* @brief Make a new MaxLengthConstraint object.
*
* @param node JSON node containing an integer value representing the
* maximum length of a string.
*
* @return pointer to a new MaxLengthConstraint that belongs to the caller
*/
template<typename AdapterType>
constraints::MaxLengthConstraint makeMaxLengthConstraint(
const AdapterType &node)
{
if (node.maybeInteger()) {
const int64_t value = node.asInteger();
if (value >= 0) {
constraints::MaxLengthConstraint constraint;
constraint.setMaxLength(value);
return constraint;
}
}
throwRuntimeError("Expected a non-negative integer value for 'maxLength' constraint.");
}
/**
* @brief Make a new MaxPropertiesConstraint object.
*
* @param node JSON node containing an integer value representing the
* maximum number of properties that may be contained by an
* object.
*
* @return pointer to a new MaxPropertiesConstraint that belongs to the
* caller
*/
template<typename AdapterType>
constraints::MaxPropertiesConstraint makeMaxPropertiesConstraint(
const AdapterType &node)
{
if (node.maybeInteger()) {
int64_t value = node.asInteger();
if (value >= 0) {
constraints::MaxPropertiesConstraint constraint;
constraint.setMaxProperties(value);
return constraint;
}
}
throwRuntimeError("Expected a non-negative integer for 'maxProperties' constraint.");
}
/**
* @brief Make a new MinimumConstraint object (draft 3 and 4).
*
* @param node JSON node containing an integer, representing
* the minimum value.
*
* @param exclusiveMinimum Optional pointer to a JSON boolean value that
* indicates whether the minimum value is
* excluded from the range of permitted values.
*
* @return pointer to a new MinimumConstraint that belongs to the caller
*/
template<typename AdapterType>
constraints::MinimumConstraint makeMinimumConstraint(
const AdapterType &node,
const AdapterType *exclusiveMinimum)
{
if (!node.maybeDouble()) {
throwRuntimeError("Expected numeric value for minimum constraint.");
}
constraints::MinimumConstraint constraint;
constraint.setMinimum(node.asDouble());
if (exclusiveMinimum) {
if (!exclusiveMinimum->maybeBool()) {
throwRuntimeError("Expected boolean value for 'exclusiveMinimum' constraint.");
}
constraint.setExclusiveMinimum(exclusiveMinimum->asBool());
}
return constraint;
}
/**
* @brief Make a new MinimumConstraint object that is always exclusive (draft 7).
*
* @param node JSON node containing an integer, representing the minimum value.
*
* @return pointer to a new MinimumConstraint that belongs to the caller
*/
template<typename AdapterType>
constraints::MinimumConstraint makeMinimumConstraintExclusive(const AdapterType &node)
{
if (!node.maybeDouble()) {
throwRuntimeError("Expected numeric value for exclusiveMinimum constraint.");
}
constraints::MinimumConstraint constraint;
constraint.setMinimum(node.asDouble());
constraint.setExclusiveMinimum(true);
return constraint;
}
/**
* @brief Make a new MinItemsConstraint object.
*
* @param node JSON node containing an integer value representing the
* minimum number of items that may be contained by an array.
*
* @return pointer to a new MinItemsConstraint that belongs to the caller
*/
template<typename AdapterType>
constraints::MinItemsConstraint makeMinItemsConstraint(const AdapterType &node)
{
if (node.maybeInteger()) {
const int64_t value = node.asInteger();
if (value >= 0) {
constraints::MinItemsConstraint constraint;
constraint.setMinItems(value);
return constraint;
}
}
throwRuntimeError("Expected a non-negative integer value for 'minItems' constraint.");
}
/**
* @brief Make a new MinLengthConstraint object.
*
* @param node JSON node containing an integer value representing the
* minimum length of a string.
*
* @return pointer to a new MinLengthConstraint that belongs to the caller
*/
template<typename AdapterType>
constraints::MinLengthConstraint makeMinLengthConstraint(const AdapterType &node)
{
if (node.maybeInteger()) {
const int64_t value = node.asInteger();
if (value >= 0) {
constraints::MinLengthConstraint constraint;
constraint.setMinLength(value);
return constraint;
}
}
throwRuntimeError("Expected a non-negative integer value for 'minLength' constraint.");
}
/**
* @brief Make a new MaxPropertiesConstraint object.
*
* @param node JSON node containing an integer value representing the
* minimum number of properties that may be contained by an
* object.
*
* @return pointer to a new MinPropertiesConstraint that belongs to the
* caller
*/
template<typename AdapterType>
constraints::MinPropertiesConstraint makeMinPropertiesConstraint(const AdapterType &node)
{
if (node.maybeInteger()) {
int64_t value = node.asInteger();
if (value >= 0) {
constraints::MinPropertiesConstraint constraint;
constraint.setMinProperties(value);
return constraint;
}
}
throwRuntimeError("Expected a non-negative integer for 'minProperties' constraint.");
}
/**
* @brief Make a new MultipleOfDoubleConstraint object
*
* @param node JSON node containing an numeric value that a target value
* must divide by in order to satisfy this constraint
*
* @return a MultipleOfConstraint
*/
template<typename AdapterType>
constraints::MultipleOfDoubleConstraint makeMultipleOfDoubleConstraint(const AdapterType &node)
{
constraints::MultipleOfDoubleConstraint constraint;
constraint.setDivisor(node.asDouble());
return constraint;
}
/**
* @brief Make a new MultipleOfIntConstraint object
*
* @param node JSON node containing a numeric value that a target value
* must divide by in order to satisfy this constraint
*
* @return a MultipleOfIntConstraint
*/
template<typename AdapterType>
constraints::MultipleOfIntConstraint makeMultipleOfIntConstraint(const AdapterType &node)
{
constraints::MultipleOfIntConstraint constraint;
constraint.setDivisor(node.asInteger());
return constraint;
}
/**
* @brief Make a new NotConstraint object
*
* @param rootSchema The Schema instance, and root subschema, through
* which other subschemas can be created and modified
* @param rootNode Reference to the node from which JSON References
* will be resolved when they refer to the current
* document; used for recursive parsing of schemas
* @param node JSON node containing a schema
* @param currentScope URI for current resolution scope
* @param nodePath JSON Pointer representing path to current node
* @param fetchDoc Function to fetch remote JSON documents (optional)
* @param docCache Cache of resolved and fetched remote documents
* @param schemaCache Cache of populated schemas
*
* @return pointer to a new NotConstraint object that belongs to the caller
*/
template<typename AdapterType>
constraints::NotConstraint makeNotConstraint(
Schema &rootSchema,
const AdapterType &rootNode,
const AdapterType &node,
const opt::optional<std::string> currentScope,
const std::string &nodePath,
const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc,
typename DocumentCache<AdapterType>::Type &docCache,
SchemaCache &schemaCache)
{
if (node.maybeObject() || (m_version == kDraft7 && node.maybeBool())) {
const Subschema *subschema = makeOrReuseSchema<AdapterType>(
rootSchema, rootNode, node, currentScope, nodePath,
fetchDoc, nullptr, nullptr, docCache, schemaCache);
constraints::NotConstraint constraint;
constraint.setSubschema(subschema);
return constraint;
}
throwRuntimeError("Expected object value for 'not' constraint.");
}
/**
* @brief Make a new OneOfConstraint object
*
* @param rootSchema The Schema instance, and root subschema, through
* which other subschemas can be created and modified
* @param rootNode Reference to the node from which JSON References
* will be resolved when they refer to the current
* document; used for recursive parsing of schemas
* @param node JSON node containing an array of child schemas
* @param currentScope URI for current resolution scope
* @param nodePath JSON Pointer representing path to current node
* @param fetchDoc Function to fetch remote JSON documents (optional)
* @param docCache Cache of resolved and fetched remote documents
* @param schemaCache Cache of populated schemas
*
* @return pointer to a new OneOfConstraint that belongs to the caller
*/
template<typename AdapterType>
constraints::OneOfConstraint makeOneOfConstraint(
Schema &rootSchema,
const AdapterType &rootNode,
const AdapterType &node,
const opt::optional<std::string> currentScope,
const std::string &nodePath,
const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc,
typename DocumentCache<AdapterType>::Type &docCache,
SchemaCache &schemaCache)
{
constraints::OneOfConstraint constraint;
int index = 0;
for (const AdapterType schemaNode : node.getArray()) {
const std::string childPath = nodePath + "/" + std::to_string(index);
const Subschema *subschema = makeOrReuseSchema<AdapterType>(
rootSchema, rootNode, schemaNode, currentScope, childPath,
fetchDoc, nullptr, nullptr, docCache, schemaCache);
constraint.addSubschema(subschema);
index++;
}
return constraint;
}
/**
* @brief Make a new PatternConstraint object.
*
* @param node JSON node containing a pattern string
*
* @return pointer to a new PatternConstraint object that belongs to the
* caller
*/
template<typename AdapterType>
constraints::PatternConstraint makePatternConstraint(
const AdapterType &node)
{
constraints::PatternConstraint constraint;
constraint.setPattern(node.getString());
return constraint;
}
/**
* @brief Make a new Properties object.
*
* @param rootSchema The Schema instance, and root
* subschema, through which other
* subschemas can be created and modified
* @param rootNode Reference to the node from which JSON
* References will be resolved when they
* refer to the current document; used
* for recursive parsing of schemas
* @param properties Optional pointer to a JSON node
* containing an object mapping property
* names to schemas.
* @param patternProperties Optional pointer to a JSON node
* containing an object mapping pattern
* property names to schemas.
* @param additionalProperties Optional pointer to a JSON node
* containing an additional properties
* schema or a boolean value.
* @param currentScope URI for current resolution scope
* @param propertiesPath JSON Pointer representing the path to
* the 'properties' node
* @param patternPropertiesPath JSON Pointer representing the path to
* the 'patternProperties' node
* @param additionalPropertiesPath JSON Pointer representing the path to
* the 'additionalProperties' node
* @param fetchDoc Function to fetch remote JSON
* documents (optional)
* @param parentSubschema Optional pointer to the Schema of the
* parent object, needed to support the
* 'required' keyword in Draft 3
* @param docCache Cache of resolved and fetched remote
* documents
* @param schemaCache Cache of populated schemas
*
* @return pointer to a new Properties that belongs to the caller
*/
template<typename AdapterType>
constraints::PropertiesConstraint makePropertiesConstraint(
Schema &rootSchema,
const AdapterType &rootNode,
const AdapterType *properties,
const AdapterType *patternProperties,
const AdapterType *additionalProperties,
const opt::optional<std::string> currentScope,
const std::string &propertiesPath,
const std::string &patternPropertiesPath,
const std::string &additionalPropertiesPath,
const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc,
const Subschema *parentSubschema,
typename DocumentCache<AdapterType>::Type &docCache,
SchemaCache &schemaCache)
{
typedef typename AdapterType::ObjectMember Member;
constraints::PropertiesConstraint constraint;
// Create subschemas for 'properties' constraint
if (properties) {
for (const Member m : properties->getObject()) {
const std::string &property = m.first;
const std::string childPath = propertiesPath + "/" + property;
const Subschema *subschema = makeOrReuseSchema<AdapterType>(
rootSchema, rootNode, m.second, currentScope, childPath,
fetchDoc, parentSubschema, &property, docCache,
schemaCache);
constraint.addPropertySubschema(property, subschema);
}
}
// Create subschemas for 'patternProperties' constraint
if (patternProperties) {
for (const Member m : patternProperties->getObject()) {
const std::string &pattern = m.first;
const std::string childPath = patternPropertiesPath + "/" + pattern;
const Subschema *subschema = makeOrReuseSchema<AdapterType>(
rootSchema, rootNode, m.second, currentScope, childPath,
fetchDoc, parentSubschema, &pattern, docCache,
schemaCache);
constraint.addPatternPropertySubschema(pattern, subschema);
}
}
// Create an additionalItems subschema if required
if (additionalProperties) {
// If additionalProperties has been set, check for a boolean value.
// Setting 'additionalProperties' to true allows the values of
// additional properties to take any form. Setting it false
// prohibits the use of additional properties.
// If additionalProperties is instead an object, it should be
// parsed as a schema. If additionalProperties has any other type,
// then the schema is not valid.
if (additionalProperties->isBool() ||
additionalProperties->maybeBool()) {
// If it has a boolean value that is 'true', then an empty
// schema should be used.
if (additionalProperties->asBool()) {
constraint.setAdditionalPropertiesSubschema(rootSchema.emptySubschema());
}
} else if (additionalProperties->isObject()) {
// If additionalProperties is an object, it should be used as
// a child schema.
const Subschema *subschema = makeOrReuseSchema<AdapterType>(
rootSchema, rootNode, *additionalProperties,
currentScope, additionalPropertiesPath, fetchDoc, nullptr,
nullptr, docCache, schemaCache);
constraint.setAdditionalPropertiesSubschema(subschema);
} else {
// All other types are invalid
throwRuntimeError("Invalid type for 'additionalProperties' constraint.");
}
} else {
// If an additionalProperties constraint is not provided, then the
// default value is an empty schema.
constraint.setAdditionalPropertiesSubschema(rootSchema.emptySubschema());
}
return constraint;
}
template<typename AdapterType>
constraints::PropertyNamesConstraint makePropertyNamesConstraint(
Schema &rootSchema,
const AdapterType &rootNode,
const AdapterType &currentNode,
const opt::optional<std::string> currentScope,
const std::string &nodePath,
const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc,
typename DocumentCache<AdapterType>::Type &docCache,
SchemaCache &schemaCache)
{
const Subschema *subschema = makeOrReuseSchema<AdapterType>(rootSchema, rootNode, currentNode, currentScope,
nodePath, fetchDoc, nullptr, nullptr, docCache, schemaCache);
constraints::PropertyNamesConstraint constraint;
constraint.setSubschema(subschema);
return constraint;
}
/**
* @brief Make a new RequiredConstraint.
*
* This function is used to create new RequiredContraint objects for
* Draft 3 schemas.
*
* @param node Node containing a boolean value.
* @param name Name of the required attribute.
*
* @return pointer to a new RequiredConstraint object that belongs to the
* caller
*/
template<typename AdapterType>
opt::optional<constraints::RequiredConstraint>
makeRequiredConstraintForSelf(const AdapterType &node,
const std::string &name)
{
if (!node.maybeBool()) {
throwRuntimeError("Expected boolean value for 'required' attribute.");
}
if (node.asBool()) {
constraints::RequiredConstraint constraint;
constraint.addRequiredProperty(name);
return constraint;
}
return opt::optional<constraints::RequiredConstraint>();
}
/**
* @brief Make a new RequiredConstraint.
*
* This function is used to create new RequiredContraint objects for
* Draft 4 schemas.
*
* @param node Node containing an array of strings.
*
* @return pointer to a new RequiredConstraint object that belongs to the
* caller
*/
template<typename AdapterType>
constraints::RequiredConstraint makeRequiredConstraint(
const AdapterType &node)
{
constraints::RequiredConstraint constraint;
for (const AdapterType v : node.getArray()) {
if (!v.maybeString()) {
throwRuntimeError("Expected required property name to be a string value");
}
constraint.addRequiredProperty(v.getString());
}
return constraint;
}
/**
* @brief Make a new TypeConstraint object
*
* @param rootSchema The Schema instance, and root subschema, through
* which other subschemas can be created and modified
* @param rootNode Reference to the node from which JSON References
* will be resolved when they refer to the current
* document; used for recursive parsing of schemas
* @param node Node containing the name of a JSON type
* @param currentScope URI for current resolution scope
* @param nodePath JSON Pointer representing path to current node
* @param fetchDoc Function to fetch remote JSON documents (optional)
* @param docCache Cache of resolved and fetched remote documents
* @param schemaCache Cache of populated schemas
*
* @return pointer to a new TypeConstraint object.
*/
template<typename AdapterType>
constraints::TypeConstraint makeTypeConstraint(
Schema &rootSchema,
const AdapterType &rootNode,
const AdapterType &node,
const opt::optional<std::string> currentScope,
const std::string &nodePath,
const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc,
typename DocumentCache<AdapterType>::Type &docCache,
SchemaCache &schemaCache)
{
typedef constraints::TypeConstraint TypeConstraint;
TypeConstraint constraint;
if (node.maybeString()) {
const TypeConstraint::JsonType type = TypeConstraint::jsonTypeFromString(node.getString());
if (type == TypeConstraint::kAny && m_version == kDraft4) {
throwRuntimeError("'any' type is not supported in version 4 schemas.");
}
constraint.addNamedType(type);
} else if (node.maybeArray()) {
int index = 0;
for (const AdapterType v : node.getArray()) {
if (v.maybeString()) {
const TypeConstraint::JsonType type = TypeConstraint::jsonTypeFromString(v.getString());
if (type == TypeConstraint::kAny && m_version == kDraft4) {
throwRuntimeError("'any' type is not supported in version 4 schemas.");
}
constraint.addNamedType(type);
} else if (v.maybeObject() && m_version == kDraft3) {
const std::string childPath = nodePath + "/" + std::to_string(index);
const Subschema *subschema = makeOrReuseSchema<AdapterType>(rootSchema, rootNode, v, currentScope,
childPath, fetchDoc, nullptr, nullptr, docCache, schemaCache);
constraint.addSchemaType(subschema);
} else {
throwRuntimeError("Type name should be a string.");
}
index++;
}
} else if (node.maybeObject() && m_version == kDraft3) {
const Subschema *subschema = makeOrReuseSchema<AdapterType>(rootSchema, rootNode, node, currentScope,
nodePath, fetchDoc, nullptr, nullptr, docCache, schemaCache);
constraint.addSchemaType(subschema);
} else {
throwRuntimeError("Type name should be a string.");
}
return constraint;
}
/**
* @brief Make a new UniqueItemsConstraint object.
*
* @param node Node containing a boolean value.
*
* @return pointer to a new UniqueItemsConstraint object that belongs to
* the caller, or nullptr if the boolean value is false.
*/
template<typename AdapterType>
opt::optional<constraints::UniqueItemsConstraint> makeUniqueItemsConstraint(const AdapterType &node)
{
if (node.isBool() || node.maybeBool()) {
// If the boolean value is true, this function will return a pointer
// to a new UniqueItemsConstraint object. If it is value, then the
// constraint is redundant, so nullptr is returned instead.
if (node.asBool()) {
return constraints::UniqueItemsConstraint();
} else {
return opt::optional<constraints::UniqueItemsConstraint>();
}
}
throwRuntimeError("Expected boolean value for 'uniqueItems' constraint.");
}
private:
/// Version of JSON Schema that should be expected when parsing
Version m_version;
};
} // namespace valijson
/**
* @file
*
* @brief Adapter implementation that wraps a single std::string value
*
* This allows property names to be validated against a schema as though they are a generic JSON
* value, while allowing the rest of Valijson's API to expose property names as plain std::string
* values.
*
* This was added while implementing draft 7 support. This included support for a constraint
* called propertyNames, which can be used to ensure that the property names in an object
* validate against a subschema.
*/
#pragma once
#include <string>
namespace valijson {
namespace adapters {
class StdStringAdapter;
class StdStringArrayValueIterator;
class StdStringObjectMemberIterator;
typedef std::pair<std::string, StdStringAdapter> StdStringObjectMember;
class StdStringArray
{
public:
typedef StdStringArrayValueIterator const_iterator;
typedef StdStringArrayValueIterator iterator;
StdStringArray() = default;
StdStringArrayValueIterator begin() const;
StdStringArrayValueIterator end() const;
static size_t size()
{
return 0;
}
};
class StdStringObject
{
public:
typedef StdStringObjectMemberIterator const_iterator;
typedef StdStringObjectMemberIterator iterator;
StdStringObject() = default;
StdStringObjectMemberIterator begin() const;
StdStringObjectMemberIterator end() const;
StdStringObjectMemberIterator find(const std::string &propertyName) const;
static size_t size()
{
return 0;
}
};
class StdStringFrozenValue: public FrozenValue
{
public:
explicit StdStringFrozenValue(std::string source)
: value(std::move(source)) { }
FrozenValue * clone() const override
{
return new StdStringFrozenValue(value);
}
bool equalTo(const Adapter &other, bool strict) const override;
private:
std::string value;
};
class StdStringAdapter: public Adapter
{
public:
typedef StdStringArray Array;
typedef StdStringObject Object;
typedef StdStringObjectMember ObjectMember;
explicit StdStringAdapter(const std::string &value)
: m_value(value) { }
bool applyToArray(ArrayValueCallback) const override
{
return maybeArray();
}
bool applyToObject(ObjectMemberCallback) const override
{
return maybeObject();
}
StdStringArray asArray() const
{
if (maybeArray()) {
return {};
}
throwRuntimeError("String value cannot be cast to array");
}
bool asBool() const override
{
return true;
}
bool asBool(bool &result) const override
{
result = true;
return true;
}
double asDouble() const override
{
return 0;
}
bool asDouble(double &result) const override
{
result = 0;
return true;
}
int64_t asInteger() const override
{
return 0;
}
bool asInteger(int64_t &result) const override
{
result = 0;
return true;
}
StdStringObject asObject() const
{
if (maybeObject()) {
return {};
}
throwRuntimeError("String value cannot be cast to object");
}
std::string asString() const override
{
return m_value;
}
bool asString(std::string &result) const override
{
result = m_value;
return true;
}
bool equalTo(const Adapter &other, bool strict) const override
{
if (strict && !other.isString()) {
return false;
}
return m_value == other.asString();
}
FrozenValue* freeze() const override
{
return new StdStringFrozenValue(m_value);
}
VALIJSON_NORETURN static StdStringArray getArray()
{
throwNotSupported();
}
VALIJSON_NORETURN size_t getArraySize() const override
{
throwNotSupported();
}
VALIJSON_NORETURN bool getArraySize(size_t &) const override
{
throwNotSupported();
}
VALIJSON_NORETURN bool getBool() const override
{
throwNotSupported();
}
VALIJSON_NORETURN bool getBool(bool &) const override
{
throwNotSupported();
}
VALIJSON_NORETURN double getDouble() const override
{
throwNotSupported();
}
VALIJSON_NORETURN bool getDouble(double &) const override
{
throwNotSupported();
}
VALIJSON_NORETURN int64_t getInteger() const override
{
throwNotSupported();
}
VALIJSON_NORETURN bool getInteger(int64_t &) const override
{
throwNotSupported();
}
VALIJSON_NORETURN double getNumber() const override
{
throwNotSupported();
}
VALIJSON_NORETURN bool getNumber(double &) const override
{
throwNotSupported();
}
VALIJSON_NORETURN size_t getObjectSize() const override
{
throwNotSupported();
}
VALIJSON_NORETURN bool getObjectSize(size_t &) const override
{
throwNotSupported();
}
std::string getString() const override
{
return m_value;
}
bool getString(std::string &result) const override
{
result = m_value;
return true;
}
bool hasStrictTypes() const override
{
return true;
}
bool isArray() const override
{
return false;
}
bool isBool() const override
{
return false;
}
bool isDouble() const override
{
return false;
}
bool isInteger() const override
{
return false;
}
bool isNull() const override
{
return false;
}
bool isNumber() const override
{
return false;
}
bool isObject() const override
{
return false;
}
bool isString() const override
{
return true;
}
bool maybeArray() const override
{
return false;
}
bool maybeBool() const override
{
return m_value == "true" || m_value == "false";
}
bool maybeDouble() const override
{
const char *b = m_value.c_str();
char *e = nullptr;
strtod(b, &e);
return e != b && e == b + m_value.length();
}
bool maybeInteger() const override
{
std::istringstream i(m_value);
int64_t x;
char c;
if (!(i >> x) || i.get(c)) {
return false;
}
return true;
}
bool maybeNull() const override
{
return m_value.empty();
}
bool maybeObject() const override
{
return m_value.empty();
}
bool maybeString() const override
{
return true;
}
private:
const std::string &m_value;
};
class StdStringArrayValueIterator
{
public:
using iterator_category = std::bidirectional_iterator_tag;
using value_type = StdStringAdapter;
using difference_type = StdStringAdapter;
using pointer = StdStringAdapter*;
using reference = StdStringAdapter&;
VALIJSON_NORETURN StdStringAdapter operator*() const
{
throwNotSupported();
}
VALIJSON_NORETURN DerefProxy<StdStringAdapter> operator->() const
{
throwNotSupported();
}
bool operator==(const StdStringArrayValueIterator &) const
{
return true;
}
bool operator!=(const StdStringArrayValueIterator &) const
{
return false;
}
VALIJSON_NORETURN const StdStringArrayValueIterator& operator++()
{
throwNotSupported();
}
VALIJSON_NORETURN StdStringArrayValueIterator operator++(int)
{
throwNotSupported();
}
VALIJSON_NORETURN const StdStringArrayValueIterator& operator--()
{
throwNotSupported();
}
VALIJSON_NORETURN void advance(std::ptrdiff_t)
{
throwNotSupported();
}
};
inline StdStringArrayValueIterator StdStringArray::begin() const
{
return {};
}
inline StdStringArrayValueIterator StdStringArray::end() const
{
return {};
}
class StdStringObjectMemberIterator
{
public:
using iterator_category = std::bidirectional_iterator_tag;
using value_type = StdStringObjectMember;
using difference_type = StdStringObjectMember;
using pointer = StdStringObjectMember*;
using reference = StdStringObjectMember&;
VALIJSON_NORETURN StdStringObjectMember operator*() const
{
throwNotSupported();
}
VALIJSON_NORETURN DerefProxy<StdStringObjectMember> operator->() const
{
throwNotSupported();
}
bool operator==(const StdStringObjectMemberIterator &) const
{
return true;
}
bool operator!=(const StdStringObjectMemberIterator &) const
{
return false;
}
VALIJSON_NORETURN const StdStringObjectMemberIterator& operator++()
{
throwNotSupported();
}
VALIJSON_NORETURN StdStringObjectMemberIterator operator++(int)
{
throwNotSupported();
}
VALIJSON_NORETURN const StdStringObjectMemberIterator& operator--()
{
throwNotSupported();
}
};
inline StdStringObjectMemberIterator StdStringObject::begin() const
{
return {};
}
inline StdStringObjectMemberIterator StdStringObject::end() const
{
return {};
}
inline StdStringObjectMemberIterator StdStringObject::find(const std::string &) const
{
return {};
}
template<>
struct AdapterTraits<valijson::adapters::StdStringAdapter>
{
typedef std::string DocumentType;
static std::string adapterName()
{
return "StdStringAdapter";
}
};
inline bool StdStringFrozenValue::equalTo(const Adapter &other, bool strict) const
{
return StdStringAdapter(value).equalTo(other, strict);
}
} // namespace adapters
} // namespace valijson
#pragma once
#include <deque>
#include <string>
#include <utility>
#include <vector>
namespace valijson {
/**
* @brief Class that encapsulates the storage of validation errors.
*
* This class maintains an internal FIFO queue of errors that are reported
* during validation. Errors are pushed on to the back of an internal
* queue, and can retrieved by popping them from the front of the queue.
*/
class ValidationResults
{
public:
/**
* @brief Describes a validation error.
*
* This struct is used to pass around the context and description of a
* validation error.
*/
struct Error
{
/// Path to the node that failed validation.
std::vector<std::string> context;
/// A detailed description of the validation error.
std::string description;
};
/**
* @brief Return begin iterator for results in the queue.
*/
std::deque<Error>::const_iterator begin() const
{
return m_errors.begin();
}
/**
* @brief Return end iterator for results in the queue.
*/
std::deque<Error>::const_iterator end() const
{
return m_errors.end();
}
/**
* @brief Return the number of errors in the queue.
*/
size_t numErrors() const
{
return m_errors.size();
}
/**
* @brief Copy an Error and push it on to the back of the queue.
*
* @param error Reference to an Error object to be copied.
*/
void pushError(const Error &error)
{
m_errors.push_back(error);
}
/**
* @brief Push an error onto the back of the queue.
*
* @param context Context of the validation error.
* @param description Description of the validation error.
*/
void
pushError(const std::vector<std::string> &context, const std::string &description)
{
m_errors.push_back({context, description});
}
/**
* @brief Pop an error from the front of the queue.
*
* @param error Reference to an Error object to populate.
*
* @returns true if an Error was popped, false otherwise.
*/
bool
popError(Error &error)
{
if (m_errors.empty()) {
return false;
}
error = m_errors.front();
m_errors.pop_front();
return true;
}
private:
/// FIFO queue of validation errors that have been reported
std::deque<Error> m_errors;
};
} // namespace valijson
#pragma once
#include <cmath>
#include <string>
#include <regex>
#include <unordered_map>
#include <utility>
#ifdef _MSC_VER
#pragma warning( push )
#pragma warning( disable : 4702 )
#endif
namespace valijson {
class ValidationResults;
/**
* @brief Implementation of the ConstraintVisitor interface that validates a
* target document
*
* @tparam AdapterType Adapter type for the target document.
*/
template<typename AdapterType>
class ValidationVisitor: public constraints::ConstraintVisitor
{
public:
/**
* @brief Construct a new validator for a given target value and context.
*
* @param target Target value to be validated
* @param context Current context for validation error descriptions,
* only used if results is set.
* @param strictTypes Use strict type comparison
* @param results Optional pointer to ValidationResults object, for
* recording error descriptions. If this pointer is set
* to nullptr, validation errors will caused validation to
* stop immediately.
* @param regexesCache Cache of already created std::regex objects for pattern
* constraints.
*/
ValidationVisitor(const AdapterType &target,
std::vector<std::string> context,
const bool strictTypes,
ValidationResults *results,
std::unordered_map<std::string, std::regex>& regexesCache)
: m_target(target),
m_context(std::move(context)),
m_results(results),
m_strictTypes(strictTypes),
m_regexesCache(regexesCache) { }
/**
* @brief Validate the target against a schema.
*
* When a ValidationResults object has been set via the 'results' member
* variable, validation will proceed as long as no fatal errors occur,
* with error descriptions added to the ValidationResults object.
*
* If a pointer to a ValidationResults instance is not provided, validation
* will only continue for as long as the constraints are validated
* successfully.
*
* @param subschema Sub-schema that the target must validate against
*
* @return \c true if validation passes; \c false otherwise
*/
bool validateSchema(const Subschema &subschema)
{
if (subschema.getAlwaysInvalid()) {
return false;
}
// Wrap the validationCallback() function below so that it will be
// passed a reference to a constraint (_1), and a reference to the
// visitor (*this).
Subschema::ApplyFunction fn(std::bind(validationCallback, std::placeholders::_1, std::ref(*this)));
// Perform validation against each constraint defined in the schema
if (m_results == nullptr) {
// The applyStrict() function will return immediately if the
// callback function returns false
if (!subschema.applyStrict(fn)) {
return false;
}
} else {
// The apply() function will iterate over all constraints in the
// schema, even if the callback function returns false. Once
// iteration is complete, the apply() function will return true
// only if all invokations of the callback function returned true.
if (!subschema.apply(fn)) {
return false;
}
}
return true;
}
/**
* @brief Validate a value against an AllOfConstraint
*
* An allOf constraint provides a set of child schemas against which the
* target must be validated in order for the constraint to the satifisfied.
*
* When a ValidationResults object has been set via the 'results' member
* variable, validation will proceed as long as no fatal errors occur,
* with error descriptions added to the ValidationResults object.
*
* If a pointer to a ValidationResults instance is not provided, validation
* will only continue for as long as the child schemas are validated
* successfully.
*
* @param constraint Constraint that the target must validate against
*
* @return \c true if validation passes; \c false otherwise
*/
bool visit(const AllOfConstraint &constraint) override
{
bool validated = true;
constraint.applyToSubschemas(
ValidateSubschemas(m_target, m_context, true, false, *this, m_results, nullptr, &validated));
return validated;
}
/**
* @brief Validate a value against an AnyOfConstraint
*
* An anyOf constraint provides a set of child schemas, any of which the
* target may be validated against in order for the constraint to the
* satifisfied.
*
* Because an anyOf constraint does not require the target to validate
* against all child schemas, if validation against a single schema fails,
* the results will not be added to a ValidationResults object. Only if
* validation fails for all child schemas will an error be added to the
* ValidationResults object.
*
* @param constraint Constraint that the target must validate against
*
* @return \c true if validation passes; \c false otherwise
*/
bool visit(const AnyOfConstraint &constraint) override
{
unsigned int numValidated = 0;
ValidationResults newResults;
ValidationResults *childResults = (m_results) ? &newResults : nullptr;
ValidationVisitor<AdapterType> v(m_target, m_context, m_strictTypes, childResults, m_regexesCache);
constraint.applyToSubschemas(
ValidateSubschemas(m_target, m_context, false, true, v, childResults, &numValidated, nullptr));
if (numValidated == 0 && m_results) {
ValidationResults::Error childError;
while (childResults->popError(childError)) {
m_results->pushError( childError.context, childError.description);
}
m_results->pushError(m_context, "Failed to validate against any schemas allowed by anyOf constraint.");
}
return numValidated > 0;
}
/**
* @brief Validate current node using a set of 'if', 'then' and 'else' subschemas
*
* A conditional constraint allows a document to be validated against one of two additional
* subschemas (specified via 'then' or 'else' properties) depending on whether the document
* satifies an optional subschema (specified via the 'if' property).
*
* @param constraint ConditionalConstraint that the current node must validate against
*
* @return \c true if validation passes; \c false otherwise
*/
bool visit(const ConditionalConstraint &constraint) override
{
ValidationResults newResults;
ValidationResults* conditionalResults = (m_results) ? &newResults : nullptr;
// Create a validator to evaluate the conditional
ValidationVisitor ifValidator(m_target, m_context, m_strictTypes, nullptr, m_regexesCache);
ValidationVisitor thenElseValidator(m_target, m_context, m_strictTypes, conditionalResults, m_regexesCache);
bool validated = false;
if (ifValidator.validateSchema(*constraint.getIfSubschema())) {
const Subschema *thenSubschema = constraint.getThenSubschema();
validated = thenSubschema == nullptr || thenElseValidator.validateSchema(*thenSubschema);
} else {
const Subschema *elseSubschema = constraint.getElseSubschema();
validated = elseSubschema == nullptr || thenElseValidator.validateSchema(*elseSubschema);
}
if (!validated && m_results) {
ValidationResults::Error conditionalError;
while (conditionalResults->popError(conditionalError)) {
m_results->pushError(conditionalError.context, conditionalError.description);
}
m_results->pushError(m_context, "Failed to validate against a conditional schema set by if-then-else constraints.");
}
return validated;
}
/**
* @brief Validate current node using a 'const' constraint
*
* A const constraint allows a document to be validated against a specific value.
*
* @param constraint ConstConstraint that the current node must validate against
*
* @return \c true if validation passes; \f false otherwise
*/
bool visit(const ConstConstraint &constraint) override
{
if (!constraint.getValue()->equalTo(m_target, m_strictTypes)) {
if (m_results) {
m_results->pushError(m_context, "Failed to match expected value set by 'const' constraint.");
}
return false;
}
return true;
}
/**
* @brief Validate current node using a 'contains' constraint
*
* A contains constraint is satisfied if the target is not an array, or if it is an array,
* only if it contains at least one value that matches the specified schema.
*
* @param constraint ContainsConstraint that the current node must validate against
*
* @return \c true if validation passes; \c false otherwise
*/
bool visit(const ContainsConstraint &constraint) override
{
if ((m_strictTypes && !m_target.isArray()) || !m_target.maybeArray()) {
return true;
}
const Subschema *subschema = constraint.getSubschema();
const typename AdapterType::Array arr = m_target.asArray();
bool validated = false;
for (const auto &el : arr) {
ValidationVisitor containsValidator(el, m_context, m_strictTypes, nullptr, m_regexesCache);
if (containsValidator.validateSchema(*subschema)) {
validated = true;
break;
}
}
if (!validated) {
if (m_results) {
m_results->pushError(m_context, "Failed to any values against subschema in 'contains' constraint.");
}
return false;
}
return validated;
}
/**
* @brief Validate current node against a 'dependencies' constraint
*
* A 'dependencies' constraint can be used to specify property-based or
* schema-based dependencies that must be fulfilled when a particular
* property is present in an object.
*
* Property-based dependencies define a set of properties that must be
* present in addition to a particular property, whereas a schema-based
* dependency defines an additional schema that the current document must
* validate against.
*
* @param constraint DependenciesConstraint that the current node
* must validate against
*
* @return \c true if validation passes; \c false otherwise
*/
bool visit(const DependenciesConstraint &constraint) override
{
// Ignore non-objects
if ((m_strictTypes && !m_target.isObject()) || (!m_target.maybeObject())) {
return true;
}
// Object to be validated
const typename AdapterType::Object object = m_target.asObject();
// Cleared if validation fails
bool validated = true;
// Iterate over all dependent properties defined by this constraint,
// invoking the DependentPropertyValidator functor once for each
// set of dependent properties
constraint.applyToPropertyDependencies(ValidatePropertyDependencies(object, m_context, m_results, &validated));
if (!m_results && !validated) {
return false;
}
// Iterate over all dependent schemas defined by this constraint,
// invoking the DependentSchemaValidator function once for each schema
// that must be validated if a given property is present
constraint.applyToSchemaDependencies(ValidateSchemaDependencies(
object, m_context, *this, m_results, &validated));
if (!m_results && !validated) {
return false;
}
return validated;
}
/**
* @brief Validate current node against an EnumConstraint
*
* Validation succeeds if the target is equal to one of the values provided
* by the EnumConstraint.
*
* @param constraint Constraint that the target must validate against
*
* @return \c true if validation succeeds; \c false otherwise
*/
bool visit(const EnumConstraint &constraint) override
{
unsigned int numValidated = 0;
constraint.applyToValues(
ValidateEquality(m_target, m_context, false, true, m_strictTypes, nullptr, &numValidated));
if (numValidated == 0) {
if (m_results) {
m_results->pushError(m_context, "Failed to match against any enum values.");
}
return false;
}
return numValidated > 0;
}
/**
* @brief Validate current node against a FormatConstraint
*
* @param constraint Constraint that the target must validate against
*
* @return \c true if validation succeeds; \c false otherwise
*/
bool visit(const FormatConstraint &constraint) override
{
//
// Don't attempt to cast validate the format constraint unless the
// target value is known to be a string. Drafts 4-7 of the spec
// suggest that 'format' should be treated as an annotation rather
// than an assertion, however this is not guaranteed. Given that we
// have been treating it as an assertion here, failing quietly on
// non-string values seems like the right thing to do, to avoid
// this throwing an exception.
//
// Schemas that need tighter validation around 'format' constaints
// should generally pair it with a 'type' constraint.
//
// Reference:
// https://json-schema.org/understanding-json-schema/reference/string.html#format
//
if (!m_target.maybeString()) {
return true;
}
const std::string s = m_target.asString();
const std::string format = constraint.getFormat();
if (format == "date") {
// Matches dates like: 2022-07-18
std::regex date_regex("^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$");
std::smatch matches;
if (std::regex_match(s, matches, date_regex)) {
const auto month = std::stoi(matches[2].str());
const auto day = std::stoi(matches[3].str());
return validate_date_range(month, day);
} else {
if (m_results) {
m_results->pushError(m_context,
"String should be a valid date");
}
return false;
}
} else if (format == "time") {
// Matches times like: 16:52:45Z, 16:52:45+02:00
std::regex time_regex("^([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\\.[0-9]+)?(([Zz])|([\\+|\\-]([01][0-9]|2[0-3]):[0-5][0-9]))$");
if (std::regex_match(s, time_regex)) {
return true;
} else {
if (m_results) {
m_results->pushError(m_context,
"String should be a valid time");
}
return false;
}
} else if (format == "date-time") {
// Matches data times like: 2022-07-18T16:52:45Z, 2022-07-18T16:52:45+02:00
std::regex datetime_regex("^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\\.[0-9]+)?(([Zz])|([\\+|\\-]([01][0-9]|2[0-3]):[0-5][0-9]))$");
std::smatch matches;
if (std::regex_match(s, matches, datetime_regex)) {
const auto month = std::stoi(matches[2].str());
const auto day = std::stoi(matches[3].str());
return validate_date_range(month, day);
} else {
if (m_results) {
m_results->pushError(m_context,
"String should be a valid date-time");
}
return false;
}
}
return true;
}
/**
* @brief Validate a value against a LinearItemsConstraint
*
* A LinearItemsConstraint represents an 'items' constraint that specifies,
* for each item in array, an individual sub-schema that the item must
* validate against. The LinearItemsConstraint class also captures the
* presence of an 'additionalItems' constraint, which specifies a default
* sub-schema that should be used if an array contains more items than
* there are sub-schemas in the 'items' constraint.
*
* If the current value is not an array, validation always succeeds.
*
* @param constraint SingularItemsConstraint to validate against
*
* @returns \c true if validation is successful; \c false otherwise
*/
bool visit(const LinearItemsConstraint &constraint) override
{
// Ignore values that are not arrays
if ((m_strictTypes && !m_target.isArray()) || (!m_target.maybeArray())) {
return true;
}
// Sub-schema to validate against when number of items in array exceeds
// the number of sub-schemas provided by the 'items' constraint
const Subschema * const additionalItemsSubschema = constraint.getAdditionalItemsSubschema();
// Track how many items are validated using 'items' constraint
unsigned int numValidated = 0;
// Array to validate
const typename AdapterType::Array arr = m_target.asArray();
const size_t arrSize = arr.size();
// Track validation status
bool validated = true;
// Validate as many items as possible using 'items' sub-schemas
const size_t itemSubschemaCount = constraint.getItemSubschemaCount();
if (itemSubschemaCount > 0) {
if (!additionalItemsSubschema) {
if (arrSize > itemSubschemaCount) {
if (!m_results) {
return false;
}
m_results->pushError(m_context, "Array contains more items than allowed by items constraint.");
validated = false;
}
}
constraint.applyToItemSubschemas(
ValidateItems(arr, m_context, true, m_results != nullptr, m_strictTypes, m_results, &numValidated,
&validated, m_regexesCache));
if (!m_results && !validated) {
return false;
}
}
// Validate remaining items using 'additionalItems' sub-schema
if (numValidated < arrSize) {
if (additionalItemsSubschema) {
// Begin validation from the first item not validated against
// an sub-schema provided by the 'items' constraint
unsigned int index = numValidated;
typename AdapterType::Array::const_iterator begin = arr.begin();
begin.advance(numValidated);
for (typename AdapterType::Array::const_iterator itr = begin;
itr != arr.end(); ++itr) {
// Update context for current array item
std::vector<std::string> newContext = m_context;
newContext.push_back("[" + std::to_string(index) + "]");
ValidationVisitor<AdapterType> validator(*itr, newContext, m_strictTypes, m_results, m_regexesCache);
if (!validator.validateSchema(*additionalItemsSubschema)) {
if (m_results) {
m_results->pushError(m_context, "Failed to validate item #" + std::to_string(index) +
" against additional items schema.");
validated = false;
} else {
return false;
}
}
index++;
}
} else if (m_results) {
m_results->pushError(m_context, "Cannot validate item #" + std::to_string(numValidated) +
" or greater using 'items' constraint or 'additionalItems' constraint.");
validated = false;
} else {
return false;
}
}
return validated;
}
/**
* @brief Validate a value against a MaximumConstraint object
*
* @param constraint Constraint that the target must validate against
*
* @return \c true if constraints are satisfied; \c false otherwise
*/
bool visit(const MaximumConstraint &constraint) override
{
if ((m_strictTypes && !m_target.isNumber()) || !m_target.maybeDouble()) {
// Ignore values that are not numbers
return true;
}
const double maximum = constraint.getMaximum();
if (constraint.getExclusiveMaximum()) {
if (m_target.asDouble() >= maximum) {
if (m_results) {
m_results->pushError(m_context, "Expected number less than " + std::to_string(maximum));
}
return false;
}
} else if (m_target.asDouble() > maximum) {
if (m_results) {
m_results->pushError(m_context, "Expected number less than or equal to " + std::to_string(maximum));
}
return false;
}
return true;
}
/**
* @brief Validate a value against a MaxItemsConstraint
*
* @param constraint Constraint that the target must validate against
*
* @return \c true if constraint is satisfied; \c false otherwise
*/
bool visit(const MaxItemsConstraint &constraint) override
{
if ((m_strictTypes && !m_target.isArray()) || !m_target.maybeArray()) {
return true;
}
const uint64_t maxItems = constraint.getMaxItems();
if (m_target.asArray().size() <= maxItems) {
return true;
}
if (m_results) {
m_results->pushError(m_context, "Array should contain no more than " + std::to_string(maxItems) +
" elements.");
}
return false;
}
/**
* @brief Validate a value against a MaxLengthConstraint
*
* @param constraint Constraint that the target must validate against
*
* @return \c true if constraint is satisfied; \c false otherwise
*/
bool visit(const MaxLengthConstraint &constraint) override
{
if ((m_strictTypes && !m_target.isString()) || !m_target.maybeString()) {
return true;
}
const std::string s = m_target.asString();
const uint64_t len = utils::u8_strlen(s.c_str());
const uint64_t maxLength = constraint.getMaxLength();
if (len <= maxLength) {
return true;
}
if (m_results) {
m_results->pushError(m_context, "String should be no more than " + std::to_string(maxLength) +
" characters in length.");
}
return false;
}
/**
* @brief Validate a value against a MaxPropertiesConstraint
*
* @param constraint Constraint that the target must validate against
*
* @return \c true if the constraint is satisfied; \c false otherwise
*/
bool visit(const MaxPropertiesConstraint &constraint) override
{
if ((m_strictTypes && !m_target.isObject()) || !m_target.maybeObject()) {
return true;
}
const uint64_t maxProperties = constraint.getMaxProperties();
if (m_target.asObject().size() <= maxProperties) {
return true;
}
if (m_results) {
m_results->pushError(m_context, "Object should have no more than " + std::to_string(maxProperties) +
" properties.");
}
return false;
}
/**
* @brief Validate a value against a MinimumConstraint
*
* @param constraint Constraint that the target must validate against
*
* @return \c true if the constraint is satisfied; \c false otherwise
*/
bool visit(const MinimumConstraint &constraint) override
{
if ((m_strictTypes && !m_target.isNumber()) || !m_target.maybeDouble()) {
// Ignore values that are not numbers
return true;
}
const double minimum = constraint.getMinimum();
if (constraint.getExclusiveMinimum()) {
if (m_target.asDouble() <= minimum) {
if (m_results) {
m_results->pushError(m_context, "Expected number greater than " + std::to_string(minimum));
}
return false;
}
} else if (m_target.asDouble() < minimum) {
if (m_results) {
m_results->pushError(m_context, "Expected number greater than or equal to " + std::to_string(minimum));
}
return false;
}
return true;
}
/**
* @brief Validate a value against a MinItemsConstraint
*
* @param constraint Constraint that the target must validate against
*
* @return \c true if the constraint is satisfied; \c false otherwise
*/
bool visit(const MinItemsConstraint &constraint) override
{
if ((m_strictTypes && !m_target.isArray()) || !m_target.maybeArray()) {
return true;
}
const uint64_t minItems = constraint.getMinItems();
if (m_target.asArray().size() >= minItems) {
return true;
}
if (m_results) {
m_results->pushError(m_context, "Array should contain no fewer than " + std::to_string(minItems) +
" elements.");
}
return false;
}
/**
* @brief Validate a value against a MinLengthConstraint
*
* @param constraint Constraint that the target must validate against
*
* @return \c true if the constraint is satisfied; \c false otherwise
*/
bool visit(const MinLengthConstraint &constraint) override
{
if ((m_strictTypes && !m_target.isString()) || !m_target.maybeString()) {
return true;
}
const std::string s = m_target.asString();
const uint64_t len = utils::u8_strlen(s.c_str());
const uint64_t minLength = constraint.getMinLength();
if (len >= minLength) {
return true;
}
if (m_results) {
m_results->pushError(m_context, "String should be no fewer than " + std::to_string(minLength) +
" characters in length.");
}
return false;
}
/**
* @brief Validate a value against a MinPropertiesConstraint
*
* @param constraint Constraint that the target must validate against
*
* @return \c true if the constraint is satisfied; \c false otherwise
*/
bool visit(const MinPropertiesConstraint &constraint) override
{
if ((m_strictTypes && !m_target.isObject()) || !m_target.maybeObject()) {
return true;
}
const uint64_t minProperties = constraint.getMinProperties();
if (m_target.asObject().size() >= minProperties) {
return true;
}
if (m_results) {
m_results->pushError(m_context, "Object should have no fewer than " + std::to_string(minProperties) +
" properties.");
}
return false;
}
/**
* @brief Validate a value against a MultipleOfDoubleConstraint
*
* @param constraint Constraint that the target must validate against
*
* @return \c true if the constraint is satisfied; \c false otherwise
*/
bool visit(const MultipleOfDoubleConstraint &constraint) override
{
const double divisor = constraint.getDivisor();
double d = 0.;
if (m_target.maybeDouble()) {
if (!m_target.asDouble(d)) {
if (m_results) {
m_results->pushError(m_context, "Value could not be converted "
"to a number to check if it is a multiple of " + std::to_string(divisor));
}
return false;
}
} else if (m_target.maybeInteger()) {
int64_t i = 0;
if (!m_target.asInteger(i)) {
if (m_results) {
m_results->pushError(m_context, "Value could not be converted "
"to a number to check if it is a multiple of " + std::to_string(divisor));
}
return false;
}
d = static_cast<double>(i);
} else {
return true;
}
if (d == 0) {
return true;
}
const double r = remainder(d, divisor);
if (fabs(r) > std::numeric_limits<double>::epsilon()) {
if (m_results) {
m_results->pushError(m_context, "Value should be a multiple of " + std::to_string(divisor));
}
return false;
}
return true;
}
/**
* @brief Validate a value against a MultipleOfIntConstraint
*
* @param constraint Constraint that the target must validate against
*
* @return \c true if the constraint is satisfied; \c false otherwise
*/
bool visit(const MultipleOfIntConstraint &constraint) override
{
const int64_t divisor = constraint.getDivisor();
int64_t i = 0;
if (m_target.maybeInteger()) {
if (!m_target.asInteger(i)) {
if (m_results) {
m_results->pushError(m_context, "Value could not be converted to an integer for multipleOf check");
}
return false;
}
} else if (m_target.maybeDouble()) {
double d;
if (!m_target.asDouble(d)) {
if (m_results) {
m_results->pushError(m_context, "Value could not be converted to a double for multipleOf check");
}
return false;
}
i = static_cast<int64_t>(d);
} else {
return true;
}
if (i == 0) {
return true;
}
if (i % divisor != 0) {
if (m_results) {
m_results->pushError(m_context, "Value should be a multiple of " + std::to_string(divisor));
}
return false;
}
return true;
}
/**
* @brief Validate a value against a NotConstraint
*
* If the subschema NotConstraint currently holds a nullptr, the
* schema will be treated like the empty schema. Therefore validation
* will always fail.
*
* @param constraint Constraint that the target must validate against
*
* @return \c true if the constraint is satisfied; \c false otherwise
*/
bool visit(const NotConstraint &constraint) override
{
const Subschema *subschema = constraint.getSubschema();
if (!subschema) {
// Treat nullptr like empty schema
return false;
}
ValidationVisitor<AdapterType> v(m_target, m_context, m_strictTypes, nullptr, m_regexesCache);
if (v.validateSchema(*subschema)) {
if (m_results) {
m_results->pushError(m_context,
"Target should not validate against schema specified in 'not' constraint.");
}
return false;
}
return true;
}
/**
* @brief Validate a value against a OneOfConstraint
*
* @param constraint Constraint that the target must validate against
*
* @return \c true if the constraint is satisfied; \c false otherwise
*/
bool visit(const OneOfConstraint &constraint) override
{
unsigned int numValidated = 0;
ValidationResults newResults;
ValidationResults *childResults = (m_results) ? &newResults : nullptr;
ValidationVisitor<AdapterType> v(m_target, m_context, m_strictTypes, childResults, m_regexesCache);
constraint.applyToSubschemas(
ValidateSubschemas(m_target, m_context, true, true, v, childResults, &numValidated, nullptr));
if (numValidated == 0) {
if (m_results) {
ValidationResults::Error childError;
while (childResults->popError(childError)) {
m_results->pushError(
childError.context,
childError.description);
}
m_results->pushError(m_context, "Failed to validate against any "
"child schemas allowed by oneOf constraint.");
}
return false;
} else if (numValidated != 1) {
if (m_results) {
m_results->pushError(m_context, "Failed to validate against exactly one child schema.");
}
return false;
}
return true;
}
/**
* @brief Validate a value against a PatternConstraint
*
* @param constraint Constraint that the target must validate against
*
* @return \c true if the constraint is satisfied; \c false otherwise
*/
bool visit(const PatternConstraint &constraint) override
{
if ((m_strictTypes && !m_target.isString()) || !m_target.maybeString()) {
return true;
}
std::string pattern(constraint.getPattern<std::string::allocator_type>());
auto it = m_regexesCache.find(pattern);
if (it == m_regexesCache.end()) {
it = m_regexesCache.emplace(pattern, std::regex(pattern)).first;
}
if (!std::regex_search(m_target.asString(), it->second)) {
if (m_results) {
m_results->pushError(m_context, "Failed to match regex specified by 'pattern' constraint.");
}
return false;
}
return true;
}
/**
* @brief Validate a value against a PatternConstraint
*
* @param constraint Constraint that the target must validate against
*
* @return \c true if the constraint is satisfied; \c false otherwise
*/
bool visit(const constraints::PolyConstraint &constraint) override
{
return constraint.validate(m_target, m_context, m_results);
}
/**
* @brief Validate a value against a PropertiesConstraint
*
* Validation of an object against a PropertiesConstraint proceeds in three
* stages. The first stage finds all properties in the object that have a
* corresponding subschema in the constraint, and validates those properties
* recursively.
*
* Next, the object's properties will be validated against the subschemas
* for any 'patternProperties' that match a given property name. A property
* is required to validate against the sub-schema for all patterns that it
* matches.
*
* Finally, any properties that have not yet been validated against at least
* one subschema will be validated against the 'additionalItems' subschema.
* If this subschema is not present, then all properties must have been
* validated at least once.
*
* Non-object values are always considered valid.
*
* @param constraint Constraint that the target must validate against
*
* @return \c true if the constraint is satisfied; \c false otherwise
*/
bool visit(const PropertiesConstraint &constraint) override
{
if ((m_strictTypes && !m_target.isObject()) || !m_target.maybeObject()) {
return true;
}
bool validated = true;
// Track which properties have already been validated
std::set<std::string> propertiesMatched;
// Validate properties against subschemas for matching 'properties'
// constraints
const typename AdapterType::Object object = m_target.asObject();
constraint.applyToProperties(
ValidatePropertySubschemas(
object, m_context, true, m_results != nullptr, true, m_strictTypes, m_results,
&propertiesMatched, &validated, m_regexesCache));
// Exit early if validation failed, and we're not collecting exhaustive
// validation results
if (!validated && !m_results) {
return false;
}
// Validate properties against subschemas for matching patternProperties
// constraints
constraint.applyToPatternProperties(
ValidatePatternPropertySubschemas(
object, m_context, true, false, true, m_strictTypes, m_results, &propertiesMatched,
&validated, m_regexesCache));
// Validate against additionalProperties subschema for any properties
// that have not yet been matched
const Subschema *additionalPropertiesSubschema =
constraint.getAdditionalPropertiesSubschema();
if (!additionalPropertiesSubschema) {
if (propertiesMatched.size() != m_target.getObjectSize()) {
if (m_results) {
std::string unwanted;
for (const typename AdapterType::ObjectMember m : object) {
if (propertiesMatched.find(m.first) == propertiesMatched.end()) {
unwanted = m.first;
break;
}
}
m_results->pushError(m_context, "Object contains a property "
"that could not be validated using 'properties' "
"or 'additionalProperties' constraints: '" + unwanted + "'.");
}
return false;
}
return validated;
}
for (const typename AdapterType::ObjectMember m : object) {
if (propertiesMatched.find(m.first) == propertiesMatched.end()) {
// Update context
std::vector<std::string> newContext = m_context;
newContext.push_back("[" + m.first + "]");
// Create a validator to validate the property's value
ValidationVisitor validator(m.second, newContext, m_strictTypes, m_results, m_regexesCache);
if (!validator.validateSchema(*additionalPropertiesSubschema)) {
if (m_results) {
m_results->pushError(m_context, "Failed to validate against additional properties schema");
}
validated = false;
}
}
}
return validated;
}
/**
* @brief Validate a value against a PropertyNamesConstraint
*
* @param constraint Constraint that the target must validate against
*
* @return \c true if validation succeeds; \c false otherwise
*/
bool visit(const PropertyNamesConstraint &constraint) override
{
if ((m_strictTypes && !m_target.isObject()) || !m_target.maybeObject()) {
return true;
}
for (const typename AdapterType::ObjectMember m : m_target.asObject()) {
adapters::StdStringAdapter stringAdapter(m.first);
ValidationVisitor<adapters::StdStringAdapter> validator(stringAdapter, m_context, m_strictTypes, nullptr, m_regexesCache);
if (!validator.validateSchema(*constraint.getSubschema())) {
return false;
}
}
return true;
}
/**
* @brief Validate a value against a RequiredConstraint
*
* A required constraint specifies a list of properties that must be present
* in the target.
*
* @param constraint Constraint that the target must validate against
*
* @return \c true if validation succeeds; \c false otherwise
*/
bool visit(const RequiredConstraint &constraint) override
{
if ((m_strictTypes && !m_target.isObject()) || !m_target.maybeObject()) {
return true;
}
bool validated = true;
const typename AdapterType::Object object = m_target.asObject();
constraint.applyToRequiredProperties(
ValidateProperties(object, m_context, true, m_results != nullptr, m_results, &validated));
return validated;
}
/**
* @brief Validate a value against a SingularItemsConstraint
*
* A SingularItemsConstraint represents an 'items' constraint that specifies
* a sub-schema against which all items in an array must validate. If the
* current value is not an array, validation always succeeds.
*
* @param constraint SingularItemsConstraint to validate against
*
* @returns \c true if validation is successful; \c false otherwise
*/
bool visit(const SingularItemsConstraint &constraint) override
{
// Ignore values that are not arrays
if (!m_target.isArray()) {
return true;
}
// Schema against which all items must validate
const Subschema *itemsSubschema = constraint.getItemsSubschema();
// Default items sub-schema accepts all values
if (!itemsSubschema) {
return true;
}
// Track whether validation has failed
bool validated = true;
unsigned int index = 0;
for (const AdapterType &item : m_target.getArray()) {
// Update context for current array item
std::vector<std::string> newContext = m_context;
newContext.push_back("[" + std::to_string(index) + "]");
// Create a validator for the current array item
ValidationVisitor<AdapterType> validationVisitor(item, newContext, m_strictTypes, m_results, m_regexesCache);
// Perform validation
if (!validationVisitor.validateSchema(*itemsSubschema)) {
if (m_results) {
m_results->pushError(m_context, "Failed to validate item #" + std::to_string(index) + " in array.");
validated = false;
} else {
return false;
}
}
index++;
}
return validated;
}
/**
* @brief Validate a value against a TypeConstraint
*
* Checks that the target is one of the valid named types, or matches one
* of a set of valid sub-schemas.
*
* @param constraint TypeConstraint to validate against
*
* @return \c true if validation is successful; \c false otherwise
*/
bool visit(const TypeConstraint &constraint) override
{
// Check named types
{
// ValidateNamedTypes functor assumes target is invalid
bool validated = false;
constraint.applyToNamedTypes(ValidateNamedTypes(m_target, false, true, m_strictTypes, &validated));
if (validated) {
return true;
}
}
// Check schema-based types
{
unsigned int numValidated = 0;
constraint.applyToSchemaTypes(
ValidateSubschemas(m_target, m_context, false, true, *this, nullptr, &numValidated, nullptr));
if (numValidated > 0) {
return true;
} else if (m_results) {
m_results->pushError(m_context, "Value type not permitted by 'type' constraint.");
}
}
return false;
}
/**
* @brief Validate the uniqueItems constraint represented by a
* UniqueItems object.
*
* A uniqueItems constraint requires that each of the values in an array
* are unique. Comparison is performed recursively.
*
* @param constraint Constraint that the target must validate against
*
* @return true if validation succeeds, false otherwise
*/
bool visit(const UniqueItemsConstraint &) override
{
if ((m_strictTypes && !m_target.isArray()) || !m_target.maybeArray()) {
return true;
}
size_t array_size = m_target.getArraySize();
// Empty arrays are always valid
if (array_size == 0) {
return true;
}
bool validated = true;
const typename AdapterType::Array targetArray = m_target.asArray();
const typename AdapterType::Array::const_iterator end = targetArray.end();
typename AdapterType::Array::const_iterator outerItr = targetArray.begin();
for (unsigned int outerIndex = 0; outerIndex < array_size - 1 /*outerItr != secondLast*/; ++outerItr) {
unsigned int innerIndex = outerIndex + 1;
typename AdapterType::Array::const_iterator innerItr(outerItr);
for (++innerItr; innerItr != end; ++innerItr) {
if (outerItr->equalTo(*innerItr, true)) {
if (!m_results) {
return false;
}
m_results->pushError(m_context, "Elements at indexes #" + std::to_string(outerIndex)
+ " and #" + std::to_string(innerIndex) + " violate uniqueness constraint.");
validated = false;
}
++innerIndex;
}
++outerIndex;
}
return validated;
}
private:
/**
* @brief Functor to compare a node with a collection of values
*/
struct ValidateEquality
{
ValidateEquality(
const AdapterType &target,
const std::vector<std::string> &context,
bool continueOnSuccess,
bool continueOnFailure,
bool strictTypes,
ValidationResults *results,
unsigned int *numValidated)
: m_target(target),
m_context(context),
m_continueOnSuccess(continueOnSuccess),
m_continueOnFailure(continueOnFailure),
m_strictTypes(strictTypes),
m_results(results),
m_numValidated(numValidated) { }
template<typename OtherValue>
bool operator()(const OtherValue &value) const
{
if (value.equalTo(m_target, m_strictTypes)) {
if (m_numValidated) {
(*m_numValidated)++;
}
return m_continueOnSuccess;
}
if (m_results) {
m_results->pushError(m_context, "Target value and comparison value are not equal");
}
return m_continueOnFailure;
}
private:
const AdapterType &m_target;
const std::vector<std::string> &m_context;
bool m_continueOnSuccess;
bool m_continueOnFailure;
bool m_strictTypes;
ValidationResults * const m_results;
unsigned int * const m_numValidated;
};
/**
* @brief Functor to validate the presence of a set of properties
*/
struct ValidateProperties
{
ValidateProperties(
const typename AdapterType::Object &object,
const std::vector<std::string> &context,
bool continueOnSuccess,
bool continueOnFailure,
ValidationResults *results,
bool *validated)
: m_object(object),
m_context(context),
m_continueOnSuccess(continueOnSuccess),
m_continueOnFailure(continueOnFailure),
m_results(results),
m_validated(validated) { }
template<typename StringType>
bool operator()(const StringType &property) const
{
if (m_object.find(property.c_str()) == m_object.end()) {
if (m_validated) {
*m_validated = false;
}
if (m_results) {
m_results->pushError(m_context, "Missing required property '" +
std::string(property.c_str()) + "'.");
}
return m_continueOnFailure;
}
return m_continueOnSuccess;
}
private:
const typename AdapterType::Object &m_object;
const std::vector<std::string> &m_context;
bool m_continueOnSuccess;
bool m_continueOnFailure;
ValidationResults * const m_results;
bool * const m_validated;
};
/**
* @brief Functor to validate property-based dependencies
*/
struct ValidatePropertyDependencies
{
ValidatePropertyDependencies(
const typename AdapterType::Object &object,
const std::vector<std::string> &context,
ValidationResults *results,
bool *validated)
: m_object(object),
m_context(context),
m_results(results),
m_validated(validated) { }
template<typename StringType, typename ContainerType>
bool operator()(const StringType &propertyName, const ContainerType &dependencyNames) const
{
const std::string propertyNameKey(propertyName.c_str());
if (m_object.find(propertyNameKey) == m_object.end()) {
return true;
}
typedef typename ContainerType::value_type ValueType;
for (const ValueType &dependencyName : dependencyNames) {
const std::string dependencyNameKey(dependencyName.c_str());
if (m_object.find(dependencyNameKey) == m_object.end()) {
if (m_validated) {
*m_validated = false;
}
if (m_results) {
m_results->pushError(m_context, "Missing dependency '" + dependencyNameKey + "'.");
} else {
return false;
}
}
}
return true;
}
private:
const typename AdapterType::Object &m_object;
const std::vector<std::string> &m_context;
ValidationResults * const m_results;
bool * const m_validated;
};
/**
* @brief Functor to validate against sub-schemas in 'items' constraint
*/
struct ValidateItems
{
ValidateItems(
const typename AdapterType::Array &arr,
const std::vector<std::string> &context,
bool continueOnSuccess,
bool continueOnFailure,
bool strictTypes,
ValidationResults *results,
unsigned int *numValidated,
bool *validated,
std::unordered_map<std::string, std::regex>& regexesCache)
: m_arr(arr),
m_context(context),
m_continueOnSuccess(continueOnSuccess),
m_continueOnFailure(continueOnFailure),
m_strictTypes(strictTypes),
m_results(results),
m_numValidated(numValidated),
m_validated(validated),
m_regexesCache(regexesCache) { }
bool operator()(unsigned int index, const Subschema *subschema) const
{
// Check that there are more elements to validate
if (index >= m_arr.size()) {
return false;
}
// Update context
std::vector<std::string> newContext = m_context;
newContext.push_back("[" + std::to_string(index) + "]");
// Find array item
typename AdapterType::Array::const_iterator itr = m_arr.begin();
itr.advance(index);
// Validate current array item
ValidationVisitor validator(*itr, newContext, m_strictTypes, m_results, m_regexesCache);
if (validator.validateSchema(*subschema)) {
if (m_numValidated) {
(*m_numValidated)++;
}
return m_continueOnSuccess;
}
if (m_validated) {
*m_validated = false;
}
if (m_results) {
m_results->pushError(newContext, "Failed to validate item #" + std::to_string(index) +
" against corresponding item schema.");
}
return m_continueOnFailure;
}
private:
const typename AdapterType::Array &m_arr;
const std::vector<std::string> &m_context;
bool m_continueOnSuccess;
bool m_continueOnFailure;
bool m_strictTypes;
ValidationResults * const m_results;
unsigned int * const m_numValidated;
bool * const m_validated;
std::unordered_map<std::string, std::regex>& m_regexesCache;
};
/**
* @brief Functor to validate value against named JSON types
*/
struct ValidateNamedTypes
{
ValidateNamedTypes(
const AdapterType &target,
bool continueOnSuccess,
bool continueOnFailure,
bool strictTypes,
bool *validated)
: m_target(target),
m_continueOnSuccess(continueOnSuccess),
m_continueOnFailure(continueOnFailure),
m_strictTypes(strictTypes),
m_validated(validated) { }
bool operator()(constraints::TypeConstraint::JsonType jsonType) const
{
typedef constraints::TypeConstraint TypeConstraint;
bool valid = false;
switch (jsonType) {
case TypeConstraint::kAny:
valid = true;
break;
case TypeConstraint::kArray:
valid = m_target.isArray();
break;
case TypeConstraint::kBoolean:
valid = m_target.isBool() || (!m_strictTypes && m_target.maybeBool());
break;
case TypeConstraint::kInteger:
valid = m_target.isInteger() || (!m_strictTypes && m_target.maybeInteger());
break;
case TypeConstraint::kNull:
valid = m_target.isNull() || (!m_strictTypes && m_target.maybeNull());
break;
case TypeConstraint::kNumber:
valid = m_target.isNumber() || (!m_strictTypes && m_target.maybeDouble());
break;
case TypeConstraint::kObject:
valid = m_target.isObject();
break;
case TypeConstraint::kString:
valid = m_target.isString();
break;
default:
break;
}
if (valid && m_validated) {
*m_validated = true;
}
return (valid && m_continueOnSuccess) || m_continueOnFailure;
}
private:
const AdapterType m_target;
const bool m_continueOnSuccess;
const bool m_continueOnFailure;
const bool m_strictTypes;
bool * const m_validated;
};
/**
* @brief Functor to validate object properties against sub-schemas
* defined by a 'patternProperties' constraint
*/
struct ValidatePatternPropertySubschemas
{
ValidatePatternPropertySubschemas(
const typename AdapterType::Object &object,
const std::vector<std::string> &context,
bool continueOnSuccess,
bool continueOnFailure,
bool continueIfUnmatched,
bool strictTypes,
ValidationResults *results,
std::set<std::string> *propertiesMatched,
bool *validated,
std::unordered_map<std::string, std::regex>& regexesCache)
: m_object(object),
m_context(context),
m_continueOnSuccess(continueOnSuccess),
m_continueOnFailure(continueOnFailure),
m_continueIfUnmatched(continueIfUnmatched),
m_strictTypes(strictTypes),
m_results(results),
m_propertiesMatched(propertiesMatched),
m_validated(validated),
m_regexesCache(regexesCache) { }
template<typename StringType>
bool operator()(const StringType &patternProperty, const Subschema *subschema) const
{
const std::string patternPropertyStr(patternProperty.c_str());
// It would be nice to store pre-allocated regex objects in the
// PropertiesConstraint. does std::regex currently support
// custom allocators? Anyway, this isn't an issue here, because Valijson's
// JSON Scheme validator does not yet support custom allocators.
const std::regex r(patternPropertyStr);
bool matchFound = false;
// Recursively validate all matching properties
typedef const typename AdapterType::ObjectMember ObjectMember;
for (const ObjectMember m : m_object) {
if (std::regex_search(m.first, r)) {
matchFound = true;
if (m_propertiesMatched) {
m_propertiesMatched->insert(m.first);
}
// Update context
std::vector<std::string> newContext = m_context;
newContext.push_back("[" + m.first + "]");
// Recursively validate property's value
ValidationVisitor validator(m.second, newContext, m_strictTypes, m_results, m_regexesCache);
if (validator.validateSchema(*subschema)) {
continue;
}
if (m_results) {
m_results->pushError(m_context, "Failed to validate against schema associated with pattern '" +
patternPropertyStr + "'.");
}
if (m_validated) {
*m_validated = false;
}
if (!m_continueOnFailure) {
return false;
}
}
}
// Allow iteration to terminate if there was not at least one match
if (!matchFound && !m_continueIfUnmatched) {
return false;
}
return m_continueOnSuccess;
}
private:
const typename AdapterType::Object &m_object;
const std::vector<std::string> &m_context;
const bool m_continueOnSuccess;
const bool m_continueOnFailure;
const bool m_continueIfUnmatched;
const bool m_strictTypes;
ValidationResults * const m_results;
std::set<std::string> * const m_propertiesMatched;
bool * const m_validated;
std::unordered_map<std::string, std::regex>& m_regexesCache;
};
/**
* @brief Functor to validate object properties against sub-schemas defined
* by a 'properties' constraint
*/
struct ValidatePropertySubschemas
{
ValidatePropertySubschemas(
const typename AdapterType::Object &object,
const std::vector<std::string> &context,
bool continueOnSuccess,
bool continueOnFailure,
bool continueIfUnmatched,
bool strictTypes,
ValidationResults *results,
std::set<std::string> *propertiesMatched,
bool *validated,
std::unordered_map<std::string, std::regex>& regexesCache)
: m_object(object),
m_context(context),
m_continueOnSuccess(continueOnSuccess),
m_continueOnFailure(continueOnFailure),
m_continueIfUnmatched(continueIfUnmatched),
m_strictTypes(strictTypes),
m_results(results),
m_propertiesMatched(propertiesMatched),
m_validated(validated),
m_regexesCache(regexesCache) { }
template<typename StringType>
bool operator()(const StringType &propertyName, const Subschema *subschema) const
{
const std::string propertyNameKey(propertyName.c_str());
const typename AdapterType::Object::const_iterator itr = m_object.find(propertyNameKey);
if (itr == m_object.end()) {
return m_continueIfUnmatched;
}
if (m_propertiesMatched) {
m_propertiesMatched->insert(propertyNameKey);
}
// Update context
std::vector<std::string> newContext = m_context;
newContext.push_back("[" + propertyNameKey + "]");
// Recursively validate property's value
ValidationVisitor validator(itr->second, newContext, m_strictTypes, m_results, m_regexesCache);
if (validator.validateSchema(*subschema)) {
return m_continueOnSuccess;
}
if (m_results) {
m_results->pushError(m_context, "Failed to validate against schema associated with property name '" +
propertyNameKey + "'.");
}
if (m_validated) {
*m_validated = false;
}
return m_continueOnFailure;
}
private:
const typename AdapterType::Object &m_object;
const std::vector<std::string> &m_context;
const bool m_continueOnSuccess;
const bool m_continueOnFailure;
const bool m_continueIfUnmatched;
const bool m_strictTypes;
ValidationResults * const m_results;
std::set<std::string> * const m_propertiesMatched;
bool * const m_validated;
std::unordered_map<std::string, std::regex>& m_regexesCache;
};
/**
* @brief Functor to validate schema-based dependencies
*/
struct ValidateSchemaDependencies
{
ValidateSchemaDependencies(
const typename AdapterType::Object &object,
const std::vector<std::string> &context,
ValidationVisitor &validationVisitor,
ValidationResults *results,
bool *validated)
: m_object(object),
m_context(context),
m_validationVisitor(validationVisitor),
m_results(results),
m_validated(validated) { }
template<typename StringType>
bool operator()(const StringType &propertyName, const Subschema *schemaDependency) const
{
const std::string propertyNameKey(propertyName.c_str());
if (m_object.find(propertyNameKey) == m_object.end()) {
return true;
}
if (!m_validationVisitor.validateSchema(*schemaDependency)) {
if (m_validated) {
*m_validated = false;
}
if (m_results) {
m_results->pushError(m_context, "Failed to validate against dependent schema.");
} else {
return false;
}
}
return true;
}
private:
const typename AdapterType::Object &m_object;
const std::vector<std::string> &m_context;
ValidationVisitor &m_validationVisitor;
ValidationResults * const m_results;
bool * const m_validated;
};
/**
* @brief Functor that can be used to validate one or more subschemas
*
* This functor is designed to be applied to collections of subschemas
* contained within 'allOf', 'anyOf' and 'oneOf' constraints.
*
* The return value depends on whether a given schema validates, with the
* actual return value for a given case being decided at construction time.
* The return value is used by the 'applyToSubschemas' functions in the
* AllOfConstraint, AnyOfConstraint and OneOfConstrant classes to decide
* whether to terminate early.
*
* The functor uses output parameters (provided at construction) to update
* validation state that may be needed by the caller.
*/
struct ValidateSubschemas
{
ValidateSubschemas(
const AdapterType &adapter,
const std::vector<std::string> &context,
bool continueOnSuccess,
bool continueOnFailure,
ValidationVisitor &validationVisitor,
ValidationResults *results,
unsigned int *numValidated,
bool *validated)
: m_adapter(adapter),
m_context(context),
m_continueOnSuccess(continueOnSuccess),
m_continueOnFailure(continueOnFailure),
m_validationVisitor(validationVisitor),
m_results(results),
m_numValidated(numValidated),
m_validated(validated) { }
bool operator()(unsigned int index, const Subschema *subschema) const
{
if (m_validationVisitor.validateSchema(*subschema)) {
if (m_numValidated) {
(*m_numValidated)++;
}
return m_continueOnSuccess;
}
if (m_validated) {
*m_validated = false;
}
if (m_results) {
m_results->pushError(m_context,
"Failed to validate against child schema #" + std::to_string(index) + ".");
}
return m_continueOnFailure;
}
private:
const AdapterType &m_adapter;
const std::vector<std::string> &m_context;
bool m_continueOnSuccess;
bool m_continueOnFailure;
ValidationVisitor &m_validationVisitor;
ValidationResults * const m_results;
unsigned int * const m_numValidated;
bool * const m_validated;
};
/**
* @brief Callback function that passes a visitor to a constraint.
*
* @param constraint Reference to constraint to be visited
* @param visitor Reference to visitor to be applied
*
* @return true if the visitor returns successfully, false otherwise.
*/
static bool validationCallback(const constraints::Constraint &constraint, ValidationVisitor<AdapterType> &visitor)
{
return constraint.accept(visitor);
}
/**
* @brief Helper function to validate if day is valid for given month
*
* @param month Month, 1-12
* @param day Day, 1-31
*
* @return \c true if day is valid for given month, \c false otherwise.
*/
bool validate_date_range(int month, int day)
{
if (month == 2) {
if (day < 0 || day > 29) {
if (m_results) {
m_results->pushError(m_context,
"String should be a valid date-time");
}
return false;
}
} else {
int limit = 31;
if (month <= 7) {
if (month % 2 == 0) {
limit = 30;
}
} else {
if (month % 2 != 0) {
limit = 30;
}
}
if (day < 0 || day > limit) {
if (m_results) {
m_results->pushError(m_context,
"String should be a valid date-time");
}
return false;
}
}
return true;
}
/// The JSON value being validated
AdapterType m_target;
/// Vector of strings describing the current object context
std::vector<std::string> m_context;
/// Optional pointer to a ValidationResults object to be populated
ValidationResults *m_results;
/// Option to use strict type comparison
bool m_strictTypes;
/// Cached regex objects for pattern constraint
std::unordered_map<std::string, std::regex>& m_regexesCache;
};
} // namespace valijson
#ifdef _MSC_VER
#pragma warning( pop )
#endif
#pragma once
namespace valijson {
class Schema;
class ValidationResults;
/**
* @brief Class that provides validation functionality.
*/
class Validator
{
public:
enum TypeCheckingMode
{
kStrongTypes,
kWeakTypes
};
/**
* @brief Construct a Validator that uses strong type checking by default
*/
Validator()
: strictTypes(true) { }
/**
* @brief Construct a Validator using a specific type checking mode
*
* @param typeCheckingMode choice of strong or weak type checking
*/
Validator(TypeCheckingMode typeCheckingMode)
: strictTypes(typeCheckingMode == kStrongTypes) { }
/**
* @brief Validate a JSON document and optionally return the results.
*
* When a ValidationResults object is provided via the \c results parameter,
* validation will be performed against each constraint defined by the
* schema, even if validation fails for some or all constraints.
*
* If a pointer to a ValidationResults instance is not provided, validation
* will only continue for as long as the constraints are validated
* successfully.
*
* @param schema The schema to validate against
* @param target A rapidjson::Value to be validated
*
* @param results An optional pointer to a ValidationResults instance that
* will be used to report validation errors
*
* @returns true if validation succeeds, false otherwise
*/
template<typename AdapterType>
bool validate(const Subschema &schema, const AdapterType &target,
ValidationResults *results)
{
// Construct a ValidationVisitor to perform validation at the root level
ValidationVisitor<AdapterType> v(target,
std::vector<std::string>(1, "<root>"), strictTypes, results, regexesCache);
return v.validateSchema(schema);
}
private:
/// Flag indicating that strict type comparisons should be used
bool strictTypes;
/// Cached regex objects for pattern constraint. Key - pattern.
std::unordered_map<std::string, std::regex> regexesCache;
};
} // namespace valijson
/**
* @file
*
* @brief Adapter implementation for the nlohmann json parser library.
*
* Include this file in your program to enable support for nlohmann json.
*
* This file defines the following classes (not in this order):
* - NlohmannJsonAdapter
* - NlohmannJsonArray
* - NlohmannJsonValueIterator
* - NlohmannJsonFrozenValue
* - NlohmannJsonObject
* - NlohmannJsonObjectMember
* - NlohmannJsonObjectMemberIterator
* - NlohmannJsonValue
*
* Due to the dependencies that exist between these classes, the ordering of
* class declarations and definitions may be a bit confusing. The best place to
* start is NlohmannJsonAdapter. This class definition is actually very small,
* since most of the functionality is inherited from the BasicAdapter class.
* Most of the classes in this file are provided as template arguments to the
* inherited BasicAdapter class.
*/
#pragma once
#include <string>
#include <nlohmann/json.hpp>
#include <utility>
namespace valijson {
namespace adapters {
class NlohmannJsonAdapter;
typedef std::pair<std::string, NlohmannJsonAdapter> NlohmannJsonObjectMember;
/**
* @brief Class for iterating over values held in a JSON array.
*
* This class provides a JSON array iterator that dereferences as an instance of
* NlohmannJsonAdapter representing a value stored in the array. It has been
* implemented using the boost iterator_facade template.
*
* @see NlohmannJsonArray
*/
template <class ValueType>
class NlohmannJsonArrayValueIterator
{
public:
using iterator_category = std::bidirectional_iterator_tag;
using value_type = ValueType;
using difference_type = ValueType;
using pointer = ValueType *;
using reference = ValueType &;
/**
* @brief Construct a new NlohmannJsonArrayValueIterator using an existing
* NlohmannJson iterator.
*
* @param itr NlohmannJson iterator to store
*/
NlohmannJsonArrayValueIterator(const nlohmann::json::const_iterator &itr)
: m_itr(itr)
{
}
/// Returns a NlohmannJsonAdapter that contains the value of the current
/// element.
ValueType operator*() const
{
return ValueType(*m_itr);
}
DerefProxy<ValueType> operator->() const
{
return DerefProxy<ValueType>(**this);
}
/**
* @brief Compare this iterator against another iterator.
*
* Note that this directly compares the iterators, not the underlying
* values, and assumes that two identical iterators will point to the same
* underlying object.
*
* @param other iterator to compare against
*
* @returns true if the iterators are equal, false otherwise.
*/
bool operator==(const NlohmannJsonArrayValueIterator &other) const
{
return m_itr == other.m_itr;
}
bool operator!=(const NlohmannJsonArrayValueIterator &other) const
{
return !(m_itr == other.m_itr);
}
const NlohmannJsonArrayValueIterator &operator++()
{
m_itr++;
return *this;
}
NlohmannJsonArrayValueIterator operator++(int)
{
NlohmannJsonArrayValueIterator iterator_pre(m_itr);
++(*this);
return iterator_pre;
}
const NlohmannJsonArrayValueIterator &operator--()
{
m_itr--;
return *this;
}
void advance(std::ptrdiff_t n)
{
m_itr += n;
}
private:
nlohmann::json::const_iterator m_itr;
};
/**
* @brief Class for iterating over the members belonging to a JSON object.
*
* This class provides a JSON object iterator that dereferences as an instance
* of NlohmannJsonObjectMember representing one of the members of the object. It
* has been implemented using the boost iterator_facade template.
*
* @see NlohmannJsonObject
* @see NlohmannJsonObjectMember
*/
template <class ValueType> class NlohmannJsonObjectMemberIterator
{
public:
using iterator_category = std::bidirectional_iterator_tag;
using value_type = ValueType;
using difference_type = ValueType;
using pointer = ValueType *;
using reference = ValueType &;
/**
* @brief Construct an iterator from a NlohmannJson iterator.
*
* @param itr NlohmannJson iterator to store
*/
NlohmannJsonObjectMemberIterator(const nlohmann::json::const_iterator &itr)
: m_itr(itr)
{
}
/**
* @brief Returns a NlohmannJsonObjectMember that contains the key and
* value belonging to the object member identified by the iterator.
*/
ValueType operator*() const
{
return ValueType(m_itr.key(), m_itr.value());
}
DerefProxy<ValueType> operator->() const
{
return DerefProxy<ValueType>(**this);
}
/**
* @brief Compare this iterator with another iterator.
*
* Note that this directly compares the iterators, not the underlying
* values, and assumes that two identical iterators will point to the same
* underlying object.
*
* @param other Iterator to compare with
*
* @returns true if the underlying iterators are equal, false otherwise
*/
bool operator==(const NlohmannJsonObjectMemberIterator &other) const
{
return m_itr == other.m_itr;
}
bool operator!=(const NlohmannJsonObjectMemberIterator &other) const
{
return !(m_itr == other.m_itr);
}
const NlohmannJsonObjectMemberIterator &operator++()
{
m_itr++;
return *this;
}
NlohmannJsonObjectMemberIterator operator++(int)
{
NlohmannJsonObjectMemberIterator iterator_pre(m_itr);
++(*this);
return iterator_pre;
}
const NlohmannJsonObjectMemberIterator &operator--()
{
m_itr--;
return *this;
}
private:
/// Iternal copy of the original NlohmannJson iterator
nlohmann::json::const_iterator m_itr;
};
/**
* @brief Light weight wrapper for a NlohmannJson array value.
*
* This class is light weight wrapper for a NlohmannJson array. It provides a
* minimum set of container functions and typedefs that allow it to be used as
* an iterable container.
*
* An instance of this class contains a single reference to the underlying
* NlohmannJson value, assumed to be an array, so there is very little overhead
* associated with copy construction and passing by value.
*/
template <class ValueType>
class NlohmannJsonArray
{
public:
typedef NlohmannJsonArrayValueIterator<ValueType> const_iterator;
typedef NlohmannJsonArrayValueIterator<ValueType> iterator;
/// Construct a NlohmannJsonArray referencing an empty array.
NlohmannJsonArray()
: m_value(emptyArray()) { }
/**
* @brief Construct a NlohmannJsonArray referencing a specific NlohmannJson
* value.
*
* @param value reference to a NlohmannJson value
*
* Note that this constructor will throw an exception if the value is not
* an array.
*/
NlohmannJsonArray(const nlohmann::json &value)
: m_value(value)
{
if (!value.is_array()) {
throwRuntimeError("Value is not an array.");
}
}
/**
* @brief Return an iterator for the first element of the array.
*
* The iterator return by this function is effectively the iterator
* returned by the underlying NlohmannJson implementation.
*/
NlohmannJsonArrayValueIterator<ValueType> begin() const {
return m_value.begin();
}
/**
* @brief Return an iterator for one-past the last element of the array.
*
* The iterator return by this function is effectively the iterator
* returned by the underlying NlohmannJson implementation.
*/
NlohmannJsonArrayValueIterator<ValueType> end() const {
return m_value.end();
}
/// Return the number of elements in the array
size_t size() const
{
return m_value.size();
}
private:
/**
* @brief Return a reference to a NlohmannJson value that is an empty array.
*
* Note that the value returned by this function is a singleton.
*/
static const nlohmann::json & emptyArray()
{
static const nlohmann::json array = nlohmann::json::array();
return array;
}
/// Reference to the contained value
const nlohmann::json &m_value;
};
/**
* @brief Light weight wrapper for a NlohmannJson object.
*
* This class is light weight wrapper for a NlohmannJson object. It provides a
* minimum set of container functions and typedefs that allow it to be used as
* an iterable container.
*
* An instance of this class contains a single reference to the underlying
* NlohmannJson value, assumed to be an object, so there is very little overhead
* associated with copy construction and passing by value.
*/
class NlohmannJsonObject
{
public:
typedef NlohmannJsonObjectMemberIterator<NlohmannJsonObjectMember> const_iterator;
typedef NlohmannJsonObjectMemberIterator<NlohmannJsonObjectMember> iterator;
/// Construct a NlohmannJsonObject referencing an empty object singleton.
NlohmannJsonObject()
: m_value(emptyObject()) { }
/**
* @brief Construct a NlohmannJsonObject referencing a specific NlohmannJson
* value.
*
* @param value reference to a NlohmannJson value
*
* Note that this constructor will throw an exception if the value is not
* an object.
*/
NlohmannJsonObject(const nlohmann::json &value)
: m_value(value)
{
if (!value.is_object()) {
throwRuntimeError("Value is not an object.");
}
}
/**
* @brief Return an iterator for this first object member
*
* The iterator return by this function is effectively a wrapper around
* the iterator value returned by the underlying NlohmannJson implementation.
*/
NlohmannJsonObjectMemberIterator<NlohmannJsonObjectMember> begin() const;
/**
* @brief Return an iterator for an invalid object member that indicates
* the end of the collection.
*
* The iterator return by this function is effectively a wrapper around
* the iterator value returned by the underlying NlohmannJson implementation.
*/
NlohmannJsonObjectMemberIterator<NlohmannJsonObjectMember> end() const;
/**
* @brief Return an iterator for the object member with the specified
* property name.
*
* If an object member with the specified name does not exist, the iterator
* returned will be the same as the iterator returned by the end() function.
*
* @param propertyName property name to search for
*/
NlohmannJsonObjectMemberIterator<NlohmannJsonObjectMember>
find(const std::string &propertyName) const;
/// Returns the number of members belonging to this object.
size_t size() const
{
return m_value.size();
}
private:
/**
* @brief Return a reference to a NlohmannJson value that is empty object.
*
* Note that the value returned by this function is a singleton.
*/
static const nlohmann::json & emptyObject()
{
static const nlohmann::json object = nlohmann::json::object();
return object;
}
/// Reference to the contained object
const nlohmann::json &m_value;
};
/**
* @brief Stores an independent copy of a NlohmannJson value.
*
* This class allows a NlohmannJson value to be stored independent of its original
* document. NlohmannJson makes this easy to do, as it does not perform any
* custom memory management.
*
* @see FrozenValue
*/
class NlohmannJsonFrozenValue: public FrozenValue
{
public:
/**
* @brief Make a copy of a NlohmannJson value
*
* @param source the NlohmannJson value to be copied
*/
explicit NlohmannJsonFrozenValue(nlohmann::json source)
: m_value(std::move(source)) { }
FrozenValue * clone() const override
{
return new NlohmannJsonFrozenValue(m_value);
}
bool equalTo(const Adapter &other, bool strict) const override;
private:
/// Stored NlohmannJson value
nlohmann::json m_value;
};
/**
* @brief Light weight wrapper for a NlohmannJson value.
*
* This class is passed as an argument to the BasicAdapter template class,
* and is used to provide access to a NlohmannJson value. This class is responsible
* for the mechanics of actually reading a NlohmannJson value, whereas the
* BasicAdapter class is responsible for the semantics of type comparisons
* and conversions.
*
* The functions that need to be provided by this class are defined implicitly
* by the implementation of the BasicAdapter template class.
*
* @see BasicAdapter
*/
template<class ValueType>
class NlohmannJsonValue
{
public:
/// Construct a wrapper for the empty object singleton
NlohmannJsonValue()
: m_value(emptyObject()) { }
/// Construct a wrapper for a specific NlohmannJson value
NlohmannJsonValue(const nlohmann::json &value)
: m_value(value) { }
/**
* @brief Create a new NlohmannJsonFrozenValue instance that contains the
* value referenced by this NlohmannJsonValue instance.
*
* @returns pointer to a new NlohmannJsonFrozenValue instance, belonging to the
* caller.
*/
FrozenValue * freeze() const
{
return new NlohmannJsonFrozenValue(m_value);
}
/**
* @brief Optionally return a NlohmannJsonArray instance.
*
* If the referenced NlohmannJson value is an array, this function will return
* a std::optional containing a NlohmannJsonArray instance referencing the
* array.
*
* Otherwise it will return an empty optional.
*/
opt::optional<NlohmannJsonArray<ValueType>> getArrayOptional() const
{
if (m_value.is_array()) {
return opt::make_optional(NlohmannJsonArray<ValueType>(m_value));
}
return {};
}
/**
* @brief Retrieve the number of elements in the array
*
* If the referenced NlohmannJson value is an array, this function will
* retrieve the number of elements in the array and store it in the output
* variable provided.
*
* @param result reference to size_t to set with result
*
* @returns true if the number of elements was retrieved, false otherwise.
*/
bool getArraySize(size_t &result) const
{
if (m_value.is_array()) {
result = m_value.size();
return true;
}
return false;
}
bool getBool(bool &result) const
{
if (m_value.is_boolean()) {
result = m_value.get<bool>();
return true;
}
return false;
}
bool getDouble(double &result) const
{
if (m_value.is_number_float()) {
result = m_value.get<double>();
return true;
}
return false;
}
bool getInteger(int64_t &result) const
{
if(m_value.is_number_integer()) {
result = m_value.get<int64_t>();
return true;
}
return false;
}
/**
* @brief Optionally return a NlohmannJsonObject instance.
*
* If the referenced NlohmannJson value is an object, this function will return a
* std::optional containing a NlohmannJsonObject instance referencing the
* object.
*
* Otherwise it will return an empty optional.
*/
opt::optional<NlohmannJsonObject> getObjectOptional() const;
/**
* @brief Retrieve the number of members in the object
*
* If the referenced NlohmannJson value is an object, this function will
* retrieve the number of members in the object and store it in the output
* variable provided.
*
* @param result reference to size_t to set with result
*
* @returns true if the number of members was retrieved, false otherwise.
*/
bool getObjectSize(size_t &result) const
{
if (m_value.is_object()) {
result = m_value.size();
return true;
}
return false;
}
bool getString(std::string &result) const
{
if (m_value.is_string()) {
result = m_value.get<std::string>();
return true;
}
return false;
}
static bool hasStrictTypes()
{
return true;
}
bool isArray() const
{
return m_value.is_array();
}
bool isBool() const
{
return m_value.is_boolean();
}
bool isDouble() const
{
return m_value.is_number_float();
}
bool isInteger() const
{
return m_value.is_number_integer();
}
bool isNull() const
{
return m_value.is_null();
}
bool isNumber() const
{
return m_value.is_number();
}
bool isObject() const
{
return m_value.is_object();
}
bool isString() const
{
return m_value.is_string();
}
private:
/// Return a reference to an empty object singleton
static const nlohmann::json & emptyObject()
{
static const nlohmann::json object = nlohmann::json::object();
return object;
}
/// Reference to the contained NlohmannJson value.
const nlohmann::json &m_value;
};
/**
* @brief An implementation of the Adapter interface supporting NlohmannJson.
*
* This class is defined in terms of the BasicAdapter template class, which
* helps to ensure that all of the Adapter implementations behave consistently.
*
* @see Adapter
* @see BasicAdapter
*/
class NlohmannJsonAdapter
: public BasicAdapter<NlohmannJsonAdapter,
NlohmannJsonArray<NlohmannJsonAdapter>,
NlohmannJsonObjectMember, NlohmannJsonObject,
NlohmannJsonValue<NlohmannJsonAdapter>>
{
public:
/// Construct a NlohmannJsonAdapter that contains an empty object
NlohmannJsonAdapter()
: BasicAdapter() { }
/// Construct a NlohmannJsonAdapter containing a specific Nlohmann Json
/// object
NlohmannJsonAdapter(const nlohmann::json &value)
: BasicAdapter(NlohmannJsonValue<NlohmannJsonAdapter>{value})
{
}
};
template <class ValueType>
opt::optional<NlohmannJsonObject>
NlohmannJsonValue<ValueType>::getObjectOptional() const
{
if (m_value.is_object()) {
return opt::make_optional(NlohmannJsonObject(m_value));
}
return {};
}
/// Specialisation of the AdapterTraits template struct for NlohmannJsonAdapter.
template<>
struct AdapterTraits<valijson::adapters::NlohmannJsonAdapter>
{
typedef nlohmann::json DocumentType;
static std::string adapterName()
{
return "NlohmannJsonAdapter";
}
};
inline bool NlohmannJsonFrozenValue::equalTo(const Adapter &other, bool strict) const
{
return NlohmannJsonAdapter(m_value).equalTo(other, strict);
}
inline NlohmannJsonObjectMemberIterator<NlohmannJsonObjectMember>
NlohmannJsonObject::begin() const
{
return m_value.begin();
}
inline NlohmannJsonObjectMemberIterator<NlohmannJsonObjectMember>
NlohmannJsonObject::end() const
{
return m_value.end();
}
inline NlohmannJsonObjectMemberIterator<NlohmannJsonObjectMember>
NlohmannJsonObject::find(
const std::string &propertyName) const
{
return m_value.find(propertyName);
}
} // namespace adapters
} // namespace valijson
#pragma once
#include <iostream>
#include <nlohmann/json.hpp>
namespace valijson {
namespace utils {
inline bool loadDocument(const std::string &path, nlohmann::json &document)
{
// Load schema JSON from file
std::string file;
if (!loadFile(path, file)) {
std::cerr << "Failed to load json from file '" << path << "'."
<< std::endl;
return false;
}
// Parse schema
#if VALIJSON_USE_EXCEPTIONS
try {
document = nlohmann::json::parse(file);
} catch (std::invalid_argument const& exception) {
std::cerr << "nlohmann::json failed to parse the document\n"
<< "Parse error:" << exception.what() << "\n";
return false;
}
#else
document = nlohmann::json::parse(file, nullptr, false);
if (document.is_discarded()) {
std::cerr << "nlohmann::json failed to parse the document.";
return false;
}
#endif
return true;
}
} // namespace utils
} // namespace valijson