From 1d055261b4144dbf86b2658437015b15d4dd9bff Mon Sep 17 00:00:00 2001 From: Richard Date: Sun, 4 Sep 2022 00:32:56 +0100 Subject: initial --- include/jsoncons_ext/jsonpatch/jsonpatch.hpp | 579 +++++++++++++++++++++ include/jsoncons_ext/jsonpatch/jsonpatch_error.hpp | 121 +++++ 2 files changed, 700 insertions(+) create mode 100644 include/jsoncons_ext/jsonpatch/jsonpatch.hpp create mode 100644 include/jsoncons_ext/jsonpatch/jsonpatch_error.hpp (limited to 'include/jsoncons_ext/jsonpatch') diff --git a/include/jsoncons_ext/jsonpatch/jsonpatch.hpp b/include/jsoncons_ext/jsonpatch/jsonpatch.hpp new file mode 100644 index 0000000..ab4ace7 --- /dev/null +++ b/include/jsoncons_ext/jsonpatch/jsonpatch.hpp @@ -0,0 +1,579 @@ +// Copyright 2017 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONPATCH_JSONPATCH_HPP +#define JSONCONS_JSONPATCH_JSONPATCH_HPP + +#include +#include +#include +#include // std::min +#include // std::move +#include +#include +#include + +namespace jsoncons { namespace jsonpatch { + +namespace detail { + + template + struct jsonpatch_names + { + static std::basic_string test_name() + { + static std::basic_string name{'t','e','s','t'}; + return name; + } + static std::basic_string add_name() + { + static std::basic_string name{'a','d','d'}; + return name; + } + static std::basic_string remove_name() + { + static std::basic_string name{'r','e','m','o','v','e'}; + return name; + } + static std::basic_string replace_name() + { + static std::basic_string name{'r','e','p','l','a','c','e'}; + return name; + } + static std::basic_string move_name() + { + static std::basic_string name{'m','o','v','e'}; + return name; + } + static std::basic_string copy_name() + { + static std::basic_string name{'c','o','p','y'}; + return name; + } + static std::basic_string op_name() + { + static std::basic_string name{'o','p'}; + return name; + } + static std::basic_string path_name() + { + static std::basic_string name{'p','a','t','h'}; + return name; + } + static std::basic_string from_name() + { + static std::basic_string name{'f','r','o','m'}; + return name; + } + static std::basic_string value_name() + { + static std::basic_string name{'v','a','l','u','e'}; + return name; + } + static std::basic_string dash_name() + { + static std::basic_string name{'-'}; + return name; + } + }; + + template + jsonpointer::basic_json_pointer definite_path(const Json& root, jsonpointer::basic_json_pointer& location) + { + using char_type = typename Json::char_type; + using string_type = std::basic_string; + + auto rit = location.rbegin(); + if (rit == location.rend()) + { + return location; + } + + if (*rit != jsonpatch_names::dash_name()) + { + return location; + } + + std::vector tokens; + for (auto it = location.begin(); it != location.rbegin().base()-1; ++it) + { + tokens.push_back(*it); + } + jsonpointer::basic_json_pointer pointer(tokens); + + std::error_code ec; + + Json val = jsonpointer::get(root, pointer, ec); + if (ec || !val.is_array()) + { + return location; + } + string_type last_token; + jsoncons::detail::from_integer(val.size(), last_token); + tokens.emplace_back(std::move(last_token)); + + return jsonpointer::basic_json_pointer(std::move(tokens)); + } + + enum class op_type {add,remove,replace}; + enum class state_type {begin,abort,commit}; + + template + struct operation_unwinder + { + using char_type = typename Json::char_type; + using string_type = std::basic_string; + using json_pointer_type = jsonpointer::basic_json_pointer; + + struct entry + { + op_type op; + json_pointer_type path; + Json value; + + entry(op_type op, const json_pointer_type& path, const Json& value) + : op(op), path(path), value(value) + { + } + + entry(const entry&) = default; + + entry(entry&&) = default; + + entry& operator=(const entry&) = default; + + entry& operator=(entry&&) = default; + }; + + Json& target; + state_type state; + std::vector stack; + + operation_unwinder(Json& j) + : target(j), state(state_type::begin) + { + } + + ~operation_unwinder() noexcept + { + std::error_code ec; + if (state != state_type::commit) + { + for (auto it = stack.rbegin(); it != stack.rend(); ++it) + { + if (it->op == op_type::add) + { + jsonpointer::add(target,it->path,it->value,ec); + if (ec) + { + //std::cout << "add: " << it->path << std::endl; + break; + } + } + else if (it->op == op_type::remove) + { + jsonpointer::remove(target,it->path,ec); + if (ec) + { + //std::cout << "remove: " << it->path << std::endl; + break; + } + } + else if (it->op == op_type::replace) + { + jsonpointer::replace(target,it->path,it->value,ec); + if (ec) + { + //std::cout << "replace: " << it->path << std::endl; + break; + } + } + } + } + } + }; + + template + Json from_diff(const Json& source, const Json& target, const typename Json::string_view_type& path) + { + using char_type = typename Json::char_type; + + Json result = typename Json::array(); + + if (source == target) + { + return result; + } + + if (source.is_array() && target.is_array()) + { + std::size_t common = (std::min)(source.size(),target.size()); + for (std::size_t i = 0; i < common; ++i) + { + std::basic_string ss(path); + ss.push_back('/'); + jsoncons::detail::from_integer(i,ss); + auto temp_diff = from_diff(source[i],target[i],ss); + result.insert(result.array_range().end(),temp_diff.array_range().begin(),temp_diff.array_range().end()); + } + // Element in source, not in target - remove + for (std::size_t i = source.size(); i-- > target.size();) + { + std::basic_string ss(path); + ss.push_back('/'); + jsoncons::detail::from_integer(i,ss); + Json val(json_object_arg); + val.insert_or_assign(jsonpatch_names::op_name(), jsonpatch_names::remove_name()); + val.insert_or_assign(jsonpatch_names::path_name(), ss); + result.push_back(std::move(val)); + } + // Element in target, not in source - add, + // Fix contributed by Alexander rog13 + for (std::size_t i = source.size(); i < target.size(); ++i) + { + const auto& a = target[i]; + std::basic_string ss(path); + ss.push_back('/'); + jsoncons::detail::from_integer(i,ss); + Json val(json_object_arg); + val.insert_or_assign(jsonpatch_names::op_name(), jsonpatch_names::add_name()); + val.insert_or_assign(jsonpatch_names::path_name(), ss); + val.insert_or_assign(jsonpatch_names::value_name(), a); + result.push_back(std::move(val)); + } + } + else if (source.is_object() && target.is_object()) + { + for (const auto& a : source.object_range()) + { + std::basic_string ss(path); + ss.push_back('/'); + jsonpointer::escape(a.key(),ss); + auto it = target.find(a.key()); + if (it != target.object_range().end()) + { + auto temp_diff = from_diff(a.value(),it->value(),ss); + result.insert(result.array_range().end(),temp_diff.array_range().begin(),temp_diff.array_range().end()); + } + else + { + Json val(json_object_arg); + val.insert_or_assign(jsonpatch_names::op_name(), jsonpatch_names::remove_name()); + val.insert_or_assign(jsonpatch_names::path_name(), ss); + result.push_back(std::move(val)); + } + } + for (const auto& a : target.object_range()) + { + auto it = source.find(a.key()); + if (it == source.object_range().end()) + { + std::basic_string ss(path); + ss.push_back('/'); + jsonpointer::escape(a.key(),ss); + Json val(json_object_arg); + val.insert_or_assign(jsonpatch_names::op_name(), jsonpatch_names::add_name()); + val.insert_or_assign(jsonpatch_names::path_name(), ss); + val.insert_or_assign(jsonpatch_names::value_name(), a.value()); + result.push_back(std::move(val)); + } + } + } + else + { + Json val(json_object_arg); + val.insert_or_assign(jsonpatch_names::op_name(), jsonpatch_names::replace_name()); + val.insert_or_assign(jsonpatch_names::path_name(), path); + val.insert_or_assign(jsonpatch_names::value_name(), target); + result.push_back(std::move(val)); + } + + return result; + } +} + +template +void apply_patch(Json& target, const Json& patch, std::error_code& ec) +{ + using char_type = typename Json::char_type; + using string_type = std::basic_string; + using json_pointer_type = jsonpointer::basic_json_pointer; + + jsoncons::jsonpatch::detail::operation_unwinder unwinder(target); + std::error_code local_ec; + + // Validate + + for (const auto& operation : patch.array_range()) + { + unwinder.state =jsoncons::jsonpatch::detail::state_type::begin; + + auto it_op = operation.find(detail::jsonpatch_names::op_name()); + if (it_op == operation.object_range().end()) + { + ec = jsonpatch_errc::invalid_patch; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + string_type op = it_op->value().template as(); + + auto it_path = operation.find(detail::jsonpatch_names::path_name()); + if (it_path == operation.object_range().end()) + { + ec = jsonpatch_errc::invalid_patch; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + string_type path = it_path->value().template as(); + auto location = json_pointer_type::parse(path, local_ec); + if (local_ec) + { + ec = jsonpatch_errc::invalid_patch; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + + if (op ==jsoncons::jsonpatch::detail::jsonpatch_names::test_name()) + { + Json val = jsonpointer::get(target,location,local_ec); + if (local_ec) + { + ec = jsonpatch_errc::test_failed; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + auto it_value = operation.find(detail::jsonpatch_names::value_name()); + if (it_value == operation.object_range().end()) + { + ec = jsonpatch_errc::invalid_patch; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + if (val != it_value->value()) + { + ec = jsonpatch_errc::test_failed; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + } + else if (op ==jsoncons::jsonpatch::detail::jsonpatch_names::add_name()) + { + auto it_value = operation.find(detail::jsonpatch_names::value_name()); + if (it_value == operation.object_range().end()) + { + ec = jsonpatch_errc::invalid_patch; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + Json val = it_value->value(); + auto npath = jsonpatch::detail::definite_path(target,location); + + std::error_code insert_ec; + jsonpointer::add_if_absent(target,npath,val,insert_ec); // try insert without replace + if (insert_ec) // try a replace + { + std::error_code select_ec; + Json orig_val = jsonpointer::get(target,npath,select_ec); + if (select_ec) // shouldn't happen + { + ec = jsonpatch_errc::add_failed; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + std::error_code replace_ec; + jsonpointer::replace(target,npath,val,replace_ec); + if (replace_ec) + { + ec = jsonpatch_errc::add_failed; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + unwinder.stack.emplace_back(detail::op_type::replace,npath,orig_val); + } + else // insert without replace succeeded + { + unwinder.stack.emplace_back(detail::op_type::remove,npath,Json::null()); + } + } + else if (op ==jsoncons::jsonpatch::detail::jsonpatch_names::remove_name()) + { + Json val = jsonpointer::get(target,location,local_ec); + if (local_ec) + { + ec = jsonpatch_errc::remove_failed; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + jsonpointer::remove(target,location,local_ec); + if (local_ec) + { + ec = jsonpatch_errc::remove_failed; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + unwinder.stack.emplace_back(detail::op_type::add, location, val); + } + else if (op ==jsoncons::jsonpatch::detail::jsonpatch_names::replace_name()) + { + Json val = jsonpointer::get(target,location,local_ec); + if (local_ec) + { + ec = jsonpatch_errc::replace_failed; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + auto it_value = operation.find(detail::jsonpatch_names::value_name()); + if (it_value == operation.object_range().end()) + { + ec = jsonpatch_errc::invalid_patch; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + jsonpointer::replace(target, location, it_value->value(), local_ec); + if (local_ec) + { + ec = jsonpatch_errc::replace_failed; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + unwinder.stack.emplace_back(detail::op_type::replace,location,val); + } + else if (op ==jsoncons::jsonpatch::detail::jsonpatch_names::move_name()) + { + auto it_from = operation.find(detail::jsonpatch_names::from_name()); + if (it_from == operation.object_range().end()) + { + ec = jsonpatch_errc::invalid_patch; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + string_type from = it_from->value().as_string(); + auto from_pointer = json_pointer_type::parse(from, local_ec); + if (local_ec) + { + ec = jsonpatch_errc::move_failed; + unwinder.state = jsoncons::jsonpatch::detail::state_type::abort; + return; + } + + Json val = jsonpointer::get(target, from_pointer, local_ec); + if (local_ec) + { + ec = jsonpatch_errc::move_failed; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + jsonpointer::remove(target, from_pointer, local_ec); + if (local_ec) + { + ec = jsonpatch_errc::move_failed; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + unwinder.stack.emplace_back(detail::op_type::add, from_pointer, val); + // add + std::error_code insert_ec; + auto npath = jsonpatch::detail::definite_path(target,location); + jsonpointer::add_if_absent(target,npath,val,insert_ec); // try insert without replace + if (insert_ec) // try a replace + { + std::error_code select_ec; + Json orig_val = jsonpointer::get(target,npath,select_ec); + if (select_ec) // shouldn't happen + { + ec = jsonpatch_errc::copy_failed; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + std::error_code replace_ec; + jsonpointer::replace(target, npath, val, replace_ec); + if (replace_ec) + { + ec = jsonpatch_errc::copy_failed; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + unwinder.stack.emplace_back(jsoncons::jsonpatch::detail::op_type::replace,npath,orig_val); + } + else + { + unwinder.stack.emplace_back(detail::op_type::remove,npath,Json::null()); + } + } + else if (op ==jsoncons::jsonpatch::detail::jsonpatch_names::copy_name()) + { + auto it_from = operation.find(detail::jsonpatch_names::from_name()); + if (it_from == operation.object_range().end()) + { + ec = jsonpatch_errc::invalid_patch; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + string_type from = it_from->value().as_string(); + Json val = jsonpointer::get(target,from,local_ec); + if (local_ec) + { + ec = jsonpatch_errc::copy_failed; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + // add + auto npath = jsonpatch::detail::definite_path(target,location); + std::error_code insert_ec; + jsonpointer::add_if_absent(target,npath,val,insert_ec); // try insert without replace + if (insert_ec) // Failed, try a replace + { + std::error_code select_ec; + Json orig_val = jsonpointer::get(target,npath, select_ec); + if (select_ec) // shouldn't happen + { + ec = jsonpatch_errc::copy_failed; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + std::error_code replace_ec; + jsonpointer::replace(target, npath, val,replace_ec); + if (replace_ec) + { + ec = jsonpatch_errc::copy_failed; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + unwinder.stack.emplace_back(jsoncons::jsonpatch::detail::op_type::replace,npath,orig_val); + } + else + { + unwinder.stack.emplace_back(detail::op_type::remove,npath,Json::null()); + } + } + } + if (unwinder.state ==jsoncons::jsonpatch::detail::state_type::begin) + { + unwinder.state =jsoncons::jsonpatch::detail::state_type::commit; + } +} + +template +Json from_diff(const Json& source, const Json& target) +{ + std::basic_string path; + return jsoncons::jsonpatch::detail::from_diff(source, target, path); +} + +template +void apply_patch(Json& target, const Json& patch) +{ + std::error_code ec; + apply_patch(target, patch, ec); + if (ec) + { + JSONCONS_THROW(jsonpatch_error(ec)); + } +} + +}} + +#endif diff --git a/include/jsoncons_ext/jsonpatch/jsonpatch_error.hpp b/include/jsoncons_ext/jsonpatch/jsonpatch_error.hpp new file mode 100644 index 0000000..33f8007 --- /dev/null +++ b/include/jsoncons_ext/jsonpatch/jsonpatch_error.hpp @@ -0,0 +1,121 @@ +/// Copyright 2017 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONPATCH_JSONPATCH_ERROR_HPP +#define JSONCONS_JSONPATCH_JSONPATCH_ERROR_HPP + +#include +#include + +namespace jsoncons { namespace jsonpatch { + + enum class jsonpatch_errc + { + success = 0, + invalid_patch = 1, + test_failed, + add_failed, + remove_failed, + replace_failed, + move_failed, + copy_failed + + }; + + class jsonpatch_error_category_impl + : public std::error_category + { + public: + const char* name() const noexcept override + { + return "jsoncons/jsonpatch"; + } + std::string message(int ev) const override + { + switch (static_cast(ev)) + { + case jsonpatch_errc::invalid_patch: + return "Invalid JSON Patch document"; + case jsonpatch_errc::test_failed: + return "JSON Patch test operation failed"; + case jsonpatch_errc::add_failed: + return "JSON Patch add operation failed"; + case jsonpatch_errc::remove_failed: + return "JSON Patch remove operation failed"; + case jsonpatch_errc::replace_failed: + return "JSON Patch replace operation failed"; + case jsonpatch_errc::move_failed: + return "JSON Patch move operation failed"; + case jsonpatch_errc::copy_failed: + return "JSON Patch copy operation failed"; + default: + return "Unknown JSON Patch error"; + } + } + }; + + inline + const std::error_category& jsonpatch_error_category() + { + static jsonpatch_error_category_impl instance; + return instance; + } + + inline + std::error_code make_error_code(jsonpatch_errc result) + { + return std::error_code(static_cast(result),jsonpatch_error_category()); + } + +} // jsonpatch +} // jsoncons + +namespace std { + template<> + struct is_error_code_enum : public true_type + { + }; +} + +namespace jsoncons { namespace jsonpatch { + +// allow to disable exceptions +#if !defined(JSONCONS_NO_EXCEPTIONS) + #define JSONCONS_THROW(exception) throw exception + #define JSONCONS_RETHROW throw + #define JSONCONS_TRY try + #define JSONCONS_CATCH(exception) catch(exception) +#else + #define JSONCONS_THROW(exception) std::terminate() + #define JSONCONS_RETHROW std::terminate() + #define JSONCONS_TRY if (true) + #define JSONCONS_CATCH(exception) if (false) +#endif + + class jsonpatch_error : public std::system_error, public virtual json_exception + { + public: + jsonpatch_error(const std::error_code& ec) + : std::system_error(ec) + { + } + + jsonpatch_error(const jsonpatch_error& other) = default; + + jsonpatch_error(jsonpatch_error&& other) = default; + + jsonpatch_error& operator=(const jsonpatch_error& e) = default; + jsonpatch_error& operator=(jsonpatch_error&& e) = default; + + const char* what() const noexcept override + { + return std::system_error::what(); + } + }; +} // jsonpatch +} // jsoncons + +#endif -- cgit v1.2.3