mirror of
https://github.com/tristanpenman/valijson.git
synced 2024-12-12 10:13:51 +01:00
10773 lines
351 KiB
C++
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 ¤tNode,
|
|
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
|