json: add basic json-schema draft 4 support
This commit is contained in:
parent
f73b39f063
commit
5674f2a2e9
@ -85,6 +85,8 @@ UTIL_FILES = \
|
|||||||
json/except.hpp \
|
json/except.hpp \
|
||||||
json/flat.cpp \
|
json/flat.cpp \
|
||||||
json/flat.hpp \
|
json/flat.hpp \
|
||||||
|
json/schema.cpp \
|
||||||
|
json/schema.hpp \
|
||||||
json/tree.cpp \
|
json/tree.cpp \
|
||||||
json/tree.hpp \
|
json/tree.hpp \
|
||||||
lerp.cpp \
|
lerp.cpp \
|
||||||
@ -283,6 +285,6 @@ test_hton_LDFLAGS = -lws2_32
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
TEST_LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) $(top_srcdir)/build-aux/tap-driver.sh
|
TEST_LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) $(top_srcdir)/build-aux/tap-driver.sh
|
||||||
TESTS = $(top_builddir)/test/static.test $(top_builddir)/test/json.test
|
TESTS = $(top_builddir)/test/static.test $(top_builddir)/test/json.test $(top_builddir)/test/json-schema.test
|
||||||
check_PROGRAMS = $(TEST_BIN)
|
check_PROGRAMS = $(TEST_BIN)
|
||||||
EXTRA_DIST += test/static.test test/json.test test/json
|
EXTRA_DIST += test/static.test test/json.test test/json test/json-schema
|
||||||
|
@ -109,5 +109,6 @@ AC_CONFIG_FILES([
|
|||||||
test/Makefile
|
test/Makefile
|
||||||
])
|
])
|
||||||
AC_CONFIG_FILES([test/json.test], [chmod a+x test/json.test])
|
AC_CONFIG_FILES([test/json.test], [chmod a+x test/json.test])
|
||||||
|
AC_CONFIG_FILES([test/json-schema.test], [chmod a+x test/json-schema.test])
|
||||||
AC_CONFIG_FILES([test/static.test], [chmod a+x test/static.test])
|
AC_CONFIG_FILES([test/static.test], [chmod a+x test/static.test])
|
||||||
AC_OUTPUT
|
AC_OUTPUT
|
||||||
|
383
json/schema.cpp
Normal file
383
json/schema.cpp
Normal file
@ -0,0 +1,383 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of libgim.
|
||||||
|
*
|
||||||
|
* libgim is free software: you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU General Public License as published by the Free Software
|
||||||
|
* Foundation, either version 3 of the License, or (at your option) any later
|
||||||
|
* version.
|
||||||
|
*
|
||||||
|
* libgim is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
* details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with libgim. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Copyright 2015 Danny Robson <danny@nerdcruft.net>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "schema.hpp"
|
||||||
|
|
||||||
|
#include "debug.hpp"
|
||||||
|
#include "maths.hpp"
|
||||||
|
#include "./tree.hpp"
|
||||||
|
#include "./except.hpp"
|
||||||
|
|
||||||
|
#include <regex>
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
static bool validate (json::tree::node&, const json::tree::object&);
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
static bool
|
||||||
|
validate (json::tree::object &node,
|
||||||
|
const json::tree::object &schema)
|
||||||
|
{
|
||||||
|
bool success = true;
|
||||||
|
|
||||||
|
auto properties = schema.find ("properties");
|
||||||
|
auto additional = schema.find ("additionalProperties");
|
||||||
|
auto pattern = schema.find ("patternProperties");
|
||||||
|
|
||||||
|
if (properties != schema.cend ()) {
|
||||||
|
for (const auto &kv: properties->second->as_object ()) {
|
||||||
|
auto p = node.find (kv.first);
|
||||||
|
if (p != node.cend ())
|
||||||
|
success = success && validate (*p->second, kv.second->as_object ());
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
node.insert (kv.first, (*kv.second)["default"].clone ());
|
||||||
|
success = success && validate (node[kv.first], kv.second->as_object ());
|
||||||
|
} catch (const json::key_error&)
|
||||||
|
{ ; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pattern != schema.cend ())
|
||||||
|
not_implemented ();
|
||||||
|
|
||||||
|
if (additional != schema.cend ())
|
||||||
|
not_implemented ();
|
||||||
|
|
||||||
|
if (schema.has ("dependencies"))
|
||||||
|
not_implemented ();
|
||||||
|
|
||||||
|
// properties must be checked after the 'properties' check has a chance to
|
||||||
|
// create the defaulted entries.
|
||||||
|
auto maxProperties = schema.find ("maxProperties");
|
||||||
|
if (maxProperties != schema.cend ())
|
||||||
|
success = success && node.size () <= maxProperties->second->as_uint ();
|
||||||
|
|
||||||
|
auto minProperties = schema.find ("minProperties");
|
||||||
|
if (minProperties != schema.cend ())
|
||||||
|
success = success && node.size () >= minProperties->second->as_uint ();
|
||||||
|
|
||||||
|
auto required = schema.find ("required");
|
||||||
|
if (required != schema.cend ())
|
||||||
|
for (const auto &i: required->second->as_array ())
|
||||||
|
success = success && node.has (i.as_string ());
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
static bool
|
||||||
|
validate (json::tree::array &node,
|
||||||
|
const json::tree::object &schema)
|
||||||
|
{
|
||||||
|
bool success = true;
|
||||||
|
|
||||||
|
// attempt to match the item and additionalItem schemas
|
||||||
|
auto items = schema.find ("items");
|
||||||
|
auto additional = schema.find ("additionalItems");
|
||||||
|
|
||||||
|
if (items != schema.cend ()) {
|
||||||
|
// items is an object, test all elements with it as a schema
|
||||||
|
if (items->second->is_object ()) {
|
||||||
|
for (auto &i: node)
|
||||||
|
success = success && validate (i, items->second->as_object ());
|
||||||
|
// items is a list of schemas, test n-elements with it as a schema
|
||||||
|
} else if (items->second->is_array ()) {
|
||||||
|
const auto &itemArray = items->second->as_array ();
|
||||||
|
|
||||||
|
size_t i = 0;
|
||||||
|
for (; i < itemArray.size () && i < node.size (); ++i)
|
||||||
|
success = success && validate (node[i], itemArray[i].as_object ());
|
||||||
|
|
||||||
|
// we've exhausted the schema list, use the additional schema
|
||||||
|
if (i == itemArray.size ()) {
|
||||||
|
if (additional->second->is_boolean ()) {
|
||||||
|
success = success && additional->second->as_boolean ();
|
||||||
|
} else if (additional->second->is_object ()) {
|
||||||
|
for ( ; i < node.size (); ++i)
|
||||||
|
success = success && validate (node[i], additional->second->as_object ());
|
||||||
|
} else {
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto maxItems = schema.find ("maxItems");
|
||||||
|
if (maxItems != schema.cend ())
|
||||||
|
success = success && node.size () <= maxItems->second->as_uint ();
|
||||||
|
|
||||||
|
auto minItems = schema.find ("minItems");
|
||||||
|
if (minItems != schema.cend ())
|
||||||
|
success = success && node.size () >= minItems->second->as_uint ();
|
||||||
|
|
||||||
|
// check all element are unique
|
||||||
|
// XXX: uses a naive n^2 brute force search on equality because it's 2am
|
||||||
|
// and I don't want to write a type aware comparator for the sort.
|
||||||
|
auto unique = schema.find ("uniqueItems");
|
||||||
|
if (unique != schema.cend () && unique->second->as_boolean ()) {
|
||||||
|
for (size_t a = 0; a < node.size (); ++a)
|
||||||
|
for (size_t b = a + 1; b < node.size (); ++b)
|
||||||
|
if (node[a] == node[b]) {
|
||||||
|
success = false;
|
||||||
|
goto notunique;
|
||||||
|
}
|
||||||
|
notunique: ;
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
static bool
|
||||||
|
validate (json::tree::string &node,
|
||||||
|
const json::tree::object &schema)
|
||||||
|
{
|
||||||
|
const auto &val = node.native ();
|
||||||
|
|
||||||
|
// check length is less than a maximum
|
||||||
|
auto maxLength = schema.find ("maxLength");
|
||||||
|
if (maxLength != schema.cend ()) {
|
||||||
|
auto cmp = maxLength->second->as_number ().native ();
|
||||||
|
if (!is_integer (cmp))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (val.size () > cmp)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check length is greater than a maximum
|
||||||
|
auto minLength = schema.find ("minLength");
|
||||||
|
if (minLength != schema.cend ()) {
|
||||||
|
auto cmp = minLength->second->as_number ().native ();
|
||||||
|
if (!is_integer (cmp))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (val.size () < cmp)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the string conforms to a regex
|
||||||
|
// Note: this uses the c++11 regex engine which slightly differs from ECMA 262
|
||||||
|
auto pattern = schema.find ("pattern");
|
||||||
|
if (pattern != schema.cend ()) {
|
||||||
|
std::regex r (pattern->second->as_string ().native (),
|
||||||
|
std::regex_constants::ECMAScript);
|
||||||
|
if (!std::regex_search (val, r))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
static bool
|
||||||
|
validate (json::tree::number &node,
|
||||||
|
const json::tree::object &schema)
|
||||||
|
{
|
||||||
|
const auto &val = node.native ();
|
||||||
|
|
||||||
|
// check strictly positive integer multiple
|
||||||
|
auto mult = schema.find ("multipleOf");
|
||||||
|
if (mult != schema.cend ()) {
|
||||||
|
auto div = mult->second->as_number ().native ();
|
||||||
|
|
||||||
|
if (val <= 0 || almost_equal (val, div))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check maximum holds. exclusive requires max condition.
|
||||||
|
auto max = schema.find ("maximum");
|
||||||
|
auto exclusiveMax = schema.find ("exclusiveMaximum");
|
||||||
|
if (max != schema.cend ()) {
|
||||||
|
auto cmp = max->second->as_number ().native ();
|
||||||
|
|
||||||
|
if (exclusiveMax->second->as_boolean () ? (val <= cmp) : (val < cmp))
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
if (exclusiveMax != schema.cend ())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check minimum holds. exclusive requires min condition
|
||||||
|
auto min = schema.find ("minimum");
|
||||||
|
auto exclusiveMin = schema.find ("exclusiveMinimum");
|
||||||
|
if (min != schema.cend ()) {
|
||||||
|
auto cmp = min->second->as_number ().native ();
|
||||||
|
|
||||||
|
if (exclusiveMin->second->as_boolean () ? val >= cmp : val > cmp)
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
if (exclusiveMin != schema.cend ())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
static bool
|
||||||
|
validate (json::tree::boolean&,
|
||||||
|
const json::tree::object&)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
static bool
|
||||||
|
validate (json::tree::null&,
|
||||||
|
const json::tree::object&)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
static std::string
|
||||||
|
to_string (json::tree::type_t t)
|
||||||
|
{
|
||||||
|
switch (t) {
|
||||||
|
case json::tree::OBJECT: return "object";
|
||||||
|
case json::tree::ARRAY: return "array";
|
||||||
|
case json::tree::STRING: return "string";
|
||||||
|
case json::tree::NUMBER: return "number";
|
||||||
|
case json::tree::BOOLEAN: return "boolean";
|
||||||
|
case json::tree::NONE: return "null";
|
||||||
|
}
|
||||||
|
|
||||||
|
unreachable ();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
static bool
|
||||||
|
validate (json::tree::node &node,
|
||||||
|
const json::tree::object &schema)
|
||||||
|
{
|
||||||
|
// check the value is in the prescribed list
|
||||||
|
auto enumPos = schema.find ("enum");
|
||||||
|
if (enumPos != schema.cend ()) {
|
||||||
|
auto pos = std::find (enumPos->second->as_array ().cbegin (),
|
||||||
|
enumPos->second->as_array ().cend (),
|
||||||
|
node);
|
||||||
|
if (pos == enumPos->second->as_array ().cend ())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto type = schema.find ("type");
|
||||||
|
if (type != schema.cend ()) {
|
||||||
|
if (type->second->is_string ()) {
|
||||||
|
auto a = type->second->as_string ();
|
||||||
|
auto b = to_string (node.type ());
|
||||||
|
|
||||||
|
if (a != b) {
|
||||||
|
std::cerr << a << " != " << b << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (type->second->is_array ()) {
|
||||||
|
auto pos = std::find_if (type->second->as_array ().begin (),
|
||||||
|
type->second->as_array ().end (),
|
||||||
|
[&] (const auto &i) { return i.as_string () == to_string (node.type ()); });
|
||||||
|
if (pos == type->second->as_array ().end ())
|
||||||
|
return false;
|
||||||
|
} else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto allOf = schema.find ("allOf");
|
||||||
|
if (allOf != schema.cend ()) {
|
||||||
|
for (const auto &i: allOf->second->as_array ())
|
||||||
|
if (!validate (node, i.as_object ()))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto anyOf = schema.find ("anyOf");
|
||||||
|
if (anyOf != schema.cend ()) {
|
||||||
|
bool success = false;
|
||||||
|
for (const auto &i: anyOf->second->as_array ()) {
|
||||||
|
success = validate (node, i.as_object ());
|
||||||
|
if (success)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!success)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto oneOf = schema.find ("oneOf");
|
||||||
|
if (oneOf != schema.cend ()) {
|
||||||
|
unsigned count = 0;
|
||||||
|
|
||||||
|
for (const auto &i: oneOf->second->as_array ()) {
|
||||||
|
if (validate (node, i.as_object ()))
|
||||||
|
count++;
|
||||||
|
if (count > 1)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count != 1)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto notSchema = schema.find ("not");
|
||||||
|
if (notSchema != schema.cend ()) {
|
||||||
|
for (const auto &i: notSchema->second->as_array ())
|
||||||
|
if (validate (node, i.as_object ()))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (node.type ()) {
|
||||||
|
case json::tree::OBJECT: return validate (node.as_object (), schema);
|
||||||
|
case json::tree::ARRAY: return validate (node.as_array (), schema);
|
||||||
|
case json::tree::STRING: return validate (node.as_string (), schema);
|
||||||
|
case json::tree::NUMBER: return validate (node.as_number (), schema);
|
||||||
|
case json::tree::BOOLEAN: return validate (node.as_boolean (), schema);
|
||||||
|
case json::tree::NONE: return validate (node.as_null (), schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
unreachable ();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
bool
|
||||||
|
json::schema::validate (json::tree::node &data,
|
||||||
|
const json::tree::object &schema)
|
||||||
|
{
|
||||||
|
auto title = schema.find ("title");
|
||||||
|
if (title != schema.cend ())
|
||||||
|
if (!title->second->is_string ())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto description = schema.find ("description");
|
||||||
|
if (description != schema.cend ())
|
||||||
|
if (!description->second->is_string ())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return ::validate (data, schema.as_object ());
|
||||||
|
}
|
33
json/schema.hpp
Normal file
33
json/schema.hpp
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of libgim.
|
||||||
|
*
|
||||||
|
* libgim is free software: you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU General Public License as published by the Free Software
|
||||||
|
* Foundation, either version 3 of the License, or (at your option) any later
|
||||||
|
* version.
|
||||||
|
*
|
||||||
|
* libgim is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
* details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with libgim. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Copyright 2015 Danny Robson <danny@nerdcruft.net>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __UTIL_JSON_SCHEMA_HPP
|
||||||
|
#define __UTIL_JSON_SCHEMA_HPP
|
||||||
|
|
||||||
|
#include "./fwd.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace json { namespace schema {
|
||||||
|
bool
|
||||||
|
validate (json::tree::node &data,
|
||||||
|
const json::tree::object &schema);
|
||||||
|
} }
|
||||||
|
|
||||||
|
#endif
|
38
test/json-schema.test.in
Normal file
38
test/json-schema.test.in
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import glob
|
||||||
|
import os.path
|
||||||
|
import subprocess
|
||||||
|
import re
|
||||||
|
|
||||||
|
SDIR = "@abs_top_srcdir@"
|
||||||
|
BDIR = "@abs_top_builddir@"
|
||||||
|
|
||||||
|
TOOL = os.path.join(BDIR, "tools/json-schema")
|
||||||
|
|
||||||
|
TEST_EXTRACT = re.compile("(.*?)_(\d{4})_(pass|fail).json")
|
||||||
|
|
||||||
|
SCHEMA_DIR = os.path.join(SDIR, "test/json/schema")
|
||||||
|
SCHEMAS = glob.iglob(os.path.join(SCHEMA_DIR, "*.schema"))
|
||||||
|
|
||||||
|
EXPECTED = {
|
||||||
|
"pass": 0,
|
||||||
|
"fail": 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
print("1..%s" % len(glob.glob(os.path.join(SCHEMA_DIR, "*.json"))))
|
||||||
|
|
||||||
|
for schema in SCHEMAS:
|
||||||
|
(name, _) = os.path.splitext(os.path.basename(schema))
|
||||||
|
test_glob = name + "_*.json"
|
||||||
|
|
||||||
|
for test in glob.iglob(os.path.join(SCHEMA_DIR, test_glob)):
|
||||||
|
command = [TOOL, schema, test]
|
||||||
|
(name, seq, success) = TEST_EXTRACT.match(test).groups()
|
||||||
|
res = subprocess.call(command, stdout=subprocess.DEVNULL,stderr=subprocess.STDOUT)
|
||||||
|
|
||||||
|
if res != EXPECTED[success]:
|
||||||
|
print('not ok -', os.path.basename(test), '#', ' '.join(command))
|
||||||
|
else:
|
||||||
|
print('ok -', os.path.basename(test))
|
1
test/json/schema/empty.schema
Normal file
1
test/json/schema/empty.schema
Normal file
@ -0,0 +1 @@
|
|||||||
|
{}
|
1
test/json/schema/empty_0001_pass.json
Normal file
1
test/json/schema/empty_0001_pass.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
[1,"a", {}]
|
1
test/json/schema/empty_0002_pass.json
Normal file
1
test/json/schema/empty_0002_pass.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
"foo"
|
7
test/json/schema/string_length.schema
Normal file
7
test/json/schema/string_length.schema
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 2,
|
||||||
|
"maxLength": 6
|
||||||
|
}
|
1
test/json/schema/string_length_0001_pass.json
Normal file
1
test/json/schema/string_length_0001_pass.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
"ab"
|
1
test/json/schema/string_length_0002_pass.json
Normal file
1
test/json/schema/string_length_0002_pass.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
"abcdef"
|
1
test/json/schema/string_length_0003_fail.json
Normal file
1
test/json/schema/string_length_0003_fail.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
"a"
|
1
test/json/schema/string_length_0004_fail.json
Normal file
1
test/json/schema/string_length_0004_fail.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
"abcdefg"
|
1
test/json/schema/type_any.schema
Normal file
1
test/json/schema/type_any.schema
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"type": ["object", "array", "string", "number", "boolean", "null"]}
|
1
test/json/schema/type_any_0001_pass.json
Normal file
1
test/json/schema/type_any_0001_pass.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{}
|
1
test/json/schema/type_any_0002_pass.json
Normal file
1
test/json/schema/type_any_0002_pass.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
[]
|
1
test/json/schema/type_any_0003_pass.json
Normal file
1
test/json/schema/type_any_0003_pass.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
"foo"
|
1
test/json/schema/type_any_0004_pass.json
Normal file
1
test/json/schema/type_any_0004_pass.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
3.14
|
1
test/json/schema/type_any_0005_pass.json
Normal file
1
test/json/schema/type_any_0005_pass.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
true
|
1
test/json/schema/type_any_0006_pass.json
Normal file
1
test/json/schema/type_any_0006_pass.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
null
|
1
test/json/schema/type_number.schema
Normal file
1
test/json/schema/type_number.schema
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"type":"number"}
|
1
test/json/schema/type_number_0001_pass.json
Normal file
1
test/json/schema/type_number_0001_pass.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
3.14
|
1
test/json/schema/type_number_0002_pass.json
Normal file
1
test/json/schema/type_number_0002_pass.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
3
|
1
test/json/schema/type_number_0003_fail.json
Normal file
1
test/json/schema/type_number_0003_fail.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
"foo"
|
1
test/json/schema/type_object.schema
Normal file
1
test/json/schema/type_object.schema
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"type":"object"}
|
1
test/json/schema/type_object_0001_pass.json
Normal file
1
test/json/schema/type_object_0001_pass.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{}
|
1
test/json/schema/type_object_0002_fail.json
Normal file
1
test/json/schema/type_object_0002_fail.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
[]
|
@ -14,31 +14,20 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with libgim. If not, see <http://www.gnu.org/licenses/>.
|
* along with libgim. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
* Copyright 2012 Danny Robson <danny@nerdcruft.net>
|
* Copyright 2015 Danny Robson <danny@nerdcruft.net>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
#include "json/except.hpp"
|
#include "json/except.hpp"
|
||||||
#include "json/tree.hpp"
|
#include "json/tree.hpp"
|
||||||
|
#include "json/schema.hpp"
|
||||||
#include "debug.hpp"
|
|
||||||
#include "maths.hpp"
|
|
||||||
|
|
||||||
#include <cmath>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <functional>
|
|
||||||
#include <iostream>
|
|
||||||
#include <map>
|
|
||||||
#include <memory>
|
|
||||||
#include <regex>
|
|
||||||
|
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
|
|
||||||
using namespace std;
|
namespace fs = boost::filesystem;
|
||||||
using namespace std::placeholders;
|
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
ARG_COMMAND,
|
ARG_CMD,
|
||||||
ARG_SCHEMA,
|
ARG_SCHEMA,
|
||||||
ARG_INPUT,
|
ARG_INPUT,
|
||||||
|
|
||||||
@ -46,563 +35,17 @@ enum {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
print_usage (int argc, char **argv) {
|
|
||||||
if (argc <= ARG_COMMAND)
|
|
||||||
abort ();
|
|
||||||
|
|
||||||
std::cerr << "Usage: " << argv[ARG_COMMAND] << " <schema> <input>\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const char*
|
|
||||||
type_to_string (const json::tree::node &node) {
|
|
||||||
if (node.is_array ()) return "array";
|
|
||||||
if (node.is_boolean ()) return "boolean";
|
|
||||||
if (node.is_null ()) return "null";
|
|
||||||
if (node.is_number ()) return "number";
|
|
||||||
if (node.is_object ()) return "object";
|
|
||||||
if (node.is_string ()) return "string";
|
|
||||||
|
|
||||||
unreachable ();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
is_node_valid (const json::tree::node &node,
|
|
||||||
const json::tree::object &schema);
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
is_type_valid (const json::tree::node &node,
|
|
||||||
const json::tree::node &type) {
|
|
||||||
if (type.is_array ()) {
|
|
||||||
return any_of (type.as_array ().begin (),
|
|
||||||
type.as_array ().end (),
|
|
||||||
bind (is_type_valid, ref (node), _1));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!type.is_string ())
|
|
||||||
throw json::schema_error ("schema type requires array, string, or object");
|
|
||||||
|
|
||||||
static const auto ANY_VALIDATOR = [] (const json::tree::node &) { return true; };
|
|
||||||
static const auto INT_VALIDATOR = [] (const json::tree::node &n) {
|
|
||||||
return n.is_number () && is_integer (n.as_number ());
|
|
||||||
};
|
|
||||||
|
|
||||||
static const map<string, function<bool(const json::tree::node&)>> TYPE_VALIDATORS ({
|
|
||||||
{ "array", bind (&json::tree::node::is_array, _1) },
|
|
||||||
{ "boolean", bind (&json::tree::node::is_boolean, _1) },
|
|
||||||
{ "null", bind (&json::tree::node::is_null, _1) },
|
|
||||||
{ "number", bind (&json::tree::node::is_number, _1) },
|
|
||||||
{ "object", bind (&json::tree::node::is_object, _1) },
|
|
||||||
{ "string", bind (&json::tree::node::is_string, _1) },
|
|
||||||
{ "any", ANY_VALIDATOR },
|
|
||||||
{ "integer", INT_VALIDATOR },
|
|
||||||
});
|
|
||||||
|
|
||||||
auto pos = TYPE_VALIDATORS.find (type.as_string ().native ());
|
|
||||||
if (pos == TYPE_VALIDATORS.end ()) {
|
|
||||||
std::cerr << "warning: unknown type " << type.as_string ().native () << ". assuming valid.\n";
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return pos->second(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
is_disallow_valid (const json::tree::node &node,
|
|
||||||
const json::tree::node &constraint)
|
|
||||||
{ return !is_type_valid (node, constraint); }
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
is_enum_valid (const json::tree::node &node,
|
|
||||||
const json::tree::node &constraint) {
|
|
||||||
if (!constraint.is_array ())
|
|
||||||
throw json::schema_error ("enum validation requires an array");
|
|
||||||
|
|
||||||
const json::tree::array &valids = constraint.as_array ();
|
|
||||||
return valids.end () != std::find (valids.begin (),
|
|
||||||
valids.end (),
|
|
||||||
node);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
is_enum_valid (const json::tree::string &node,
|
|
||||||
const json::tree::node &constraint) {
|
|
||||||
return is_enum_valid (static_cast<const json::tree::node&> (node), constraint);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
is_always_valid (const json::tree::node &,
|
|
||||||
const json::tree::node &)
|
|
||||||
{ return true; }
|
|
||||||
|
|
||||||
|
|
||||||
/*static const map<string, validator_t> COMMON_VALIDATORS ({
|
|
||||||
{ "description", &is_always_valid },
|
|
||||||
{ "disallow", &is_disallow_valid },
|
|
||||||
{ "enum", &is_enum_valid },
|
|
||||||
{ "title", &is_always_valid },
|
|
||||||
{ "type", &is_type_valid },
|
|
||||||
});*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
is_boolean_valid (const json::tree::node &node,
|
|
||||||
const json::tree::object &)
|
|
||||||
{ return node.is_boolean (); }
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
is_null_valid (const json::tree::node &node,
|
|
||||||
const json::tree::object &)
|
|
||||||
{ return node.is_null (); }
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
// JSON number
|
|
||||||
//
|
|
||||||
|
|
||||||
bool
|
|
||||||
is_minimum_valid (const json::tree::number &node,
|
|
||||||
const json::tree::node &constraint) {
|
|
||||||
return constraint["minimum"].as_number () <= node;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
is_maximum_valid (const json::tree::number &node,
|
|
||||||
const json::tree::node &constraint) {
|
|
||||||
return constraint["maximum"].as_number () >= node;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
is_exclusive_minimum_valid (const json::tree::number &node,
|
|
||||||
const json::tree::node &constraint) {
|
|
||||||
return constraint["exclusiveMinimum"].as_number () < node;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
is_exclusive_maximum_valid (const json::tree::number &node,
|
|
||||||
const json::tree::node &constraint) {
|
|
||||||
return constraint["exclusiveMaximum"].as_number () > node;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
is_divisible_by_valid (const json::tree::number &node,
|
|
||||||
const json::tree::node &constraint) {
|
|
||||||
return exactly_equal (fmod (node.native (),
|
|
||||||
constraint["divisibleBy"].as_number ()),
|
|
||||||
0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
is_number_valid (const json::tree::number &node,
|
|
||||||
const json::tree::object &schema) {
|
|
||||||
typedef bool (*number_validator_t)(const json::tree::number&, const json::tree::node&);
|
|
||||||
static const map<string, number_validator_t> VALIDATORS = {
|
|
||||||
{ "minimum", &is_minimum_valid },
|
|
||||||
{ "maximum", &is_maximum_valid },
|
|
||||||
{ "exclusiveMinimum", &is_exclusive_minimum_valid },
|
|
||||||
{ "exclusiveMaximum", &is_exclusive_maximum_valid },
|
|
||||||
{ "divisibleBy", &is_divisible_by_valid },
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const auto &i: schema) {
|
|
||||||
const std::string &key = i.first;
|
|
||||||
const auto &validator = VALIDATORS.find (key);
|
|
||||||
if (validator == VALIDATORS.end ()) {
|
|
||||||
std::cerr << "Unknown validation constraint: " << key << "\n";
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validator->second (node, schema))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
// JSON string
|
|
||||||
//
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
is_min_length_valid (const json::tree::string &node,
|
|
||||||
const json::tree::node &constraint) {
|
|
||||||
if (!is_integer (constraint))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return node.size () >= constraint.as_number ();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
is_max_length_valid (const json::tree::string &node,
|
|
||||||
const json::tree::node &constraint) {
|
|
||||||
if (!is_integer (constraint))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return node.size () <= constraint.as_number ();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
is_pattern_valid (const json::tree::string &node,
|
|
||||||
const json::tree::node &constraint) {
|
|
||||||
if (!constraint.is_string ())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
regex pattern (constraint.as_string ().native (),
|
|
||||||
regex_constants::ECMAScript);
|
|
||||||
return regex_match (node.native (), pattern);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
is_string_valid (const json::tree::string &node,
|
|
||||||
const json::tree::object &schema) {
|
|
||||||
typedef bool (*string_validator_t)(const json::tree::string&, const json::tree::node&);
|
|
||||||
static const map<std::string, string_validator_t> VALIDATORS = {
|
|
||||||
{ "minLength", &is_min_length_valid },
|
|
||||||
{ "maxLength", &is_max_length_valid },
|
|
||||||
{ "pattern", &is_pattern_valid },
|
|
||||||
{ "enum", &is_enum_valid },
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const json::tree::object::const_iterator::value_type &i: schema) {
|
|
||||||
const std::string &key = i.first;
|
|
||||||
const json::tree::node &constraint = *i.second;
|
|
||||||
|
|
||||||
auto validator = VALIDATORS.find (key);
|
|
||||||
if (validator == VALIDATORS.end ()) {
|
|
||||||
std::cerr << "Unknown validation constraint: " << key << "\n";
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validator->second (node, constraint)) {
|
|
||||||
std::cerr << "Failed string constraint: " << key << "\n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
is_string_valid (const json::tree::node &node,
|
|
||||||
const json::tree::object &schema) {
|
|
||||||
if (!node.is_string ())
|
|
||||||
return false;
|
|
||||||
return is_string_valid (node.as_string (), schema);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// JSON array
|
|
||||||
//
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
is_max_items_valid (const json::tree::array &node,
|
|
||||||
const json::tree::node &constraint) {
|
|
||||||
if (!constraint.is_number () && is_integer (constraint.as_number ()))
|
|
||||||
throw json::schema_error ("max_items should be an integer");
|
|
||||||
|
|
||||||
return node.size () <= constraint.as_number ();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
is_min_items_valid (const json::tree::array &node,
|
|
||||||
const json::tree::node &constraint) {
|
|
||||||
if (!constraint.is_number () && is_integer (constraint.as_number ()))
|
|
||||||
throw json::schema_error ("min_items should be an integer");
|
|
||||||
|
|
||||||
return node.size () >= constraint.as_number ();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
is_unique_items_valid (const json::tree::array &node,
|
|
||||||
const json::tree::node &constraint) {
|
|
||||||
if (!constraint.is_boolean ())
|
|
||||||
throw json::schema_error ("uniqueItems must be a boolean");
|
|
||||||
|
|
||||||
if (node.size () < 2)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
|
|
||||||
for (json::tree::array::const_iterator i = node.begin (); i != node.end () - 1; ++i) {
|
|
||||||
if (find (i + 1, node.end (), *i) != node.end ())
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
is_items_valid (const json::tree::array &node,
|
|
||||||
const json::tree::node &_schema) {
|
|
||||||
if (!_schema.is_object ())
|
|
||||||
throw json::schema_error ("array_items constraint must be an object");
|
|
||||||
const json::tree::object &schema = _schema.as_object ();
|
|
||||||
|
|
||||||
for (const json::tree::node &i: node)
|
|
||||||
if (!is_node_valid (i, schema))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
not_implemented ();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
is_additional_items_valid (const json::tree::array &,
|
|
||||||
const json::tree::node &) {
|
|
||||||
not_implemented ();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
is_array_valid (const json::tree::array &node,
|
|
||||||
const json::tree::object &schema) {
|
|
||||||
CHECK (node.is_array ());
|
|
||||||
|
|
||||||
typedef bool (*array_validator_t)(const json::tree::array&, const json::tree::node&);
|
|
||||||
static const map<string, array_validator_t> VALIDATORS ({
|
|
||||||
{ "items", &is_items_valid },
|
|
||||||
{ "minItems", &is_min_items_valid },
|
|
||||||
{ "maxItems", &is_max_items_valid },
|
|
||||||
{ "uniqueItems", &is_unique_items_valid },
|
|
||||||
{ "additionalItems", &is_additional_items_valid },
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const json::tree::object::const_iterator::value_type &i: schema) {
|
|
||||||
const std::string &key = i.first;
|
|
||||||
const json::tree::node &constraint = *i.second;
|
|
||||||
|
|
||||||
auto validator = VALIDATORS.find (key);
|
|
||||||
if (validator == VALIDATORS.end ()) {
|
|
||||||
std::cerr << "Ignoring unknown contraint key: " << key << "\n";
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validator->second (node, constraint)) {
|
|
||||||
std::cerr << "Failed validating array constraint: " << key << "\n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
// JSON object
|
|
||||||
//
|
|
||||||
|
|
||||||
bool
|
|
||||||
is_properties_valid (const json::tree::object &node,
|
|
||||||
const json::tree::object &schema) {
|
|
||||||
for (const json::tree::object::const_iterator::value_type &element: node) {
|
|
||||||
const std::string &key = element.first;
|
|
||||||
const json::tree::node &val = *element.second;
|
|
||||||
|
|
||||||
if (!schema.has (key)) {
|
|
||||||
std::cerr << "[warning] no constraint found for key: " << key << "\n";
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is_node_valid (val, schema[key].as_object ())) {
|
|
||||||
std::cerr << "failed validation on property: " << key << "\n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
is_properties_valid (const json::tree::object &node,
|
|
||||||
const json::tree::node &constraint) {
|
|
||||||
CHECK (node.is_object ());
|
|
||||||
|
|
||||||
if (!constraint.is_object ())
|
|
||||||
throw json::schema_error ("properties needs an object");
|
|
||||||
|
|
||||||
return is_properties_valid (node, constraint.as_object ());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
is_object_valid (const json::tree::object &node,
|
|
||||||
const json::tree::object &schema) {
|
|
||||||
typedef bool (*object_validator_t)(const json::tree::object&, const json::tree::node&);
|
|
||||||
static const map<string, object_validator_t> VALIDATORS = {
|
|
||||||
{ "properties", &is_properties_valid },
|
|
||||||
//{ "patternProperties", &is_pattern_properties_valid },
|
|
||||||
//{ "additionalProperties", &is_additionaL_properties_valid },
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const json::tree::object::const_iterator::value_type &i: schema) {
|
|
||||||
const std::string &name = i.first;
|
|
||||||
const json::tree::node &constraint = *i.second;
|
|
||||||
|
|
||||||
auto validator = VALIDATORS.find (name);
|
|
||||||
if (validator == VALIDATORS.end ()) {
|
|
||||||
std::cerr << "Unknown constraint name \"" << name << "\". Ignoring.\n";
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validator->second(node, constraint)) {
|
|
||||||
std::cerr << "Failed validation on: " << name << "\n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
is_object_valid (const json::tree::node &node,
|
|
||||||
const json::tree::object &schema) {
|
|
||||||
if (!node.is_object ())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return is_object_valid (node.as_object (), schema);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
// JSON node
|
|
||||||
//
|
|
||||||
|
|
||||||
bool
|
|
||||||
is_node_valid (const json::tree::node &node,
|
|
||||||
const json::tree::object &schema) {
|
|
||||||
if (schema.has ("$ref")) {
|
|
||||||
const std::string &uri = schema["$ref"].as_string ();
|
|
||||||
std::cerr << "loading referenced schema: " << uri << "\n";
|
|
||||||
|
|
||||||
if (uri[0] == '#') {
|
|
||||||
std::cerr << "[error] schema fragments are not supported\n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto referenced = json::tree::parse (boost::filesystem::path (uri));
|
|
||||||
return is_node_valid (node, referenced->as_object ());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (schema.has ("type") &&
|
|
||||||
!is_type_valid (node, schema["type"]))
|
|
||||||
{
|
|
||||||
std::cerr << "node type is \"" << type_to_string (node) << "\", expected " << schema["type"] << "\n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define IS_VALID(T) \
|
|
||||||
do { \
|
|
||||||
if (node.is_##T ()) { \
|
|
||||||
if (!is_##T##_valid (node.as_##T (), schema)) { \
|
|
||||||
std::cerr << "Failed validation as " #T "\n"; \
|
|
||||||
return false; \
|
|
||||||
} \
|
|
||||||
return true; \
|
|
||||||
} \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
IS_VALID(array);
|
|
||||||
IS_VALID(boolean);
|
|
||||||
IS_VALID(null);
|
|
||||||
IS_VALID(number);
|
|
||||||
IS_VALID(object);
|
|
||||||
IS_VALID(string);
|
|
||||||
|
|
||||||
#undef IS_VALID
|
|
||||||
|
|
||||||
return false;
|
|
||||||
|
|
||||||
|
|
||||||
/*static const map<string, validator_t> VALIDATORS ({
|
|
||||||
{ "description", &is_always_valid },
|
|
||||||
{ "disallow", &is_disallow_valid },
|
|
||||||
{ "enum", &is_enum_valid },
|
|
||||||
{ "title", &is_always_valid },
|
|
||||||
{ "type", &is_type_valid },
|
|
||||||
});*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//"required";
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
is_root_valid (const json::tree::node &node,
|
|
||||||
const json::tree::object &schema) {
|
|
||||||
if (!node.is_array () && !node.is_object ())
|
|
||||||
return false;
|
|
||||||
return is_node_valid (node, schema);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
// Driver
|
|
||||||
//
|
|
||||||
|
|
||||||
int
|
int
|
||||||
main (int argc, char **argv) {
|
main (int argc, char **argv) {
|
||||||
// Basic argument checking
|
|
||||||
if (argc != NUM_ARGS) {
|
if (argc != NUM_ARGS) {
|
||||||
print_usage (argc, argv);
|
std::cerr << argv[ARG_CMD] << " <schema> <json>\n";
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the schema and input
|
auto schema = json::tree::parse (fs::path (argv[ARG_SCHEMA]));
|
||||||
unique_ptr<json::tree::node> schema, input;
|
auto input = json::tree::parse (fs::path (argv[ARG_INPUT]));
|
||||||
try {
|
|
||||||
schema = json::tree::parse (boost::filesystem::path (argv[ARG_SCHEMA]));
|
|
||||||
input = json::tree::parse (boost::filesystem::path (argv[ARG_INPUT]));
|
|
||||||
} catch (const json::parse_error &err) {
|
|
||||||
std::cerr << "malformed json for schema or input. " << err.what () << "\n";
|
|
||||||
return EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check schema is valid
|
bool success = json::schema::validate (*input, schema->as_object ());
|
||||||
if (!schema->is_object ()) {
|
std::cerr << (success ? "success\n" : "failure\n");
|
||||||
std::cerr << "Schema should be an object\n";
|
return success ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||||
return EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
const json::tree::object &schema_object = schema->as_object ();
|
|
||||||
|
|
||||||
// Check input is valid
|
|
||||||
if (!is_node_valid (*input, schema_object)) {
|
|
||||||
std::cerr << "input does not satisfy the schema\n";
|
|
||||||
return EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user