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 "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
|
namespace nlohmann
|
||||||
{
|
{
|
||||||
|
|
||||||
json_patch::json_patch(json &&patch)
|
json_patch::json_patch(json &&patch)
|
||||||
: j_{std::move(patch)}
|
: j_(std::move(patch))
|
||||||
{
|
{
|
||||||
validateJsonPatch(j_);
|
validateJsonPatch(j_);
|
||||||
}
|
}
|
||||||
|
|
||||||
json_patch::json_patch(const json &patch)
|
json_patch::json_patch(const json &patch)
|
||||||
: j_{std::move(patch)}
|
: j_(std::move(patch))
|
||||||
{
|
{
|
||||||
validateJsonPatch(j_);
|
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;
|
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;
|
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;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
void json_patch::validateJsonPatch(json const &patch)
|
void json_patch::validateJsonPatch(json const &patch)
|
||||||
{
|
{
|
||||||
if (!patch.is_array()) {
|
// static put here to have it created at the first usage of validateJsonPatch
|
||||||
throw JsonPatchFormatException{"Json is not an array"};
|
static nlohmann::json_schema::json_validator patch_validator(patch_schema);
|
||||||
}
|
|
||||||
|
|
||||||
for (auto const &op : patch) {
|
patch_validator.validate(patch);
|
||||||
if (!op.is_object()) {
|
|
||||||
throw JsonPatchFormatException{"Each json patch entry needs to be an op object"};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!op.contains("op")) {
|
for (auto const &op : patch)
|
||||||
throw JsonPatchFormatException{"Each json patch entry needs op element"};
|
json::json_pointer(op["path"].get<std::string>());
|
||||||
}
|
|
||||||
|
|
||||||
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"};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace nlohmann
|
} // namespace nlohmann
|
||||||
|
|||||||
@ -24,9 +24,9 @@ public:
|
|||||||
json_patch(json &&patch);
|
json_patch(json &&patch);
|
||||||
json_patch(const json &patch);
|
json_patch(const json &patch);
|
||||||
|
|
||||||
json_patch &add(std::string path, json value);
|
json_patch &add(const json::json_pointer &, json value);
|
||||||
json_patch &replace(std::string path, json value);
|
json_patch &replace(const json::json_pointer &, json value);
|
||||||
json_patch &remove(std::string path);
|
json_patch &remove(const json::json_pointer &);
|
||||||
|
|
||||||
operator json() const { return j_; }
|
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_validator::validate(const json &instance, error_handler &err) const
|
||||||
{
|
{
|
||||||
json::json_pointer ptr;
|
json::json_pointer ptr;
|
||||||
json_patch patch{};
|
json_patch patch;
|
||||||
root_->validate(ptr, instance, patch, err);
|
root_->validate(ptr, instance, patch, err);
|
||||||
return patch;
|
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)
|
add_test(NAME issue-98-erase-exception-unknown-keywords COMMAND issue-98)
|
||||||
|
|
||||||
# Unit test for string format checks
|
# Unit test for string format checks
|
||||||
add_executable("string-format-check-test" "string-format-check-test.cpp")
|
add_executable(string-format-check-test string-format-check-test.cpp)
|
||||||
target_include_directories("string-format-check-test" PRIVATE "${PROJECT_SOURCE_DIR}/src/")
|
target_include_directories(string-format-check-test PRIVATE ${PROJECT_SOURCE_DIR}/src/)
|
||||||
target_link_libraries("string-format-check-test" nlohmann_json_schema_validator)
|
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