json: move json code to external module

This module can now be found at git://git.nerdcruft.net/libcruft-json.git
This commit is contained in:
Danny Robson 2018-08-04 15:02:06 +10:00
parent c6760c8566
commit 3aaddd1d2b
264 changed files with 4 additions and 6189 deletions

View File

@ -22,7 +22,6 @@ endif()
###############################################################################
RAGEL_TARGET(json-flat json/flat.cpp.rl ${CMAKE_CURRENT_BINARY_DIR}/json/flat.cpp COMPILE_FLAGS -G2)
RAGEL_TARGET(uri uri.cpp.rl ${CMAKE_CURRENT_BINARY_DIR}/uri.cpp COMPILE_FLAGS -G2)
RAGEL_TARGET(version version.cpp.rl ${CMAKE_CURRENT_BINARY_DIR}/version.cpp)
RAGEL_TARGET(format.cpp format.cpp.rl ${CMAKE_CURRENT_BINARY_DIR}/format.cpp)
@ -291,46 +290,6 @@ list (
job/dispatch.hpp
job/queue.cpp
job/queue.hpp
json/fwd.hpp
json/except.cpp
json/except.hpp
${CMAKE_CURRENT_BINARY_DIR}/json/flat.cpp
json/flat.hpp
json/pointer.cpp
json/pointer.hpp
json/schema.cpp
json/schema.hpp
json/constraint/fwd.hpp
json/constraint/base.cpp
json/constraint/base.hpp
json/constraint/combine.cpp
json/constraint/combine.hpp
json/constraint/enum.cpp
json/constraint/enum.hpp
json/constraint/except.hpp
json/constraint/introspection.hpp
json/constraint/length.cpp
json/constraint/length.hpp
json/constraint/inequality.cpp
json/constraint/inequality.hpp
json/constraint/properties.cpp
json/constraint/properties.hpp
json/constraint/type.cpp
json/constraint/type.hpp
json/tree.cpp
json/tree.hpp
json2/fwd.hpp
json2/event.hpp
json2/event.cpp
json2/except.hpp
json2/personality/base.cpp
json2/personality/base.hpp
json2/personality/jsonish.cpp
json2/personality/jsonish.hpp
json2/personality/rfc7519.cpp
json2/personality/rfc7519.hpp
json2/tree.cpp
json2/tree.hpp
kmeans.hpp
library.hpp
log.cpp
@ -481,7 +440,7 @@ target_link_libraries(cruft-util dl)
###############################################################################
foreach (tool cpuid json-clean json-schema json-validate json-compare json-pointer poisson macro scratch)
foreach (tool cpuid poisson macro scratch)
add_executable (util_${tool} tools/${tool}.cpp)
set_target_properties (util_${tool} PROPERTIES OUTPUT_NAME ${tool})
target_link_libraries (util_${tool} cruft-util)
@ -542,8 +501,6 @@ if (TESTS)
introspection
iterator
job/queue
json_types
json2/event
kmeans
maths
maths/fast
@ -596,18 +553,6 @@ if (TESTS)
set_tests_properties(util_${name} PROPERTIES FAIL_REGULAR_EXPRESSION "not ok -")
endforeach(t)
foreach (jtest compare schema pointer)
configure_file ("test/json/${jtest}.py.in" "util_test_json_${jtest}.py" @ONLY)
add_test(NAME "util_test_json_${jtest}" COMMAND "util_test_json_${jtest}.py")
set_property(TEST "util_test_json_${jtest}" APPEND PROPERTY DEPENDS "util_json-${jtest}")
set_tests_properties(util_test_json_${jtest} PROPERTIES FAIL_REGULAR_EXPRESSION "not ok -")
endforeach()
configure_file (test/json-parse.sh.in util_test_json_parse.sh @ONLY)
add_test(NAME util_test_json_parse COMMAND util_test_json_parse.sh)
set_property(TEST util_test_json_parse APPEND PROPERTY DEPENDS util_json-validate)
set_tests_properties(util_test_json_parse PROPERTIES FAIL_REGULAR_EXPRESSION "not ok -")
configure_file (test/cpp.sh.in util_test_cpp.sh @ONLY)
add_test (NAME util_test_cpp COMMAND util_test_cpp.sh)
set_property (TEST util_test_cpp APPEND PROPERTY DEPENDS util_macro)

View File

@ -1,46 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2018 Danny Robson <danny@nerdcruft.net>
*/
#include "base.hpp"
#include "type.hpp"
#include "length.hpp"
#include "inequality.hpp"
#include "properties.hpp"
#include "enum.hpp"
using util::json::schema::constraint::base;
///////////////////////////////////////////////////////////////////////////////
std::unique_ptr<base>
base::instantiate (std::string const &name, ::json::tree::node const &def)
{
if (name == "type") return std::make_unique<type> (def);
if (name == "minLength") return std::make_unique<min_length> (def);
if (name == "maxLength") return std::make_unique<max_length> (def);
if (name == "minimum") return std::make_unique<minimum> (def);
if (name == "exclusiveMaximum") return std::make_unique<exclusive_maximum> (def);
if (name == "exclusiveMinimum") return std::make_unique<exclusive_minimum> (def);
if (name == "multipleOf") return std::make_unique<multiple_of > (def);
if (name == "maximum") return std::make_unique<maximum> (def);
if (name == "properties") return std::make_unique<properties> (def);
if (name == "patternProperties") return std::make_unique<pattern_properties> (def);
if (name == "additionalProperties") return std::make_unique<additional_properties> (def);
if (name == "enum") return std::make_unique<enumeration> (def);
throw unknown_constraint (name);
}

View File

@ -1,57 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2018 Danny Robson <danny@nerdcruft.net>
*/
#pragma once
#include "except.hpp"
#include <string>
#include <vector>
#include <iosfwd>
namespace util::json::schema::constraint {
struct failure {
constraint::base const &rule;
::json::tree::node const &target;
};
class base {
public:
using output_container = std::vector<failure>;
using output_iterator = std::back_insert_iterator<output_container>;
base () = default;
base (base &&) = default;
base& operator= (base &&) = default;
base (base const&) = delete;
base& operator= (base const&) = delete;
virtual ~base () = default;
virtual output_iterator validate (output_iterator res, ::json::tree::node &) const noexcept = 0;
virtual std::ostream& describe (std::ostream&) const = 0;
static std::unique_ptr<base> instantiate (std::string const &name, ::json::tree::node const&);
};
inline std::ostream&
operator<< (std::ostream &os, base const &obj)
{
return obj.describe (os);
}
}

View File

@ -1,95 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2018 Danny Robson <danny@nerdcruft.net>
*/
#include "combine.hpp"
#include "properties.hpp"
#include "../tree.hpp"
#include "../../iterator.hpp"
#include "../../cast.hpp"
using util::json::schema::constraint::combine;
///////////////////////////////////////////////////////////////////////////////
combine::combine (::json::tree::node const &def)
{
for (auto const &[key,val]: def.as_object ()) {
if (key == "$schema") {
m_version = val->as_string ();
continue;
}
if (key == "default") {
m_default = val->clone ();
continue;
}
m_constraints.push_back (instantiate (key, *val));
if (key == "additionalProperties") {
m_additional = util::cast::known<additional_properties> (m_constraints.back ().get ());
m_additional->use (m_properties);
m_additional->use (m_patterns);
} else if (key == "properties") {
m_properties = util::cast::known<properties> (m_constraints.back().get ());
if (m_additional)
m_additional->use (m_properties);
} else if (key == "patternProperties") {
m_patterns = util::cast::known<pattern_properties> (m_constraints.back ().get ());
if (m_additional)
m_additional->use (m_patterns);
}
}
}
///////////////////////////////////////////////////////////////////////////////
combine::output_iterator
combine::validate (output_iterator res, ::json::tree::node &data) const noexcept
{
for (auto const &i: m_constraints)
res = i->validate (res, data);
return res;
}
///////////////////////////////////////////////////////////////////////////////
std::ostream&
combine::describe (std::ostream &os) const
{
return os << "{ combine: [ ";
for (auto const &i: m_constraints)
os << *i << ", ";
return os << " ] }";
}
///////////////////////////////////////////////////////////////////////////////
bool
combine::has_default (void) const
{
return !!m_default;
}
//-----------------------------------------------------------------------------
std::unique_ptr<::json::tree::node>
combine::default_value (void) const
{
return m_default->clone ();
}

View File

@ -1,58 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2018 Danny Robson <danny@nerdcruft.net>
*/
#pragma once
#include "base.hpp"
#include "fwd.hpp"
#include "../tree.hpp"
#include <memory>
#include <vector>
///////////////////////////////////////////////////////////////////////////////
namespace util::json::schema::constraint {
class combine final : public base {
public:
combine (::json::tree::node const &def);
combine (combine const&) = delete;
combine& operator= (combine const&) = delete;
combine (combine &&) = default;
combine& operator= (combine &&) = default;
virtual ~combine () = default;
output_iterator validate (output_iterator res, ::json::tree::node &) const noexcept override;
std::ostream& describe (std::ostream&) const override;
bool has_default (void) const;
std::unique_ptr<::json::tree::node> default_value (void) const;
private:
std::string m_version;
std::unique_ptr<::json::tree::node> m_default;
properties *m_properties = nullptr;
pattern_properties *m_patterns = nullptr;
additional_properties *m_additional = nullptr;
std::vector<std::unique_ptr<base>> m_constraints;
};
}

View File

@ -1,39 +0,0 @@
#include "enum.hpp"
#include "../tree.hpp"
using util::json::schema::constraint::enumeration;
///////////////////////////////////////////////////////////////////////////////
enumeration::enumeration (::json::tree::node const &def)
{
for (auto const &i: def.as_array ())
m_values.push_back (i.clone ());
}
///////////////////////////////////////////////////////////////////////////////
enumeration::output_iterator
enumeration::validate (util::json::schema::constraint::base::output_iterator res,
::json::tree::node &target) const noexcept
{
for (auto const &i: m_values)
if (target == *i)
return res;
return *res++ = { .rule = *this, .target = target };
}
///////////////////////////////////////////////////////////////////////////////
std::ostream&
enumeration::describe (std::ostream &os) const
{
os << "{ enumeration: [ ";
for (auto const &i: m_values)
os << *i << ", ";
return os << " ] }";
}

View File

@ -1,38 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2018 Danny Robson <danny@nerdcruft.net>
*/
#pragma once
#include "base.hpp"
#include "../fwd.hpp"
#include <memory>
namespace util::json::schema::constraint {
class enumeration final : public base {
public:
enumeration (::json::tree::node const &def);
virtual ~enumeration () = default;
output_iterator validate (output_iterator res, ::json::tree::node &) const noexcept override;
std::ostream& describe (std::ostream&) const override;
private:
std::vector<std::unique_ptr<::json::tree::node>> m_values;
};
}

View File

@ -1,64 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2018 Danny Robson <danny@nerdcruft.net>
*/
#pragma once
#include "fwd.hpp"
#include "introspection.hpp"
#include "../fwd.hpp"
#include <exception>
///////////////////////////////////////////////////////////////////////////////
namespace util::json::schema {
class error : public std::exception { };
template <typename T>
class constraint_error : public error {
public:
virtual ~constraint_error () = default;
constraint_error (::json::tree::node const &_def):
def (_def)
{ ; }
char const* what (void) const noexcept override
{
return util::type_name_v<T>;
}
::json::tree::node const &def;
};
class unknown_constraint : public error {
public:
unknown_constraint (std::string const &name):
m_name (name)
{ ; }
virtual ~unknown_constraint () = default;
char const* what (void) const noexcept override
{
return m_name.c_str ();
}
private:
std::string m_name;
};
}

View File

@ -1,32 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2018 Danny Robson <danny@nerdcruft.net>
*/
#pragma once
namespace util::json::schema::constraint {
struct failure;
class base;
class type;
class combine;
template <typename> class length;
template <template <typename> class> class inequality;
class properties;
class pattern_properties;
class additional_properties;
}

View File

@ -1,90 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2018 Danny Robson <danny@nerdcruft.net>
*/
#include "inequality.hpp"
#include "../tree.hpp"
using util::json::schema::constraint::inequality;
///////////////////////////////////////////////////////////////////////////////
template <template <typename> class ComparatorT>
inequality<ComparatorT>::inequality (::json::tree::node const &def)
{
if (!def.is_number ())
throw constraint_error<inequality> (def);
auto const &val = def.as_number ();
switch (val.repr ()) {
case ::json::tree::number::UINT: m_value = val.uint (); break;
case ::json::tree::number::SINT: m_value = val.sint (); break;
case ::json::tree::number::REAL: m_value = val.real (); break;
}
}
///////////////////////////////////////////////////////////////////////////////
template <template <typename> class ComparatorT>
typename inequality<ComparatorT>::output_iterator
inequality<ComparatorT>::validate (util::json::schema::constraint::base::output_iterator res,
::json::tree::node &target) const noexcept
{
if (!target.is_number ()) {
return *res++ = failure { .rule = *this, .target = target };
}
// HACK: this shouldn't be casting to double for the comparison, but it
// greatly simplifies the logic.
auto const &value = target.as_number ();
std::visit([&,this] (auto const &inner) noexcept {
if (!ComparatorT<double>{} (value.as<double> (), inner))
*res++ = failure { .rule = *this, .target = target };
}, m_value);
return res;
}
///////////////////////////////////////////////////////////////////////////////
template <template <typename> class ComparatorT>
std::ostream&
inequality<ComparatorT>::describe (std::ostream &os) const
{
os << "{ " << util::type_name_v<inequality> << ": ";
std::visit ([&os] (auto const &i) -> void { os << i; }, m_value);
return os << " }";
}
///////////////////////////////////////////////////////////////////////////////
template class util::json::schema::constraint::inequality<std::less_equal>;
template class util::json::schema::constraint::inequality<std::greater_equal>;
template class util::json::schema::constraint::inequality<std::less>;
template class util::json::schema::constraint::inequality<std::greater>;
///////////////////////////////////////////////////////////////////////////////
template <>
bool
util::json::schema::constraint::is_multiple<double>::operator() (double a, double b) const noexcept
{
return std::fmod (a, b) <= 1e-8;
}
//-----------------------------------------------------------------------------
template class util::json::schema::constraint::inequality<util::json::schema::constraint::is_multiple>;

View File

@ -1,61 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2018 Danny Robson <danny@nerdcruft.net>
*/
#pragma once
#include "base.hpp"
#include "../tree.hpp"
#include <functional>
#include <variant>
///////////////////////////////////////////////////////////////////////////////
namespace util::json::schema::constraint {
template <template <typename> class ComparatorT>
class inequality final : public base {
public:
inequality (::json::tree::node const&);
virtual ~inequality () = default;
output_iterator validate (output_iterator, ::json::tree::node &target) const noexcept override;
std::ostream& describe (std::ostream&) const override;
private:
using uint_t = ::json::tree::number::uint_t;
using sint_t = ::json::tree::number::sint_t;
using real_t = ::json::tree::number::real_t;
std::variant<uint_t,sint_t,real_t> m_value;
};
using minimum = inequality<std::greater_equal>;
using maximum = inequality<std::less_equal>;
using exclusive_minimum = inequality<std::greater>;
using exclusive_maximum = inequality<std::less>;
// this is not technically an inequality, but the implementation fits in
// nicely here.
template <typename T>
struct is_multiple {
bool operator() (T, T) const noexcept;
};
using multiple_of = inequality<is_multiple>;
}

View File

@ -1,35 +0,0 @@
#pragma once
#include "../../introspection.hpp"
#include "fwd.hpp"
namespace util {
#define SCHEMA_INTROSPECTION(K) \
namespace detail { \
struct type_name_cruft_json_schema_constraint_##K { \
static constexpr const char value[] = #K; \
}; \
} \
\
template <> \
struct type_name<::util::json::schema::constraint::K> : \
public detail::type_name_cruft_json_schema_constraint_##K \
{ };
template <template <typename> class OperatorT>
struct type_name<::util::json::schema::constraint::inequality<OperatorT>> {
static constexpr const char value[] = "inequality";
};
template <typename OperatorT>
struct type_name<::util::json::schema::constraint::length<OperatorT>> {
static constexpr const char value[] = "length";
};
MAP0(SCHEMA_INTROSPECTION, type, combine, properties, pattern_properties, additional_properties)
#undef SCHEMA_INTROSPECTION
}

View File

@ -1,83 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2018 Danny Robson <danny@nerdcruft.net>
*/
#include "length.hpp"
#include "../tree.hpp"
using util::json::schema::constraint::length;
///////////////////////////////////////////////////////////////////////////////
namespace {
template <typename ComparatorT>
struct field_name { };
template <>
struct field_name<std::greater_equal<std::size_t>> {
static constexpr char const *const value = "minLength";
};
template <>
struct field_name<std::less_equal<std::size_t>> {
static constexpr char const *const value = "maxLength";
};
template <typename ComparatorT>
static constexpr auto field_name_v = field_name<ComparatorT>::value;
}
///////////////////////////////////////////////////////////////////////////////
template <typename ComparatorT>
length<ComparatorT>::length(::json::tree::node const &def):
m_length(def.as<std::size_t> ())
{ ; }
///////////////////////////////////////////////////////////////////////////////
template <typename ComparatorT>
typename length<ComparatorT>::output_iterator
length<ComparatorT>::validate (output_iterator res, ::json::tree::node &target) const noexcept
{
if (!target.is_string ()) {
return *res++ = failure { .rule = *this, .target = target };
}
auto const &val = target.as_string ();
if (!ComparatorT{}(val.size (), m_length))
*res++ = failure { .rule = *this, .target = target };
return res;
}
///////////////////////////////////////////////////////////////////////////////
template <typename ComparatorT>
std::ostream&
length<ComparatorT>::describe (std::ostream &os) const
{
return os << "{ " << ::field_name_v<ComparatorT> << ": " << m_length << " }";
}
///////////////////////////////////////////////////////////////////////////////
template class util::json::schema::constraint::length<std::less_equal<std::size_t>>;
template class util::json::schema::constraint::length<std::greater_equal<std::size_t>>;

View File

@ -1,42 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2018 Danny Robson <danny@nerdcruft.net>
*/
#pragma once
#include "base.hpp"
#include <functional>
///////////////////////////////////////////////////////////////////////////////
namespace util::json::schema::constraint {
template <typename ComparatorT>
class length final : public base {
public:
length (::json::tree::node const&);
virtual ~length () = default;
output_iterator validate (output_iterator, ::json::tree::node &target) const noexcept override;
std::ostream& describe (std::ostream&) const override;
private:
std::size_t m_length;
};
using max_length = length<std::less_equal<std::size_t>>;
using min_length = length<std::greater_equal<std::size_t>>;
}

View File

@ -1,217 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2018 Danny Robson <danny@nerdcruft.net>
*/
#include "properties.hpp"
#include "../tree.hpp"
using util::json::schema::constraint::properties;
using util::json::schema::constraint::pattern_properties;
using util::json::schema::constraint::additional_properties;
///////////////////////////////////////////////////////////////////////////////
properties::properties (::json::tree::node const &def)
{
if (!def.is_object ())
throw constraint_error<properties> (def);
for (auto const &[key,val]: def.as_object ()) {
m_properties.emplace (key, *val);
}
}
//-----------------------------------------------------------------------------
properties::output_iterator
properties::validate (output_iterator res, ::json::tree::node &target) const noexcept
{
if (!target.is_object ())
return *res++ = { .rule = *this, .target = target };
auto &obj = target.as_object ();
// validate the keys that are present against the property schemas
for (const auto &[key,val]: obj) {
auto const pos = m_properties.find (key);
if (pos == m_properties.end ())
continue;
res = pos->second.validate (res, *val);
}
// check if there's a key in the schema that isn't present but has a default
for (auto const &[key,doc]: m_properties) {
if (!doc.has_default ())
continue;
auto pos = obj.find (key);
if (pos == obj.end ())
obj.insert (key, doc.default_value ());
}
return res;
}
//-----------------------------------------------------------------------------
bool
properties::has (std::string const &key) const noexcept
{
return m_properties.cend () != m_properties.find (key);
}
//-----------------------------------------------------------------------------
std::ostream&
properties::describe (std::ostream &os) const
{
os << "{ properties: [ ";
for (auto const &[key,val]: m_properties) {
os << key << ": ";
val.describe (os);
os << ", ";
}
return os << " ] }";
}
///////////////////////////////////////////////////////////////////////////////
pattern_properties::pattern_properties (::json::tree::node const &def)
{
if (!def.is_object ())
throw constraint_error<pattern_properties> (def);
for (auto const &[key,val]: def.as_object ()) {
m_patterns.push_back (pattern_t {
.pattern = key,
.matcher = std::regex (key),
.validator = *val
});
}
}
//-----------------------------------------------------------------------------
pattern_properties::output_iterator
pattern_properties::validate (output_iterator res,
::json::tree::node &target) const noexcept
{
if (!target.is_object ())
return *res++ = { .rule = *this, .target = target };
// validate each key and value in turn
for (auto &[key, val]: target.as_object ()) {
// find any regex matches and forward the validation onwards
for (auto const &[pattern,regex,validator]: m_patterns) {
if (std::regex_search (key, regex)) {
res = validator.validate (res, *val);
}
}
}
return res;
}
//-----------------------------------------------------------------------------
bool
pattern_properties::has (std::string const &key) const noexcept
{
return std::any_of (
m_patterns.begin (),
m_patterns.end (),
[&key] (auto const &i)
{
return std::regex_search (key, i.matcher);
});
}
//-----------------------------------------------------------------------------
std::ostream&
pattern_properties::describe (std::ostream &os) const
{
os << "{ pattern_properties: { ";
for (auto const &[pattern,regex,validator]: m_patterns)
os << '"' << pattern << '"' << ": " << validator << ", ";
return os << " } }";
};
///////////////////////////////////////////////////////////////////////////////
additional_properties::additional_properties (
::json::tree::node const &def,
properties *_properties,
pattern_properties *_patterns
)
: m_fallback (def)
, m_properties (_properties)
, m_patterns (_patterns)
{ ; }
//-----------------------------------------------------------------------------
additional_properties::additional_properties (::json::tree::node const &def):
additional_properties (def, nullptr, nullptr)
{ }
//-----------------------------------------------------------------------------
void
additional_properties::use (properties *ptr)
{
m_properties = ptr;
}
//-----------------------------------------------------------------------------
void
additional_properties::use (pattern_properties *ptr)
{
m_patterns = ptr;
}
//-----------------------------------------------------------------------------
additional_properties::output_iterator
additional_properties::validate (output_iterator res,
::json::tree::node &target) const noexcept
{
if (!target.is_object ())
return *res++ = { .rule = *this, .target = target };
for (auto &[key,val]: target.as_object ()) {
// don't check properties that are handled elsewhere
if (m_patterns && m_patterns->has (key))
continue;
if (m_properties && m_properties->has (key))
continue;
res = m_fallback.validate (res, *val);
}
return res;
}
//-----------------------------------------------------------------------------
std::ostream&
additional_properties::describe (std::ostream &os) const
{
return os << "{ additional_properties: " << m_fallback << " }";
}

View File

@ -1,83 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2018 Danny Robson <danny@nerdcruft.net>
*/
#pragma once
#include "base.hpp"
#include "combine.hpp"
#include <map>
#include <string>
#include <memory>
#include <regex>
///////////////////////////////////////////////////////////////////////////////
namespace util::json::schema::constraint {
class properties : public base {
public:
properties (::json::tree::node const&);
virtual ~properties () = default;
output_iterator validate (output_iterator, ::json::tree::node &target) const noexcept override;
std::ostream& describe (std::ostream&) const override;
bool has (std::string const &key) const noexcept;
private:
std::map<std::string,combine> m_properties;
};
class pattern_properties : public base {
public:
pattern_properties (::json::tree::node const&);
output_iterator validate (output_iterator, ::json::tree::node &target) const noexcept override;
std::ostream& describe (std::ostream&) const override;
bool has (std::string const &key) const noexcept;
private:
struct pattern_t {
std::string pattern;
std::regex matcher;
combine validator;
};
std::vector<pattern_t> m_patterns;
};
class additional_properties : public base {
public:
additional_properties (::json::tree::node const&, properties*, pattern_properties*);
additional_properties (::json::tree::node const&);
virtual ~additional_properties () = default;
void use (properties*);
void use (pattern_properties*);
output_iterator validate (output_iterator, ::json::tree::node &target) const noexcept override;
std::ostream& describe (std::ostream&) const override;
private:
combine m_fallback;
properties *m_properties = nullptr;
pattern_properties *m_patterns = nullptr;
};
}

View File

@ -1,128 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2018 Danny Robson <danny@nerdcruft.net>
*/
#include "type.hpp"
#include "except.hpp"
#include "../../parse.hpp"
using util::json::schema::constraint::type;
///////////////////////////////////////////////////////////////////////////////
template <>
util::json::schema::constraint::type::type_t
util::parse<util::json::schema::constraint::type::type_t> (util::view<const char*> str)
{
if (str == "integer") return ::util::json::schema::constraint::type::INTEGER;
if (str == "number") return ::util::json::schema::constraint::type::NUMBER;
if (str == "string") return ::util::json::schema::constraint::type::STRING;
if (str == "object") return ::util::json::schema::constraint::type::OBJECT;
if (str == "array") return ::util::json::schema::constraint::type::ARRAY;
if (str == "boolean") return ::util::json::schema::constraint::type::BOOLEAN;
if (str == "null") return ::util::json::schema::constraint::type::NONE;
throw std::invalid_argument (std::string (str.begin (), str.end ()));
}
///////////////////////////////////////////////////////////////////////////////
namespace util::json::schema::constraint {
std::string const&
to_string (type::type_t t)
{
switch (t) {
case type::INTEGER: { static std::string s_name = "integer"; return s_name; }
case type::NUMBER: { static std::string s_name = "number"; return s_name; }
case type::STRING: { static std::string s_name = "string"; return s_name; }
case type::OBJECT: { static std::string s_name = "object"; return s_name; }
case type::ARRAY: { static std::string s_name = "array"; return s_name; }
case type::BOOLEAN: { static std::string s_name = "boolean"; return s_name; }
case type::NONE: { static std::string s_name = "none"; return s_name; }
}
unreachable ();
}
}
static type::type_t
valid_types (::json::tree::node const &def)
{
if (def.is_string ()) {
return util::parse<type::type_t> (def.as_string ());
} else if (def.is_array ()) {
std::underlying_type_t<type::type_t> accum = 0;
for (auto const &i: def.as_array ())
accum |= util::parse<type::type_t> (i.as_string ());
return type::type_t (accum);
} else {
throw util::json::schema::constraint_error<type> (def);
}
}
///////////////////////////////////////////////////////////////////////////////
type::type (::json::tree::node const &def):
m_type (valid_types (def))
{ ; }
///////////////////////////////////////////////////////////////////////////////
static bool
is_valid (type::type_t t, ::json::tree::node const &target) noexcept
{
switch (target.type ()) {
case ::json::tree::STRING: return t & type::STRING;
case ::json::tree::OBJECT: return t & type::OBJECT;
case ::json::tree::ARRAY: return t & type::ARRAY;
case ::json::tree::BOOLEAN: return t & type::BOOLEAN;
case ::json::tree::NONE: return t & type::NONE;
case ::json::tree::NUMBER:
if (t & type::NUMBER)
return true;
switch (target.as_number ().repr ()) {
case ::json::tree::number::UINT:
case ::json::tree::number::SINT:
return t & type::INTEGER;
case ::json::tree::number::REAL:
return false;
}
}
unreachable ();
}
type::output_iterator
type::validate (output_iterator res, ::json::tree::node &target) const noexcept
{
if (!is_valid (m_type, target))
*res++ = failure { .rule = *this, .target = target };
return res;
}
std::ostream&
type::describe (std::ostream &os) const
{
return os << "{type: " << to_string (m_type) << " }";
}

View File

@ -1,47 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2018 Danny Robson <danny@nerdcruft.net>
*/
#pragma once
#include "base.hpp"
#include "../tree.hpp"
///////////////////////////////////////////////////////////////////////////////
namespace util::json::schema::constraint {
class type final : public base {
public:
enum type_t {
INTEGER = 1 << 0,
NUMBER = 1 << 1,
STRING = 1 << 2,
OBJECT = 1 << 3,
ARRAY = 1 << 4,
BOOLEAN = 1 << 5,
NONE = 1 << 6,
};
type (::json::tree::node const &definition);
virtual ~type () = default;
output_iterator validate (output_iterator, ::json::tree::node &target) const noexcept override;
std::ostream& describe (std::ostream&) const override;
private:
type_t const m_type;
};
}

View File

@ -1,42 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2015 Danny Robson <danny@nerdcruft.net>
*/
#include "except.hpp"
#include "../format.hpp"
///////////////////////////////////////////////////////////////////////////////
json::parse_error::parse_error (const std::string &_what, size_t _line):
error (_what),
line (_line),
desc (_what + " at line " + std::to_string (_line))
{ ; }
//-----------------------------------------------------------------------------
const char*
json::parse_error::what (void) const noexcept
{
return desc.c_str ();
}
///////////////////////////////////////////////////////////////////////////////
json::key_error::key_error (std::string _key):
error (to_string (util::format::printf ("missing key '%s'", _key))),
key (std::move (_key))
{ ; }

View File

@ -1,68 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2015 Danny Robson <danny@nerdcruft.net>
*/
#ifndef __UTIL_JSON_EXCEPT_HPP
#define __UTIL_JSON_EXCEPT_HPP
#include <stdexcept>
#include <string>
#include <cstdlib>
namespace json {
/// The base class for all exceptions throw directly by the json namespace.
struct error : public std::runtime_error {
explicit error (const std::string &what):
runtime_error (what)
{ ; }
};
/// Base class for all type conversion errors
struct type_error : public error {
explicit type_error (const std::string &what):
error (what)
{ ; }
};
/// Base class for errors thrown during parsing
struct parse_error : public error {
explicit parse_error (const std::string &_what, size_t _line = 0);
const char* what (void) const noexcept override;
size_t line;
std::string desc;
};
/// Base class for errors thrown during schema validation
struct schema_error : public error {
explicit schema_error (const std::string &what):
error (what)
{ ; }
};
/// Exception class for invalid object indexing
struct key_error : public error {
explicit key_error (std::string);
std::string key;
};
}
#endif

View File

@ -1,213 +0,0 @@
/*
* 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 2010-2017 Danny Robson <danny@nerdcruft.net>
*/
#include "json/flat.hpp"
#include "debug.hpp"
#include "json/except.hpp"
#include "preprocessor.hpp"
#include <deque>
#include <iostream>
// We generate some really old style C code via ragel here, so we have to
// disable some noisy warnings (doubly so given -Werror)
#pragma GCC diagnostic ignored "-Wold-style-cast"
///////////////////////////////////////////////////////////////////////////////
%%{
# JSON (rfc7159)
machine json;
action trace { if (false) std::cerr << *p; }
action success { __success = true; }
action failure { }
action new_line { ++line; }
action first { parsed.push_back ({ type::UNKNOWN, p, p}); }
action last { parsed.back ().last = p; }
action tag_nul { parsed.back ().tag = type::NUL; }
action tag_boolean { parsed.back ().tag = type::BOOLEAN; }
action tag_string { parsed.back ().tag = type::STRING; }
action tag_integer { parsed.back ().tag = type::INTEGER; }
action tag_real { parsed.back ().tag = type::REAL; }
action tag_object_begin { parsed.push_back ({ type::OBJECT_BEGIN, p, p + 1 }); }
action tag_object_end { parsed.push_back ({ type::OBJECT_END, p, p + 1 }); }
action tag_array_begin { parsed.push_back ({ type::ARRAY_BEGIN, p, p + 1 }); }
action tag_array_end { parsed.push_back ({ type::ARRAY_END, p, p + 1 }); }
# Line counter
lines = (
any | '\n' @new_line
)*;
# UTF-8 (rfc3629)
utf8_tail = 0x80..0xbf;
utf8_1 = 0x00..0x7f;
utf8_2 = 0xc2..0xdf utf8_tail;
utf8_3 = 0xe0 0xa0..0xbf utf8_tail |
0xe1..0xec utf8_tail{2} |
0xed 0x80..0x9f utf8_tail |
0xee..0xef utf8_tail{2};
utf8_4 = 0xf0 0x90..0xbf utf8_tail{2} |
0xf1..0xf3 utf8_tail{3} |
0xf4 0x80..0x8f utf8_tail{2};
utf8 = utf8_1 | utf8_2 | utf8_3 | utf8_4;
# Utility
ws = 0x20 | 0x09 | 0x0A | 0x0D;
array_start = '[';
array_end = ']';
object_start = '{';
object_end = '}';
# Strings
char =
(utf8 - ["\\])
| "\\" (
[\\"/bfnrt]
| "u" xdigit{4}
)
;
string = ('"' char* '"') >first >tag_string %*last;
# numbers
int = '0' | [1-9] digit*;
frac = '.' digit+;
e = 'e'i[+\-]?;
exp = e digit+;
number = (
'-'?
int
(frac >tag_real)?
exp?
) >tag_integer;
# wrapper types
array = array_start @{ fhold; fcall array_members; } array_end;
object = object_start @{ fhold; fcall object_members; } object_end;
# simple types; case sensitive literals
bool = ("true" | "false") >tag_boolean;
nul = "null" >tag_nul;
literal = bool | nul;
value = object | array | (number | string | literal) >first %last;
# Complex
member = string ws* ':' ws* value;
array_members := ((
array_start >tag_array_begin ws* (value ws* (',' ws* value ws*)*)? array_end >tag_array_end
) & lines)
@{ fhold; fret; } $trace $!failure;
object_members := ((
object_start >tag_object_begin ws* (member ws* (',' ws* member ws*)*)? object_end >tag_object_end
) & lines)
@{ fhold; fret; } $trace $!failure;
# meta types
document := ((ws* value ws*) & lines)
%success
$!failure
$trace;
variable stack ragelstack;
prepush { ragelstack.push_back (0); }
postpop { ragelstack.pop_back (); }
write data;
}%%
//-----------------------------------------------------------------------------
template <typename T>
std::vector<json::flat::item<T>>
json::flat::parse (const util::view<T> src)
{
auto p = src.cbegin ();
auto pe = src.cend ();
auto eof = pe;
std::deque<int> ragelstack;
std::vector<item<T>> parsed;
size_t line = 0;
int cs, top;
bool __success = false;
%%write init;
%%write exec;
if (!__success)
throw json::parse_error ("parse error", line);
return parsed;
}
#define INSTANTIATE(KLASS) \
template \
std::vector<json::flat::item<KLASS>> \
json::flat::parse (util::view<KLASS>);
MAP0(INSTANTIATE,
std::string::iterator,
std::string::const_iterator,
const char*,
char *
)
#undef INSTANTIATE
//-----------------------------------------------------------------------------
std::ostream&
json::flat::operator<< (std::ostream &os, json::flat::type t)
{
using T = json::flat::type;
switch (t) {
case T::STRING: return os << "STRING";
case T::NUL: return os << "NUL";
case T::BOOLEAN: return os << "BOOLEAN";
case T::INTEGER: return os << "INTEGER";
case T::REAL: return os << "REAL";
case T::OBJECT_BEGIN: return os << "OBJECT_BEGIN";
case T::OBJECT_END: return os << "OBJECT_END";
case T::ARRAY_BEGIN: return os << "ARRAY_BEGIN";
case T::ARRAY_END: return os << "ARRAY_END";
case T::UNKNOWN: ;
// fall out
}
unreachable ();
}

View File

@ -1,61 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2010-2015 Danny Robson <danny@nerdcruft.net>
*/
#ifndef __UTIL_JSON_FLAT_HPP
#define __UTIL_JSON_FLAT_HPP
#include <iosfwd>
#include <vector>
#include "../view.hpp"
///////////////////////////////////////////////////////////////////////////////
namespace json::flat {
enum class type {
UNKNOWN,
NUL,
BOOLEAN,
STRING,
INTEGER,
REAL,
OBJECT_BEGIN,
OBJECT_END,
ARRAY_BEGIN,
ARRAY_END
};
template <typename T>
struct item {
type tag;
T first;
T last;
template <typename U>
U as (void) const;
};
template <typename T>
std::vector<item<T>>
parse (util::view<T> data);
std::ostream& operator<< (std::ostream&, type);
}
#endif

View File

@ -1,38 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2015 Danny Robson <danny@nerdcruft.net>
*/
#ifndef __UTIL_JSON_FWD_HPP
#define __UTIL_JSON_FWD_HPP
namespace json {
namespace tree {
class node;
class object;
class array;
class string;
class number;
class boolean;
class null;
}
struct error;
struct type_error;
struct parse_error;
struct schema_error;
struct key_error;
}
#endif

View File

@ -1,60 +0,0 @@
#include "pointer.hpp"
#include "tree.hpp"
#include "../parse.hpp"
#include <algorithm>
#include <stdexcept>
///////////////////////////////////////////////////////////////////////////////
::json::tree::node&
util::json::pointer::resolve (std::string const &path,
::json::tree::node &root)
{
// we only handle fragment represenations currently, which start with a '#'
auto cursor = path.begin ();
if (cursor == path.end () || *cursor != '#')
throw std::invalid_argument ("must be fragment representation");
++cursor;
auto obj = &root;
// * step over each path segment
// * index into the current node
// * advance the node
// * repeat until we reach the end of the query string
for ( ; cursor != path.end (); ) {
// step over the delimiting '/'
if (*cursor != '/')
break;
++cursor;
// find the start of the next segment (or the end of the string)
// and extract the key.
auto next = std::find (cursor, path.end (), '/');
std::string const key { cursor, next };
// try to index into the node and advance the node we're operating on
switch (obj->type ()) {
case ::json::tree::OBJECT:
obj = &(*obj)[key];
break;
case ::json::tree::ARRAY:
obj = &(*obj)[util::parse<size_t> (key)];
break;
case ::json::tree::STRING:
case ::json::tree::NUMBER:
case ::json::tree::BOOLEAN:
case ::json::tree::NONE:
throw std::invalid_argument ("indexing into wrong type");
}
// step to the beginning of the next component
cursor = next;
}
return *obj;
}

View File

@ -1,26 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2018 Danny Robson <danny@nerdcruft.net>
*/
#pragma once
#include "fwd.hpp"
#include <string>
namespace util::json::pointer {
::json::tree::node&
resolve (std::string const &path, ::json::tree::node &root);
}

View File

@ -1,568 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2015-2018 Danny Robson <danny@nerdcruft.net>
*/
#include "schema.hpp"
#include "tree.hpp"
#include "except.hpp"
#include "../debug.hpp"
#include "../io.hpp"
#include "../maths.hpp"
#include <regex>
///////////////////////////////////////////////////////////////////////////////
struct length_error : public json::schema_error {
length_error (const std::string &what):
schema_error (what)
{ ; }
};
//-----------------------------------------------------------------------------
struct format_error : public json::schema_error {
format_error (const std::string &what):
schema_error (what)
{ ; }
};
///////////////////////////////////////////////////////////////////////////////
static void validate (json::tree::node&, const json::tree::object&);
///////////////////////////////////////////////////////////////////////////////
static void
validate (json::tree::object &node,
const json::tree::object &schema)
{
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 ())
validate (*p->second, kv.second->as_object ());
else {
try {
node.insert (kv.first, (*kv.second)["default"].clone ());
validate (node[kv.first], kv.second->as_object ());
continue;
} catch (const json::key_error&)
{ ; }
if (additional != schema.cend ()) {
if (additional->second->is_boolean () && !additional->second->as_bool ())
throw json::schema_error ("additionalProperties");
validate (*p->second, additional->second->as_object ());
}
}
}
}
if (pattern != schema.cend ()) {
for (auto &cond: pattern->second->as_object ()) {
std::regex expr (cond.first, std::regex_constants::ECMAScript);
for (auto &props: node) {
if (std::regex_search (props.first, expr))
validate (*props.second, cond.second->as_object ());
}
}
}
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 ())
if (node.size () > maxProperties->second->as_uint ())
throw json::schema_error ("maxProperties");
auto minProperties = schema.find ("minProperties");
if (minProperties != schema.cend ())
if (node.size () < minProperties->second->as_uint ())
throw json::schema_error ("minProperties");
auto required = schema.find ("required");
if (required != schema.cend ())
for (const auto &i: required->second->as_array ())
if (!node.has (i.as_string ()))
throw json::schema_error ("required");
}
//-----------------------------------------------------------------------------
static void
validate (json::tree::array &node,
const json::tree::object &schema)
{
// 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)
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)
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 ()) {
if (!additional->second->as_boolean ())
throw json::schema_error ("additional");
} else if (additional->second->is_object ()) {
for ( ; i < node.size (); ++i)
validate (node[i], additional->second->as_object ());
} else {
throw json::schema_error ("items");
}
}
}
}
auto maxItems = schema.find ("maxItems");
if (maxItems != schema.cend ())
if (node.size () > maxItems->second->as_uint ())
throw json::schema_error ("maxItems");
auto minItems = schema.find ("minItems");
if (minItems != schema.cend ())
if (node.size () < minItems->second->as_uint ())
throw json::schema_error ("minItems");
// 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])
throw json::schema_error ("uniqueItems");
}
//-----------------------------------------------------------------------------
static void
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 ().uint ();
if (!util::is_integer (cmp))
throw length_error ("maxLength");
if (val.size () > cmp)
throw length_error ("maxLength");
}
// check length is greater than a maximum
auto minLength = schema.find ("minLength");
if (minLength != schema.cend ()) {
auto cmp = minLength->second->as_number ().uint ();
if (!util::is_integer (cmp))
throw length_error ("minLength");
if (val.size () < cmp)
throw length_error ("minLength");
}
// 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))
throw format_error ("pattern");
}
}
//-----------------------------------------------------------------------------
template <typename T>
static void
validate_number (T val, const json::tree::object &schema) {
using R = json::tree::number::repr_t;
// check strictly positive integer multiple
auto mult = schema.find ("multipleOf");
if (mult != schema.cend ()) {
const auto &div = mult->second->as_number ();
switch (div.repr ()) {
case R::REAL: if (util::exactly_zero (std::fmod (val, div.real ()))) throw json::schema_error ("multipleOf"); break;
case R::SINT: if (util::exactly_zero (std::fmod (val, div.sint ()))) throw json::schema_error ("multipleOf"); break;
case R::UINT: if (util::exactly_zero (std::fmod (val, div.uint ()))) throw json::schema_error ("multipleOf"); break;
}
}
// check maximum holds. exclusive requires max condition.
auto max = schema.find ("maximum");
auto exclusiveMax = schema.find ("exclusiveMaximum");
if (max != schema.end ()) {
const auto &cmp = max->second->as_number ();
if (exclusiveMax != schema.end () && exclusiveMax->second->as_boolean ()) {
switch (cmp.repr ()) {
case R::REAL:
if (T(val) >= cmp.real ())
throw json::schema_error ("exclusiveMax");
break;
case R::SINT:
if (json::tree::number::sint_t(std::numeric_limits<T>::max ()) >= cmp.sint () &&
val >= T(cmp.sint ()))
{
throw json::schema_error ("exclusiveMax");
}
break;
case R::UINT:
if (json::tree::number::uint_t(std::numeric_limits<T>::max ()) >= cmp.uint () &&
val >= T(cmp.uint ()))
{
throw json::schema_error ("exclusiveMax");
}
break;
}
} else {
switch (cmp.repr ()) {
case R::REAL:
if (T(val) > cmp.real ())
throw json::schema_error ("maximum");
break;
case R::SINT:
if (json::tree::number::sint_t(std::numeric_limits<T>::max ()) >= cmp.sint () &&
val >= T(cmp.sint ()))
{
throw json::schema_error ("maximum");
}
break;
case R::UINT:
if (json::tree::number::uint_t(std::numeric_limits<T>::max ()) >= cmp.uint () &&
val >= T(cmp.uint ()))
{
throw json::schema_error ("maximum");
}
break;
}
}
} else {
if (exclusiveMax != schema.cend ())
throw json::schema_error ("exclusiveMax");
}
// check minimum holds. exclusive requires min condition
auto min = schema.find ("minimum");
auto exclusiveMin = schema.find ("exclusiveMinimum");
if (min != schema.end ()) {
const auto &cmp = min->second->as_number ();
if (exclusiveMin != schema.end () && exclusiveMin->second->as_boolean ()) {
switch (cmp.repr ()) {
case R::REAL:
if (T(val) < cmp.real ())
throw json::schema_error ("exclusiveMin");
break;
case R::SINT:
if (cmp.sint () > json::tree::number::sint_t(std::numeric_limits<T>::min ()) &&
val < T(cmp.sint ()))
{
throw json::schema_error ("exclusiveMin");
}
break;
case R::UINT:
if (cmp.uint () > json::tree::number::uint_t(std::numeric_limits<T>::min ()) &&
val < T(cmp.uint ()))
{
throw json::schema_error ("exclusiveMin");
}
break;
}
} else {
switch (cmp.repr ()) {
case R::REAL:
if (T(val) <= cmp.real ())
throw json::schema_error ("minimum");
break;
case R::SINT:
if (cmp.sint () >= json::tree::number::sint_t(std::numeric_limits<T>::min ()) &&
val <= T(cmp.sint ()))
{
throw json::schema_error ("minimum");
}
break;
case R::UINT:
if (cmp.uint () >= json::tree::number::uint_t(std::numeric_limits<T>::min ()) &&
val <= T(cmp.uint ()))
{
throw json::schema_error ("minimum");
}
break;
}
}
} else {
if (exclusiveMin != schema.cend ())
throw json::schema_error ("exclusiveMin");
}
}
//-----------------------------------------------------------------------------
static void
validate (json::tree::number &node,
const json::tree::object &schema)
{
using N = json::tree::number;
using R = N::repr_t;
switch (node.repr ()) {
case R::REAL: validate_number<N::real_t> (node.real (), schema); break;
case R::SINT: validate_number<N::sint_t> (node.sint (), schema); break;
case R::UINT: validate_number<N::uint_t> (node.uint (), schema); break;
}
}
//-----------------------------------------------------------------------------
static void
validate (json::tree::boolean&,
const json::tree::object&)
{ ; }
//-----------------------------------------------------------------------------
static void
validate (json::tree::null&,
const json::tree::object&)
{ ; }
//-----------------------------------------------------------------------------
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
is_type_valid (const json::tree::node &node, const std::string &type)
{
if (type == "integer") return node.is_integer ();
if (type == "number") return node.is_number ();
if (type == "string") return node.is_string ();
if (type == "object") return node.is_object ();
if (type == "array") return node.is_array ();
if (type == "boolean") return node.is_boolean ();
if (type == "null") return node.is_null ();
return false;
}
//-----------------------------------------------------------------------------
static void
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 ())
throw json::schema_error ("enum");
}
// check the value is the correct type
auto type = schema.find ("type");
if (type != schema.cend ()) {
// check against a single named type
if (type->second->is_string ()) {
if (!is_type_valid (node, type->second->as_string ()))
throw json::schema_error ("type");
// check against an array of types
} 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 ())
throw json::schema_error ("type");
} else
throw json::schema_error ("type");
}
auto allOf = schema.find ("allOf");
if (allOf != schema.cend ()) {
for (const auto &i: allOf->second->as_array ())
validate (node, i.as_object ());
}
auto anyOf = schema.find ("anyOf");
if (anyOf != schema.cend ()) {
bool success = false;
for (const auto &i: anyOf->second->as_array ()) {
try {
validate (node, i.as_object ());
success = true;
break;
} catch (const json::schema_error&)
{ continue; }
}
if (!success)
throw json::schema_error ("anyOf");
}
auto oneOf = schema.find ("oneOf");
if (oneOf != schema.cend ()) {
unsigned count = 0;
for (const auto &i: oneOf->second->as_array ()) {
try {
validate (node, i.as_object ());
count++;
} catch (const json::schema_error&)
{ ; }
if (count > 1)
throw json::schema_error ("oneOf");
}
if (count != 1)
throw json::schema_error ("oneOf");
}
auto notSchema = schema.find ("not");
if (notSchema != schema.cend ()) {
for (const auto &i: notSchema->second->as_array ()) {
bool valid = false;
try {
validate (node, i.as_object ());
valid = true;
} catch (const json::schema_error&)
{ ; }
if (valid)
throw json::schema_error ("not");
}
}
switch (node.type ()) {
case json::tree::OBJECT: validate (node.as_object (), schema); return;
case json::tree::ARRAY: validate (node.as_array (), schema); return;
case json::tree::STRING: validate (node.as_string (), schema); return;
case json::tree::NUMBER: validate (node.as_number (), schema); return;
case json::tree::BOOLEAN: validate (node.as_boolean (), schema); return;
case json::tree::NONE: validate (node.as_null (), schema); return;
}
unreachable ();
}
//-----------------------------------------------------------------------------
void
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 ())
throw json::schema_error ("title");
auto description = schema.find ("description");
if (description != schema.cend ())
if (!description->second->is_string ())
throw json::schema_error ("description");
return ::validate (data, schema.as_object ());
}
//-----------------------------------------------------------------------------
void
json::schema::validate (json::tree::node &data,
const std::experimental::filesystem::path &schema_path)
{
const util::mapped_file schema_data (schema_path);
auto schema_object = json::tree::parse (util::view(schema_data).cast<const char*> ());
validate (data, schema_object->as_object ());
}
///////////////////////////////////////////////////////////////////////////////
#include "constraint/except.hpp"
#include "constraint/base.hpp"
#include "../io.hpp"
#include "../view.hpp"
#include <map>
json::schema::validator::validator(std::experimental::filesystem::path const &path):
validator (*json::tree::parse (path))
{ ; }
json::schema::validator::validator (json::tree::node const &definition):
m_base (definition)
{ ; }
bool
json::schema::validator::validate (json::tree::node &data) const
{
std::vector<util::json::schema::constraint::failure> res;
m_base.validate (std::back_inserter(res), data);
return res.empty ();
}

View File

@ -1,51 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2015 Danny Robson <danny@nerdcruft.net>
*/
#ifndef __UTIL_JSON_SCHEMA_HPP
#define __UTIL_JSON_SCHEMA_HPP
#include "fwd.hpp"
#include "constraint/combine.hpp"
#include <experimental/filesystem>
///////////////////////////////////////////////////////////////////////////////
namespace json::schema {
class validator {
public:
validator (json::tree::node const &definition);
validator (std::experimental::filesystem::path const&);
bool validate (json::tree::node &data) const;
private:
std::string m_version;
util::json::schema::constraint::combine m_base;
};
// Validate the json tree using the provide schema object or path.
//
// Note that the data object being validated may be altered in the process
// of validation. If a value is not present but the schema specifies a
// default, it will be realised in the data object.
void validate (json::tree::node &data, const json::tree::object &schema);
void validate (json::tree::node &data, const std::experimental::filesystem::path &schema);
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -1,397 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2010-2018 Danny Robson <danny@nerdcruft.net>
*/
#ifndef __UTIL_JSON_TREE_HPP
#define __UTIL_JSON_TREE_HPP
#include "flat.hpp"
#include "fwd.hpp"
#include "../iterator.hpp"
#include "../view.hpp"
#include <iosfwd>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include <experimental/filesystem>
///////////////////////////////////////////////////////////////////////////////
namespace json::tree {
enum type_t {
OBJECT,
ARRAY,
STRING,
NUMBER,
BOOLEAN,
NONE
};
/// Parse an encoded form of JSON into a tree structure
template <typename T>
std::unique_ptr<node>
parse (const util::view<T> &data);
std::unique_ptr<node>
parse (const std::experimental::filesystem::path &);
extern void write (const json::tree::node&, std::ostream&);
/// Abstract base for all JSON values
class node {
public:
node (const node&) = delete;
virtual ~node () { ; }
virtual std::unique_ptr<node> clone (void) const = 0;
virtual const object& as_object (void) const&;
virtual const array& as_array (void) const&;
virtual const string& as_string (void) const&;
virtual const number& as_number (void) const&;
virtual const boolean& as_boolean (void) const&;
virtual const null& as_null (void) const&;
virtual object& as_object (void)&;
virtual array& as_array (void)&;
virtual string& as_string (void)&;
virtual number& as_number (void)&;
virtual boolean& as_boolean (void)&;
virtual null& as_null (void)&;
// we don't provide operators for conversion due to ambiguities
// introduced when using indexing operators with pointer
// arguments.
virtual bool as_bool (void) const;
virtual float as_float (void) const;
virtual double as_double (void) const;
virtual intmax_t as_sint (void) const;
virtual uintmax_t as_uint (void) const;
virtual const char* as_chars (void) const&;
template <typename T>
T as (void) const;
virtual bool is_object (void) const { return false; }
virtual bool is_array (void) const { return false; }
virtual bool is_string (void) const { return false; }
virtual bool is_number (void) const { return false; }
virtual bool is_integer (void) const { return false; }
virtual bool is_boolean (void) const { return false; }
virtual bool is_null (void) const { return false; }
virtual type_t type (void) const = 0;
virtual bool operator==(const node &rhs) const = 0;
virtual bool operator!=(const node &rhs) const;
virtual bool operator==(const object &) const { return false; }
virtual bool operator==(const array &) const { return false; }
virtual bool operator==(const string &) const { return false; }
virtual bool operator==(const number &) const { return false; }
virtual bool operator==(const boolean &) const { return false; }
virtual bool operator==(const null &) const { return false; }
virtual bool operator==(const char *rhs) const;
virtual bool operator!=(const char *rhs) const { return !(*this == rhs); }
virtual node& operator[] (const std::string&)&;
virtual node& operator[] (size_t)&;
virtual const node& operator[] (const std::string&) const&;
virtual const node& operator[] (size_t) const&;
virtual std::ostream& write (std::ostream &os) const = 0;
protected:
node () = default;
};
/// Represents a JSON object, and contains its children.
class object final : public node {
private:
using value_store = std::map<std::string, std::unique_ptr<node>>;
public:
typedef value_store::iterator iterator;
typedef value_store::const_iterator const_iterator;
public:
virtual ~object ();
virtual std::unique_ptr<node> clone (void) const override;
virtual const object& as_object (void) const& override { return *this; }
virtual object& as_object (void) & override { return *this; }
virtual bool is_object (void) const override { return true; }
virtual type_t type (void) const override { return OBJECT; }
virtual bool operator==(const object &rhs) const override;
virtual bool operator==(const node &rhs) const override
{ return rhs == *this; }
virtual void insert (const std::string &key, std::unique_ptr<node>&& value);
virtual const node& operator[] (const std::string &key) const& override;
virtual node& operator[] (const std::string &key)& override;
virtual bool has (const std::string&) const;
virtual const_iterator find (const std::string&) const;
virtual const_iterator begin (void) const;
virtual const_iterator end (void) const;
virtual const_iterator cbegin (void) const { return begin (); }
virtual const_iterator cend (void) const { return end (); }
virtual size_t size (void) const;
virtual void clear (void);
virtual void erase (const std::string &key);
virtual std::ostream& write (std::ostream &os) const override;
private:
value_store m_values;
};
/// Represents a JSON array, and contains its children.
class array final : public node {
protected:
typedef std::vector<std::unique_ptr<node>>::iterator pointer_array_iterator;
typedef std::vector<std::unique_ptr<node>>::const_iterator const_pointer_array_iterator;
public:
typedef referencing_iterator<pointer_array_iterator> iterator;
typedef referencing_iterator<const_pointer_array_iterator> const_iterator;
protected:
std::vector<std::unique_ptr<node>> m_values;
public:
virtual ~array();
virtual std::unique_ptr<node> clone (void) const override;
virtual const array& as_array (void) const& override { return *this; }
virtual array& as_array (void) & override { return *this; }
virtual bool is_array (void) const override { return true; }
virtual type_t type (void) const override { return ARRAY; }
virtual bool operator==(const array &rhs) const override;
virtual bool operator==(const node &rhs) const override;
virtual size_t size (void) const;
virtual node& operator[] (size_t idx)& override;
virtual const node& operator[] (size_t idx) const& override;
virtual iterator begin (void);
virtual iterator end (void);
virtual const_iterator begin (void) const;
virtual const_iterator end (void) const;
virtual const_iterator cbegin (void) const;
virtual const_iterator cend (void) const;
virtual void insert (std::unique_ptr<json::tree::node> &&_value);
virtual std::ostream& write (std::ostream &os) const override;
};
/// Represents a JSON string literal.
class string final : public node {
protected:
std::string m_value;
public:
template <typename T>
string (T first, T last): m_value (first, last) { ; }
explicit string (const std::string &_value): m_value (_value) { ; }
explicit string (const char *_value): m_value (_value) { ; }
string (const char *_first, const char *_last): m_value (_first, _last) { ; }
virtual std::unique_ptr<node> clone (void) const override;
virtual const string& as_string (void) const& override { return *this; }
virtual string& as_string (void) & override { return *this; }
virtual bool is_string (void) const override { return true; }
virtual type_t type (void) const override { return STRING; }
virtual bool operator==(const char *rhs) const override;
virtual bool operator==(const string &rhs) const override;
virtual bool operator==(const std::string &rhs) const;
virtual bool operator==(const node &rhs) const override
{ return rhs == *this; }
virtual bool operator!= (const std::string &rhs) const { return !(*this == rhs); }
virtual size_t size (void) const { return m_value.size (); }
operator const std::string&(void) const { return m_value; }
const std::string& native (void) const { return m_value; }
virtual std::ostream& write (std::ostream &os) const override;
};
/// Represents a JSON integer/float literal.
class number final : public node {
public:
enum repr_t {
REAL,
SINT,
UINT
};
using real_t = double;
using sint_t = intmax_t;
using uint_t = uintmax_t;
explicit number (real_t _value): m_repr (REAL) { m_value.r = _value; }
explicit number (sint_t _value): m_repr (SINT) { m_value.s = _value; }
explicit number (uint_t _value): m_repr (UINT) { m_value.u = _value; }
virtual std::unique_ptr<node> clone (void) const override;
virtual const number& as_number (void) const& override { return *this; }
virtual number& as_number (void) & override { return *this; }
virtual bool is_number (void) const override { return true; }
virtual bool is_integer (void) const override { return repr () == UINT || repr () == SINT; }
virtual type_t type (void) const override { return NUMBER; }
virtual repr_t repr (void) const { return m_repr; }
virtual bool operator==(const number &rhs) const override;
virtual bool operator==(const node &rhs) const override
{ return rhs == *this; }
operator real_t (void) const;
operator sint_t (void) const;
operator uint_t (void) const;
real_t real (void) const;
sint_t sint (void) const;
uint_t uint (void) const;
virtual std::ostream& write (std::ostream &os) const override;
private:
union {
real_t r;
sint_t s;
uint_t u;
} m_value;
repr_t m_repr;
};
/// Represents a JSON boolean literal.
class boolean final : public node {
protected:
bool m_value;
public:
explicit boolean (bool _value): m_value (_value) { ; }
virtual std::unique_ptr<node> clone (void) const override;
virtual const boolean& as_boolean (void) const& override { return *this; }
virtual boolean& as_boolean (void) & override { return *this; }
virtual bool is_boolean (void) const override { return true; }
virtual type_t type (void) const override { return BOOLEAN; }
virtual bool operator==(const boolean &rhs) const override;
virtual bool operator==(const node &rhs) const override
{ return rhs == *this; }
operator bool (void) const { return m_value; }
bool native (void) const { return m_value; }
virtual std::ostream& write (std::ostream &os) const override;
};
/// Represents a JSON null value.
class null final : public node {
public:
virtual type_t type (void) const override { return NONE; }
virtual std::unique_ptr<node> clone (void) const override;
virtual bool operator==(const null&) const override { return true; }
virtual bool operator==(const node &rhs) const override
{ return rhs == *this; }
virtual const null& as_null (void) const& override { return *this; }
virtual null& as_null (void) & override { return *this; }
virtual bool is_null (void) const override { return true; }
virtual std::ostream& write (std::ostream &os) const override;
};
std::ostream&
operator <<(std::ostream &os, const json::tree::node &n);
// Instantiate this template for the type you wish to output. We use a
// helper class here to avoid partial template specialisation of a
// function (eg, for templated types).
template <typename T, typename ...Args>
struct io {
static std::unique_ptr<json::tree::node>
serialise (const T&, Args &&...args);
static T
deserialise (const json::tree::node&, Args &&...args);
};
}
///////////////////////////////////////////////////////////////////////////////
template <typename T, class ...Args>
std::unique_ptr<json::tree::node>
to_json (const T &t, Args&&... args)
{
return json::tree::io<T,Args...>::serialise (
t, std::forward<Args>(args)...
);
}
///////////////////////////////////////////////////////////////////////////////
template <typename T, class ...Args>
T
from_json (const json::tree::node &n, Args&&... args)
{
return json::tree::io<T,Args...>::deserialise (
n, std::forward<Args>(args)...
);
}
//-----------------------------------------------------------------------------
template <typename T, class ...Args>
T
from_json (const std::experimental::filesystem::path &src, Args &&...args)
{
return from_json<T,Args...> (
*json::tree::parse (src),
std::forward<Args> (args)...
);
}
#endif

View File

@ -1,94 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2017 Danny Robson <danny@nerdcruft.net>
*/
#include "./event.hpp"
#include "./except.hpp"
#include "../debug.hpp"
using util::json2::event::packet;
///////////////////////////////////////////////////////////////////////////////
util::json2::event::type_t
packet::type (void) const noexcept
{
CHECK_NEQ (first, last);
const auto &c = *first;
switch (c) {
case '{': return type_t::OBJECT_BEGIN;
case '}': return type_t::OBJECT_END;
case '[': return type_t::ARRAY_BEGIN;
case ']': return type_t::ARRAY_END;
case '"': return type_t::STRING;
case 'n': return type_t::NONE;
case 't':
case 'f':
return type_t::BOOLEAN;
// TODO: leading plus isn't valid json, but other similar formats support
// this syntax and it's easier to claim it as a number globally here until
// we do a little refactoring.
case '-':
case '+':
case '0'...'9':
return type_t::NUMBER;
}
unhandled (c);
}
///////////////////////////////////////////////////////////////////////////////
#include "./personality/rfc7519.hpp"
#include "./personality/jsonish.hpp"
template <typename PersonalityT>
const char*
util::json2::event::parse (const std::function<util::json2::callback_t> &cb,
const char *first,
const char *last)
{
auto cursor = first;
PersonalityT p {};
cursor = p.consume_whitespace (cursor, last);
cursor = p.parse_value (cb, cursor, last);
cursor = p.consume_whitespace (cursor, last);
return cursor;
}
//-----------------------------------------------------------------------------
template
const char* util::json2::event::parse<util::json2::personality::rfc7159> (
const std::function<util::json2::callback_t> &,
const char*,
const char*
);
//-----------------------------------------------------------------------------
template
const char* util::json2::event::parse<util::json2::personality::jsonish> (
const std::function<util::json2::callback_t> &,
const char*,
const char*
);

View File

@ -1,55 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2017 Danny Robson <danny@nerdcruft.net>
*/
#ifndef CRUFT_JSON2_EVENT_HPP
#define CRUFT_JSON2_EVENT_HPP
#include "./fwd.hpp"
#include <functional>
namespace util::json2::event {
// It is important that scalars come after compound types because it
// simplifies categorisation as we can use a simple '>' to classify the
// types.
//
// the value of the enumerants isn't important, but they might make it
// fractionally easier to visualise in a debugger on some occasions.
enum class type_t {
OBJECT_BEGIN = '{',
OBJECT_END = '}',
ARRAY_BEGIN = '[',
ARRAY_END = ']',
STRING = '"',
NUMBER = '1',
BOOLEAN = 't',
NONE = 'n',
};
struct packet {
type_t type (void) const noexcept;
const char *first;
const char *last;
};
template <typename PersonalityT = personality::rfc7159>
const char*
parse (const std::function<callback_t>&, const char *first, const char *last);
};
#endif

View File

@ -1,40 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2017 Danny Robson <danny@nerdcruft.net>
*/
#ifndef CRUFT_UTIL_JSON2_EXCEPT_HPP
#define CRUFT_UTIL_JSON2_EXCEPT_HPP
#include <exception>
namespace util::json2 {
struct error : public std::exception {};
struct parse_error : public error {
parse_error (const char *_position):
position (_position)
{ ; }
const char *position;
};
struct overrun_error : public parse_error {
using parse_error::parse_error;
};
}
#endif

View File

@ -1,47 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2017 Danny Robson <danny@nerdcruft.net>
*/
#ifndef CRUFT_UTIL_JSON2_FWD_HPP
#define CRUFT_UTIL_JSON2_FWD_HPP
#include "../preprocessor.hpp"
namespace util::json2 {
namespace personality {
template <typename> struct base;
struct rfc7159;
struct jsonish;
#define MAP_JSON2_PERSONALITY_TYPES(FUNC) \
MAP0(FUNC, \
util::json2::personality::rfc7159,\
util::json2::personality::jsonish)
}
namespace event {
enum class type_t;
struct packet;
}
using callback_t = void(const event::packet&);
struct error;
struct parse_error;
struct overrun_error;
}
#endif

View File

@ -1,332 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2017 Danny Robson <danny@nerdcruft.net>
*/
#include "./base.hpp"
#include "../event.hpp"
#include "../except.hpp"
#include "../../debug.hpp"
#include "./rfc7519.hpp"
#include "./jsonish.hpp"
using util::json2::personality::base;
///////////////////////////////////////////////////////////////////////////////
static const char*
expect [[nodiscard]] (const char *first, const char *last, const char value)
{
if (first == last || *first != value)
throw util::json2::parse_error {first};
return first + 1;
}
///////////////////////////////////////////////////////////////////////////////
template <typename ParentT>
const char*
base<ParentT>::consume_whitespace (const char *first, const char *last) noexcept
{
auto cursor = first;
while (cursor != last) {
switch (*cursor) {
case 0x20:
case 0x09:
case 0x0A:
case 0x0D:
++cursor;
continue;
}
break;
}
return cursor;
}
///////////////////////////////////////////////////////////////////////////////
template <typename ParentT>
const char*
base<ParentT>::parse_number (const std::function<util::json2::callback_t> &cb,
const char *first,
const char *last)
{
(void)last;
// number: minus? int frac? exp?
auto cursor = first;
// minus: '-'
if (*cursor == '-')
++cursor;
// int: '0' | [1-9] DIGIT*
switch (*cursor) {
case '1'...'9':
{
++cursor;
while ('0' <= *cursor && *cursor <= '9')
++cursor;
break;
}
case '0':
// leading zero means we _must_ be parsing a fractional value so we
// look ahead to ensure we're about to do so. note that we don't use
// `expect' here because it implies consumption of '.'
++cursor;
if (*cursor != '.')
throw util::json2::parse_error { cursor };
break;
default:
throw util::json2::parse_error { cursor };
}
// frac: '.' digit+
if (*cursor == '.') {
++cursor;
auto frac_start = cursor;
while ('0' <= *cursor && *cursor <= '9')
++cursor;
if (frac_start == cursor)
throw util::json2::parse_error { cursor };
}
// exp: [eE] [-+]? digit+
if (*cursor == 'e' || *cursor == 'E') {
++cursor;
if (*cursor == '-' || *cursor == '+')
++cursor;
auto exp_digits = cursor;
while ('0' <= *cursor && *cursor <= '9')
++cursor;
if (exp_digits == cursor)
throw util::json2::parse_error { cursor };
}
cb ({ first, cursor });
return cursor;
}
//-----------------------------------------------------------------------------
template <typename ParentT>
template <int N>
const char*
base<ParentT>::parse_literal (const std::function<util::json2::callback_t> &cb,
const char *first,
const char *last,
const char (&value)[N])
{
CHECK_LE (first, last);
if (last - first < N - 1)
throw util::json2::overrun_error { first };
if (!std::equal (first, first + N - 1, value))
throw util::json2::parse_error { first };
cb ({ first, first + N - 1 });
return first + N - 1;
}
//-----------------------------------------------------------------------------
template <typename ParentT>
const char*
base<ParentT>::parse_string (const std::function<util::json2::callback_t> &cb,
const char *first,
const char *last)
{
CHECK_LE (first, last);
auto cursor = first;
cursor = expect (cursor, last, '"');
for ( ; cursor != last && *cursor != '"'; ) {
// advance the simple case first; unescaped character
if (*cursor++ != '\\') {
continue;
}
if (*cursor++ == 'u') {
for (int i = 0; i < 4; ++i) {
switch (*cursor) {
case 'a'...'f':
case 'A'...'F':
++cursor;
continue;
default:
throw util::json2::parse_error { cursor };
}
}
}
}
cursor = expect (cursor, last, '"');
cb ({ first, cursor });
return cursor;
}
//-----------------------------------------------------------------------------
template <typename ParentT>
const char*
base<ParentT>::parse_array (const std::function<util::json2::callback_t> &cb,
const char *first,
const char *last)
{
CHECK_LE (first, last);
auto cursor = first;
if (*cursor != '[')
throw util::json2::parse_error {cursor};
cb ({ cursor, cursor + 1 });
++cursor;
cursor = ParentT::consume_whitespace (cursor, last);
if (*cursor == ']') {
cb ({cursor, cursor + 1});
return ++cursor;
}
cursor = ParentT::parse_value (cb, cursor, last);
if (*cursor == ']') {
cb ({cursor, cursor + 1});
return ++cursor;
}
do {
cursor = ParentT::consume_whitespace (cursor, last);
cursor = expect (cursor, last, ',');
cursor = ParentT::consume_whitespace (cursor, last);
cursor = ParentT::parse_value (cb, cursor, last);
} while (*cursor != ']');
cb ({cursor, cursor + 1});
++cursor;
return cursor;
}
//-----------------------------------------------------------------------------
template <typename ParentT>
const char*
base<ParentT>::parse_object (const std::function<util::json2::callback_t> &cb,
const char *first,
const char *last)
{
CHECK_LE (first, last);
auto cursor = first;
cursor = expect (cursor, last, '{');
cb ({ cursor - 1, cursor });
cursor = ParentT::consume_whitespace (cursor, last);
if (*cursor == '}') {
cb ({cursor, cursor + 1});
return ++cursor;
};
auto parse_member = [] (auto _cb, auto _cursor, auto _last) {
_cursor = ParentT::parse_key (_cb, _cursor, _last);
_cursor = ParentT::consume_whitespace (_cursor, _last);
_cursor = expect (_cursor, _last, ':');
_cursor = ParentT::consume_whitespace (_cursor, _last);
_cursor = ParentT::parse_value (_cb, _cursor, _last);
_cursor = ParentT::consume_whitespace (_cursor, _last);
return _cursor;
};
cursor = parse_member (cb, cursor, last);
if (*cursor == '}') {
cb ({cursor, cursor + 1});
return ++cursor;
}
do {
cursor = expect (cursor, last, ',');
cursor = ParentT::consume_whitespace (cursor, last);
cursor = parse_member (cb, cursor, last);
} while (*cursor != '}');
cursor = expect (cursor, last, '}');
cb ({cursor - 1, cursor});
return cursor;
}
///////////////////////////////////////////////////////////////////////////////
template <typename ParentT>
const char*
base<ParentT>::parse_value (const std::function<util::json2::callback_t> &cb,
const char *first,
const char *last)
{
switch (*first) {
case '+':
case '-':
case '0'...'9':
return ParentT::parse_number (cb, first, last);
case '"':
return ParentT::parse_string (cb, first, last);
case 't': return ParentT::parse_literal (cb, first, last, "true");
case 'f': return ParentT::parse_literal (cb, first, last, "false");
case 'n': return ParentT::parse_literal (cb, first, last, "null");
case '[': return ParentT::parse_array (cb, first, last);
case '{': return ParentT::parse_object (cb, first, last);
}
return ParentT::parse_unknown (cb, first, last);
}
///////////////////////////////////////////////////////////////////////////////
template <typename ParentT>
const char*
base<ParentT>::parse_unknown (const std::function<util::json2::callback_t>&,
const char *first,
const char *last)
{
(void)last;
throw parse_error {first};
};
//-----------------------------------------------------------------------------
#define INSTANTIATE(KLASS) template struct util::json2::personality::base<KLASS>;
MAP_JSON2_PERSONALITY_TYPES (INSTANTIATE)

View File

@ -1,90 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2017 Danny Robson <danny@nerdcruft.net>
*/
#ifndef CRUFT_UTIL_JSON2_PERSONALITY_BASE_HPP
#define CRUFT_UTIL_JSON2_PERSONALITY_BASE_HPP
#include "../fwd.hpp"
#include <functional>
namespace util::json2::personality {
template <typename T>
struct base {
static const char*
consume_whitespace [[nodiscard]] (const char *first, const char *last) noexcept;
static const char*
parse_number [[nodiscard]] (
const std::function<callback_t>&,
const char *first,
const char *last
);
template <int N>
static const char*
parse_literal [[nodiscard]] (
const std::function<callback_t>&,
const char *first,
const char *last,
const char (&value)[N]
);
static const char*
parse_string [[nodiscard]] (
const std::function<callback_t>&,
const char *first,
const char *last
);
static const char*
parse_array [[nodiscard]] (
const std::function<callback_t>&,
const char *first,
const char *last
);
static const char*
parse_object [[nodiscard]] (
const std::function<callback_t>&,
const char *first,
const char *last
);
static const char*
parse_value [[nodiscard]] (
const std::function<callback_t>&,
const char *first,
const char *last
);
static const char*
parse_unknown [[noreturn]] (
const std::function<callback_t>&,
const char *first,
const char *last
);
};
};
#endif

View File

@ -1,201 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2017 Danny Robson <danny@nerdcruft.net>
*/
#include "./jsonish.hpp"
#include "./base.hpp"
#include "../event.hpp"
#include "../except.hpp"
#include "../../debug.hpp"
using util::json2::personality::jsonish;
///////////////////////////////////////////////////////////////////////////////
const char*
jsonish::consume_whitespace (const char *first, const char *last) noexcept
{
auto cursor = base<jsonish>::consume_whitespace (first, last);
// consume a comment
if (cursor != last && *cursor == '#') {
while (cursor != last && *cursor != '\n')
++cursor;
return consume_whitespace (cursor, last);
}
return cursor;
}
///////////////////////////////////////////////////////////////////////////////
// format is:
// int: '0x' hex+ | '0' oct+ | '0b' bit+
//
// float: significand exp?
// significand: digit+ ('.' digit*)?
// exp: [eE] sign? digit+
//
// number: [+-] (int | float)
const char*
jsonish::parse_number (const std::function<callback_t> &cb,
const char *first,
const char *last)
{
auto cursor = first;
if (cursor != last && (*cursor == '+' || *cursor == '-'))
++cursor;
if (cursor != last && *cursor == '0') {
++cursor;
if (cursor == last)
throw parse_error {cursor};
char max = '9';
switch (*cursor) {
case 'x': {
// parse the hex integer here because we can simplify the
// remaining cases somewhat if we don't need to care about the
// multiple ranges of valid digits.
++cursor;
auto digit_start = cursor;
while (cursor != last && ((('0' <= *cursor) && (*cursor <= '9')) ||
(('a' <= *cursor) && (*cursor <= 'f')) ||
(('A' <= *cursor) && (*cursor <= 'F'))))
++cursor;
if (digit_start == cursor)
throw parse_error {cursor};
cb ({first, cursor});
return cursor;
};
case 'b': max = '1'; break;
case '0'...'7': max = '7'; break;
case '.':
goto frac;
}
auto digit_start = ++cursor;
while (cursor != last && '0' <= *cursor && *cursor <= max)
++cursor;
if (digit_start == cursor)
throw parse_error {cursor};
cb ({first, cursor});
return cursor;
}
while (cursor != last && '0' <= *cursor && *cursor <= '9')
++cursor;
if (cursor == last)
goto done;
if (*cursor != '.')
goto exp;
frac:
++cursor;
while (cursor != last && *cursor >= '0' && *cursor <= '9')
++cursor;
if (cursor == last)
goto done;
exp:
if (cursor != last && (*cursor == 'e' || *cursor == 'E')) {
++cursor;
if (cursor != last && (*cursor == '+' || *cursor == '-'))
++cursor;
auto digit_start = cursor;
while (cursor != last && '0' <= *cursor && *cursor <= '9')
++cursor;
if (digit_start == cursor)
throw parse_error {cursor};
}
if (first == cursor)
throw parse_error {cursor};
done:
cb ({first, cursor});
return cursor;
}
///////////////////////////////////////////////////////////////////////////////
const char*
jsonish::parse_key (const std::function<callback_t> &cb,
const char *first,
const char *last)
{
auto cursor = first;
if (cursor == last)
throw parse_error {cursor};
// must start with alpha or underscore
switch (*cursor) {
case 'a'...'z':
case 'A'...'Z':
case '_':
++cursor;
break;
default:
throw parse_error {cursor};
}
while (cursor != last) {
switch (*cursor) {
case 'a'...'z':
case 'A'...'Z':
case '_':
case '0'...'9':
++cursor;
break;
default:
cb ({first, cursor});
return cursor;
}
}
cb ({first, cursor});
return cursor;
}
///////////////////////////////////////////////////////////////////////////////
const char*
jsonish::parse_string (const std::function<callback_t> &cb,
const char *first,
const char *last)
{
if (first == last)
throw parse_error {first};
if (*first == '"')
return base<jsonish>::parse_string (cb, first, last);
else
return parse_key (cb, first, last);
}

View File

@ -1,101 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2017 Danny Robson <danny@nerdcruft.net>
*/
#ifndef CRUFT_UTIL_JSON2_PERSONALITY_JSONISH_HPP
#define CRUFT_UTIL_JSON2_PERSONALITY_JSONISH_HPP
#include "../fwd.hpp"
#include "./base.hpp"
#include <functional>
namespace util::json2::personality {
struct jsonish {
static const char*
consume_whitespace [[nodiscard]] (
const char *first,
const char *last
) noexcept;
static const char*
parse_value [[nodiscard]] (
const std::function<callback_t> &cb,
const char *first, const char *last
) { return base<jsonish>::parse_value (cb, first, last); }
static const char*
parse_number [[nodiscard]] (
const std::function<callback_t> &cb,
const char *first,
const char *last
);
template <int N>
static const char*
parse_literal [[nodiscard]] (
const std::function<callback_t> &cb,
const char *first,
const char *last,
const char (&value)[N]
) { return base<jsonish>::parse_literal (cb, first, last, value); }
static const char*
parse_string [[nodiscard]] (
const std::function<callback_t> &cb,
const char *first,
const char *last
);
static const char*
parse_array [[nodiscard]] (
const std::function<callback_t> &cb,
const char *first,
const char *last
) { return base<jsonish>::parse_array (cb, first, last); }
static const char*
parse_key [[nodiscard]] (
const std::function<callback_t> &cb,
const char *first,
const char *last);
static const char*
parse_object [[nodiscard]] (
const std::function<callback_t> &cb,
const char *first,
const char *last
) { return base<jsonish>::parse_object (cb, first, last); }
static const char*
parse_unknown [[nodiscard]] (
const std::function<callback_t> &cb,
const char *first,
const char *last
) { return parse_string (cb, first, last); }
};
};
#endif

View File

@ -1,21 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2017 Danny Robson <danny@nerdcruft.net>
*/
#include "./rfc7519.hpp"
#include "./base.hpp"
using util::json2::personality::rfc7159;

View File

@ -1,100 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2017 Danny Robson <danny@nerdcruft.net>
*/
#ifndef CRUFT_UTIL_JSON2_PERSONALITY_RFC7159_HPP
#define CRUFT_UTIL_JSON2_PERSONALITY_RFC7159_HPP
#include "./base.hpp"
#include "../fwd.hpp"
#include <functional>
namespace util::json2::personality {
struct rfc7159 {
static const char*
consume_whitespace [[nodiscard]] (const char *first, const char *last) noexcept
{ return base<rfc7159>::consume_whitespace (first, last); }
static const char*
parse_value [[nodiscard]] (
const std::function<callback_t> &cb,
const char *first, const char *last
) { return base<rfc7159>::parse_value (cb, first, last); }
static const char*
parse_number [[nodiscard]] (
const std::function<callback_t> &cb,
const char *first,
const char *last
) { return base<rfc7159>::parse_number (cb, first, last); }
template <int N>
static const char*
parse_literal [[nodiscard]] (
const std::function<callback_t> &cb,
const char *first,
const char *last,
const char (&value)[N]
) { return base<rfc7159>::parse_literal (cb, first, last, value); }
static const char*
parse_string [[nodiscard]] (
const std::function<callback_t> &cb,
const char *first,
const char *last
) { return base<rfc7159>::parse_string (cb, first, last); }
static const char*
parse_array [[nodiscard]] (
const std::function<callback_t> &cb,
const char *first,
const char *last
) { return base<rfc7159>::parse_array (cb, first, last); }
static const char*
parse_key [[nodiscard]] (
const std::function<callback_t> &cb,
const char *first,
const char *last)
{ return parse_string (cb, first, last); }
static const char*
parse_object [[nodiscard]] (
const std::function<callback_t> &cb,
const char *first,
const char *last
) { return base<rfc7159>::parse_object (cb, first, last); }
static const char *
parse_unknown [[noreturn]] (
const std::function<callback_t> &cb,
const char *first,
const char *last)
{ throw base<rfc7159>::parse_unknown (cb, first, last); }
};
};
#endif

View File

@ -1 +0,0 @@
#include "./tree.hpp"

View File

@ -1,22 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2017 Danny Robson <danny@nerdcruft.net>
*/
#ifndef CRUFT_JSON2_TREE_HPP
#define CRUFT_JSON2_TREE_HPP
#endif

View File

@ -18,7 +18,6 @@
#include "range.hpp"
#include "debug.hpp"
#include "json/tree.hpp"
#include "maths.hpp"
#include "random.hpp"
@ -199,25 +198,3 @@ template struct util::range<uint8_t>;
template struct util::range<uint16_t>;
template struct util::range<uint32_t>;
template struct util::range<uint64_t>;
//-----------------------------------------------------------------------------
namespace json::tree {
template <>
util::range<double>
io<util::range<double>>::deserialise (const json::tree::node &node)
{
if (node.is_string () && (node == "UNIT" ||
node == "unit")) {
return util::range<double>::unit ();
} else if (node.is_string () && (node == "UNLIMITED" ||
node == "unlimited")) {
return util::range<double>::unlimited ();
} else {
return {
node[0].as_number (),
node[1].as_number ()
};
}
}
}

View File

@ -1,38 +0,0 @@
#!/bin/sh
validate="@CMAKE_CURRENT_BINARY_DIR@/json-validate"
basedir="@CMAKE_CURRENT_SOURCE_DIR@/test/json/validate"
count=0
code=0
for i in $(ls "${basedir}/good/"*);
do
$validate $i 2>/dev/null 1>&2
if [ $? -eq 0 ]; then
echo "ok - good/$(basename $i .json)"
else
echo "not ok - good/$(basename $i .json)"
code=1
fi
count=$((count+1))
done
for i in $(ls "${basedir}/bad/"*);
do
$validate $i 1>&2 2>/dev/null
if [ $? -eq 0 ]; then
echo "not ok - bad/$(basename $i .json)"
code=1
else
echo "ok - bad/$(basename $i .json)"
fi
count=$((count+1))
done
echo "1..$count"
exit $code

View File

@ -1,54 +0,0 @@
#!/usr/bin/env python3
import sys
import glob
import os.path
import subprocess
import re
## fix paths for running under Wine
def systemise_path(path):
if "@EXEEXT@" == ".exe":
return "Z:%s" % os.path.abspath(path)
return path
SDIR = "@abs_top_srcdir@"
BDIR = "@abs_top_builddir@"
TOOL = os.path.join(BDIR, "tools", "json-schema@EXEEXT@")
RUNNER = os.path.join("@abs_top_srcdir@", "build-aux", "wine-crlf.sh") if "@EXEEXT@" == ".exe" else "/usr/bin/env"
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"))))
code=0
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 = [RUNNER, TOOL, systemise_path(schema), systemise_path(test)]
(name, seq, success) = TEST_EXTRACT.match(test).groups()
res = subprocess.call(command, stdout=subprocess.DEVNULL,stderr=subprocess.STDOUT)
if res != EXPECTED[success]:
print('got res', res)
print('not ok -', os.path.basename(test), '#', ' '.join(command))
code=1
else:
print('ok -', os.path.basename(test))
sys.exit(code)

View File

@ -1,30 +0,0 @@
#!/usr/bin/env python3
from glob import glob
from subprocess import call
from os.path import basename
tool="@CMAKE_CURRENT_BINARY_DIR@/json-compare"
src="@CMAKE_CURRENT_SOURCE_DIR@/test/json/compare"
suffix=".a.json"
failures = 0
for expected in [ "good", "bad" ]:
for path in glob(f"{src}/{expected}/*{suffix}"):
base = path[:-len(suffix)]
inputs = glob(f"{base}*.json")
code = call([tool, *inputs])
success = (expected == "good") == (code == 0)
prefix = ""
if not success:
prefix = "not "
failures += 1
print(prefix, "ok - ", expected, basename(base), code)
exit(1 if failures else 0)

View File

@ -1 +0,0 @@
3.14

View File

@ -1 +0,0 @@
3.142

View File

@ -1 +0,0 @@
[0,1,2]

View File

@ -1 +0,0 @@
[0,1]

View File

@ -1 +0,0 @@
{"a":0,"b":1}

View File

@ -1 +0,0 @@
{"a":0}

View File

@ -1 +0,0 @@
{"a":0}

View File

@ -1 +0,0 @@
{"a":0.00001}

View File

@ -1 +0,0 @@
638

View File

@ -1 +0,0 @@
638

View File

@ -1 +0,0 @@
"the quick brown fox"

View File

@ -1 +0,0 @@
"the quick brown fox"

View File

@ -1 +0,0 @@
3.14

View File

@ -1 +0,0 @@
3.14

View File

@ -1 +0,0 @@
true

View File

@ -1 +0,0 @@
true

View File

@ -1 +0,0 @@
false

View File

@ -1 +0,0 @@
false

View File

@ -1 +0,0 @@
null

View File

@ -1 +0,0 @@
null

View File

@ -1 +0,0 @@
{"foo":"bar"}

View File

@ -1 +0,0 @@
{"foo":"bar"}

View File

@ -1 +0,0 @@
[true]

View File

@ -1 +0,0 @@
[true]

View File

@ -1 +0,0 @@
{"a":0,"b":1}

View File

@ -1 +0,0 @@
{"b":1,"a":0}

View File

@ -1,44 +0,0 @@
#!/usr/bin/env python3
import os.path
import tempfile
import subprocess
from glob import glob
if __name__ == '__main__':
json_tests = "@CMAKE_CURRENT_SOURCE_DIR@/test/json/pointer/validate/"
json_pointer = "@CMAKE_CURRENT_BINARY_DIR@/json-pointer"
json_compare = "@CMAKE_CURRENT_BINARY_DIR@/json-compare"
paths = glob(os.path.join(json_tests, "*.pointer"))
paths = (os.path.splitext(p)[0] for p in paths)
names = sorted(p for p in paths)
failures = 0
for n in names:
input = f"{n}.input.json"
truth = f"{n}.truth.json"
with open(f"{n}.pointer") as f: pointer = f.read()
success = False
try:
with tempfile.NamedTemporaryFile(delete=True) as result:
do_query = [json_pointer, pointer.rstrip(), input]
do_compare = [json_compare, result.name, truth]
#print(do_query)
#print(do_compare)
subprocess.check_call(do_query, stdout=result)
subprocess.check_call(do_compare)
success = True
except subprocess.CalledProcessError:
failures += 1
prefix = "not " if not success else ""
print(f"{prefix}ok - {n}")
exit(1 if failures else 0)

View File

@ -1 +0,0 @@
object.json

View File

@ -1 +0,0 @@
object.json

View File

@ -1 +0,0 @@
object.json

View File

@ -1 +0,0 @@
"empty"

View File

@ -1 +0,0 @@
object.json

View File

@ -1 +0,0 @@
#/string

View File

@ -1 +0,0 @@
"value"

View File

@ -1 +0,0 @@
object.json

View File

@ -1 +0,0 @@
#/array/2

View File

@ -1 +0,0 @@
object.json

View File

@ -1 +0,0 @@
#/object/inner/value/2

View File

@ -1,11 +0,0 @@
{
"": "empty",
"string": "value",
"array": [ 0, 1, 2, 3 ],
"object": {
"boolean": true,
"inner": {
"value": [ 3, 1, 4 ]
}
}
}

View File

@ -1,108 +0,0 @@
#!/usr/bin/env python3
from glob import glob
import subprocess
import os.path
import os
import tempfile
validate="@CMAKE_CURRENT_BINARY_DIR@/json-schema"
compare="@CMAKE_CURRENT_BINARY_DIR@/json-compare"
src="@CMAKE_CURRENT_SOURCE_DIR@/test/json/schema"
devnull=open(os.devnull, 'w')
def test_bad(schema:str, dir:str) -> int:
failures = 0
for input in glob(os.path.join(dir, "*.json")):
code = subprocess.call([validate, schema, input], stdout=devnull, stderr=devnull)
prefix = ""
if code == 0:
prefix = "not "
failures += 1
prefix = "not " if code == 0 else ""
print(f"{prefix}ok - {input}")
return failures
def test_good(schema:str, dir:str) -> int:
# extract a list of inputs and truths. the truth list may be incomplete
# (if we're not dealing with defaults) so we can't use it directly.
inputs = glob(os.path.join(dir, "*.input.json"))
results = glob(os.path.join(dir, "*.result.json"))
unused = [x for x in glob(f"{dir}/*") if x not in inputs and x not in results]
if unused:
raise RuntimeError("unused inputs", unused)
inputs.sort()
failures = 0
for test in inputs:
# check if we have a corresponding .result.json file as the ground
# truth. if not then we assume that there shouldn't be any chance in
# the resulting json and set the truth to the test file.
(base,_) = os.path.splitext(test)
(base,_) = os.path.splitext(base)
truth = f"{base}.result.json"
if not os.path.isfile(truth):
truth = test
success = False
try:
# a two stage check:
# * apply the schema to the test file
# * test it matches the truth file
with tempfile.NamedTemporaryFile(delete=True) as out:
subprocess.check_call([validate, schema, test], stdout=out)
subprocess.check_call([compare, out.name, truth])
success = True
except subprocess.CalledProcessError:
failures += 1
prefix = "not " if not success else ""
print(f"{prefix}ok - {test}")
return failures
def validation_group(dir:str) -> int:
schema = os.path.join(dir, "schema.json")
if not os.path.isfile(schema):
raise Exception(f"schema is not present, {schema}")
failures = 0
failures += test_good(schema, os.path.join(dir, "good"))
failures += test_bad(schema, os.path.join(dir, "bad"))
return failures
def test_validation(dir:str) -> int:
failures = 0
groups = (x for x in os.listdir(dir))
groups = (os.path.join(dir, x) for x in groups)
groups = (x for x in groups if os.path.isdir(x))
for name in sorted(groups):
path = os.path.join(src, name)
if not os.path.isdir(path):
continue
failures += validation_group(path)
return failures
if __name__ == "__main__":
failures = 0
failures += test_validation(os.path.join(src, "validation"))
exit(1 if failures else 0)

View File

@ -1,4 +0,0 @@
{
"description": "multipleOf must be strictly greater than zero",
"multipleOf": 0
}

View File

@ -1,4 +0,0 @@
{
"description": "multipleOf must be strictly greater than zero",
"multipleOf": -1
}

View File

@ -1 +0,0 @@
[1,"a", {}]

View File

@ -1 +0,0 @@
0001.input.json

View File

@ -1 +0,0 @@
0002.input.json

View File

@ -1 +0,0 @@
0001.input.json

View File

@ -1 +0,0 @@
0002.input.json

Some files were not shown because too many files have changed in this diff Show More