use schema to validate json-patch and use json_pointer to verify path

fix #107
This commit is contained in:
Patrick Boettcher 2020-05-15 08:53:52 +02:00
parent a0fca479f6
commit c12a27eee1
5 changed files with 143 additions and 49 deletions

View File

@ -1,75 +1,115 @@
#include "json-patch.hpp"
#include <nlohmann/json-schema.hpp>
namespace
{
// originally from http://jsonpatch.com/, http://json.schemastore.org/json-patch
// with fixes
const nlohmann::json patch_schema = R"patch({
"title": "JSON schema for JSONPatch files",
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "array",
"items": {
"oneOf": [
{
"additionalProperties": false,
"required": [ "value", "op", "path"],
"properties": {
"path" : { "$ref": "#/definitions/path" },
"op": {
"description": "The operation to perform.",
"type": "string",
"enum": [ "add", "replace", "test" ]
},
"value": {
"description": "The value to add, replace or test."
}
}
},
{
"additionalProperties": false,
"required": [ "op", "path"],
"properties": {
"path" : { "$ref": "#/definitions/path" },
"op": {
"description": "The operation to perform.",
"type": "string",
"enum": [ "remove" ]
}
}
},
{
"additionalProperties": false,
"required": [ "from", "op", "path" ],
"properties": {
"path" : { "$ref": "#/definitions/path" },
"op": {
"description": "The operation to perform.",
"type": "string",
"enum": [ "move", "copy" ]
},
"from": {
"$ref": "#/definitions/path",
"description": "A JSON Pointer path pointing to the location to move/copy from."
}
}
}
]
},
"definitions": {
"path": {
"description": "A JSON Pointer path.",
"type": "string"
}
}
})patch"_json;
}; // namespace
namespace nlohmann
{
json_patch::json_patch(json &&patch)
: j_{std::move(patch)}
: j_(std::move(patch))
{
validateJsonPatch(j_);
}
json_patch::json_patch(const json &patch)
: j_{std::move(patch)}
: j_(std::move(patch))
{
validateJsonPatch(j_);
}
json_patch &json_patch::add(std::string path, json value)
json_patch &json_patch::add(const json::json_pointer &ptr, json value)
{
j_.push_back(json{{"op", "add"}, {"path", std::move(path)}, {"value", std::move(value)}});
j_.push_back(json{{"op", "add"}, {"path", ptr}, {"value", std::move(value)}});
return *this;
}
json_patch &json_patch::replace(std::string path, json value)
json_patch &json_patch::replace(const json::json_pointer &ptr, json value)
{
j_.push_back(json{{"op", "replace"}, {"path", std::move(path)}, {"value", std::move(value)}});
j_.push_back(json{{"op", "replace"}, {"path", ptr}, {"value", std::move(value)}});
return *this;
}
json_patch &json_patch::remove(std::string path)
json_patch &json_patch::remove(const json::json_pointer &ptr)
{
j_.push_back(json{{"op", "remove"}, {"path", std::move(path)}});
j_.push_back(json{{"op", "remove"}, {"path", ptr}});
return *this;
}
void json_patch::validateJsonPatch(json const &patch)
{
if (!patch.is_array()) {
throw JsonPatchFormatException{"Json is not an array"};
}
// static put here to have it created at the first usage of validateJsonPatch
static nlohmann::json_schema::json_validator patch_validator(patch_schema);
for (auto const &op : patch) {
if (!op.is_object()) {
throw JsonPatchFormatException{"Each json patch entry needs to be an op object"};
}
patch_validator.validate(patch);
if (!op.contains("op")) {
throw JsonPatchFormatException{"Each json patch entry needs op element"};
}
const auto opType = op["op"].get<std::string>();
if ((opType != "add") && (opType != "remove") && (opType != "replace")) {
throw JsonPatchFormatException{std::string{"Operation "} + opType + std::string{"is invalid"}};
}
if (!op.contains("path")) {
throw JsonPatchFormatException{"Each json patch entry needs path element"};
}
try {
// try parse to path
[[maybe_unused]] const auto p = json::json_pointer{op["path"].get<std::string>()};
} catch (json::exception &e) {
throw JsonPatchFormatException{e.what()};
}
if (opType != "remove") {
if (!op.contains("value")) {
throw JsonPatchFormatException{"Remove and replace needs value element"};
}
}
}
for (auto const &op : patch)
json::json_pointer(op["path"].get<std::string>());
}
} // namespace nlohmann

View File

@ -24,9 +24,9 @@ public:
json_patch(json &&patch);
json_patch(const json &patch);
json_patch &add(std::string path, json value);
json_patch &replace(std::string path, json value);
json_patch &remove(std::string path);
json_patch &add(const json::json_pointer &, json value);
json_patch &replace(const json::json_pointer &, json value);
json_patch &remove(const json::json_pointer &);
operator json() const { return j_; }

View File

@ -1245,7 +1245,7 @@ json json_validator::validate(const json &instance) const
json json_validator::validate(const json &instance, error_handler &err) const
{
json::json_pointer ptr;
json_patch patch{};
json_patch patch;
root_->validate(ptr, instance, patch, err);
return patch;
}

View File

@ -44,8 +44,14 @@ target_link_libraries(issue-98 nlohmann_json_schema_validator)
add_test(NAME issue-98-erase-exception-unknown-keywords COMMAND issue-98)
# Unit test for string format checks
add_executable("string-format-check-test" "string-format-check-test.cpp")
target_include_directories("string-format-check-test" PRIVATE "${PROJECT_SOURCE_DIR}/src/")
target_link_libraries("string-format-check-test" nlohmann_json_schema_validator)
add_executable(string-format-check-test string-format-check-test.cpp)
target_include_directories(string-format-check-test PRIVATE ${PROJECT_SOURCE_DIR}/src/)
target_link_libraries(string-format-check-test nlohmann_json_schema_validator)
add_test(NAME "string-format-check-test" COMMAND "string-format-check-test")
add_test(NAME string-format-check-test COMMAND string-format-check-test)
# Unit test for json-patch
add_executable(json-patch json-patch.cpp)
target_include_directories(json-patch PRIVATE ${PROJECT_SOURCE_DIR}/src)
target_link_libraries(json-patch nlohmann_json_schema_validator)
add_test(NAME json-patch COMMAND json-patch)

48
test/json-patch.cpp Normal file
View File

@ -0,0 +1,48 @@
#include "../src/json-patch.hpp"
#include <iostream>
using nlohmann::json_patch;
#define OK(code) \
do { \
try { \
code; \
} catch (const std::exception &e) { \
std::cerr << "UNEXPECTED FAILED: " << e.what() << "\n"; \
return 1; \
} \
} while (0)
#define KO(code) \
do { \
try { \
code; \
std::cerr << "UNEXPECTED SUCCESS.\n"; \
return 1; \
} catch (const std::exception &e) { \
std::cerr << "EXPECTED FAIL: " << e.what() << "\n"; \
} \
} while (0)
int main(void)
{
OK( json_patch p1( R"([{"op":"add","path":"/0/renderable/bg","value":"Black"}])"_json));
OK( json_patch p1( R"([{"op":"replace","path":"/0/renderable/bg","value":"Black"}])"_json));
OK( json_patch p1( R"([{"op":"remove","path":"/0/renderable/bg"}])"_json));
// value not needed
KO( json_patch p1( R"([{"op":"remove","path":"/0/renderable/bg", "value":"Black"}])"_json));
// value missing
KO( json_patch p1( R"([{"op":"add","path":"/0/renderable/bg"}])"_json));
// value missing
KO( json_patch p1( R"([{"op":"replace","path":"/0/renderable/bg"}])"_json));
// wrong op
KO( json_patch p1( R"([{"op":"ad","path":"/0/renderable/bg","value":"Black"}])"_json));
// invalid json-pointer
KO( json_patch p1( R"([{"op":"add","path":"0/renderable/bg","value":"Black"}])"_json));
return 0;
}