use schema to validate json-patch and use json_pointer to verify path
fix #107
This commit is contained in:
parent
a0fca479f6
commit
c12a27eee1
@ -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
|
||||
|
||||
@ -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_; }
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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
48
test/json-patch.cpp
Normal 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;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user