Compare commits

..

2 Commits

Author SHA1 Message Date
dhmemi
a33fe4bcdd make clang-tidy happy. 2023-09-22 15:29:02 +02:00
dhmemi
a19d71ddcb fix: validate multipleOf fails on float-point value 2023-09-22 15:29:02 +02:00
32 changed files with 141 additions and 855 deletions

View File

@ -1 +0,0 @@
1

4
.distro/.gitignore vendored
View File

@ -1,4 +0,0 @@
/main.fmf
/plans/main.fmf
/tests/main.fmf
*.tar.gz

View File

@ -1,61 +0,0 @@
Name: json-schema-validator
Summary: JSON schema validator for JSON for Modern C++
Version: 0.0.0
Release: %autorelease
License: MIT
URL: https://github.com/pboettch/json-schema-validator
Source: https://github.com/pboettch/json-schema-validator/archive/refs/tags/v%{version}.tar.gz
BuildRequires: ninja-build
BuildRequires: cmake
BuildRequires: gcc-c++
BuildRequires: json-devel
%description
Json schema validator library for C++ projects using nlohmann/json
%package devel
Summary: Development files for JSON schema validator
Requires: json-schema-validator%{?_isa} = %{version}-%{release}
Requires: json-devel
%description devel
Json schema validator development files for C++ projects using nlohmann/json
%prep
%autosetup -n json-schema-validator-%{version}
%build
%cmake \
-DJSON_VALIDATOR_SHARED_LIBS=ON \
-DJSON_VALIDATOR_INSTALL=ON \
-DJSON_VALIDATOR_BUILD_EXAMPLES=OFF \
-DJSON_VALIDATOR_BUILD_TESTS=ON
%cmake_build
%install
%cmake_install
%check
%ctest
%files
%doc README.md
%license LICENSE
%{_libdir}/libnlohmann_json_validator.so.*
%files devel
%{_libdir}/libnlohmann_json_validator.so
%{_includedir}/nlohmann/json-schema.hpp
%{_libdir}/cmake/nlohmann_json_schema_validator
%changelog
%autochangelog

View File

@ -1,3 +0,0 @@
Filters = [
"unknown-key",
]

View File

@ -1,12 +0,0 @@
summary:
Basic importing tests
prepare+:
- name: Include minimum fetching packages
how: install
package:
- git
discover+:
how: fmf
filter: "tag: import"
execute:
how: tmt

View File

@ -1,4 +0,0 @@
discover:
how: fmf
dist-git-source: true
path: .distro

View File

@ -1,14 +0,0 @@
summary:
Perform rpmlint and rpminspect tests
prepare:
- name: Download the source rpm
how: shell
script: cd /tmp && curl -O ${PACKIT_SRPM_URL}
- name: Download rpm packages
how: shell
script: cd /tmp && dnf download ${PACKIT_COPR_RPMS}
discover+:
how: fmf
filter: "tag: rpmlint"
execute:
how: tmt

View File

@ -1,7 +0,0 @@
summary:
Basic smoke tests
discover+:
how: fmf
filter: "tag: smoke"
execute:
how: tmt

View File

@ -1,15 +0,0 @@
# This is a simple project that tests using cmake to load the installed libraries
cmake_minimum_required(VERSION 3.14)
project(test_fetch_content LANGUAGES CXX)
FetchContent_Declare(nlohmann_json_schema_validator
GIT_REPOSITORY https://github.com/pboettch/json-schema-validator
GIT_TAG main
)
FetchContent_MakeAvailable(nlohmann_json_schema_validator)
if (NOT TARGET nlohmann_json_schema_validator::validator)
message(FATAL_ERROR "Missing target nlohmann_json_schema_validator::validator")
endif ()

View File

@ -1,11 +0,0 @@
# This is a simple project that tests using cmake to load the installed libraries
cmake_minimum_required(VERSION 3.14)
project(test_find_package LANGUAGES CXX)
set(CMAKE_FIND_DEBUG_MODE ON)
find_package(nlohmann_json_schema_validator REQUIRED)
if (NOT TARGET nlohmann_json_schema_validator::validator)
message(FATAL_ERROR "Missing target nlohmann_json_schema_validator::validator")
endif ()

View File

@ -1,11 +0,0 @@
# Common test variables
tag:
- import
tier: 0
path: /tests/import
# Define tests
/find_package:
test: ./test_find_package.sh
/FetchContent:
test: ./test_FetchContent.sh

View File

@ -1,4 +0,0 @@
#!/bin/bash -eux
tmp_dir=$(mktemp -d)
cmake -S ./FetchContent -B ${tmp_dir}

View File

@ -1,4 +0,0 @@
#!/bin/bash -eux
tmp_dir=$(mktemp -d)
cmake -S ./find_package -B ${tmp_dir}

View File

@ -1,13 +0,0 @@
# Common test variables
tag:
- rpmlint
tier: 0
path: /
# Define tests
/rpmlint:
summary: Rpmlint spec and rpmfiles
test: rpmlint -c packit.toml -r json-schema-validator.rpmlintrc ./*.spec /tmp/*.rpm
/rpminspect-rpms:
summary: Rpminspect the rpms
test: ls /tmp/*.rpm | xargs -L1 rpminspect-fedora -E metadata,disttag

View File

@ -1,9 +0,0 @@
# Common test variables
tag:
- smoke
tier: 0
path: /
# Define tests
/version:
test: echo "TODO: Write a minimum working example"

View File

@ -1,79 +0,0 @@
specfile_path: .distro/json-schema-validator.spec
files_to_sync:
- src: .distro/json-schema-validator.spec
dest: json-schema-validator.spec
- .packit.yaml
- src: .distro/json-schema-validator.rpmlintrc
dest: json-schema-validator.rpmlintrc
# tmt setup
- src: .distro/.fmf/
dest: .fmf/
- src: .distro/plans/
dest: plans/
filters:
- "- .distro/plans/main.fmf.dist-git"
- "- .distro/plans/rpmlint.fmf"
- src: .distro/plans/main.fmf.dist-git
dest: plans/main.fmf
upstream_package_name: json-schema-validator
downstream_package_name: json-schema-validator
update_release: false
upstream_tag_template: v{version}
jobs:
- job: copr_build
trigger: pull_request
owner: lecris
project: json-schema-validator
update_release: true
release_suffix: "{PACKIT_RPMSPEC_RELEASE}"
targets:
- fedora-development
- job: tests
trigger: pull_request
targets:
- fedora-development
fmf_path: .distro
- job: copr_build
trigger: commit
branch: main
owner: lecris
project: nightly
# TODO: Remove when upstream issue is resolved
# https://github.com/packit/packit/issues/1924
additional_repos:
- copr://@scikit-build/release
targets:
- fedora-development-x86_64
- fedora-latest-x86_64
- fedora-development-aarch64
- fedora-latest-aarch64
- job: copr_build
trigger: release
owner: lecris
project: release
targets:
- fedora-development-x86_64
- fedora-latest-x86_64
- fedora-development-aarch64
- fedora-latest-aarch64
- job: tests
trigger: commit
branch: main
targets:
- fedora-development
- fedora-latest
fmf_path: .distro
- job: propose_downstream
trigger: release
dist_git_branches:
- fedora-development
- fedora-latest
- job: koji_build
trigger: commit
dist_git_branches:
- fedora-all
- job: bodhi_update
trigger: commit
dist_git_branches:
- fedora-branched

View File

@ -13,7 +13,6 @@ repos:
- '-Bcmake-build-pre-commit' - '-Bcmake-build-pre-commit'
- '--preset' - '--preset'
- 'pre-commit' - 'pre-commit'
stages: [ manual ]
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0 rev: v4.4.0
hooks: hooks:

View File

@ -10,12 +10,9 @@ endif ()
# Basic project definition # # Basic project definition #
]==============================================================================================] ]==============================================================================================]
# TODO: CMake >= 3.19 can use string(JSON VERSION GET "${METADATA}" "version") to load from JSON
set(PROJECT_VERSION 2.4.0)
# TODO: Version 3, rename the project and namespace to something more compact # TODO: Version 3, rename the project and namespace to something more compact
project(nlohmann_json_schema_validator project(nlohmann_json_schema_validator
VERSION ${PROJECT_VERSION} VERSION 2.2.0
DESCRIPTION "Json validator for nlohmann::json library" DESCRIPTION "Json validator for nlohmann::json library"
HOMEPAGE_URL "https://github.com/pboettch/json-schema-validator" HOMEPAGE_URL "https://github.com/pboettch/json-schema-validator"
LANGUAGES CXX) LANGUAGES CXX)
@ -83,17 +80,16 @@ endif ()
]==============================================================================================] ]==============================================================================================]
set(fetch_packages "") set(fetch_packages "")
if (NOT TARGET nlohmann_json) # Fetch/Find nlohmann_json
# Fetch/Find nlohmann_json # TODO: Remove when bumping cmake >= 3.24
# TODO: Remove when bumping cmake >= 3.24 if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.24)
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.24)
FetchContent_Declare(nlohmann_json FetchContent_Declare(nlohmann_json
GIT_REPOSITORY https://github.com/nlohmann/json GIT_REPOSITORY https://github.com/nlohmann/json
GIT_TAG ${JSON_FETCH_VERSION} GIT_TAG ${JSON_FETCH_VERSION}
FIND_PACKAGE_ARGS FIND_PACKAGE_ARGS
) )
list(APPEND fetch_packages nlohmann_json) list(APPEND fetch_packages nlohmann_json)
else () else ()
# Try to get system installed version # Try to get system installed version
find_package(nlohmann_json QUIET) find_package(nlohmann_json QUIET)
if (NOT nlohmann_json_FOUND) if (NOT nlohmann_json_FOUND)
@ -104,7 +100,6 @@ if (NOT TARGET nlohmann_json)
) )
list(APPEND fetch_packages nlohmann_json) list(APPEND fetch_packages nlohmann_json)
endif () endif ()
endif ()
endif () endif ()
# Handle configure flags # Handle configure flags

View File

@ -74,7 +74,7 @@ types, depending on if the schema type is "integer" or "number". Bignum
This library is based on Niels Lohmann's JSON-library and thus has This library is based on Niels Lohmann's JSON-library and thus has
a build-dependency to it. a build-dependency to it.
Currently at least version **3.8.0** of NLohmann's JSON library Currently at least version **3.6.0** of NLohmann's JSON library
is required. is required.
Various methods using CMake can be used to build this project. Various methods using CMake can be used to build this project.

View File

@ -1,10 +1,7 @@
import os import os
import re import re
from conans import load, tools, ConanFile, CMake
from conan import ConanFile
from conan.tools.cmake import cmake_layout, CMake, CMakeToolchain
from conans.tools import load
from conans import tools as ctools
def get_version(): def get_version():
try: try:
@ -23,59 +20,52 @@ class JsonSchemaValidatorConan(ConanFile):
version = get_version() version = get_version()
url = 'https://github.com/pboettch/json-schema-validator' url = 'https://github.com/pboettch/json-schema-validator'
license = 'MIT' license = 'MIT'
settings = 'os', 'compiler', 'build_type', 'arch' settings = 'os', 'compiler', 'build_type', 'arch'
options = { options = {
'shared': [True, False], 'shared': [True, False],
'fPIC': [True, False], 'fPIC': [True, False],
'build_examples': [True, False], 'build_examples': [True, False],
'build_tests': [True, False], 'build_tests': [True, False]
'test_coverage': [True, False],
} }
default_options = { default_options = {
'shared': False, 'shared': False,
'fPIC': True, 'fPIC': True,
'build_examples': True, 'build_examples': True,
'build_tests': False, 'build_tests': False
'test_coverage': False,
} }
generators = "CMakeDeps"
generators = 'CMakeDeps', 'CMakeToolchain', 'VirtualBuildEnv', 'VirtualRunEnv'
exports_sources = [ exports_sources = [
'CMakeLists.txt', 'CMakeLists.txt',
'conanfile.py', 'nlohmann_json_schema_validatorConfig.cmake.in',
'cmake/*',
'src/*', 'src/*',
'example/*', 'app/*',
'test/*', 'test/*',
] ]
requires = (
requires = [
'nlohmann_json/3.11.2' 'nlohmann_json/3.11.2'
] )
_cmake = None
def generate(self): def _configure_cmake(self):
tc = CMakeToolchain(self) if self._cmake:
tc.variables['JSON_VALIDATOR_BUILD_EXAMPLES'] = self.options.build_examples return self._cmake
tc.variables['JSON_VALIDATOR_BUILD_TESTS'] = self.options.build_tests self._cmake = CMake(self)
tc.variables['JSON_VALIDATOR_SHARED_LIBS '] = self.options.shared self._cmake.definitions['JSON_VALIDATOR_BUILD_EXAMPLES'] = self.options.build_examples
tc.variables['JSON_VALIDATOR_TEST_COVERAGE '] = self.options.test_coverage self._cmake.definitions['JSON_VALIDATOR_BUILD_TESTS'] = self.options.build_tests
tc.generate() self._cmake.configure()
return self._cmake
def layout(self): def layout(self):
cmake_layout(self) build_type = str(self.settings.build_type).lower()
self.folders.build = "build-{}".format(build_type)
def build(self): def build(self):
cmake = CMake(self) cmake = self._configure_cmake()
cmake.configure() cmake.configure()
cmake.verbose = True
cmake.build() cmake.build()
def package(self): def package(self):
cmake = CMake(self) cmake = self._configure_cmake()
cmake.install() cmake.install()
def package_info(self): def package_info(self):
@ -84,7 +74,7 @@ class JsonSchemaValidatorConan(ConanFile):
libdir = os.path.join(self.package_folder, "lib") libdir = os.path.join(self.package_folder, "lib")
self.cpp_info.libdirs = [libdir] self.cpp_info.libdirs = [libdir]
self.cpp_info.libs += ctools.collect_libs(self, libdir) self.cpp_info.libs += tools.collect_libs(self, libdir)
bindir = os.path.join(self.package_folder, "bin") bindir = os.path.join(self.package_folder, "bin")
self.output.info("Appending PATH environment variable: {}".format(bindir)) self.output.info("Appending PATH environment variable: {}".format(bindir))

View File

@ -54,11 +54,21 @@ target_link_libraries(nlohmann_json_schema_validator PUBLIC
if (JSON_VALIDATOR_INSTALL) if (JSON_VALIDATOR_INSTALL)
# Normal installation target to system. When using scikit-build check python subdirectory # Normal installation target to system. When using scikit-build check python subdirectory
if (WIN32)
# TODO: Probably wrong, please fix
install(TARGETS nlohmann_json_schema_validator install(TARGETS nlohmann_json_schema_validator
EXPORT nlohmann_json_schema_validatorTargets EXPORT nlohmann_json_schema_validatorTargets
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT nlohmann_json_schema_validator_Runtime LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT nlohmann_json_schema_validator_Runtime
NAMELINK_COMPONENT nlohmann_json_schema_validator_Development NAMELINK_COMPONENT nlohmann_json_schema_validator_Development
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT nlohmann_json_schema_validator_Development ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT nlohmann_json_schema_validator_Development
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/nlohmann COMPONENT nlohmann_json_schema_validator_Development PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/nlohmann COMPONENT nlohmann_json_schema_validator_Development
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT nlohmann_json_schema_validator_Runtime) RUNTIME DESTINATION ${CMAKE_INSTALL_RUNTIMEDIR} COMPONENT nlohmann_json_schema_validator_Runtime)
else ()
install(TARGETS nlohmann_json_schema_validator
EXPORT nlohmann_json_schema_validatorTargets
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT nlohmann_json_schema_validator_Runtime
NAMELINK_COMPONENT nlohmann_json_schema_validator_Development
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT nlohmann_json_schema_validator_Development
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/nlohmann COMPONENT nlohmann_json_schema_validator_Development)
endif ()
endif () endif ()

View File

@ -28,13 +28,10 @@ public:
json_patch &replace(const json::json_pointer &, json value); json_patch &replace(const json::json_pointer &, json value);
json_patch &remove(const json::json_pointer &); json_patch &remove(const json::json_pointer &);
json &get_json() { return j_; }
const json &get_json() const { return j_; }
operator json() const { return j_; } operator json() const { return j_; }
private: private:
json j_ = nlohmann::json::array(); json j_;
static void validateJsonPatch(json const &patch); static void validateJsonPatch(json const &patch);
}; };

View File

@ -226,11 +226,10 @@ public:
// for each token create an object, if not already existing // for each token create an object, if not already existing
auto unk_kw = &file.unknown_keywords; auto unk_kw = &file.unknown_keywords;
for (auto &rt : ref_tokens) { for (auto &rt : ref_tokens) {
// create a json_pointer from rt as rt can be an stringified integer doing find on an array won't work auto existing_object = unk_kw->find(rt);
json::json_pointer rt_ptr{"/" + rt}; if (existing_object == unk_kw->end())
if (unk_kw->contains(rt_ptr) == false)
(*unk_kw)[rt] = json::object(); (*unk_kw)[rt] = json::object();
unk_kw = &(*unk_kw)[rt_ptr]; unk_kw = &(*unk_kw)[rt];
} }
(*unk_kw)[key] = value; (*unk_kw)[key] = value;
} }
@ -254,15 +253,15 @@ public:
// //
// an unknown keyword can only be referenced by a json-pointer, // an unknown keyword can only be referenced by a json-pointer,
// not by a plain name fragment // not by a plain name fragment
if (!uri.pointer().to_string().empty()) { if (uri.pointer().to_string() != "") {
bool contains_pointer = file.unknown_keywords.contains(uri.pointer()); try {
if (contains_pointer) { auto &subschema = file.unknown_keywords.at(uri.pointer()); // null is returned if not existing
auto &subschema = file.unknown_keywords.at(uri.pointer()); auto s = schema::make(subschema, this, {}, {{uri}}); // A JSON Schema MUST be an object or a boolean.
auto s = schema::make(subschema, this, {}, {{uri}}); if (s) { // nullptr if invalid schema, e.g. null
if (s) { // if schema is valid (non-null)
file.unknown_keywords.erase(uri.fragment()); file.unknown_keywords.erase(uri.fragment());
return s; return s;
} }
} catch (nlohmann::detail::out_of_range &) { // at() did not find it
} }
} }
@ -426,31 +425,6 @@ enum logical_combination_types {
oneOf oneOf
}; };
class logical_combination_error_handler : public error_handler
{
public:
struct error_entry {
json::json_pointer ptr_;
json instance_;
std::string message_;
};
std::vector<error_entry> error_entry_list_;
void error(const json::json_pointer &ptr, const json &instance, const std::string &message) override
{
error_entry_list_.push_back(error_entry{ptr, instance, message});
}
void propagate(error_handler &e, const std::string &prefix) const
{
for (const error_entry &entry : error_entry_list_)
e.error(entry.ptr_, entry.instance_, prefix + entry.message_);
}
operator bool() const { return !error_entry_list_.empty(); }
};
template <enum logical_combination_types combine_logic> template <enum logical_combination_types combine_logic>
class logical_combination : public schema class logical_combination : public schema
{ {
@ -459,33 +433,29 @@ class logical_combination : public schema
void validate(const json::json_pointer &ptr, const json &instance, json_patch &patch, error_handler &e) const final void validate(const json::json_pointer &ptr, const json &instance, json_patch &patch, error_handler &e) const final
{ {
size_t count = 0; size_t count = 0;
logical_combination_error_handler error_summary;
for (std::size_t index = 0; index < subschemata_.size(); ++index) { for (auto &s : subschemata_) {
const std::shared_ptr<schema> &s = subschemata_[index]; first_error_handler esub;
logical_combination_error_handler esub; json_patch old_patch(patch);
auto oldPatchSize = patch.get_json().size();
s->validate(ptr, instance, patch, esub); s->validate(ptr, instance, patch, esub);
if (!esub) if (!esub)
count++; count++;
else { else
patch.get_json().get_ref<nlohmann::json::array_t &>().resize(oldPatchSize); patch = old_patch;
esub.propagate(error_summary, "case#" + std::to_string(index) + "] ");
}
if (is_validate_complete(instance, ptr, e, esub, count, index)) if (is_validate_complete(instance, ptr, e, esub, count))
return; return;
} }
if (count == 0) { // could accumulate esub details for anyOf and oneOf, but not clear how to select which subschema failure to report
e.error(ptr, instance, "no subschema has succeeded, but one of them is required to validate. Type: " + key + ", number of failed subschemas: " + std::to_string(subschemata_.size())); // or how to report multiple such failures
error_summary.propagate(e, "[combination: " + key + " / "); if (count == 0)
} e.error(ptr, instance, "no subschema has succeeded, but one of them is required to validate");
} }
// specialized for each of the logical_combination_types // specialized for each of the logical_combination_types
static const std::string key; static const std::string key;
static bool is_validate_complete(const json &, const json::json_pointer &, error_handler &, const logical_combination_error_handler &, size_t, size_t); static bool is_validate_complete(const json &, const json::json_pointer &, error_handler &, const first_error_handler &, size_t);
public: public:
logical_combination(json &sch, logical_combination(json &sch,
@ -510,23 +480,21 @@ template <>
const std::string logical_combination<oneOf>::key = "oneOf"; const std::string logical_combination<oneOf>::key = "oneOf";
template <> template <>
bool logical_combination<allOf>::is_validate_complete(const json &, const json::json_pointer &, error_handler &e, const logical_combination_error_handler &esub, size_t, size_t current_schema_index) bool logical_combination<allOf>::is_validate_complete(const json &, const json::json_pointer &, error_handler &e, const first_error_handler &esub, size_t)
{ {
if (esub) { if (esub)
e.error(esub.error_entry_list_.front().ptr_, esub.error_entry_list_.front().instance_, "at least one subschema has failed, but all of them are required to validate - " + esub.error_entry_list_.front().message_); e.error(esub.ptr_, esub.instance_, "at least one subschema has failed, but all of them are required to validate - " + esub.message_);
esub.propagate(e, "[combination: allOf / case#" + std::to_string(current_schema_index) + "] ");
}
return esub; return esub;
} }
template <> template <>
bool logical_combination<anyOf>::is_validate_complete(const json &, const json::json_pointer &, error_handler &, const logical_combination_error_handler &, size_t count, size_t) bool logical_combination<anyOf>::is_validate_complete(const json &, const json::json_pointer &, error_handler &, const first_error_handler &, size_t count)
{ {
return count == 1; return count == 1;
} }
template <> template <>
bool logical_combination<oneOf>::is_validate_complete(const json &instance, const json::json_pointer &ptr, error_handler &e, const logical_combination_error_handler &, size_t count, size_t) bool logical_combination<oneOf>::is_validate_complete(const json &instance, const json::json_pointer &ptr, error_handler &e, const first_error_handler &, size_t count)
{ {
if (count > 1) if (count > 1)
e.error(ptr, instance, "more than one subschema has succeeded, but exactly one of them is required to validate"); e.error(ptr, instance, "more than one subschema has succeeded, but exactly one of them is required to validate");
@ -895,45 +863,35 @@ class numeric : public schema
// multipleOf - if the remainder of the division is 0 -> OK // multipleOf - if the remainder of the division is 0 -> OK
bool violates_multiple_of(T x) const bool violates_multiple_of(T x) const
{ {
double res = std::remainder(x, multipleOf_.second); if constexpr (std::is_floating_point<T>::value) {
double multiple = std::fabs(x / multipleOf_.second); auto multiple = x / multipleOf_.second;
if (multiple > 1) { auto error = std::abs((multiple - std::round(multiple)) * multipleOf_.second);
res = res / multiple; return error > std::numeric_limits<T>::epsilon();
} else {
return x % static_cast<T>(multipleOf_.second) != 0;
} }
double eps = std::nextafter(x, 0) - static_cast<double>(x);
return std::fabs(res) > std::fabs(eps);
} }
void validate(const json::json_pointer &ptr, const json &instance, json_patch &, error_handler &e) const override void validate(const json::json_pointer &ptr, const json &instance, json_patch &, error_handler &e) const override
{ {
T value = instance; // conversion of json to value_type T value = instance; // conversion of json to value_type
std::ostringstream oss;
if (multipleOf_.first && value != 0) // zero is multiple of everything if (multipleOf_.first && value != 0) // zero is multiple of everything
if (violates_multiple_of(value)) if (violates_multiple_of(value))
oss << "instance is not a multiple of " << json(multipleOf_.second); e.error(ptr, instance, "instance is not a multiple of " + std::to_string(multipleOf_.second));
if (maximum_.first) { if (maximum_.first) {
if (exclusiveMaximum_ && value >= maximum_.second) if (exclusiveMaximum_ && value >= maximum_.second)
oss << "instance exceeds or equals maximum of " << json(maximum_.second); e.error(ptr, instance, "instance exceeds or equals maximum of " + std::to_string(maximum_.second));
else if (value > maximum_.second) else if (value > maximum_.second)
oss << "instance exceeds maximum of " << json(maximum_.second); e.error(ptr, instance, "instance exceeds maximum of " + std::to_string(maximum_.second));
} }
if (minimum_.first) { if (minimum_.first) {
if (exclusiveMinimum_ && value <= minimum_.second) if (exclusiveMinimum_ && value <= minimum_.second)
oss << "instance is below or equals minimum of " << json(minimum_.second); e.error(ptr, instance, "instance is below or equals minimum of " + std::to_string(minimum_.second));
else if (value < minimum_.second) else if (value < minimum_.second)
oss << "instance is below minimum of " << json(minimum_.second); e.error(ptr, instance, "instance is below minimum of " + std::to_string(minimum_.second));
}
oss.seekp(0, std::ios::end);
auto size = oss.tellp();
if (size != 0) {
oss.seekp(0, std::ios::beg);
e.error(ptr, instance, oss.str());
} }
} }
@ -1386,18 +1344,11 @@ std::shared_ptr<schema> schema::make(json &schema,
schema.erase(attr); schema.erase(attr);
} }
auto findDefinitions = [&](const std::string &defs) -> bool { attr = schema.find("definitions");
attr = schema.find(defs);
if (attr != schema.end()) { if (attr != schema.end()) {
for (auto &def : attr.value().items()) for (auto &def : attr.value().items())
schema::make(def.value(), root, {defs, def.key()}, uris); schema::make(def.value(), root, {"definitions", def.key()}, uris);
schema.erase(attr); schema.erase(attr);
return true;
}
return false;
};
if (!findDefinitions("$defs")) {
findDefinitions("definitions");
} }
attr = schema.find("$ref"); attr = schema.find("$ref");

View File

@ -669,6 +669,10 @@ static const short _address_eof_trans[] = {
1278, 1279, 1280, 1281, 1282, 1283, 0}; 1278, 1279, 1280, 1281, 1282, 1283, 0};
static const int address_start = 1; static const int address_start = 1;
static const int address_first_final = 196;
static const int address_error = 0;
static const int address_en_main = 1;
bool is_address(const char *p, const char *pe) bool is_address(const char *p, const char *pe)
{ {

View File

@ -85,11 +85,3 @@ add_test(NAME issue-229-oneof-default-values COMMAND issue-229-oneof-default-val
add_executable(issue-243-root-default-values issue-243-root-default-values.cpp) add_executable(issue-243-root-default-values issue-243-root-default-values.cpp)
target_link_libraries(issue-243-root-default-values nlohmann_json_schema_validator) target_link_libraries(issue-243-root-default-values nlohmann_json_schema_validator)
add_test(NAME issue-243-root-default-values COMMAND issue-243-root-default-values) add_test(NAME issue-243-root-default-values COMMAND issue-243-root-default-values)
add_executable(issue-255-error-message-limit-precision issue-255-error-message-limit-precision.cpp)
target_link_libraries(issue-255-error-message-limit-precision nlohmann_json_schema_validator)
add_test(NAME issue-255-error-message-limit-precision COMMAND issue-255-error-message-limit-precision)
add_executable(issue-105-verbose-combination-errors issue-105-verbose-combination-errors.cpp)
target_link_libraries(issue-105-verbose-combination-errors nlohmann_json_schema_validator)
add_test(NAME issue-105-verbose-combination-errors COMMAND issue-105-verbose-combination-errors)

View File

@ -1,321 +0,0 @@
#include "nlohmann/json-schema.hpp"
#include "nlohmann/json.hpp"
#include <iostream>
#include <regex>
#include <string>
#include <vector>
//==============================================================================
// Test macros
//==============================================================================
#define LOG_ERROR(LOG_ERROR__ARGS) \
std::cerr << __FILE__ << ":" << __LINE__ << ": " << LOG_ERROR__ARGS << std::endl
#define EXPECT_THROW_WITH_MESSAGE(EXPRESSION, MESSAGE) \
do { \
try { \
EXPRESSION; \
LOG_ERROR("Expected exception not thrown with matching regex: \"" << MESSAGE << "\""); \
++g_error_count; \
} catch (const std::exception &error) { \
const std::regex error_re{MESSAGE}; \
if (!std::regex_search(error.what(), error_re)) { \
LOG_ERROR("Expected exception with matching regex: \"" << MESSAGE << "\", but got this instead: " << error.what()); \
++g_error_count; \
} \
} \
} while (false)
#define ASSERT_OR_EXPECT_EQ(FIRST_THING, SECOND_THING, RETURN_IN_CASE_OF_ERROR) \
do { \
if ((FIRST_THING) != (SECOND_THING)) { \
LOG_ERROR("The two values of " << (FIRST_THING) << " (" #FIRST_THING << ") and " << (SECOND_THING) << " (" #SECOND_THING << ") should be equal"); \
if (RETURN_IN_CASE_OF_ERROR) { \
return; \
} \
} \
} while (false)
#define ASSERT_EQ(FIRST_THING, SECOND_THING) ASSERT_OR_EXPECT_EQ(FIRST_THING, SECOND_THING, true)
#define EXPECT_EQ(FIRST_THING, SECOND_THING) ASSERT_OR_EXPECT_EQ(FIRST_THING, SECOND_THING, true)
#define EXPECT_MATCH(STRING, REGEX) \
do { \
if (!std::regex_search((STRING), std::regex{(REGEX)})) { \
LOG_ERROR("String \"" << (STRING) << "\" doesn't match with regex: \"" << (REGEX) << "\""); \
++g_error_count; \
} \
} while (false)
namespace
{
//==============================================================================
// Test environment
//==============================================================================
int g_error_count = 0;
//==============================================================================
// The schema used for testing
//==============================================================================
const std::string g_schema_template = R"(
{
"properties": {
"first": {
"%COMBINATION_FIRST_LEVEL%": [
{
"properties": {
"second": {
"%COMBINATION_SECOND_LEVEL%": [
{
"minimum": 5,
"type": "integer"
},
{
"multipleOf": 2,
"type": "integer"
}
]
}
},
"type": "object"
},
{
"minimum": 20,
"type": "integer"
},
{
"minLength": 10,
"type": "string"
}
]
}
},
"type": "object"
}
)";
auto generateSchema(const std::string &first_combination, const std::string &second_combination) -> nlohmann::json
{
static const std::regex first_replace_re{"%COMBINATION_FIRST_LEVEL%"};
static const std::regex second_replace_re{"%COMBINATION_SECOND_LEVEL%"};
std::string intermediate = std::regex_replace(g_schema_template, first_replace_re, first_combination);
return nlohmann::json::parse(std::regex_replace(intermediate, second_replace_re, second_combination));
}
//==============================================================================
// Error handler to catch all the errors generated by the validator - also inside the combinations
//==============================================================================
class MyErrorHandler : public nlohmann::json_schema::error_handler
{
public:
struct ErrorEntry {
nlohmann::json::json_pointer ptr;
nlohmann::json intance;
std::string message;
};
using ErrorEntryList = std::vector<ErrorEntry>;
auto getErrors() const -> const ErrorEntryList &
{
return m_error_list;
}
private:
auto error(const nlohmann::json::json_pointer &ptr, const nlohmann::json &instance, const std::string &message) -> void override
{
m_error_list.push_back(ErrorEntry{ptr, instance, message});
}
ErrorEntryList m_error_list;
};
//==============================================================================
// Error string helpers
//==============================================================================
auto operator<<(std::string first, const std::string &second) -> std::string
{
first += ".*";
first += second;
return first;
}
auto rootError(const std::string &combination_type, std::size_t number_of_subschemas) -> std::string
{
return "no subschema has succeeded, but one of them is required to validate. Type: " + combination_type + ", number of failed subschemas: " + std::to_string(number_of_subschemas);
}
auto combinationError(const std::string &combination_type, std::size_t test_case_number) -> std::string
{
return "[combination: " + combination_type + " / case#" + std::to_string(test_case_number) + "]";
}
//==============================================================================
// Validator function - for simplicity
//==============================================================================
auto validate(const nlohmann::json &schema, const nlohmann::json &instance, nlohmann::json_schema::error_handler *error_handler = nullptr) -> void
{
nlohmann::json_schema::json_validator validator;
validator.set_root_schema(schema);
if (error_handler) {
validator.validate(instance, *error_handler);
} else {
validator.validate(instance);
}
}
//==============================================================================
// The test cases
//==============================================================================
auto simpleTest(const std::string &first_combination, const std::string &second_combination) -> void
{
const nlohmann::json schema = generateSchema(first_combination, second_combination);
EXPECT_THROW_WITH_MESSAGE(validate(schema, nlohmann::json{{"first", {{"second", 1}}}}), rootError(first_combination, 3));
if (second_combination == "oneOf") {
EXPECT_THROW_WITH_MESSAGE(validate(schema, nlohmann::json{{"first", {{"second", 8}}}}), rootError(first_combination, 3));
}
EXPECT_THROW_WITH_MESSAGE(validate(schema, nlohmann::json{{"first", 10}}), rootError(first_combination, 3));
EXPECT_THROW_WITH_MESSAGE(validate(schema, nlohmann::json{{"first", "short"}}), rootError(first_combination, 3));
}
auto verboseTest(const std::string &first_combination, const std::string &second_combination) -> void
{
const nlohmann::json schema = generateSchema(first_combination, second_combination);
{
MyErrorHandler error_handler;
validate(schema, nlohmann::json{{"first", {{"second", 1}}}}, &error_handler);
const MyErrorHandler::ErrorEntryList &error_list = error_handler.getErrors();
EXPECT_EQ(error_list.size(), 6);
EXPECT_EQ(error_list[0].ptr, nlohmann::json::json_pointer{"/first"});
EXPECT_MATCH(error_list[0].message, rootError(first_combination, 3));
EXPECT_EQ(error_list[1].ptr, nlohmann::json::json_pointer{"/first/second"});
EXPECT_MATCH(error_list[1].message, combinationError(first_combination, 0) << rootError(second_combination, 2));
EXPECT_EQ(error_list[2].ptr, nlohmann::json::json_pointer{"/first/second"});
EXPECT_MATCH(error_list[2].message, combinationError(first_combination, 0) << combinationError(second_combination, 0) << "instance is below minimum of 5");
EXPECT_EQ(error_list[3].ptr, nlohmann::json::json_pointer{"/first/second"});
EXPECT_MATCH(error_list[3].message, combinationError(first_combination, 0) << combinationError(second_combination, 1) << "instance is not a multiple of 2.0");
EXPECT_EQ(error_list[4].ptr, nlohmann::json::json_pointer{"/first"});
EXPECT_MATCH(error_list[4].message, combinationError(first_combination, 1) << "unexpected instance type");
EXPECT_EQ(error_list[5].ptr, nlohmann::json::json_pointer{"/first"});
EXPECT_MATCH(error_list[5].message, combinationError(first_combination, 2) << "unexpected instance type");
}
{
MyErrorHandler error_handler;
validate(schema, nlohmann::json{{"first", {{"second", "not-an-integer"}}}}, &error_handler);
const MyErrorHandler::ErrorEntryList &error_list = error_handler.getErrors();
EXPECT_EQ(error_list.size(), 6);
EXPECT_EQ(error_list[0].ptr, nlohmann::json::json_pointer{"/first"});
EXPECT_MATCH(error_list[0].message, rootError(first_combination, 3));
EXPECT_EQ(error_list[1].ptr, nlohmann::json::json_pointer{"/first/second"});
EXPECT_MATCH(error_list[1].message, combinationError(first_combination, 0) << rootError(second_combination, 2));
EXPECT_EQ(error_list[2].ptr, nlohmann::json::json_pointer{"/first/second"});
EXPECT_MATCH(error_list[2].message, combinationError(first_combination, 0) << combinationError(second_combination, 0) << "unexpected instance type");
EXPECT_EQ(error_list[3].ptr, nlohmann::json::json_pointer{"/first/second"});
EXPECT_MATCH(error_list[3].message, combinationError(first_combination, 0) << combinationError(second_combination, 1) << "unexpected instance type");
EXPECT_EQ(error_list[4].ptr, nlohmann::json::json_pointer{"/first"});
EXPECT_MATCH(error_list[4].message, combinationError(first_combination, 1) << "unexpected instance type");
EXPECT_EQ(error_list[5].ptr, nlohmann::json::json_pointer{"/first"});
EXPECT_MATCH(error_list[5].message, combinationError(first_combination, 2) << "unexpected instance type");
}
if (second_combination == "oneOf") {
MyErrorHandler error_handler;
validate(schema, nlohmann::json{{"first", {{"second", 8}}}}, &error_handler);
const MyErrorHandler::ErrorEntryList &error_list = error_handler.getErrors();
EXPECT_EQ(error_list.size(), 4);
EXPECT_EQ(error_list[0].ptr, nlohmann::json::json_pointer{"/first"});
EXPECT_MATCH(error_list[0].message, rootError(first_combination, 3));
EXPECT_EQ(error_list[1].ptr, nlohmann::json::json_pointer{"/first/second"});
EXPECT_MATCH(error_list[1].message, combinationError(first_combination, 0) << "more than one subschema has succeeded, but exactly one of them is required to validate");
EXPECT_EQ(error_list[2].ptr, nlohmann::json::json_pointer{"/first"});
EXPECT_MATCH(error_list[2].message, combinationError(first_combination, 1) << "unexpected instance type");
EXPECT_EQ(error_list[3].ptr, nlohmann::json::json_pointer{"/first"});
EXPECT_MATCH(error_list[3].message, combinationError(first_combination, 2) << "unexpected instance type");
}
{
MyErrorHandler error_handler;
validate(schema, nlohmann::json{{"first", 10}}, &error_handler);
const MyErrorHandler::ErrorEntryList &error_list = error_handler.getErrors();
EXPECT_EQ(error_list.size(), 4);
EXPECT_EQ(error_list[0].ptr, nlohmann::json::json_pointer{"/first"});
EXPECT_MATCH(error_list[0].message, rootError(first_combination, 3));
EXPECT_EQ(error_list[1].ptr, nlohmann::json::json_pointer{"/first"});
EXPECT_MATCH(error_list[1].message, combinationError(first_combination, 0) << "unexpected instance type");
EXPECT_EQ(error_list[2].ptr, nlohmann::json::json_pointer{"/first"});
EXPECT_MATCH(error_list[2].message, combinationError(first_combination, 1) << "instance is below minimum of 20");
EXPECT_EQ(error_list[3].ptr, nlohmann::json::json_pointer{"/first"});
EXPECT_MATCH(error_list[3].message, combinationError(first_combination, 2) << "unexpected instance type");
}
{
MyErrorHandler error_handler;
validate(schema, nlohmann::json{{"first", "short"}}, &error_handler);
const MyErrorHandler::ErrorEntryList &error_list = error_handler.getErrors();
EXPECT_EQ(error_list.size(), 4);
EXPECT_EQ(error_list[0].ptr, nlohmann::json::json_pointer{"/first"});
EXPECT_MATCH(error_list[0].message, rootError(first_combination, 3));
EXPECT_EQ(error_list[1].ptr, nlohmann::json::json_pointer{"/first"});
EXPECT_MATCH(error_list[1].message, combinationError(first_combination, 0) << "unexpected instance type");
EXPECT_EQ(error_list[2].ptr, nlohmann::json::json_pointer{"/first"});
EXPECT_MATCH(error_list[2].message, combinationError(first_combination, 1) << "unexpected instance type");
EXPECT_EQ(error_list[3].ptr, nlohmann::json::json_pointer{"/first"});
EXPECT_MATCH(error_list[3].message, combinationError(first_combination, 2) << "instance is too short as per minLength:10");
}
}
} // namespace
//==============================================================================
// MAIN - calling the test cases
//==============================================================================
auto main() -> int
{
simpleTest("anyOf", "anyOf");
simpleTest("anyOf", "oneOf");
simpleTest("oneOf", "anyOf");
simpleTest("oneOf", "oneOf");
verboseTest("anyOf", "anyOf");
verboseTest("anyOf", "oneOf");
verboseTest("oneOf", "anyOf");
verboseTest("oneOf", "oneOf");
return g_error_count;
}

View File

@ -1,41 +0,0 @@
#include <nlohmann/json-schema.hpp>
#include <stdexcept>
using nlohmann::json;
using nlohmann::json_schema::json_validator;
static const json schema = R"(
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "arc.schema.json",
"properties": {
"angle": {
"type": "number",
"description": "Radians, from -π to π.",
"minimum": -3.14159265358979323846,
"maximum": 3.14159265358979323846
}
}
})"_json;
class custom_error_handler : public nlohmann::json_schema::basic_error_handler
{
void error(const nlohmann::json::json_pointer &ptr, const json &instance, const std::string &message) override
{
if (message != "instance exceeds maximum of 3.141592653589793")
throw std::invalid_argument("Precision print does not work.");
}
};
int main(void)
{
json_validator validator;
auto instance = R"({ "angle": 3.1415927410125732 })"_json;
validator.set_root_schema(schema);
custom_error_handler err;
validator.validate(instance, err);
return 0;
}

View File

@ -1,32 +1,27 @@
#include "nlohmann/json-schema.hpp" #include "nlohmann/json-schema.hpp"
using nlohmann::json_schema::json_validator;
template <typename T>
int should_throw(const nlohmann::json &schema, T value)
{
try {
json_validator(schema).validate(value);
} catch (const std::exception &ex) {
return 0;
}
return 1;
}
int main(void) int main(void)
{ {
using nlohmann::json_schema::json_validator;
json_validator({{"type", "number"}, {"multipleOf", 0.001}}).validate(0.3 - 0.2); json_validator({{"type", "number"}, {"multipleOf", 0.001}}).validate(0.3 - 0.2);
json_validator({{"type", "number"}, {"multipleOf", 3.3}}).validate(8.0 - 1.4); json_validator({{"type", "number"}, {"multipleOf", 3.3}}).validate(8.0 - 1.4);
json_validator({{"type", "number"}, {"multipleOf", 1000.01}}).validate((1000.03 - 0.02) * 15.0); json_validator({{"type", "number"}, {"multipleOf", 1000.01}}).validate((1000.03 - 0.02) * 15.0);
json_validator({{"type", "number"}, {"multipleOf", 0.001}}).validate(0.030999999999999993);
json_validator({{"type", "number"}, {"multipleOf", 0.100000}}).validate(1.9);
json_validator({{"type", "number"}, {"multipleOf", 100000.1}}).validate(9000009);
int exc_count = 0; int expect_exception = 2;
exc_count += should_throw({{"type", "number"}, {"multipleOf", 0.001}}, 0.3 - 0.2005); try {
exc_count += should_throw({{"type", "number"}, {"multipleOf", 1000.02}}, (1000.03 - 0.02) * 15.0); json_validator({{"type", "number"}, {"multipleOf", 0.001}}).validate(0.3 - 0.2005);
exc_count += should_throw({{"type", "number"}, {"multipleOf", 100000.11}}, 9000009); } catch (const std::exception &ex) {
expect_exception--;
}
return exc_count; try {
json_validator({{"type", "number"}, {"multipleOf", 1000.02}}).validate((1000.03 - 0.02) * 15.0);
} catch (const std::exception &ex) {
expect_exception--;
}
return expect_exception;
} }

View File

@ -1,3 +0,0 @@
add_test_simple_schema(Issue::311
${CMAKE_CURRENT_SOURCE_DIR}/schema.json
${CMAKE_CURRENT_SOURCE_DIR}/instance.json)

View File

@ -1,4 +0,0 @@
{
"element": [1],
"element2": "test"
}

View File

@ -1,26 +0,0 @@
{
"type": "object",
"properties": {
"element": {
"$ref": "#/$defs/element"
},
"element2": {
"$ref": "#/$defs/element/items/0/$defs/element2"
}
},
"$defs": {
"element": {
"type": "array",
"items": [
{
"$defs": {
"element2": {
"type": "string"
}
},
"type": "number"
}
]
}
}
}