libcruft-util/json/constraint/properties.cpp

218 lines
6.1 KiB
C++

/*
* 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 << " }";
}