Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ include(jse_use_colors)
# IPC Toolkit utils
include(jse_prepend_current_path)
include(jse_set_source_group)
include(jse_add_embedded_spec)

# Sort projects inside the solution
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
Expand Down Expand Up @@ -105,6 +106,11 @@ target_link_libraries(jse PUBLIC nlohmann_json::nlohmann_json)
# Use C++17
target_compile_features(jse PUBLIC cxx_std_17)

add_executable(jse_embed_spec_tool EXCLUDE_FROM_ALL tools/jse_embed_spec.cpp)
target_link_libraries(jse_embed_spec_tool PRIVATE jse::jse)
target_compile_features(jse_embed_spec_tool PRIVATE cxx_std_17)
set_target_properties(jse_embed_spec_tool PROPERTIES FOLDER "JSE")

################################################################################
# Tests
################################################################################
Expand Down
121 changes: 121 additions & 0 deletions cmake/jse/jse_add_embedded_spec.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# jse_add_embedded_spec(
# INPUT root_spec.json
# OUTPUT generated/spec.hpp
# INCLUDE_DIRS include_dir_1 include_dir_2
# )
#
# Adds generated .hpp/.cpp files to the fixed jse::embed library target. Each
# generated header exposes jse::embed::<output_stem>::spec(), where output_stem
# is the OUTPUT filename stem converted to a valid C++ identifier.
function(jse_add_embedded_spec)
set(options)
set(one_value_args INPUT OUTPUT)
set(multi_value_args INCLUDE_DIRS)
cmake_parse_arguments(JSE_EMBED
"${options}"
"${one_value_args}"
"${multi_value_args}"
${ARGN}
)

MESSAGE(STATUS "Configuring embedded spec with input ${JSE_EMBED_INPUT} and output ${JSE_EMBED_OUTPUT} using include dirs ${JSE_EMBED_INCLUDE_DIRS}")

if(JSE_EMBED_UNPARSED_ARGUMENTS)
message(FATAL_ERROR "Unknown arguments for jse_add_embedded_spec: ${JSE_EMBED_UNPARSED_ARGUMENTS}")
endif()

if(NOT JSE_EMBED_INPUT)
message(FATAL_ERROR "jse_add_embedded_spec requires INPUT")
endif()

if(NOT JSE_EMBED_OUTPUT)
message(FATAL_ERROR "jse_add_embedded_spec requires OUTPUT")
endif()

if(NOT TARGET jse_embed_spec_tool)
message(FATAL_ERROR "jse_add_embedded_spec requires the jse_embed_spec_tool generator target")
endif()

get_filename_component(_jse_embed_input "${JSE_EMBED_INPUT}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
get_filename_component(_jse_embed_header "${JSE_EMBED_OUTPUT}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}")
get_filename_component(_jse_embed_output_dir "${_jse_embed_header}" DIRECTORY)
get_filename_component(_jse_embed_output_stem "${_jse_embed_header}" NAME_WE)
set(_jse_embed_source "${_jse_embed_output_dir}/${_jse_embed_output_stem}.cpp")
string(MAKE_C_IDENTIFIER "${_jse_embed_output_stem}" _jse_embed_spec_namespace)

get_property(_jse_embed_namespaces GLOBAL PROPERTY JSE_EMBED_SPEC_NAMESPACES)
if(_jse_embed_spec_namespace IN_LIST _jse_embed_namespaces)
message(FATAL_ERROR "jse_add_embedded_spec already has an embedded spec named ${_jse_embed_spec_namespace}")
endif()
set_property(GLOBAL APPEND PROPERTY JSE_EMBED_SPEC_NAMESPACES "${_jse_embed_spec_namespace}")

get_filename_component(_jse_embed_input_dir "${_jse_embed_input}" DIRECTORY)
set(_jse_embed_include_dirs "${_jse_embed_input_dir}" ${JSE_EMBED_INCLUDE_DIRS})
set(_jse_embed_abs_include_dirs)
set(_jse_embed_include_args)
set(_jse_embed_depends "${_jse_embed_input}")

foreach(_jse_embed_include_dir IN LISTS _jse_embed_include_dirs)
get_filename_component(_jse_embed_abs_include_dir "${_jse_embed_include_dir}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
list(APPEND _jse_embed_abs_include_dirs "${_jse_embed_abs_include_dir}")
endforeach()

list(REMOVE_DUPLICATES _jse_embed_abs_include_dirs)

foreach(_jse_embed_abs_include_dir IN LISTS _jse_embed_abs_include_dirs)
list(APPEND _jse_embed_include_args "--include-dir" "${_jse_embed_abs_include_dir}")
file(GLOB_RECURSE _jse_embed_json_depends
CONFIGURE_DEPENDS
"${_jse_embed_abs_include_dir}/*.json"
)
list(APPEND _jse_embed_depends ${_jse_embed_json_depends})
endforeach()
list(REMOVE_DUPLICATES _jse_embed_depends)

file(MAKE_DIRECTORY "${_jse_embed_output_dir}")

add_custom_command(
OUTPUT
"${_jse_embed_header}"
"${_jse_embed_source}"
COMMAND
jse_embed_spec_tool
--input "${_jse_embed_input}"
--output-header "${_jse_embed_header}"
--output-source "${_jse_embed_source}"
--namespace "jse::embed::${_jse_embed_spec_namespace}"
--function "spec"
${_jse_embed_include_args}
DEPENDS
jse_embed_spec_tool
${_jse_embed_depends}
COMMENT
"Generating embedded JSON spec ${_jse_embed_output_stem}"
VERBATIM
)

set_source_files_properties(
"${_jse_embed_header}"
"${_jse_embed_source}"
PROPERTIES GENERATED TRUE
)

if(NOT TARGET jse_embed)
add_library(jse_embed)
add_library(jse::embed ALIAS jse_embed)
target_link_libraries(jse_embed PUBLIC nlohmann_json::nlohmann_json)
target_compile_features(jse_embed PUBLIC cxx_std_17)
endif()

target_sources(jse_embed PRIVATE
"${_jse_embed_source}"
"${_jse_embed_header}"
)
target_include_directories(jse_embed PUBLIC
"$<BUILD_INTERFACE:${_jse_embed_output_dir}>"
)
source_group("Generated" FILES "${_jse_embed_source}" "${_jse_embed_header}")

set(JSE_EMBED_HEADER "${_jse_embed_header}" PARENT_SCOPE)
set(JSE_EMBED_SOURCE "${_jse_embed_source}" PARENT_SCOPE)
endfunction()
4 changes: 2 additions & 2 deletions src/jse/jse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ namespace jse
{
assert(rule.at("type") == "float");

if (!input.is_number() && !input.is_null())
if (!input.is_number() && !(allow_null_numbers && input.is_null()))
return false;

if (rule.contains("min") && input < rule["min"])
Expand All @@ -383,7 +383,7 @@ namespace jse
{
assert(rule.at("type") == "int");

if (!input.is_number_integer() && !input.is_null())
if (!input.is_number_integer() && !(allow_null_numbers && input.is_null()))
return false;

if (rule.contains("min") && input < rule["min"])
Expand Down
3 changes: 3 additions & 0 deletions src/jse/jse.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ namespace jse
// if all rules fail for a basic type, try boxing it once and try again
bool boxing_primitive = true;

// allow null values to satisfy int and float rules
bool allow_null_numbers = false;

// message list
typedef std::pair<std::string, std::string> log_item;
std::vector<log_item> log;
Expand Down
11 changes: 11 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ set(test_sources
)
add_executable(unit_tests ${test_sources})

jse_add_embedded_spec(
INPUT "${CMAKE_SOURCE_DIR}/data/rules_04.json"
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/embedded_specs/rules_04.hpp"
)

jse_add_embedded_spec(
INPUT "${CMAKE_SOURCE_DIR}/data/rules_01.json"
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/embedded_specs/rules_01.hpp"
)

################################################################################
# Required Libraries
################################################################################
Expand All @@ -16,6 +26,7 @@ include(catch2)
target_link_libraries(unit_tests PUBLIC Catch2::Catch2)

target_link_libraries(unit_tests PUBLIC jse::jse)
target_link_libraries(unit_tests PUBLIC jse::embed)

include(jse_warnings)
target_link_libraries(unit_tests PRIVATE jse::warnings)
Expand Down
63 changes: 62 additions & 1 deletion tests/test_validator.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//////////////////////////////////////////////////////////////////////////
#include <jse/jse.h>
#include <rules_01.hpp>
#include <rules_04.hpp>
#include <catch2/catch.hpp>
#include <iostream>
#include <fstream>
Expand Down Expand Up @@ -250,6 +252,64 @@ TEST_CASE("include_rule", "[validator]")
REQUIRE(new_rules == matching);
}

TEST_CASE("embedded_include_rule", "[validator]")
{
std::ifstream ifs(root_path + "/rules_03.json");
json matching = json::parse(ifs);

REQUIRE(jse::embed::rules_04::spec() == matching);
}

TEST_CASE("embedded_multiple_specs", "[validator]")
{
std::ifstream ifs(root_path + "/rules_01.json");
json matching = json::parse(ifs);

REQUIRE(jse::embed::rules_01::spec() == matching);
}

TEST_CASE("null_number_rules_are_opt_in", "[validator]")
{
nlohmann::json rules = R"(
[
{
"pointer": "/",
"type": "object",
"optional": ["field"]
},
{
"pointer": "/field",
"type": "int"
},
{
"pointer": "/field",
"type": "object",
"optional": ["offset"],
"default": null
},
{
"pointer": "/field/offset",
"type": "int",
"default": 0
}
]
)"_json;

nlohmann::json input = R"(
{
"field": null
}
)"_json;

JSE jse;
jse.strict = true;

REQUIRE(jse.verify_json(input, rules));

jse.allow_null_numbers = true;
REQUIRE(!jse.verify_json(input, rules));
}

TEST_CASE("file_01", "[validator]")
{
std::ifstream ifs1(root_path + "/input_01.json");
Expand Down Expand Up @@ -642,6 +702,7 @@ TEST_CASE("null_as_nan", "[validator][inject]")

JSE jse;
jse.strict = true;
jse.allow_null_numbers = true;

bool r = jse.verify_json(input, rules);
REQUIRE(r);
Expand All @@ -655,4 +716,4 @@ TEST_CASE("null_as_nan", "[validator][inject]")
CHECK(return_json["f2"].is_null());

input["f2"] = std::nan("");
}
}
Loading