2021-03-12 05:59:23 +00:00
/*
* JSON schema validator for JSON for modern C + +
*
* Copyright ( c ) 2016 - 2019 Patrick Boettcher < p @ yai . se > .
*
* SPDX - License - Identifier : MIT
*
*/
# include <nlohmann/json-schema.hpp>
# include "json-patch.hpp"
# include <deque>
# include <memory>
# include <set>
# include <sstream>
2022-11-04 02:22:35 +00:00
# include <string>
2021-03-12 05:59:23 +00:00
using nlohmann : : json ;
using nlohmann : : json_patch ;
using nlohmann : : json_uri ;
using nlohmann : : json_schema : : root_schema ;
using namespace nlohmann : : json_schema ;
# ifdef JSON_SCHEMA_BOOST_REGEX
# include <boost / regex.hpp>
# define REGEX_NAMESPACE boost
# elif defined(JSON_SCHEMA_NO_REGEX)
# define NO_STD_REGEX
# else
# include <regex>
# define REGEX_NAMESPACE std
# endif
namespace
{
class schema
{
protected :
root_schema * root_ ;
2022-11-04 02:22:35 +00:00
json default_value_ = nullptr ;
protected :
virtual std : : shared_ptr < schema > make_for_default_ (
std : : shared_ptr < : : schema > & /* sch */ ,
root_schema * /* root */ ,
std : : vector < nlohmann : : json_uri > & /* uris */ ,
nlohmann : : json & /* default_value */ ) const
{
return nullptr ;
} ;
2021-03-12 05:59:23 +00:00
public :
virtual ~ schema ( ) = default ;
schema ( root_schema * root )
: root_ ( root ) { }
virtual void validate ( const json : : json_pointer & ptr , const json & instance , json_patch & patch , error_handler & e ) const = 0 ;
2022-11-04 02:22:35 +00:00
virtual const json & default_value ( const json : : json_pointer & , const json & , error_handler & ) const
2021-03-12 05:59:23 +00:00
{
2022-11-04 02:22:35 +00:00
return default_value_ ;
2021-03-12 05:59:23 +00:00
}
2022-11-04 02:22:35 +00:00
void set_default_value ( const json & v ) { default_value_ = v ; }
2021-03-12 05:59:23 +00:00
static std : : shared_ptr < schema > make ( json & schema ,
root_schema * root ,
const std : : vector < std : : string > & key ,
std : : vector < nlohmann : : json_uri > uris ) ;
} ;
class schema_ref : public schema
{
const std : : string id_ ;
std : : weak_ptr < schema > target_ ;
2022-11-04 02:22:35 +00:00
std : : shared_ptr < schema > target_strong_ ; // for references to references keep also the shared_ptr because
// no one else might use it after resolving
2021-03-12 05:59:23 +00:00
void validate ( const json : : json_pointer & ptr , const json & instance , json_patch & patch , error_handler & e ) const final
{
auto target = target_ . lock ( ) ;
if ( target )
target - > validate ( ptr , instance , patch , e ) ;
else
e . error ( ptr , instance , " unresolved or freed schema-reference " + id_ ) ;
}
2022-11-04 02:22:35 +00:00
const json & default_value ( const json : : json_pointer & ptr , const json & instance , error_handler & e ) const override final
2021-03-12 05:59:23 +00:00
{
2022-11-04 02:22:35 +00:00
if ( ! default_value_ . is_null ( ) )
return default_value_ ;
2021-03-12 05:59:23 +00:00
2022-11-04 02:22:35 +00:00
auto target = target_ . lock ( ) ;
2021-03-12 05:59:23 +00:00
if ( target )
2022-11-04 02:22:35 +00:00
return target - > default_value ( ptr , instance , e ) ;
e . error ( ptr , instance , " unresolved or freed schema-reference " + id_ ) ;
2021-03-12 05:59:23 +00:00
2022-11-04 02:22:35 +00:00
return default_value_ ;
2021-03-12 05:59:23 +00:00
}
2022-11-04 02:22:35 +00:00
protected :
virtual std : : shared_ptr < schema > make_for_default_ (
std : : shared_ptr < : : schema > & sch ,
root_schema * root ,
std : : vector < nlohmann : : json_uri > & uris ,
nlohmann : : json & default_value ) const override
{
// create a new reference schema using the original reference (which will be resolved later)
// to store this overloaded default value #209
auto result = std : : make_shared < schema_ref > ( uris [ 0 ] . to_string ( ) , root ) ;
result - > set_target ( sch , true ) ;
result - > set_default_value ( default_value ) ;
return result ;
} ;
2021-03-12 05:59:23 +00:00
public :
schema_ref ( const std : : string & id , root_schema * root )
: schema ( root ) , id_ ( id ) { }
const std : : string & id ( ) const { return id_ ; }
2022-11-04 02:22:35 +00:00
void set_target ( const std : : shared_ptr < schema > & target , bool strong = false )
{
target_ = target ;
if ( strong )
target_strong_ = target ;
}
2021-03-12 05:59:23 +00:00
} ;
} // namespace
namespace nlohmann
{
namespace json_schema
{
class root_schema
{
schema_loader loader_ ;
format_checker format_check_ ;
content_checker content_check_ ;
std : : shared_ptr < schema > root_ ;
struct schema_file {
std : : map < std : : string , std : : shared_ptr < schema > > schemas ;
std : : map < std : : string , std : : shared_ptr < schema_ref > > unresolved ; // contains all unresolved references from any other file seen during parsing
json unknown_keywords ;
} ;
// location as key
std : : map < std : : string , schema_file > files_ ;
schema_file & get_or_create_file ( const std : : string & loc )
{
auto file = files_ . lower_bound ( loc ) ;
if ( file ! = files_ . end ( ) & & ! ( files_ . key_comp ( ) ( loc , file - > first ) ) )
return file - > second ;
else
return files_ . insert ( file , { loc , { } } ) - > second ;
}
public :
root_schema ( schema_loader & & loader ,
format_checker & & format ,
content_checker & & content )
: loader_ ( std : : move ( loader ) ) ,
format_check_ ( std : : move ( format ) ) ,
content_check_ ( std : : move ( content ) )
{
}
format_checker & format_check ( ) { return format_check_ ; }
content_checker & content_check ( ) { return content_check_ ; }
void insert ( const json_uri & uri , const std : : shared_ptr < schema > & s )
{
auto & file = get_or_create_file ( uri . location ( ) ) ;
auto sch = file . schemas . lower_bound ( uri . fragment ( ) ) ;
if ( sch ! = file . schemas . end ( ) & & ! ( file . schemas . key_comp ( ) ( uri . fragment ( ) , sch - > first ) ) ) {
throw std : : invalid_argument ( " schema with " + uri . to_string ( ) + " already inserted " ) ;
return ;
}
file . schemas . insert ( { uri . fragment ( ) , s } ) ;
// was someone referencing this newly inserted schema?
auto unresolved = file . unresolved . find ( uri . fragment ( ) ) ;
if ( unresolved ! = file . unresolved . end ( ) ) {
unresolved - > second - > set_target ( s ) ;
file . unresolved . erase ( unresolved ) ;
}
}
void insert_unknown_keyword ( const json_uri & uri , const std : : string & key , json & value )
{
auto & file = get_or_create_file ( uri . location ( ) ) ;
auto new_uri = uri . append ( key ) ;
auto fragment = new_uri . pointer ( ) ;
// is there a reference looking for this unknown-keyword, which is thus no longer a unknown keyword but a schema
2022-11-04 02:22:35 +00:00
auto unresolved = file . unresolved . find ( fragment . to_string ( ) ) ;
2021-03-12 05:59:23 +00:00
if ( unresolved ! = file . unresolved . end ( ) )
schema : : make ( value , this , { } , { { new_uri } } ) ;
else { // no, nothing ref'd it, keep for later
// need to create an object for each reference-token in the
// JSON-Pointer When not existing, a stringified integer reference
// token (e.g. "123") in the middle of the pointer will be
// interpreted a an array-index and an array will be created.
// json_pointer's reference_tokens is private - get them
std : : deque < std : : string > ref_tokens ;
auto uri_pointer = uri . pointer ( ) ;
while ( ! uri_pointer . empty ( ) ) {
ref_tokens . push_front ( uri_pointer . back ( ) ) ;
uri_pointer . pop_back ( ) ;
}
// for each token create an object, if not already existing
auto unk_kw = & file . unknown_keywords ;
for ( auto & rt : ref_tokens ) {
2024-04-13 00:00:12 +00:00
// create a json_pointer from rt as rt can be an stringified integer doing find on an array won't work
json : : json_pointer rt_ptr { " / " + rt } ;
if ( unk_kw - > contains ( rt_ptr ) = = false )
2021-03-12 05:59:23 +00:00
( * unk_kw ) [ rt ] = json : : object ( ) ;
2024-04-13 00:00:12 +00:00
unk_kw = & ( * unk_kw ) [ rt_ptr ] ;
2021-03-12 05:59:23 +00:00
}
( * unk_kw ) [ key ] = value ;
}
// recursively add possible subschemas of unknown keywords
if ( value . type ( ) = = json : : value_t : : object )
for ( auto & subsch : value . items ( ) )
insert_unknown_keyword ( new_uri , subsch . key ( ) , subsch . value ( ) ) ;
}
std : : shared_ptr < schema > get_or_create_ref ( const json_uri & uri )
{
auto & file = get_or_create_file ( uri . location ( ) ) ;
// existing schema
auto sch = file . schemas . find ( uri . fragment ( ) ) ;
if ( sch ! = file . schemas . end ( ) )
return sch - > second ;
// referencing an unknown keyword, turn it into schema
//
// an unknown keyword can only be referenced by a json-pointer,
// not by a plain name fragment
2022-11-04 02:22:35 +00:00
if ( uri . pointer ( ) . to_string ( ) ! = " " ) {
2021-03-12 05:59:23 +00:00
try {
auto & subschema = file . unknown_keywords . at ( uri . pointer ( ) ) ; // null is returned if not existing
auto s = schema : : make ( subschema , this , { } , { { uri } } ) ; // A JSON Schema MUST be an object or a boolean.
if ( s ) { // nullptr if invalid schema, e.g. null
file . unknown_keywords . erase ( uri . fragment ( ) ) ;
return s ;
}
} catch ( nlohmann : : detail : : out_of_range & ) { // at() did not find it
}
}
// get or create a schema_ref
auto r = file . unresolved . lower_bound ( uri . fragment ( ) ) ;
if ( r ! = file . unresolved . end ( ) & & ! ( file . unresolved . key_comp ( ) ( uri . fragment ( ) , r - > first ) ) ) {
return r - > second ; // unresolved, already seen previously - use existing reference
} else {
return file . unresolved . insert ( r ,
{ uri . fragment ( ) , std : : make_shared < schema_ref > ( uri . to_string ( ) , this ) } )
- > second ; // unresolved, create reference
}
}
void set_root_schema ( json sch )
{
files_ . clear ( ) ;
root_ = schema : : make ( sch , this , { } , { { " # " } } ) ;
// load all files which have not yet been loaded
do {
bool new_schema_loaded = false ;
// files_ is modified during parsing, iterators are invalidated
std : : vector < std : : string > locations ;
for ( auto & file : files_ )
locations . push_back ( file . first ) ;
for ( auto & loc : locations ) {
if ( files_ [ loc ] . schemas . size ( ) = = 0 ) { // nothing has been loaded for this file
if ( loader_ ) {
json loaded_schema ;
loader_ ( loc , loaded_schema ) ;
schema : : make ( loaded_schema , this , { } , { { loc } } ) ;
new_schema_loaded = true ;
} else {
throw std : : invalid_argument ( " external schema reference ' " + loc + " ' needs loading, but no loader callback given " ) ;
}
}
}
if ( ! new_schema_loaded ) // if no new schema loaded, no need to try again
break ;
} while ( 1 ) ;
2022-11-04 02:22:35 +00:00
for ( const auto & file : files_ ) {
if ( file . second . unresolved . size ( ) ! = 0 ) {
// Build a representation of the undefined
// references as a list of comma-separated strings.
auto n_urefs = file . second . unresolved . size ( ) ;
std : : string urefs = " [ " ;
decltype ( n_urefs ) counter = 0 ;
for ( const auto & p : file . second . unresolved ) {
urefs + = p . first ;
if ( counter ! = n_urefs - 1u ) {
urefs + = " , " ;
}
+ + counter ;
}
urefs + = " ] " ;
2021-03-12 05:59:23 +00:00
throw std : : invalid_argument ( " after all files have been parsed, ' " +
( file . first = = " " ? " <root> " : file . first ) +
2022-11-04 02:22:35 +00:00
" ' has still the following undefined references: " + urefs ) ;
}
}
2021-03-12 05:59:23 +00:00
}
void validate ( const json : : json_pointer & ptr ,
const json & instance ,
json_patch & patch ,
error_handler & e ,
const json_uri & initial ) const
{
if ( ! root_ ) {
e . error ( ptr , " " , " no root schema has yet been set for validating an instance " ) ;
return ;
}
auto file_entry = files_ . find ( initial . location ( ) ) ;
if ( file_entry = = files_ . end ( ) ) {
e . error ( ptr , " " , " no file found serving requested root-URI. " + initial . location ( ) ) ;
return ;
}
auto & file = file_entry - > second ;
auto sch = file . schemas . find ( initial . fragment ( ) ) ;
if ( sch = = file . schemas . end ( ) ) {
e . error ( ptr , " " , " no schema find for request initial URI: " + initial . to_string ( ) ) ;
return ;
}
sch - > second - > validate ( ptr , instance , patch , e ) ;
}
} ;
} // namespace json_schema
} // namespace nlohmann
namespace
{
class first_error_handler : public error_handler
{
public :
bool error_ { false } ;
json : : json_pointer ptr_ ;
json instance_ ;
std : : string message_ ;
void error ( const json : : json_pointer & ptr , const json & instance , const std : : string & message ) override
{
if ( * this )
return ;
error_ = true ;
ptr_ = ptr ;
instance_ = instance ;
message_ = message ;
}
operator bool ( ) const { return error_ ; }
} ;
class logical_not : public schema
{
std : : shared_ptr < schema > subschema_ ;
void validate ( const json : : json_pointer & ptr , const json & instance , json_patch & patch , error_handler & e ) const final
{
first_error_handler esub ;
subschema_ - > validate ( ptr , instance , patch , esub ) ;
if ( ! esub )
e . error ( ptr , instance , " the subschema has succeeded, but it is required to not validate " ) ;
}
2022-11-04 02:22:35 +00:00
const json & default_value ( const json : : json_pointer & ptr , const json & instance , error_handler & e ) const override
2021-03-12 05:59:23 +00:00
{
2022-11-04 02:22:35 +00:00
return subschema_ - > default_value ( ptr , instance , e ) ;
2021-03-12 05:59:23 +00:00
}
public :
logical_not ( json & sch ,
root_schema * root ,
const std : : vector < nlohmann : : json_uri > & uris )
: schema ( root )
{
subschema_ = schema : : make ( sch , root , { " not " } , uris ) ;
}
} ;
enum logical_combination_types {
allOf ,
anyOf ,
oneOf
} ;
2024-04-13 00:00:12 +00:00
class logical_combination_error_handler : public error_handler
{
public :
struct error_entry
{
json : : json_pointer ptr_ ;
json instance_ ;
std : : string message_ ;
} ;
std : : vector < error_entry > error_entry_list_ ;
void error ( const json : : json_pointer & ptr , const json & instance , const std : : string & message ) override
{
error_entry_list_ . push_back ( error_entry { ptr , instance , message } ) ;
}
void propagate ( error_handler & e , const std : : string & prefix ) const
{
for ( const error_entry & entry : error_entry_list_ )
e . error ( entry . ptr_ , entry . instance_ , prefix + entry . message_ ) ;
}
operator bool ( ) const { return ! error_entry_list_ . empty ( ) ; }
} ;
2021-03-12 05:59:23 +00:00
template < enum logical_combination_types combine_logic >
class logical_combination : public schema
{
std : : vector < std : : shared_ptr < schema > > subschemata_ ;
void validate ( const json : : json_pointer & ptr , const json & instance , json_patch & patch , error_handler & e ) const final
{
size_t count = 0 ;
2024-04-13 00:00:12 +00:00
logical_combination_error_handler error_summary ;
2021-03-12 05:59:23 +00:00
2024-04-13 00:00:12 +00:00
for ( std : : size_t index = 0 ; index < subschemata_ . size ( ) ; + + index ) {
const std : : shared_ptr < schema > & s = subschemata_ [ index ] ;
logical_combination_error_handler esub ;
auto oldPatchSize = patch . get_json ( ) . size ( ) ;
2021-03-12 05:59:23 +00:00
s - > validate ( ptr , instance , patch , esub ) ;
if ( ! esub )
count + + ;
2024-04-13 00:00:12 +00:00
else {
patch . get_json ( ) . get_ref < nlohmann : : json : : array_t & > ( ) . resize ( oldPatchSize ) ;
esub . propagate ( error_summary , " case# " + std : : to_string ( index ) + " ] " ) ;
}
2021-03-12 05:59:23 +00:00
2024-04-13 00:00:12 +00:00
if ( is_validate_complete ( instance , ptr , e , esub , count , index ) )
2021-03-12 05:59:23 +00:00
return ;
}
2024-04-13 00:00:12 +00:00
if ( count = = 0 ) {
e . error ( ptr , instance , " no subschema has succeeded, but one of them is required to validate. Type: " + key + " , number of failed subschemas: " + std : : to_string ( subschemata_ . size ( ) ) ) ;
error_summary . propagate ( e , " [combination: " + key + " / " ) ;
}
2021-03-12 05:59:23 +00:00
}
// specialized for each of the logical_combination_types
static const std : : string key ;
2024-04-13 00:00:12 +00:00
static bool is_validate_complete ( const json & , const json : : json_pointer & , error_handler & , const logical_combination_error_handler & , size_t , size_t ) ;
2021-03-12 05:59:23 +00:00
public :
logical_combination ( json & sch ,
root_schema * root ,
const std : : vector < nlohmann : : json_uri > & uris )
: schema ( root )
{
size_t c = 0 ;
for ( auto & subschema : sch )
subschemata_ . push_back ( schema : : make ( subschema , root , { key , std : : to_string ( c + + ) } , uris ) ) ;
// value of allOf, anyOf, and oneOf "MUST be a non-empty array"
// TODO error/throw? when subschemata_.empty()
}
} ;
template < >
const std : : string logical_combination < allOf > : : key = " allOf " ;
template < >
const std : : string logical_combination < anyOf > : : key = " anyOf " ;
template < >
const std : : string logical_combination < oneOf > : : key = " oneOf " ;
template < >
2024-04-13 00:00:12 +00:00
bool logical_combination < allOf > : : is_validate_complete ( const json & , const json : : json_pointer & , error_handler & e , const logical_combination_error_handler & esub , size_t , size_t current_schema_index )
2021-03-12 05:59:23 +00:00
{
if ( esub )
2024-04-13 00:00:12 +00:00
{
e . error ( esub . error_entry_list_ . front ( ) . ptr_ , esub . error_entry_list_ . front ( ) . instance_ , " at least one subschema has failed, but all of them are required to validate - " + esub . error_entry_list_ . front ( ) . message_ ) ;
esub . propagate ( e , " [combination: allOf / case# " + std : : to_string ( current_schema_index ) + " ] " ) ;
}
2021-03-12 05:59:23 +00:00
return esub ;
}
template < >
2024-04-13 00:00:12 +00:00
bool logical_combination < anyOf > : : is_validate_complete ( const json & , const json : : json_pointer & , error_handler & , const logical_combination_error_handler & , size_t count , size_t )
2021-03-12 05:59:23 +00:00
{
return count = = 1 ;
}
template < >
2024-04-13 00:00:12 +00:00
bool logical_combination < oneOf > : : is_validate_complete ( const json & instance , const json : : json_pointer & ptr , error_handler & e , const logical_combination_error_handler & , size_t count , size_t )
2021-03-12 05:59:23 +00:00
{
if ( count > 1 )
e . error ( ptr , instance , " more than one subschema has succeeded, but exactly one of them is required to validate " ) ;
return count > 1 ;
}
class type_schema : public schema
{
std : : vector < std : : shared_ptr < schema > > type_ ;
std : : pair < bool , json > enum_ , const_ ;
std : : vector < std : : shared_ptr < schema > > logic_ ;
static std : : shared_ptr < schema > make ( json & schema ,
json : : value_t type ,
root_schema * ,
const std : : vector < nlohmann : : json_uri > & ,
std : : set < std : : string > & ) ;
std : : shared_ptr < schema > if_ , then_ , else_ ;
void validate ( const json : : json_pointer & ptr , const json & instance , json_patch & patch , error_handler & e ) const override final
{
// depending on the type of instance run the type specific validator - if present
2022-11-04 02:22:35 +00:00
auto type = type_ [ static_cast < uint8_t > ( instance . type ( ) ) ] ;
2021-03-12 05:59:23 +00:00
if ( type )
type - > validate ( ptr , instance , patch , e ) ;
else
e . error ( ptr , instance , " unexpected instance type " ) ;
if ( enum_ . first ) {
bool seen_in_enum = false ;
for ( auto & v : enum_ . second )
if ( instance = = v ) {
seen_in_enum = true ;
break ;
}
if ( ! seen_in_enum )
e . error ( ptr , instance , " instance not found in required enum " ) ;
}
if ( const_ . first & &
const_ . second ! = instance )
e . error ( ptr , instance , " instance not const " ) ;
for ( auto l : logic_ )
l - > validate ( ptr , instance , patch , e ) ;
if ( if_ ) {
first_error_handler err ;
if_ - > validate ( ptr , instance , patch , err ) ;
if ( ! err ) {
if ( then_ )
then_ - > validate ( ptr , instance , patch , e ) ;
} else {
if ( else_ )
else_ - > validate ( ptr , instance , patch , e ) ;
}
}
2024-04-13 00:00:12 +00:00
if ( instance . is_null ( ) ) {
patch . add ( nlohmann : : json : : json_pointer { } , default_value_ ) ;
}
2021-03-12 05:59:23 +00:00
}
2022-11-04 02:22:35 +00:00
protected :
virtual std : : shared_ptr < schema > make_for_default_ (
std : : shared_ptr < : : schema > & /* sch */ ,
root_schema * /* root */ ,
std : : vector < nlohmann : : json_uri > & /* uris */ ,
nlohmann : : json & default_value ) const override
{
auto result = std : : make_shared < type_schema > ( * this ) ;
result - > set_default_value ( default_value ) ;
return result ;
} ;
2021-03-12 05:59:23 +00:00
public :
type_schema ( json & sch ,
root_schema * root ,
const std : : vector < nlohmann : : json_uri > & uris )
2022-11-04 02:22:35 +00:00
: schema ( root ) , type_ ( static_cast < uint8_t > ( json : : value_t : : discarded ) + 1 )
2021-03-12 05:59:23 +00:00
{
// association between JSON-schema-type and NLohmann-types
static const std : : vector < std : : pair < std : : string , json : : value_t > > schema_types = {
{ " null " , json : : value_t : : null } ,
{ " object " , json : : value_t : : object } ,
{ " array " , json : : value_t : : array } ,
{ " string " , json : : value_t : : string } ,
{ " boolean " , json : : value_t : : boolean } ,
{ " integer " , json : : value_t : : number_integer } ,
{ " number " , json : : value_t : : number_float } ,
} ;
std : : set < std : : string > known_keywords ;
auto attr = sch . find ( " type " ) ;
if ( attr = = sch . end ( ) ) // no type field means all sub-types possible
for ( auto & t : schema_types )
2022-11-04 02:22:35 +00:00
type_ [ static_cast < uint8_t > ( t . second ) ] = type_schema : : make ( sch , t . second , root , uris , known_keywords ) ;
2021-03-12 05:59:23 +00:00
else {
switch ( attr . value ( ) . type ( ) ) { // "type": "type"
case json : : value_t : : string : {
auto schema_type = attr . value ( ) . get < std : : string > ( ) ;
for ( auto & t : schema_types )
if ( t . first = = schema_type )
2022-11-04 02:22:35 +00:00
type_ [ static_cast < uint8_t > ( t . second ) ] = type_schema : : make ( sch , t . second , root , uris , known_keywords ) ;
2021-03-12 05:59:23 +00:00
} break ;
case json : : value_t : : array : // "type": ["type1", "type2"]
2024-04-13 00:00:12 +00:00
for ( auto & array_value : attr . value ( ) ) {
auto schema_type = array_value . get < std : : string > ( ) ;
2021-03-12 05:59:23 +00:00
for ( auto & t : schema_types )
if ( t . first = = schema_type )
2022-11-04 02:22:35 +00:00
type_ [ static_cast < uint8_t > ( t . second ) ] = type_schema : : make ( sch , t . second , root , uris , known_keywords ) ;
2024-04-13 00:00:12 +00:00
}
2021-03-12 05:59:23 +00:00
break ;
default :
break ;
}
sch . erase ( attr ) ;
}
2022-11-04 02:22:35 +00:00
attr = sch . find ( " default " ) ;
if ( attr ! = sch . end ( ) ) {
set_default_value ( attr . value ( ) ) ;
sch . erase ( attr ) ;
2021-03-12 05:59:23 +00:00
}
for ( auto & key : known_keywords )
sch . erase ( key ) ;
// with nlohmann::json float instance (but number in schema-definition) can be seen as unsigned or integer -
// reuse the number-validator for integer values as well, if they have not been specified explicitly
2022-11-04 02:22:35 +00:00
if ( type_ [ static_cast < uint8_t > ( json : : value_t : : number_float ) ] & & ! type_ [ static_cast < uint8_t > ( json : : value_t : : number_integer ) ] )
type_ [ static_cast < uint8_t > ( json : : value_t : : number_integer ) ] = type_ [ static_cast < uint8_t > ( json : : value_t : : number_float ) ] ;
2021-03-12 05:59:23 +00:00
// #54: JSON-schema does not differentiate between unsigned and signed integer - nlohmann::json does
// we stick with JSON-schema: use the integer-validator if instance-value is unsigned
2022-11-04 02:22:35 +00:00
type_ [ static_cast < uint8_t > ( json : : value_t : : number_unsigned ) ] = type_ [ static_cast < uint8_t > ( json : : value_t : : number_integer ) ] ;
2021-03-12 05:59:23 +00:00
// special for binary types
2022-11-04 02:22:35 +00:00
if ( type_ [ static_cast < uint8_t > ( json : : value_t : : string ) ] ) {
type_ [ static_cast < uint8_t > ( json : : value_t : : binary ) ] = type_ [ static_cast < uint8_t > ( json : : value_t : : string ) ] ;
2021-03-12 05:59:23 +00:00
}
attr = sch . find ( " enum " ) ;
if ( attr ! = sch . end ( ) ) {
enum_ = { true , attr . value ( ) } ;
sch . erase ( attr ) ;
}
attr = sch . find ( " const " ) ;
if ( attr ! = sch . end ( ) ) {
const_ = { true , attr . value ( ) } ;
sch . erase ( attr ) ;
}
attr = sch . find ( " not " ) ;
if ( attr ! = sch . end ( ) ) {
logic_ . push_back ( std : : make_shared < logical_not > ( attr . value ( ) , root , uris ) ) ;
sch . erase ( attr ) ;
}
attr = sch . find ( " allOf " ) ;
if ( attr ! = sch . end ( ) ) {
logic_ . push_back ( std : : make_shared < logical_combination < allOf > > ( attr . value ( ) , root , uris ) ) ;
sch . erase ( attr ) ;
}
attr = sch . find ( " anyOf " ) ;
if ( attr ! = sch . end ( ) ) {
logic_ . push_back ( std : : make_shared < logical_combination < anyOf > > ( attr . value ( ) , root , uris ) ) ;
sch . erase ( attr ) ;
}
attr = sch . find ( " oneOf " ) ;
if ( attr ! = sch . end ( ) ) {
logic_ . push_back ( std : : make_shared < logical_combination < oneOf > > ( attr . value ( ) , root , uris ) ) ;
sch . erase ( attr ) ;
}
attr = sch . find ( " if " ) ;
if ( attr ! = sch . end ( ) ) {
auto attr_then = sch . find ( " then " ) ;
auto attr_else = sch . find ( " else " ) ;
if ( attr_then ! = sch . end ( ) | | attr_else ! = sch . end ( ) ) {
if_ = schema : : make ( attr . value ( ) , root , { " if " } , uris ) ;
if ( attr_then ! = sch . end ( ) ) {
then_ = schema : : make ( attr_then . value ( ) , root , { " then " } , uris ) ;
sch . erase ( attr_then ) ;
}
if ( attr_else ! = sch . end ( ) ) {
else_ = schema : : make ( attr_else . value ( ) , root , { " else " } , uris ) ;
sch . erase ( attr_else ) ;
}
}
sch . erase ( attr ) ;
}
}
} ;
class string : public schema
{
std : : pair < bool , size_t > maxLength_ { false , 0 } ;
std : : pair < bool , size_t > minLength_ { false , 0 } ;
# ifndef NO_STD_REGEX
std : : pair < bool , REGEX_NAMESPACE : : regex > pattern_ { false , REGEX_NAMESPACE : : regex ( ) } ;
std : : string patternString_ ;
# endif
std : : pair < bool , std : : string > format_ ;
std : : tuple < bool , std : : string , std : : string > content_ { false , " " , " " } ;
std : : size_t utf8_length ( const std : : string & s ) const
{
size_t len = 0 ;
for ( auto c : s )
if ( ( c & 0xc0 ) ! = 0x80 )
len + + ;
return len ;
}
void validate ( const json : : json_pointer & ptr , const json & instance , json_patch & , error_handler & e ) const override
{
if ( minLength_ . first ) {
2022-11-04 02:22:35 +00:00
if ( utf8_length ( instance . get < std : : string > ( ) ) < minLength_ . second ) {
2021-03-12 05:59:23 +00:00
std : : ostringstream s ;
s < < " instance is too short as per minLength: " < < minLength_ . second ;
e . error ( ptr , instance , s . str ( ) ) ;
}
}
if ( maxLength_ . first ) {
2022-11-04 02:22:35 +00:00
if ( utf8_length ( instance . get < std : : string > ( ) ) > maxLength_ . second ) {
2021-03-12 05:59:23 +00:00
std : : ostringstream s ;
s < < " instance is too long as per maxLength: " < < maxLength_ . second ;
e . error ( ptr , instance , s . str ( ) ) ;
}
}
if ( std : : get < 0 > ( content_ ) ) {
if ( root_ - > content_check ( ) = = nullptr )
e . error ( ptr , instance , std : : string ( " a content checker was not provided but a contentEncoding or contentMediaType for this string have been present: ' " ) + std : : get < 1 > ( content_ ) + " ' ' " + std : : get < 2 > ( content_ ) + " ' " ) ;
else {
try {
root_ - > content_check ( ) ( std : : get < 1 > ( content_ ) , std : : get < 2 > ( content_ ) , instance ) ;
} catch ( const std : : exception & ex ) {
e . error ( ptr , instance , std : : string ( " content-checking failed: " ) + ex . what ( ) ) ;
}
}
} else if ( instance . type ( ) = = json : : value_t : : binary ) {
e . error ( ptr , instance , " expected string, but get binary data " ) ;
}
if ( instance . type ( ) ! = json : : value_t : : string ) {
return ; // next checks only for strings
}
# ifndef NO_STD_REGEX
if ( pattern_ . first & &
! REGEX_NAMESPACE : : regex_search ( instance . get < std : : string > ( ) , pattern_ . second ) )
e . error ( ptr , instance , " instance does not match regex pattern: " + patternString_ ) ;
# endif
if ( format_ . first ) {
if ( root_ - > format_check ( ) = = nullptr )
e . error ( ptr , instance , std : : string ( " a format checker was not provided but a format keyword for this string is present: " ) + format_ . second ) ;
else {
try {
2022-11-04 02:22:35 +00:00
root_ - > format_check ( ) ( format_ . second , instance . get < std : : string > ( ) ) ;
2021-03-12 05:59:23 +00:00
} catch ( const std : : exception & ex ) {
e . error ( ptr , instance , std : : string ( " format-checking failed: " ) + ex . what ( ) ) ;
}
}
}
}
public :
string ( json & sch , root_schema * root )
: schema ( root )
{
auto attr = sch . find ( " maxLength " ) ;
if ( attr ! = sch . end ( ) ) {
2022-11-04 02:22:35 +00:00
maxLength_ = { true , attr . value ( ) . get < size_t > ( ) } ;
2021-03-12 05:59:23 +00:00
sch . erase ( attr ) ;
}
attr = sch . find ( " minLength " ) ;
if ( attr ! = sch . end ( ) ) {
2022-11-04 02:22:35 +00:00
minLength_ = { true , attr . value ( ) . get < size_t > ( ) } ;
2021-03-12 05:59:23 +00:00
sch . erase ( attr ) ;
}
attr = sch . find ( " contentEncoding " ) ;
if ( attr ! = sch . end ( ) ) {
std : : get < 0 > ( content_ ) = true ;
std : : get < 1 > ( content_ ) = attr . value ( ) . get < std : : string > ( ) ;
// special case for nlohmann::json-binary-types
//
// https://github.com/pboettch/json-schema-validator/pull/114
//
// We cannot use explicitly in a schema: {"type": "binary"} or
// "type": ["binary", "number"] we have to be implicit. For a
// schema where "contentEncoding" is set to "binary", an instance
// of type json::value_t::binary is accepted. If a
// contentEncoding-callback has to be provided and is called
// accordingly. For encoding=binary, no other type validations are done
sch . erase ( attr ) ;
}
attr = sch . find ( " contentMediaType " ) ;
if ( attr ! = sch . end ( ) ) {
std : : get < 0 > ( content_ ) = true ;
std : : get < 2 > ( content_ ) = attr . value ( ) . get < std : : string > ( ) ;
sch . erase ( attr ) ;
}
if ( std : : get < 0 > ( content_ ) = = true & & root_ - > content_check ( ) = = nullptr ) {
throw std : : invalid_argument { " schema contains contentEncoding/contentMediaType but content checker was not set " } ;
}
# ifndef NO_STD_REGEX
attr = sch . find ( " pattern " ) ;
if ( attr ! = sch . end ( ) ) {
2022-11-04 02:22:35 +00:00
patternString_ = attr . value ( ) . get < std : : string > ( ) ;
2021-03-12 05:59:23 +00:00
pattern_ = { true , REGEX_NAMESPACE : : regex ( attr . value ( ) . get < std : : string > ( ) ,
REGEX_NAMESPACE : : regex : : ECMAScript ) } ;
sch . erase ( attr ) ;
}
# endif
attr = sch . find ( " format " ) ;
if ( attr ! = sch . end ( ) ) {
if ( root_ - > format_check ( ) = = nullptr )
throw std : : invalid_argument { " a format checker was not provided but a format keyword for this string is present: " + format_ . second } ;
2022-11-04 02:22:35 +00:00
format_ = { true , attr . value ( ) . get < std : : string > ( ) } ;
2021-03-12 05:59:23 +00:00
sch . erase ( attr ) ;
}
}
} ;
template < typename T >
class numeric : public schema
{
std : : pair < bool , T > maximum_ { false , 0 } ;
std : : pair < bool , T > minimum_ { false , 0 } ;
bool exclusiveMaximum_ = false ;
bool exclusiveMinimum_ = false ;
std : : pair < bool , json : : number_float_t > multipleOf_ { false , 0 } ;
// multipleOf - if the remainder of the division is 0 -> OK
bool violates_multiple_of ( T x ) const
{
double res = std : : remainder ( x , multipleOf_ . second ) ;
2024-04-13 00:00:12 +00:00
double multiple = std : : fabs ( x / multipleOf_ . second ) ;
if ( multiple > 1 ) {
res = res / multiple ;
}
2022-11-04 02:22:35 +00:00
double eps = std : : nextafter ( x , 0 ) - static_cast < double > ( x ) ;
2024-04-13 00:00:12 +00:00
2021-03-12 05:59:23 +00:00
return std : : fabs ( res ) > std : : fabs ( eps ) ;
}
void validate ( const json : : json_pointer & ptr , const json & instance , json_patch & , error_handler & e ) const override
{
T value = instance ; // conversion of json to value_type
2024-04-13 00:00:12 +00:00
std : : ostringstream oss ;
2021-03-12 05:59:23 +00:00
if ( multipleOf_ . first & & value ! = 0 ) // zero is multiple of everything
if ( violates_multiple_of ( value ) )
2024-04-13 00:00:12 +00:00
oss < < " instance is not a multiple of " < < json ( multipleOf_ . second ) ;
2021-03-12 05:59:23 +00:00
2022-11-04 02:22:35 +00:00
if ( maximum_ . first ) {
if ( exclusiveMaximum_ & & value > = maximum_ . second )
2024-04-13 00:00:12 +00:00
oss < < " instance exceeds or equals maximum of " < < json ( maximum_ . second ) ;
2022-11-04 02:22:35 +00:00
else if ( value > maximum_ . second )
2024-04-13 00:00:12 +00:00
oss < < " instance exceeds maximum of " < < json ( maximum_ . second ) ;
2022-11-04 02:22:35 +00:00
}
2021-03-12 05:59:23 +00:00
2022-11-04 02:22:35 +00:00
if ( minimum_ . first ) {
if ( exclusiveMinimum_ & & value < = minimum_ . second )
2024-04-13 00:00:12 +00:00
oss < < " instance is below or equals minimum of " < < json ( minimum_ . second ) ;
2022-11-04 02:22:35 +00:00
else if ( value < minimum_ . second )
2024-04-13 00:00:12 +00:00
oss < < " instance is below minimum of " < < json ( minimum_ . second ) ;
}
oss . seekp ( 0 , std : : ios : : end ) ;
auto size = oss . tellp ( ) ;
if ( size ! = 0 ) {
oss . seekp ( 0 , std : : ios : : beg ) ;
e . error ( ptr , instance , oss . str ( ) ) ;
2022-11-04 02:22:35 +00:00
}
2021-03-12 05:59:23 +00:00
}
public :
numeric ( const json & sch , root_schema * root , std : : set < std : : string > & kw )
: schema ( root )
{
auto attr = sch . find ( " maximum " ) ;
if ( attr ! = sch . end ( ) ) {
2022-11-04 02:22:35 +00:00
maximum_ = { true , attr . value ( ) . get < T > ( ) } ;
2021-03-12 05:59:23 +00:00
kw . insert ( " maximum " ) ;
}
attr = sch . find ( " minimum " ) ;
if ( attr ! = sch . end ( ) ) {
2022-11-04 02:22:35 +00:00
minimum_ = { true , attr . value ( ) . get < T > ( ) } ;
2021-03-12 05:59:23 +00:00
kw . insert ( " minimum " ) ;
}
attr = sch . find ( " exclusiveMaximum " ) ;
if ( attr ! = sch . end ( ) ) {
exclusiveMaximum_ = true ;
2022-11-04 02:22:35 +00:00
maximum_ = { true , attr . value ( ) . get < T > ( ) } ;
2021-03-12 05:59:23 +00:00
kw . insert ( " exclusiveMaximum " ) ;
}
attr = sch . find ( " exclusiveMinimum " ) ;
if ( attr ! = sch . end ( ) ) {
exclusiveMinimum_ = true ;
2022-11-04 02:22:35 +00:00
minimum_ = { true , attr . value ( ) . get < T > ( ) } ;
2021-03-12 05:59:23 +00:00
kw . insert ( " exclusiveMinimum " ) ;
}
attr = sch . find ( " multipleOf " ) ;
if ( attr ! = sch . end ( ) ) {
2022-11-04 02:22:35 +00:00
multipleOf_ = { true , attr . value ( ) . get < json : : number_float_t > ( ) } ;
2021-03-12 05:59:23 +00:00
kw . insert ( " multipleOf " ) ;
}
}
} ;
class null : public schema
{
void validate ( const json : : json_pointer & ptr , const json & instance , json_patch & , error_handler & e ) const override
{
if ( ! instance . is_null ( ) )
e . error ( ptr , instance , " expected to be null " ) ;
}
public :
null ( json & , root_schema * root )
: schema ( root ) { }
} ;
class boolean_type : public schema
{
void validate ( const json : : json_pointer & , const json & , json_patch & , error_handler & ) const override { }
public :
boolean_type ( json & , root_schema * root )
: schema ( root ) { }
} ;
class boolean : public schema
{
bool true_ ;
void validate ( const json : : json_pointer & ptr , const json & instance , json_patch & , error_handler & e ) const override
{
if ( ! true_ ) { // false schema
// empty array
2022-11-04 02:22:35 +00:00
// switch (instance.type()) {
// case json::value_t::array:
2021-03-12 05:59:23 +00:00
// if (instance.size() != 0) // valid false-schema
// e.error(ptr, instance, "false-schema required empty array");
// return;
//}
e . error ( ptr , instance , " instance invalid as per false-schema " ) ;
}
}
public :
boolean ( json & sch , root_schema * root )
: schema ( root ) , true_ ( sch ) { }
} ;
class required : public schema
{
const std : : vector < std : : string > required_ ;
void validate ( const json : : json_pointer & ptr , const json & instance , json_patch & , error_handler & e ) const override final
{
for ( auto & r : required_ )
if ( instance . find ( r ) = = instance . end ( ) )
e . error ( ptr , instance , " required property ' " + r + " ' not found in object as a dependency " ) ;
}
public :
required ( const std : : vector < std : : string > & r , root_schema * root )
: schema ( root ) , required_ ( r ) { }
} ;
class object : public schema
{
std : : pair < bool , size_t > maxProperties_ { false , 0 } ;
std : : pair < bool , size_t > minProperties_ { false , 0 } ;
std : : vector < std : : string > required_ ;
std : : map < std : : string , std : : shared_ptr < schema > > properties_ ;
# ifndef NO_STD_REGEX
std : : vector < std : : pair < REGEX_NAMESPACE : : regex , std : : shared_ptr < schema > > > patternProperties_ ;
# endif
std : : shared_ptr < schema > additionalProperties_ ;
std : : map < std : : string , std : : shared_ptr < schema > > dependencies_ ;
std : : shared_ptr < schema > propertyNames_ ;
void validate ( const json : : json_pointer & ptr , const json & instance , json_patch & patch , error_handler & e ) const override
{
if ( maxProperties_ . first & & instance . size ( ) > maxProperties_ . second )
e . error ( ptr , instance , " too many properties " ) ;
if ( minProperties_ . first & & instance . size ( ) < minProperties_ . second )
e . error ( ptr , instance , " too few properties " ) ;
for ( auto & r : required_ )
if ( instance . find ( r ) = = instance . end ( ) )
e . error ( ptr , instance , " required property ' " + r + " ' not found in object " ) ;
// for each property in instance
for ( auto & p : instance . items ( ) ) {
if ( propertyNames_ )
propertyNames_ - > validate ( ptr , p . key ( ) , patch , e ) ;
bool a_prop_or_pattern_matched = false ;
auto schema_p = properties_ . find ( p . key ( ) ) ;
// check if it is in "properties"
if ( schema_p ! = properties_ . end ( ) ) {
a_prop_or_pattern_matched = true ;
schema_p - > second - > validate ( ptr / p . key ( ) , p . value ( ) , patch , e ) ;
}
# ifndef NO_STD_REGEX
// check all matching patternProperties
for ( auto & schema_pp : patternProperties_ )
if ( REGEX_NAMESPACE : : regex_search ( p . key ( ) , schema_pp . first ) ) {
a_prop_or_pattern_matched = true ;
schema_pp . second - > validate ( ptr / p . key ( ) , p . value ( ) , patch , e ) ;
}
# endif
// check additionalProperties as a last resort
if ( ! a_prop_or_pattern_matched & & additionalProperties_ ) {
first_error_handler additional_prop_err ;
additionalProperties_ - > validate ( ptr / p . key ( ) , p . value ( ) , patch , additional_prop_err ) ;
if ( additional_prop_err )
e . error ( ptr , instance , " validation failed for additional property ' " + p . key ( ) + " ': " + additional_prop_err . message_ ) ;
}
}
// reverse search
for ( auto const & prop : properties_ ) {
const auto finding = instance . find ( prop . first ) ;
if ( instance . end ( ) = = finding ) { // if the prop is not in the instance
2022-11-04 02:22:35 +00:00
const auto & default_value = prop . second - > default_value ( ptr , instance , e ) ;
if ( ! default_value . is_null ( ) ) { // if default value is available
patch . add ( ( ptr / prop . first ) , default_value ) ;
2021-03-12 05:59:23 +00:00
}
}
}
for ( auto & dep : dependencies_ ) {
auto prop = instance . find ( dep . first ) ;
if ( prop ! = instance . end ( ) ) // if dependency-property is present in instance
dep . second - > validate ( ptr / dep . first , instance , patch , e ) ; // validate
}
}
public :
object ( json & sch ,
root_schema * root ,
const std : : vector < nlohmann : : json_uri > & uris )
: schema ( root )
{
auto attr = sch . find ( " maxProperties " ) ;
if ( attr ! = sch . end ( ) ) {
2022-11-04 02:22:35 +00:00
maxProperties_ = { true , attr . value ( ) . get < size_t > ( ) } ;
2021-03-12 05:59:23 +00:00
sch . erase ( attr ) ;
}
attr = sch . find ( " minProperties " ) ;
if ( attr ! = sch . end ( ) ) {
2022-11-04 02:22:35 +00:00
minProperties_ = { true , attr . value ( ) . get < size_t > ( ) } ;
2021-03-12 05:59:23 +00:00
sch . erase ( attr ) ;
}
attr = sch . find ( " required " ) ;
if ( attr ! = sch . end ( ) ) {
required_ = attr . value ( ) . get < std : : vector < std : : string > > ( ) ;
sch . erase ( attr ) ;
}
attr = sch . find ( " properties " ) ;
if ( attr ! = sch . end ( ) ) {
for ( auto prop : attr . value ( ) . items ( ) )
properties_ . insert (
std : : make_pair (
prop . key ( ) ,
schema : : make ( prop . value ( ) , root , { " properties " , prop . key ( ) } , uris ) ) ) ;
sch . erase ( attr ) ;
}
# ifndef NO_STD_REGEX
attr = sch . find ( " patternProperties " ) ;
if ( attr ! = sch . end ( ) ) {
for ( auto prop : attr . value ( ) . items ( ) )
patternProperties_ . push_back (
std : : make_pair (
REGEX_NAMESPACE : : regex ( prop . key ( ) , REGEX_NAMESPACE : : regex : : ECMAScript ) ,
schema : : make ( prop . value ( ) , root , { prop . key ( ) } , uris ) ) ) ;
sch . erase ( attr ) ;
}
# endif
attr = sch . find ( " additionalProperties " ) ;
if ( attr ! = sch . end ( ) ) {
additionalProperties_ = schema : : make ( attr . value ( ) , root , { " additionalProperties " } , uris ) ;
sch . erase ( attr ) ;
}
attr = sch . find ( " dependencies " ) ;
if ( attr ! = sch . end ( ) ) {
for ( auto & dep : attr . value ( ) . items ( ) )
switch ( dep . value ( ) . type ( ) ) {
case json : : value_t : : array :
dependencies_ . emplace ( dep . key ( ) ,
std : : make_shared < required > (
dep . value ( ) . get < std : : vector < std : : string > > ( ) , root ) ) ;
break ;
default :
dependencies_ . emplace ( dep . key ( ) ,
schema : : make ( dep . value ( ) , root , { " dependencies " , dep . key ( ) } , uris ) ) ;
break ;
}
sch . erase ( attr ) ;
}
attr = sch . find ( " propertyNames " ) ;
if ( attr ! = sch . end ( ) ) {
propertyNames_ = schema : : make ( attr . value ( ) , root , { " propertyNames " } , uris ) ;
sch . erase ( attr ) ;
}
2024-04-13 00:00:12 +00:00
attr = sch . find ( " default " ) ;
if ( attr ! = sch . end ( ) ) {
set_default_value ( * attr ) ;
}
2021-03-12 05:59:23 +00:00
}
} ;
class array : public schema
{
std : : pair < bool , size_t > maxItems_ { false , 0 } ;
std : : pair < bool , size_t > minItems_ { false , 0 } ;
bool uniqueItems_ = false ;
std : : shared_ptr < schema > items_schema_ ;
std : : vector < std : : shared_ptr < schema > > items_ ;
std : : shared_ptr < schema > additionalItems_ ;
std : : shared_ptr < schema > contains_ ;
void validate ( const json : : json_pointer & ptr , const json & instance , json_patch & patch , error_handler & e ) const override
{
if ( maxItems_ . first & & instance . size ( ) > maxItems_ . second )
e . error ( ptr , instance , " array has too many items " ) ;
if ( minItems_ . first & & instance . size ( ) < minItems_ . second )
e . error ( ptr , instance , " array has too few items " ) ;
if ( uniqueItems_ ) {
for ( auto it = instance . cbegin ( ) ; it ! = instance . cend ( ) ; + + it ) {
auto v = std : : find ( it + 1 , instance . end ( ) , * it ) ;
if ( v ! = instance . end ( ) )
e . error ( ptr , instance , " items have to be unique for this array " ) ;
}
}
size_t index = 0 ;
if ( items_schema_ )
for ( auto & i : instance ) {
items_schema_ - > validate ( ptr / index , i , patch , e ) ;
index + + ;
}
else {
auto item = items_ . cbegin ( ) ;
for ( auto & i : instance ) {
std : : shared_ptr < schema > item_validator ;
if ( item = = items_ . cend ( ) )
item_validator = additionalItems_ ;
else {
item_validator = * item ;
item + + ;
}
if ( ! item_validator )
break ;
item_validator - > validate ( ptr / index , i , patch , e ) ;
}
}
if ( contains_ ) {
bool contained = false ;
for ( auto & item : instance ) {
first_error_handler local_e ;
contains_ - > validate ( ptr , item , patch , local_e ) ;
if ( ! local_e ) {
contained = true ;
break ;
}
}
if ( ! contained )
e . error ( ptr , instance , " array does not contain required element as per 'contains' " ) ;
}
}
public :
array ( json & sch , root_schema * root , const std : : vector < nlohmann : : json_uri > & uris )
: schema ( root )
{
auto attr = sch . find ( " maxItems " ) ;
if ( attr ! = sch . end ( ) ) {
2022-11-04 02:22:35 +00:00
maxItems_ = { true , attr . value ( ) . get < size_t > ( ) } ;
2021-03-12 05:59:23 +00:00
sch . erase ( attr ) ;
}
attr = sch . find ( " minItems " ) ;
if ( attr ! = sch . end ( ) ) {
2022-11-04 02:22:35 +00:00
minItems_ = { true , attr . value ( ) . get < size_t > ( ) } ;
2021-03-12 05:59:23 +00:00
sch . erase ( attr ) ;
}
attr = sch . find ( " uniqueItems " ) ;
if ( attr ! = sch . end ( ) ) {
2022-11-04 02:22:35 +00:00
uniqueItems_ = attr . value ( ) . get < bool > ( ) ;
2021-03-12 05:59:23 +00:00
sch . erase ( attr ) ;
}
attr = sch . find ( " items " ) ;
if ( attr ! = sch . end ( ) ) {
if ( attr . value ( ) . type ( ) = = json : : value_t : : array ) {
size_t c = 0 ;
for ( auto & subsch : attr . value ( ) )
items_ . push_back ( schema : : make ( subsch , root , { " items " , std : : to_string ( c + + ) } , uris ) ) ;
auto attr_add = sch . find ( " additionalItems " ) ;
if ( attr_add ! = sch . end ( ) ) {
additionalItems_ = schema : : make ( attr_add . value ( ) , root , { " additionalItems " } , uris ) ;
sch . erase ( attr_add ) ;
}
} else if ( attr . value ( ) . type ( ) = = json : : value_t : : object | |
attr . value ( ) . type ( ) = = json : : value_t : : boolean )
items_schema_ = schema : : make ( attr . value ( ) , root , { " items " } , uris ) ;
sch . erase ( attr ) ;
}
attr = sch . find ( " contains " ) ;
if ( attr ! = sch . end ( ) ) {
contains_ = schema : : make ( attr . value ( ) , root , { " contains " } , uris ) ;
sch . erase ( attr ) ;
}
}
} ;
std : : shared_ptr < schema > type_schema : : make ( json & schema ,
json : : value_t type ,
root_schema * root ,
const std : : vector < nlohmann : : json_uri > & uris ,
std : : set < std : : string > & kw )
{
switch ( type ) {
case json : : value_t : : null :
return std : : make_shared < null > ( schema , root ) ;
case json : : value_t : : number_unsigned :
case json : : value_t : : number_integer :
return std : : make_shared < numeric < json : : number_integer_t > > ( schema , root , kw ) ;
case json : : value_t : : number_float :
return std : : make_shared < numeric < json : : number_float_t > > ( schema , root , kw ) ;
case json : : value_t : : string :
return std : : make_shared < string > ( schema , root ) ;
case json : : value_t : : boolean :
return std : : make_shared < boolean_type > ( schema , root ) ;
case json : : value_t : : object :
return std : : make_shared < object > ( schema , root , uris ) ;
case json : : value_t : : array :
return std : : make_shared < array > ( schema , root , uris ) ;
case json : : value_t : : discarded : // not a real type - silence please
break ;
case json : : value_t : : binary :
break ;
}
return nullptr ;
}
} // namespace
namespace
{
std : : shared_ptr < schema > schema : : make ( json & schema ,
root_schema * root ,
const std : : vector < std : : string > & keys ,
std : : vector < nlohmann : : json_uri > uris )
{
// remove URIs which contain plain name identifiers, as sub-schemas cannot be referenced
for ( auto uri = uris . begin ( ) ; uri ! = uris . end ( ) ; )
if ( uri - > identifier ( ) ! = " " )
uri = uris . erase ( uri ) ;
else
uri + + ;
// append to all URIs the keys for this sub-schema
for ( auto & key : keys )
for ( auto & uri : uris )
uri = uri . append ( key ) ;
std : : shared_ptr < : : schema > sch ;
// boolean schema
if ( schema . type ( ) = = json : : value_t : : boolean )
sch = std : : make_shared < boolean > ( schema , root ) ;
else if ( schema . type ( ) = = json : : value_t : : object ) {
auto attr = schema . find ( " $id " ) ; // if $id is present, this schema can be referenced by this ID
// as an additional URI
if ( attr ! = schema . end ( ) ) {
if ( std : : find ( uris . begin ( ) ,
uris . end ( ) ,
attr . value ( ) . get < std : : string > ( ) ) = = uris . end ( ) )
2022-11-04 02:22:35 +00:00
uris . push_back ( uris . back ( ) . derive ( attr . value ( ) . get < std : : string > ( ) ) ) ; // so add it to the list if it is not there already
2021-03-12 05:59:23 +00:00
schema . erase ( attr ) ;
}
attr = schema . find ( " definitions " ) ;
if ( attr ! = schema . end ( ) ) {
for ( auto & def : attr . value ( ) . items ( ) )
schema : : make ( def . value ( ) , root , { " definitions " , def . key ( ) } , uris ) ;
schema . erase ( attr ) ;
}
attr = schema . find ( " $ref " ) ;
if ( attr ! = schema . end ( ) ) { // this schema is a reference
// the last one on the uri-stack is the last id seen before coming here,
// so this is the origial URI for this reference, the $ref-value has thus be resolved from it
2022-11-04 02:22:35 +00:00
auto id = uris . back ( ) . derive ( attr . value ( ) . get < std : : string > ( ) ) ;
2021-03-12 05:59:23 +00:00
sch = root - > get_or_create_ref ( id ) ;
2022-11-04 02:22:35 +00:00
2021-03-12 05:59:23 +00:00
schema . erase ( attr ) ;
2022-11-04 02:22:35 +00:00
// special case where we break draft-7 and allow overriding of properties when a $ref is used
attr = schema . find ( " default " ) ;
if ( attr ! = schema . end ( ) ) {
// copy the referenced schema depending on the underlying type and modify the default value
if ( auto new_sch = sch - > make_for_default_ ( sch , root , uris , attr . value ( ) ) ) {
sch = new_sch ;
}
schema . erase ( attr ) ;
}
2021-03-12 05:59:23 +00:00
} else {
sch = std : : make_shared < type_schema > ( schema , root , uris ) ;
}
schema . erase ( " $schema " ) ;
schema . erase ( " title " ) ;
schema . erase ( " description " ) ;
} else {
throw std : : invalid_argument ( " invalid JSON-type for a schema for " + uris [ 0 ] . to_string ( ) + " , expected: boolean or object " ) ;
}
for ( auto & uri : uris ) { // for all URIs this schema is referenced by
root - > insert ( uri , sch ) ;
if ( schema . type ( ) = = json : : value_t : : object )
for ( auto & u : schema . items ( ) )
root - > insert_unknown_keyword ( uri , u . key ( ) , u . value ( ) ) ; // insert unknown keywords for later reference
}
return sch ;
}
class throwing_error_handler : public error_handler
{
void error ( const json : : json_pointer & ptr , const json & instance , const std : : string & message ) override
{
throw std : : invalid_argument ( std : : string ( " At " ) + ptr . to_string ( ) + " of " + instance . dump ( ) + " - " + message + " \n " ) ;
}
} ;
} // namespace
namespace nlohmann
{
namespace json_schema
{
json_validator : : json_validator ( schema_loader loader ,
format_checker format ,
content_checker content )
: root_ ( std : : unique_ptr < root_schema > ( new root_schema ( std : : move ( loader ) ,
std : : move ( format ) ,
std : : move ( content ) ) ) )
{
}
json_validator : : json_validator ( const json & schema ,
schema_loader loader ,
format_checker format ,
content_checker content )
: json_validator ( std : : move ( loader ) ,
std : : move ( format ) ,
std : : move ( content ) )
{
set_root_schema ( schema ) ;
}
json_validator : : json_validator ( json & & schema ,
schema_loader loader ,
format_checker format ,
content_checker content )
: json_validator ( std : : move ( loader ) ,
std : : move ( format ) ,
std : : move ( content ) )
{
set_root_schema ( std : : move ( schema ) ) ;
}
// move constructor, destructor and move assignment operator can be defaulted here
// where root_schema is a complete type
json_validator : : json_validator ( json_validator & & ) = default ;
json_validator : : ~ json_validator ( ) = default ;
json_validator & json_validator : : operator = ( json_validator & & ) = default ;
void json_validator : : set_root_schema ( const json & schema )
{
root_ - > set_root_schema ( schema ) ;
}
void json_validator : : set_root_schema ( json & & schema )
{
root_ - > set_root_schema ( std : : move ( schema ) ) ;
}
json json_validator : : validate ( const json & instance ) const
{
throwing_error_handler err ;
return validate ( instance , err ) ;
}
json json_validator : : validate ( const json & instance , error_handler & err , const json_uri & initial_uri ) const
{
json : : json_pointer ptr ;
json_patch patch ;
root_ - > validate ( ptr , instance , patch , err , initial_uri ) ;
return patch ;
}
} // namespace json_schema
2024-04-13 00:00:12 +00:00
} // namespace nlohmann