2018-07-11 19:28:13 +10:00
|
|
|
/*
|
|
|
|
* 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;
|
2018-07-12 15:10:41 +10:00
|
|
|
using util::json::schema::constraint::pattern_properties;
|
|
|
|
using util::json::schema::constraint::additional_properties;
|
2018-07-11 19:28:13 +10:00
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-07-12 15:10:41 +10:00
|
|
|
//-----------------------------------------------------------------------------
|
2018-07-11 19:28:13 +10:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-07-12 15:56:03 +10:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool
|
|
|
|
properties::has (std::string const &key) const noexcept
|
|
|
|
{
|
|
|
|
return m_properties.cend () != m_properties.find (key);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-07-12 15:10:41 +10:00
|
|
|
//-----------------------------------------------------------------------------
|
2018-07-11 19:28:13 +10:00
|
|
|
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 << " ] }";
|
|
|
|
}
|
2018-07-12 15:10:41 +10:00
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-07-12 15:56:03 +10:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
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);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-07-12 15:10:41 +10:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
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 << " } }";
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
2018-07-12 15:56:03 +10:00
|
|
|
additional_properties::additional_properties (
|
|
|
|
::json::tree::node const &def,
|
|
|
|
properties *_properties,
|
|
|
|
pattern_properties *_patterns
|
|
|
|
)
|
|
|
|
: m_fallback (def)
|
|
|
|
, m_properties (_properties)
|
|
|
|
, m_patterns (_patterns)
|
|
|
|
{ ; }
|
|
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
2018-07-12 15:10:41 +10:00
|
|
|
additional_properties::additional_properties (::json::tree::node const &def):
|
2018-07-12 15:56:03 +10:00
|
|
|
additional_properties (def, nullptr, nullptr)
|
2018-07-12 15:10:41 +10:00
|
|
|
{ }
|
|
|
|
|
|
|
|
|
2018-07-12 15:56:03 +10:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void
|
|
|
|
additional_properties::use (properties *ptr)
|
|
|
|
{
|
|
|
|
m_properties = ptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void
|
|
|
|
additional_properties::use (pattern_properties *ptr)
|
|
|
|
{
|
|
|
|
m_patterns = ptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-07-12 15:10:41 +10:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
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 };
|
|
|
|
|
2018-07-12 15:56:03 +10:00
|
|
|
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;
|
|
|
|
|
2018-07-12 15:10:41 +10:00
|
|
|
res = m_fallback.validate (res, *val);
|
2018-07-12 15:56:03 +10:00
|
|
|
}
|
2018-07-12 15:10:41 +10:00
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
std::ostream&
|
|
|
|
additional_properties::describe (std::ostream &os) const
|
|
|
|
{
|
|
|
|
return os << "{ additional_properties: " << m_fallback << " }";
|
|
|
|
}
|