json/schema: add pattern_properties and associated tests

This commit is contained in:
Danny Robson 2018-07-12 15:10:41 +10:00
parent 85b5853f7f
commit 8c222300ca
19 changed files with 152 additions and 10 deletions

View File

@ -29,13 +29,15 @@ using util::json::schema::constraint::base;
std::unique_ptr<base> std::unique_ptr<base>
base::instantiate (std::string const &name, ::json::tree::node const &def) base::instantiate (std::string const &name, ::json::tree::node const &def)
{ {
if (name == "type") return std::make_unique<type> (def); if (name == "type") return std::make_unique<type> (def);
if (name == "minLength") return std::make_unique<min_length> (def); if (name == "minLength") return std::make_unique<min_length> (def);
if (name == "maxLength") return std::make_unique<max_length> (def); if (name == "maxLength") return std::make_unique<max_length> (def);
if (name == "minimum") return std::make_unique<minimum> (def); if (name == "minimum") return std::make_unique<minimum> (def);
if (name == "maximum") return std::make_unique<maximum> (def); if (name == "maximum") return std::make_unique<maximum> (def);
if (name == "properties") return std::make_unique<properties> (def); if (name == "properties") return std::make_unique<properties> (def);
if (name == "enum") return std::make_unique<enumeration> (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); throw unknown_constraint (name);
} }

View File

@ -27,4 +27,6 @@ namespace util::json::schema::constraint {
template <typename> class length; template <typename> class length;
template <template <typename> class> class inequality; template <template <typename> class> class inequality;
class properties; class properties;
class pattern_properties;
class additional_properties;
} }

View File

@ -29,7 +29,7 @@ namespace util {
}; };
MAP0(SCHEMA_INTROSPECTION, type, combine, properties) MAP0(SCHEMA_INTROSPECTION, type, combine, properties, pattern_properties, additional_properties)
#undef SCHEMA_INTROSPECTION #undef SCHEMA_INTROSPECTION
} }

View File

@ -19,6 +19,8 @@
#include "../tree.hpp" #include "../tree.hpp"
using util::json::schema::constraint::properties; using util::json::schema::constraint::properties;
using util::json::schema::constraint::pattern_properties;
using util::json::schema::constraint::additional_properties;
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -33,7 +35,7 @@ properties::properties (::json::tree::node const &def)
} }
/////////////////////////////////////////////////////////////////////////////// //-----------------------------------------------------------------------------
properties::output_iterator properties::output_iterator
properties::validate (output_iterator res, ::json::tree::node &target) const noexcept properties::validate (output_iterator res, ::json::tree::node &target) const noexcept
{ {
@ -65,7 +67,7 @@ properties::validate (output_iterator res, ::json::tree::node &target) const noe
} }
/////////////////////////////////////////////////////////////////////////////// //-----------------------------------------------------------------------------
std::ostream& std::ostream&
properties::describe (std::ostream &os) const properties::describe (std::ostream &os) const
{ {
@ -79,3 +81,81 @@ properties::describe (std::ostream &os) const
return 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;
}
//-----------------------------------------------------------------------------
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):
m_fallback (def)
{ }
//-----------------------------------------------------------------------------
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 ())
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

@ -22,6 +22,7 @@
#include <map> #include <map>
#include <string> #include <string>
#include <memory> #include <memory>
#include <regex>
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -37,4 +38,39 @@ namespace util::json::schema::constraint {
private: private:
std::map<std::string,combine> m_properties; 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;
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;
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

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

View File

@ -0,0 +1 @@
{ "istring": "value" }

View File

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

View File

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

View File

@ -0,0 +1 @@
{ "sFoo": "var" }

View File

@ -0,0 +1,3 @@
{
"sFoo": null
}

View File

@ -0,0 +1 @@
{ "s": "value" }

View File

@ -0,0 +1 @@
{ "skey": "value" }

View File

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

View File

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

View File

@ -0,0 +1 @@
{ "thisEndsWithFoo": null }

View File

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

View File

@ -0,0 +1,7 @@
{
"patternProperties": {
"^s": { "type": "string" },
"^i": { "type": "integer" },
"Foo$": { "enum": [ "foo", null ] }
}
}