From c12a27eee1e36b25828a809550e3df8df0c89fc1 Mon Sep 17 00:00:00 2001
From: Patrick Boettcher
Date: Fri, 15 May 2020 08:53:52 +0200
Subject: [PATCH] use schema to validate json-patch and use json_pointer to
verify path
fix #107
---
src/json-patch.cpp | 122 +++++++++++++++++++++++++++--------------
src/json-patch.hpp | 6 +-
src/json-validator.cpp | 2 +-
test/CMakeLists.txt | 14 +++--
test/json-patch.cpp | 48 ++++++++++++++++
5 files changed, 143 insertions(+), 49 deletions(-)
create mode 100644 test/json-patch.cpp
diff --git a/src/json-patch.cpp b/src/json-patch.cpp
index 3f24d80..2276c92 100644
--- a/src/json-patch.cpp
+++ b/src/json-patch.cpp
@@ -1,75 +1,115 @@
#include "json-patch.hpp"
+#include
+
+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();
- 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()};
- } 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());
}
} // namespace nlohmann
diff --git a/src/json-patch.hpp b/src/json-patch.hpp
index 4f204be..80fe1df 100644
--- a/src/json-patch.hpp
+++ b/src/json-patch.hpp
@@ -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_; }
diff --git a/src/json-validator.cpp b/src/json-validator.cpp
index 26831a1..62bac0a 100644
--- a/src/json-validator.cpp
+++ b/src/json-validator.cpp
@@ -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;
}
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 0de2a05..8d67e90 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -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)
diff --git a/test/json-patch.cpp b/test/json-patch.cpp
new file mode 100644
index 0000000..6e9da4d
--- /dev/null
+++ b/test/json-patch.cpp
@@ -0,0 +1,48 @@
+#include "../src/json-patch.hpp"
+
+#include
+
+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;
+}