Skip to content

Commit c12a27e

Browse files
committed
use schema to validate json-patch and use json_pointer to verify path
fix #107
1 parent a0fca47 commit c12a27e

File tree

5 files changed

+143
-49
lines changed

5 files changed

+143
-49
lines changed

src/json-patch.cpp

Lines changed: 81 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,115 @@
11
#include "json-patch.hpp"
22

3+
#include <nlohmann/json-schema.hpp>
4+
5+
namespace
6+
{
7+
8+
// originally from http://jsonpatch.com/, http://json.schemastore.org/json-patch
9+
// with fixes
10+
const nlohmann::json patch_schema = R"patch({
11+
"title": "JSON schema for JSONPatch files",
12+
"$schema": "http://json-schema.org/draft-04/schema#",
13+
"type": "array",
14+
15+
"items": {
16+
"oneOf": [
17+
{
18+
"additionalProperties": false,
19+
"required": [ "value", "op", "path"],
20+
"properties": {
21+
"path" : { "$ref": "#/definitions/path" },
22+
"op": {
23+
"description": "The operation to perform.",
24+
"type": "string",
25+
"enum": [ "add", "replace", "test" ]
26+
},
27+
"value": {
28+
"description": "The value to add, replace or test."
29+
}
30+
}
31+
},
32+
{
33+
"additionalProperties": false,
34+
"required": [ "op", "path"],
35+
"properties": {
36+
"path" : { "$ref": "#/definitions/path" },
37+
"op": {
38+
"description": "The operation to perform.",
39+
"type": "string",
40+
"enum": [ "remove" ]
41+
}
42+
}
43+
},
44+
{
45+
"additionalProperties": false,
46+
"required": [ "from", "op", "path" ],
47+
"properties": {
48+
"path" : { "$ref": "#/definitions/path" },
49+
"op": {
50+
"description": "The operation to perform.",
51+
"type": "string",
52+
"enum": [ "move", "copy" ]
53+
},
54+
"from": {
55+
"$ref": "#/definitions/path",
56+
"description": "A JSON Pointer path pointing to the location to move/copy from."
57+
}
58+
}
59+
}
60+
]
61+
},
62+
"definitions": {
63+
"path": {
64+
"description": "A JSON Pointer path.",
65+
"type": "string"
66+
}
67+
}
68+
})patch"_json;
69+
}; // namespace
70+
371
namespace nlohmann
472
{
573

674
json_patch::json_patch(json &&patch)
7-
: j_{std::move(patch)}
75+
: j_(std::move(patch))
876
{
977
validateJsonPatch(j_);
1078
}
1179

1280
json_patch::json_patch(const json &patch)
13-
: j_{std::move(patch)}
81+
: j_(std::move(patch))
1482
{
1583
validateJsonPatch(j_);
1684
}
1785

18-
json_patch &json_patch::add(std::string path, json value)
86+
json_patch &json_patch::add(const json::json_pointer &ptr, json value)
1987
{
20-
j_.push_back(json{{"op", "add"}, {"path", std::move(path)}, {"value", std::move(value)}});
88+
j_.push_back(json{{"op", "add"}, {"path", ptr}, {"value", std::move(value)}});
2189
return *this;
2290
}
2391

24-
json_patch &json_patch::replace(std::string path, json value)
92+
json_patch &json_patch::replace(const json::json_pointer &ptr, json value)
2593
{
26-
j_.push_back(json{{"op", "replace"}, {"path", std::move(path)}, {"value", std::move(value)}});
94+
j_.push_back(json{{"op", "replace"}, {"path", ptr}, {"value", std::move(value)}});
2795
return *this;
2896
}
2997

30-
json_patch &json_patch::remove(std::string path)
98+
json_patch &json_patch::remove(const json::json_pointer &ptr)
3199
{
32-
j_.push_back(json{{"op", "remove"}, {"path", std::move(path)}});
100+
j_.push_back(json{{"op", "remove"}, {"path", ptr}});
33101
return *this;
34102
}
35103

36104
void json_patch::validateJsonPatch(json const &patch)
37105
{
38-
if (!patch.is_array()) {
39-
throw JsonPatchFormatException{"Json is not an array"};
40-
}
41-
42-
for (auto const &op : patch) {
43-
if (!op.is_object()) {
44-
throw JsonPatchFormatException{"Each json patch entry needs to be an op object"};
45-
}
46-
47-
if (!op.contains("op")) {
48-
throw JsonPatchFormatException{"Each json patch entry needs op element"};
49-
}
50-
51-
const auto opType = op["op"].get<std::string>();
52-
if ((opType != "add") && (opType != "remove") && (opType != "replace")) {
53-
throw JsonPatchFormatException{std::string{"Operation "} + opType + std::string{"is invalid"}};
54-
}
55-
56-
if (!op.contains("path")) {
57-
throw JsonPatchFormatException{"Each json patch entry needs path element"};
58-
}
106+
// static put here to have it created at the first usage of validateJsonPatch
107+
static nlohmann::json_schema::json_validator patch_validator(patch_schema);
59108

60-
try {
61-
// try parse to path
62-
[[maybe_unused]] const auto p = json::json_pointer{op["path"].get<std::string>()};
63-
} catch (json::exception &e) {
64-
throw JsonPatchFormatException{e.what()};
65-
}
109+
patch_validator.validate(patch);
66110

67-
if (opType != "remove") {
68-
if (!op.contains("value")) {
69-
throw JsonPatchFormatException{"Remove and replace needs value element"};
70-
}
71-
}
72-
}
111+
for (auto const &op : patch)
112+
json::json_pointer(op["path"].get<std::string>());
73113
}
74114

75115
} // namespace nlohmann

src/json-patch.hpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ class json_patch
2424
json_patch(json &&patch);
2525
json_patch(const json &patch);
2626

27-
json_patch &add(std::string path, json value);
28-
json_patch &replace(std::string path, json value);
29-
json_patch &remove(std::string path);
27+
json_patch &add(const json::json_pointer &, json value);
28+
json_patch &replace(const json::json_pointer &, json value);
29+
json_patch &remove(const json::json_pointer &);
3030

3131
operator json() const { return j_; }
3232

src/json-validator.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1245,7 +1245,7 @@ json json_validator::validate(const json &instance) const
12451245
json json_validator::validate(const json &instance, error_handler &err) const
12461246
{
12471247
json::json_pointer ptr;
1248-
json_patch patch{};
1248+
json_patch patch;
12491249
root_->validate(ptr, instance, patch, err);
12501250
return patch;
12511251
}

test/CMakeLists.txt

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,14 @@ target_link_libraries(issue-98 nlohmann_json_schema_validator)
4444
add_test(NAME issue-98-erase-exception-unknown-keywords COMMAND issue-98)
4545

4646
# Unit test for string format checks
47-
add_executable("string-format-check-test" "string-format-check-test.cpp")
48-
target_include_directories("string-format-check-test" PRIVATE "${PROJECT_SOURCE_DIR}/src/")
49-
target_link_libraries("string-format-check-test" nlohmann_json_schema_validator)
47+
add_executable(string-format-check-test string-format-check-test.cpp)
48+
target_include_directories(string-format-check-test PRIVATE ${PROJECT_SOURCE_DIR}/src/)
49+
target_link_libraries(string-format-check-test nlohmann_json_schema_validator)
5050

51-
add_test(NAME "string-format-check-test" COMMAND "string-format-check-test")
51+
add_test(NAME string-format-check-test COMMAND string-format-check-test)
52+
53+
# Unit test for json-patch
54+
add_executable(json-patch json-patch.cpp)
55+
target_include_directories(json-patch PRIVATE ${PROJECT_SOURCE_DIR}/src)
56+
target_link_libraries(json-patch nlohmann_json_schema_validator)
57+
add_test(NAME json-patch COMMAND json-patch)

test/json-patch.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#include "../src/json-patch.hpp"
2+
3+
#include <iostream>
4+
5+
using nlohmann::json_patch;
6+
7+
#define OK(code) \
8+
do { \
9+
try { \
10+
code; \
11+
} catch (const std::exception &e) { \
12+
std::cerr << "UNEXPECTED FAILED: " << e.what() << "\n"; \
13+
return 1; \
14+
} \
15+
} while (0)
16+
17+
#define KO(code) \
18+
do { \
19+
try { \
20+
code; \
21+
std::cerr << "UNEXPECTED SUCCESS.\n"; \
22+
return 1; \
23+
} catch (const std::exception &e) { \
24+
std::cerr << "EXPECTED FAIL: " << e.what() << "\n"; \
25+
} \
26+
} while (0)
27+
28+
int main(void)
29+
{
30+
OK( json_patch p1( R"([{"op":"add","path":"/0/renderable/bg","value":"Black"}])"_json));
31+
OK( json_patch p1( R"([{"op":"replace","path":"/0/renderable/bg","value":"Black"}])"_json));
32+
OK( json_patch p1( R"([{"op":"remove","path":"/0/renderable/bg"}])"_json));
33+
34+
// value not needed
35+
KO( json_patch p1( R"([{"op":"remove","path":"/0/renderable/bg", "value":"Black"}])"_json));
36+
// value missing
37+
KO( json_patch p1( R"([{"op":"add","path":"/0/renderable/bg"}])"_json));
38+
// value missing
39+
KO( json_patch p1( R"([{"op":"replace","path":"/0/renderable/bg"}])"_json));
40+
41+
// wrong op
42+
KO( json_patch p1( R"([{"op":"ad","path":"/0/renderable/bg","value":"Black"}])"_json));
43+
44+
// invalid json-pointer
45+
KO( json_patch p1( R"([{"op":"add","path":"0/renderable/bg","value":"Black"}])"_json));
46+
47+
return 0;
48+
}

0 commit comments

Comments
 (0)