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'
- '--preset'
- 'pre-commit'
stages: [ manual ]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:

View File

@ -10,12 +10,9 @@ endif ()
# 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
project(nlohmann_json_schema_validator
VERSION ${PROJECT_VERSION}
VERSION 2.2.0
DESCRIPTION "Json validator for nlohmann::json library"
HOMEPAGE_URL "https://github.com/pboettch/json-schema-validator"
LANGUAGES CXX)
@ -66,16 +63,16 @@ endif ()
set(BUILD_SHARED_LIBS ${nlohmann_json_schema_validator_SHARED_LIBS})
if (JSON_VALIDATOR_TEST_COVERAGE)
if (CMAKE_CXX_COMPILER_ID STREQUAL Clang)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-instr-generate -fcoverage-mapping")
elseif (CMAKE_CXX_COMPILER_ID STREQUAL GNU)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage")
else ()
message(WARNING
"JsonValidator: Other toolchain coverage flags unknown.\n"
"Using --coverage as default")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage")
endif ()
if (CMAKE_CXX_COMPILER_ID STREQUAL Clang)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-instr-generate -fcoverage-mapping")
elseif (CMAKE_CXX_COMPILER_ID STREQUAL GNU)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage")
else ()
message(WARNING
"JsonValidator: Other toolchain coverage flags unknown.\n"
"Using --coverage as default")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage")
endif ()
endif ()
#[==============================================================================================[
@ -83,45 +80,43 @@ endif ()
]==============================================================================================]
set(fetch_packages "")
if (NOT TARGET nlohmann_json)
# Fetch/Find nlohmann_json
# TODO: Remove when bumping cmake >= 3.24
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.24)
FetchContent_Declare(nlohmann_json
GIT_REPOSITORY https://github.com/nlohmann/json
GIT_TAG ${JSON_FETCH_VERSION}
FIND_PACKAGE_ARGS
)
list(APPEND fetch_packages nlohmann_json)
else ()
# Try to get system installed version
find_package(nlohmann_json QUIET)
if (NOT nlohmann_json_FOUND)
# If failed fetch the desired version
FetchContent_Declare(nlohmann_json
GIT_REPOSITORY https://github.com/nlohmann/json
GIT_TAG ${JSON_FETCH_VERSION}
)
list(APPEND fetch_packages nlohmann_json)
endif ()
endif ()
# Fetch/Find nlohmann_json
# TODO: Remove when bumping cmake >= 3.24
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.24)
FetchContent_Declare(nlohmann_json
GIT_REPOSITORY https://github.com/nlohmann/json
GIT_TAG ${JSON_FETCH_VERSION}
FIND_PACKAGE_ARGS
)
list(APPEND fetch_packages nlohmann_json)
else ()
# Try to get system installed version
find_package(nlohmann_json QUIET)
if (NOT nlohmann_json_FOUND)
# If failed fetch the desired version
FetchContent_Declare(nlohmann_json
GIT_REPOSITORY https://github.com/nlohmann/json
GIT_TAG ${JSON_FETCH_VERSION}
)
list(APPEND fetch_packages nlohmann_json)
endif ()
endif ()
# Handle configure flags
if (JSON_VALIDATOR_INSTALL)
# TODO: This is not ideal, this package should not be installing nlohmann::json
# Currently required in order to satisfy cmake exporter
set(JSON_Install ON CACHE BOOL "")
# TODO: This is not ideal, this package should not be installing nlohmann::json
# Currently required in order to satisfy cmake exporter
set(JSON_Install ON CACHE BOOL "")
endif ()
# Get all dependencies
FetchContent_MakeAvailable(${fetch_packages})
if (JSON_VALIDATOR_INSTALL AND NOT nlohmann_json_FOUND AND JSON_Install)
# TODO: This is not ideal
message(WARNING
"JsonValidator: No nlohmann::json found on the system and nlohmann_json_schema_validator will be installed\n"
"This will also install nlohmann::json in its typical installation path\n"
"This is not ideal because it might overwrite system installed")
# TODO: This is not ideal
message(WARNING
"JsonValidator: No nlohmann::json found on the system and nlohmann_json_schema_validator will be installed\n"
"This will also install nlohmann::json in its typical installation path\n"
"This is not ideal because it might overwrite system installed")
endif ()
#[==============================================================================================[

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
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.
Various methods using CMake can be used to build this project.

View File

@ -1,10 +1,7 @@
import os
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():
try:
@ -23,59 +20,52 @@ class JsonSchemaValidatorConan(ConanFile):
version = get_version()
url = 'https://github.com/pboettch/json-schema-validator'
license = 'MIT'
settings = 'os', 'compiler', 'build_type', 'arch'
options = {
'shared': [True, False],
'fPIC': [True, False],
'build_examples': [True, False],
'build_tests': [True, False],
'test_coverage': [True, False],
'build_tests': [True, False]
}
default_options = {
'shared': False,
'fPIC': True,
'build_examples': True,
'build_tests': False,
'test_coverage': False,
'build_tests': False
}
generators = 'CMakeDeps', 'CMakeToolchain', 'VirtualBuildEnv', 'VirtualRunEnv'
generators = "CMakeDeps"
exports_sources = [
'CMakeLists.txt',
'conanfile.py',
'cmake/*',
'nlohmann_json_schema_validatorConfig.cmake.in',
'src/*',
'example/*',
'app/*',
'test/*',
]
requires = [
requires = (
'nlohmann_json/3.11.2'
]
)
_cmake = None
def generate(self):
tc = CMakeToolchain(self)
tc.variables['JSON_VALIDATOR_BUILD_EXAMPLES'] = self.options.build_examples
tc.variables['JSON_VALIDATOR_BUILD_TESTS'] = self.options.build_tests
tc.variables['JSON_VALIDATOR_SHARED_LIBS '] = self.options.shared
tc.variables['JSON_VALIDATOR_TEST_COVERAGE '] = self.options.test_coverage
tc.generate()
def _configure_cmake(self):
if self._cmake:
return self._cmake
self._cmake = CMake(self)
self._cmake.definitions['JSON_VALIDATOR_BUILD_EXAMPLES'] = self.options.build_examples
self._cmake.definitions['JSON_VALIDATOR_BUILD_TESTS'] = self.options.build_tests
self._cmake.configure()
return self._cmake
def layout(self):
cmake_layout(self)
build_type = str(self.settings.build_type).lower()
self.folders.build = "build-{}".format(build_type)
def build(self):
cmake = CMake(self)
cmake = self._configure_cmake()
cmake.configure()
cmake.verbose = True
cmake.build()
def package(self):
cmake = CMake(self)
cmake = self._configure_cmake()
cmake.install()
def package_info(self):
@ -84,7 +74,7 @@ class JsonSchemaValidatorConan(ConanFile):
libdir = os.path.join(self.package_folder, "lib")
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")
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)
# Normal installation target to system. When using scikit-build check python subdirectory
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
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT nlohmann_json_schema_validator_Runtime)
if (WIN32)
# TODO: Probably wrong, please fix
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
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 ()

View File

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

View File

@ -226,11 +226,10 @@ public:
// for each token create an object, if not already existing
auto unk_kw = &file.unknown_keywords;
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
json::json_pointer rt_ptr{"/" + rt};
if (unk_kw->contains(rt_ptr) == false)
auto existing_object = unk_kw->find(rt);
if (existing_object == unk_kw->end())
(*unk_kw)[rt] = json::object();
unk_kw = &(*unk_kw)[rt_ptr];
unk_kw = &(*unk_kw)[rt];
}
(*unk_kw)[key] = value;
}
@ -254,15 +253,15 @@ public:
//
// an unknown keyword can only be referenced by a json-pointer,
// not by a plain name fragment
if (!uri.pointer().to_string().empty()) {
bool contains_pointer = file.unknown_keywords.contains(uri.pointer());
if (contains_pointer) {
auto &subschema = file.unknown_keywords.at(uri.pointer());
auto s = schema::make(subschema, this, {}, {{uri}});
if (s) { // if schema is valid (non-null)
if (uri.pointer().to_string() != "") {
try {
auto &subschema = file.unknown_keywords.at(uri.pointer()); // null is returned if not existing
auto s = schema::make(subschema, this, {}, {{uri}}); // A JSON Schema MUST be an object or a boolean.
if (s) { // nullptr if invalid schema, e.g. null
file.unknown_keywords.erase(uri.fragment());
return s;
}
} catch (nlohmann::detail::out_of_range &) { // at() did not find it
}
}
@ -426,31 +425,6 @@ enum logical_combination_types {
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>
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
{
size_t count = 0;
logical_combination_error_handler error_summary;
for (std::size_t index = 0; index < subschemata_.size(); ++index) {
const std::shared_ptr<schema> &s = subschemata_[index];
logical_combination_error_handler esub;
auto oldPatchSize = patch.get_json().size();
for (auto &s : subschemata_) {
first_error_handler esub;
json_patch old_patch(patch);
s->validate(ptr, instance, patch, esub);
if (!esub)
count++;
else {
patch.get_json().get_ref<nlohmann::json::array_t &>().resize(oldPatchSize);
esub.propagate(error_summary, "case#" + std::to_string(index) + "] ");
}
else
patch = old_patch;
if (is_validate_complete(instance, ptr, e, esub, count, index))
if (is_validate_complete(instance, ptr, e, esub, count))
return;
}
if (count == 0) {
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()));
error_summary.propagate(e, "[combination: " + key + " / ");
}
// could accumulate esub details for anyOf and oneOf, but not clear how to select which subschema failure to report
// or how to report multiple such failures
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
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:
logical_combination(json &sch,
@ -510,23 +480,21 @@ template <>
const std::string logical_combination<oneOf>::key = "oneOf";
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) {
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_);
esub.propagate(e, "[combination: allOf / case#" + std::to_string(current_schema_index) + "] ");
}
if (esub)
e.error(esub.ptr_, esub.instance_, "at least one subschema has failed, but all of them are required to validate - " + esub.message_);
return esub;
}
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;
}
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)
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
bool violates_multiple_of(T x) const
{
double res = std::remainder(x, multipleOf_.second);
double multiple = std::fabs(x / multipleOf_.second);
if (multiple > 1) {
res = res / multiple;
if constexpr (std::is_floating_point<T>::value) {
auto multiple = x / multipleOf_.second;
auto error = std::abs((multiple - std::round(multiple)) * multipleOf_.second);
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
{
T value = instance; // conversion of json to value_type
std::ostringstream oss;
if (multipleOf_.first && value != 0) // zero is multiple of everything
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 (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)
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 (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)
oss << "instance is below minimum of " << json(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());
e.error(ptr, instance, "instance is below minimum of " + std::to_string(minimum_.second));
}
}
@ -1386,18 +1344,11 @@ std::shared_ptr<schema> schema::make(json &schema,
schema.erase(attr);
}
auto findDefinitions = [&](const std::string &defs) -> bool {
attr = schema.find(defs);
if (attr != schema.end()) {
for (auto &def : attr.value().items())
schema::make(def.value(), root, {defs, def.key()}, uris);
schema.erase(attr);
return true;
}
return false;
};
if (!findDefinitions("$defs")) {
findDefinitions("definitions");
attr = schema.find("definitions");
if (attr != schema.end()) {
for (auto &def : attr.value().items())
schema::make(def.value(), root, {"definitions", def.key()}, uris);
schema.erase(attr);
}
attr = schema.find("$ref");

View File

@ -669,6 +669,10 @@ static const short _address_eof_trans[] = {
1278, 1279, 1280, 1281, 1282, 1283, 0};
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)
{

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)
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_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"
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)
{
using nlohmann::json_schema::json_validator;
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", 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;
exc_count += should_throw({{"type", "number"}, {"multipleOf", 0.001}}, 0.3 - 0.2005);
exc_count += should_throw({{"type", "number"}, {"multipleOf", 1000.02}}, (1000.03 - 0.02) * 15.0);
exc_count += should_throw({{"type", "number"}, {"multipleOf", 100000.11}}, 9000009);
int expect_exception = 2;
try {
json_validator({{"type", "number"}, {"multipleOf", 0.001}}).validate(0.3 - 0.2005);
} 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"
}
]
}
}
}