clean up and added tests

This commit is contained in:
Niels 2016-04-24 19:03:33 +02:00
parent 09e9f6dcd4
commit fb54e212b6
3 changed files with 310 additions and 122 deletions

View file

@ -9518,42 +9518,44 @@ basic_json_parser_63:
*/
basic_json apply_patch(const basic_json& patch) const
{
// make a working copy to apply the patch to
basic_json result = *this;
if (not patch.is_array())
{
// a JSON patch must be an array of objects
throw std::domain_error("JSON patch must be an array of objects");
}
const auto operation_add = [&result](json_pointer & ptr,
basic_json & value)
// wrapper for "add" operation; add value at ptr
const auto operation_add = [&result](json_pointer & ptr, basic_json value)
{
// get reference to parent of JSON pointer ptr
const auto last_path = ptr.pop_back();
basic_json& parent = result.at(ptr);
if (parent.is_object())
{
// use operator[] to add value
parent[last_path] = value;
}
else if (parent.is_array())
{
if (last_path == "-")
{
// special case: append to back
parent.push_back(value);
}
else
{
parent.insert(parent.begin() + std::stoi(last_path),
value);
// default case: insert add offset
parent.insert(parent.begin() + std::stoi(last_path), value);
}
}
};
// wrapper for "remove" operation; remove value at ptr
const auto operation_remove = [&result](json_pointer & ptr)
{
// get reference to parent of JSON pointer ptr
const auto last_path = ptr.pop_back();
basic_json& parent = result.at(ptr);
// remove child
if (parent.is_object())
{
parent.erase(parent.find(last_path));
@ -9564,41 +9566,57 @@ basic_json_parser_63:
}
};
// type check
if (not patch.is_array())
{
// a JSON patch must be an array of objects
throw std::domain_error("JSON patch must be an array of objects");
}
// iterate and apply th eoperations
for (const auto& val : patch)
{
// wrapper to get a value for an operation
const auto get_value = [&val](const std::string & op,
const std::string & member,
bool string_type = false) -> basic_json&
{
// find value
auto it = val.m_value.object->find(member);
// context-sensitive error message
const auto error_msg = (op == "op") ? "operation" : "operation '" + op + "'";
// check if desired value is present
if (it == val.m_value.object->end())
{
throw std::domain_error(error_msg + " must have member '" + member + "'");
}
// check if result is of type string
if (string_type and not it->second.is_string())
{
throw std::domain_error(error_msg + " must have string member '" + member + "'");
}
// no error: return value
return it->second;
};
// type check
if (not val.is_object())
{
throw std::domain_error("JSON patch must be an array of objects");
}
// collect members
const auto it_op = val.m_value.object->find("op");
const auto it_path = val.m_value.object->find("path");
const auto it_value = val.m_value.object->find("value");
const auto it_from = val.m_value.object->find("from");
if (it_op == val.m_value.object->end() or not it_op->second.is_string())
{
throw std::domain_error("operation must have a string 'op' member");
}
if (it_path == val.m_value.object->end() or not it_op->second.is_string())
{
throw std::domain_error("operation must have a string 'path' member");
}
const std::string op = it_op->second;
const std::string path = it_path->second;
json_pointer ptr(path);
// collect mandatory members
const std::string op = get_value("op", "op", true);
const std::string path = get_value(op, "path", true);
json_pointer ptr(get_value(op, "path", true));
if (op == "add")
{
if (it_value == val.m_value.object->end())
{
throw std::domain_error("'add' operation must have member 'value'");
}
operation_add(ptr, it_value->second);
operation_add(ptr, get_value("add", "value"));
}
else if (op == "remove")
{
@ -9606,21 +9624,11 @@ basic_json_parser_63:
}
else if (op == "replace")
{
if (it_value == val.m_value.object->end())
{
throw std::domain_error("'replace' operation must have member 'value'");
}
result.at(ptr) = it_value->second;
result.at(ptr) = get_value("replace", "value");
}
else if (op == "move")
{
if (it_from == val.m_value.object->end())
{
throw std::domain_error("'move' operation must have member 'from'");
}
const std::string from_path = it_from->second;
const std::string from_path = get_value("move", "from", true);
json_pointer from_ptr(from_path);
basic_json v = result[from_ptr];
@ -9629,32 +9637,22 @@ basic_json_parser_63:
}
else if (op == "copy")
{
if (it_from == val.m_value.object->end())
{
throw std::domain_error("'copy' operation must have member 'from'");
}
const std::string from_path = it_from->second;
const std::string from_path = get_value("copy", "from", true);;
const json_pointer from_ptr(from_path);
result[ptr] = result.at(from_ptr);
}
else if (op == "test")
{
if (it_value == val.m_value.object->end())
{
throw std::domain_error("'test' operation must have member 'value'");
}
if (result.at(ptr) != it_value->second)
if (result.at(ptr) != get_value("test", "value"))
{
throw std::domain_error("unsuccessful: " + val.dump());
}
}
else
{
// op must be "add", "remove", "replace", "move",
// "copy", or "test"
// op must be "add", "remove", "replace", "move", "copy", or
// "test"
throw std::domain_error("operation value '" + op + "' is invalid");
}
}

View file

@ -8828,42 +8828,44 @@ class basic_json
*/
basic_json apply_patch(const basic_json& patch) const
{
// make a working copy to apply the patch to
basic_json result = *this;
if (not patch.is_array())
{
// a JSON patch must be an array of objects
throw std::domain_error("JSON patch must be an array of objects");
}
const auto operation_add = [&result](json_pointer & ptr,
basic_json & value)
// wrapper for "add" operation; add value at ptr
const auto operation_add = [&result](json_pointer & ptr, basic_json value)
{
// get reference to parent of JSON pointer ptr
const auto last_path = ptr.pop_back();
basic_json& parent = result.at(ptr);
if (parent.is_object())
{
// use operator[] to add value
parent[last_path] = value;
}
else if (parent.is_array())
{
if (last_path == "-")
{
// special case: append to back
parent.push_back(value);
}
else
{
parent.insert(parent.begin() + std::stoi(last_path),
value);
// default case: insert add offset
parent.insert(parent.begin() + std::stoi(last_path), value);
}
}
};
// wrapper for "remove" operation; remove value at ptr
const auto operation_remove = [&result](json_pointer & ptr)
{
// get reference to parent of JSON pointer ptr
const auto last_path = ptr.pop_back();
basic_json& parent = result.at(ptr);
// remove child
if (parent.is_object())
{
parent.erase(parent.find(last_path));
@ -8874,41 +8876,57 @@ class basic_json
}
};
// type check
if (not patch.is_array())
{
// a JSON patch must be an array of objects
throw std::domain_error("JSON patch must be an array of objects");
}
// iterate and apply th eoperations
for (const auto& val : patch)
{
// wrapper to get a value for an operation
const auto get_value = [&val](const std::string & op,
const std::string & member,
bool string_type = false) -> basic_json&
{
// find value
auto it = val.m_value.object->find(member);
// context-sensitive error message
const auto error_msg = (op == "op") ? "operation" : "operation '" + op + "'";
// check if desired value is present
if (it == val.m_value.object->end())
{
throw std::domain_error(error_msg + " must have member '" + member + "'");
}
// check if result is of type string
if (string_type and not it->second.is_string())
{
throw std::domain_error(error_msg + " must have string member '" + member + "'");
}
// no error: return value
return it->second;
};
// type check
if (not val.is_object())
{
throw std::domain_error("JSON patch must be an array of objects");
}
// collect members
const auto it_op = val.m_value.object->find("op");
const auto it_path = val.m_value.object->find("path");
const auto it_value = val.m_value.object->find("value");
const auto it_from = val.m_value.object->find("from");
if (it_op == val.m_value.object->end() or not it_op->second.is_string())
{
throw std::domain_error("operation must have a string 'op' member");
}
if (it_path == val.m_value.object->end() or not it_op->second.is_string())
{
throw std::domain_error("operation must have a string 'path' member");
}
const std::string op = it_op->second;
const std::string path = it_path->second;
json_pointer ptr(path);
// collect mandatory members
const std::string op = get_value("op", "op", true);
const std::string path = get_value(op, "path", true);
json_pointer ptr(get_value(op, "path", true));
if (op == "add")
{
if (it_value == val.m_value.object->end())
{
throw std::domain_error("'add' operation must have member 'value'");
}
operation_add(ptr, it_value->second);
operation_add(ptr, get_value("add", "value"));
}
else if (op == "remove")
{
@ -8916,21 +8934,11 @@ class basic_json
}
else if (op == "replace")
{
if (it_value == val.m_value.object->end())
{
throw std::domain_error("'replace' operation must have member 'value'");
}
result.at(ptr) = it_value->second;
result.at(ptr) = get_value("replace", "value");
}
else if (op == "move")
{
if (it_from == val.m_value.object->end())
{
throw std::domain_error("'move' operation must have member 'from'");
}
const std::string from_path = it_from->second;
const std::string from_path = get_value("move", "from", true);
json_pointer from_ptr(from_path);
basic_json v = result[from_ptr];
@ -8939,32 +8947,22 @@ class basic_json
}
else if (op == "copy")
{
if (it_from == val.m_value.object->end())
{
throw std::domain_error("'copy' operation must have member 'from'");
}
const std::string from_path = it_from->second;
const std::string from_path = get_value("copy", "from", true);;
const json_pointer from_ptr(from_path);
result[ptr] = result.at(from_ptr);
}
else if (op == "test")
{
if (it_value == val.m_value.object->end())
{
throw std::domain_error("'test' operation must have member 'value'");
}
if (result.at(ptr) != it_value->second)
if (result.at(ptr) != get_value("test", "value"))
{
throw std::domain_error("unsuccessful: " + val.dump());
}
}
else
{
// op must be "add", "remove", "replace", "move",
// "copy", or "test"
// op must be "add", "remove", "replace", "move", "copy", or
// "test"
throw std::domain_error("operation value '" + op + "' is invalid");
}
}

View file

@ -12822,6 +12822,198 @@ TEST_CASE("JSON patch")
CHECK(doc.apply_patch(patch) == expected);
}
}
SECTION("errors")
{
SECTION("unknown operation")
{
SECTION("missing 'op'")
{
json j;
json patch = {{{"foo", "bar"}}};
CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
CHECK_THROWS_WITH(j.apply_patch(patch), "operation must have member 'op'");
}
SECTION("non-string 'op'")
{
json j;
json patch = {{{"op", 1}}};
CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
CHECK_THROWS_WITH(j.apply_patch(patch), "operation must have string member 'op'");
}
}
SECTION("add")
{
SECTION("missing 'path'")
{
json j;
json patch = {{{"op", "add"}}};
CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'add' must have member 'path'");
}
SECTION("non-string 'path'")
{
json j;
json patch = {{{"op", "add"}, {"path", 1}}};
CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'add' must have string member 'path'");
}
SECTION("missing 'value'")
{
json j;
json patch = {{{"op", "add"}, {"path", ""}}};
CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'add' must have member 'value'");
}
}
SECTION("remove")
{
SECTION("missing 'path'")
{
json j;
json patch = {{{"op", "remove"}}};
CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'remove' must have member 'path'");
}
SECTION("non-string 'path'")
{
json j;
json patch = {{{"op", "remove"}, {"path", 1}}};
CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'remove' must have string member 'path'");
}
}
SECTION("replace")
{
SECTION("missing 'path'")
{
json j;
json patch = {{{"op", "replace"}}};
CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'replace' must have member 'path'");
}
SECTION("non-string 'path'")
{
json j;
json patch = {{{"op", "replace"}, {"path", 1}}};
CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'replace' must have string member 'path'");
}
SECTION("missing 'value'")
{
json j;
json patch = {{{"op", "replace"}, {"path", ""}}};
CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'replace' must have member 'value'");
}
}
SECTION("move")
{
SECTION("missing 'path'")
{
json j;
json patch = {{{"op", "move"}}};
CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'move' must have member 'path'");
}
SECTION("non-string 'path'")
{
json j;
json patch = {{{"op", "move"}, {"path", 1}}};
CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'move' must have string member 'path'");
}
SECTION("missing 'from'")
{
json j;
json patch = {{{"op", "move"}, {"path", ""}}};
CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'move' must have member 'from'");
}
SECTION("non-string 'from'")
{
json j;
json patch = {{{"op", "move"}, {"path", ""}, {"from", 1}}};
CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'move' must have string member 'from'");
}
}
SECTION("copy")
{
SECTION("missing 'path'")
{
json j;
json patch = {{{"op", "copy"}}};
CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'copy' must have member 'path'");
}
SECTION("non-string 'path'")
{
json j;
json patch = {{{"op", "copy"}, {"path", 1}}};
CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'copy' must have string member 'path'");
}
SECTION("missing 'from'")
{
json j;
json patch = {{{"op", "copy"}, {"path", ""}}};
CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'copy' must have member 'from'");
}
SECTION("non-string 'from'")
{
json j;
json patch = {{{"op", "copy"}, {"path", ""}, {"from", 1}}};
CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'copy' must have string member 'from'");
}
}
SECTION("test")
{
SECTION("missing 'path'")
{
json j;
json patch = {{{"op", "test"}}};
CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'test' must have member 'path'");
}
SECTION("non-string 'path'")
{
json j;
json patch = {{{"op", "test"}, {"path", 1}}};
CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'test' must have string member 'path'");
}
SECTION("missing 'value'")
{
json j;
json patch = {{{"op", "test"}, {"path", ""}}};
CHECK_THROWS_AS(j.apply_patch(patch), std::domain_error);
CHECK_THROWS_WITH(j.apply_patch(patch), "operation 'test' must have member 'value'");
}
}
}
}
TEST_CASE("regression tests")