拆分核心库和服务
This commit is contained in:
@@ -0,0 +1,264 @@
|
||||
#pragma once
|
||||
#include "adminive/amis.hpp"
|
||||
#include "adminive/synchronized.hpp"
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
namespace adminive {
|
||||
struct Request_Context {
|
||||
std::string user;
|
||||
std::string request_id;
|
||||
std::string remote_address;
|
||||
std::map<std::string, std::string> attributes;
|
||||
};
|
||||
template <Json_Type Json>
|
||||
struct Http_Response {
|
||||
int status{200};
|
||||
Json body{};
|
||||
};
|
||||
template <Json_Type Json, class Data>
|
||||
Json make_http_result(int status, std::string message, Data&& data) {
|
||||
Json result = json_object<Json>();
|
||||
json_set(result, "status", status);
|
||||
json_set(result, "msg", std::move(message));
|
||||
json_set(result, "data", std::forward<Data>(data));
|
||||
return result;
|
||||
}
|
||||
template <Json_Type Json>
|
||||
Http_Response<Json> make_http_success(Json data, std::string message = {}) {
|
||||
return Http_Response<Json>{200, make_http_result<Json>(0, std::move(message), std::move(data))};
|
||||
}
|
||||
template <Json_Type Json>
|
||||
Http_Response<Json> make_http_error(int status, const Update_Result& update) {
|
||||
Json result = make_http_result<Json>(status, update.message, json_object<Json>());
|
||||
Json errors = json_object<Json>();
|
||||
Json field_errors = json_object<Json>();
|
||||
for(const auto& [name, message] : update.field_errors) {
|
||||
json_set(errors, name, message);
|
||||
json_set(field_errors, name, message);
|
||||
}
|
||||
json_set(result, "errors", std::move(errors));
|
||||
json_set(result, "field_errors", std::move(field_errors));
|
||||
return Http_Response<Json>{status, std::move(result)};
|
||||
}
|
||||
template <Json_Type Json>
|
||||
Http_Response<Json> make_http_error(int status, std::string message) {
|
||||
return Http_Response<Json>{status, make_http_result<Json>(status, std::move(message), json_object<Json>())};
|
||||
}
|
||||
enum class Resource_Lock_Scope {
|
||||
local,
|
||||
root
|
||||
};
|
||||
template <class Model>
|
||||
struct Resource_Transaction {
|
||||
using Prepare_Function = std::function<void(const Model&, const Request_Context&)>;
|
||||
using Commit_Function = std::function<void(const Model&, const Request_Context&)>;
|
||||
using Rollback_Function = std::function<void(const Model&, const Request_Context&)>;
|
||||
Prepare_Function prepare;
|
||||
Commit_Function commit;
|
||||
Rollback_Function rollback;
|
||||
};
|
||||
template <class Model, class Function>
|
||||
Resource_Transaction<Model> make_resource_transaction(Function&& function) {
|
||||
Resource_Transaction<Model> result;
|
||||
result.commit = std::forward<Function>(function);
|
||||
return result;
|
||||
}
|
||||
template <class T, Json_Type Json, Basic_Lock Lock = std::shared_mutex>
|
||||
requires Writable_Adapted_Object<T> && std::copy_constructible<Object_Model_Type<T>>
|
||||
class Resource_Service {
|
||||
public:
|
||||
using Object = std::remove_cvref_t<T>;
|
||||
using Model = Object_Model_Type<Object>;
|
||||
using Transaction = Resource_Transaction<Model>;
|
||||
Resource_Service(const Resource_Service&) = delete;
|
||||
Resource_Service& operator=(const Resource_Service&) = delete;
|
||||
Resource_Service(Resource_Service&&) = delete;
|
||||
Resource_Service& operator=(Resource_Service&&) = delete;
|
||||
Resource_Service(T& value, std::string path, Transaction transaction = {}, Lock* shared_lock = nullptr) : path_(std::move(path)), transaction_(std::move(transaction)) {
|
||||
if(shared_lock) {
|
||||
configure_raw_access(value, *shared_lock);
|
||||
} else {
|
||||
owned_lock_ = std::make_shared<Lock>();
|
||||
configure_raw_access(value, *owned_lock_);
|
||||
}
|
||||
}
|
||||
template <Basic_Lock Field_Lock>
|
||||
explicit Resource_Service(Synchronized_Value<Object, Lock, Field_Lock>& value, std::string path, Transaction transaction = {}) : path_(std::move(path)), transaction_(std::move(transaction)) {
|
||||
read_access_ = [&value](const Read_Function& function) {
|
||||
value.read([&](const Object& object) {
|
||||
function(object);
|
||||
});
|
||||
};
|
||||
write_access_ = [&value](const Write_Function& function) {
|
||||
value.write([&](Object& object) {
|
||||
function(object);
|
||||
});
|
||||
};
|
||||
}
|
||||
template <class Root, Basic_Lock Field_Lock, auto... Members>
|
||||
requires (!std::is_const_v<Root>) && std::same_as<typename Synchronized_Field<Root, Lock, Field_Lock, Members...>::value_type, Object>
|
||||
explicit Resource_Service(Synchronized_Field<Root, Lock, Field_Lock, Members...> value, std::string path, Transaction transaction = {}, Resource_Lock_Scope lock_scope = Resource_Lock_Scope::local) : path_(std::move(path)), transaction_(std::move(transaction)) {
|
||||
if(lock_scope == Resource_Lock_Scope::root) {
|
||||
configure_raw_access(detail::Synchronized_Access::value(value), detail::Synchronized_Access::mutex(value));
|
||||
return;
|
||||
}
|
||||
read_access_ = [value](const Read_Function& function) {
|
||||
value.object_read([&](const Object& object) {
|
||||
function(object);
|
||||
});
|
||||
};
|
||||
write_access_ = [value](const Write_Function& function) mutable {
|
||||
value.object_write([&](Object& object) {
|
||||
function(object);
|
||||
});
|
||||
};
|
||||
}
|
||||
const std::string& path() const noexcept {
|
||||
return path_;
|
||||
}
|
||||
Json amis_schema() const {
|
||||
Json result;
|
||||
read_access_([&](const Object& value) {
|
||||
result = to_amis_form_schema<Json>(value, path_ + "/data", describe<Model>().list_options().confirm_label);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
Http_Response<Json> descriptor_response() const noexcept {
|
||||
return safe_response([&] {
|
||||
return make_http_success<Json>(to_descriptor_json<Json, Object>());
|
||||
});
|
||||
}
|
||||
Http_Response<Json> data_response() const noexcept {
|
||||
return safe_response([&] {
|
||||
Http_Response<Json> response;
|
||||
read_access_([&](const Object& value) {
|
||||
response = make_http_success<Json>(to_frontend_json<Json>(value));
|
||||
});
|
||||
return response;
|
||||
});
|
||||
}
|
||||
Http_Response<Json> amis_response() const noexcept {
|
||||
return safe_response([&] {
|
||||
return make_http_success<Json>(amis_schema());
|
||||
});
|
||||
}
|
||||
Http_Response<Json> update_response(std::string_view body, const Request_Context& context = {}) noexcept {
|
||||
return safe_response([&] {
|
||||
Json patch;
|
||||
try {
|
||||
patch = parse_json<Json>(body);
|
||||
} catch(const std::exception& error) {
|
||||
return make_http_error<Json>(400, error.what());
|
||||
}
|
||||
Http_Response<Json> response;
|
||||
write_access_([&](Object& value) {
|
||||
response = update_locked(value, patch, context);
|
||||
});
|
||||
return response;
|
||||
});
|
||||
}
|
||||
private:
|
||||
using Read_Function = std::function<void(const Object&)>;
|
||||
using Write_Function = std::function<void(Object&)>;
|
||||
using Read_Access = std::function<void(const Read_Function&)>;
|
||||
using Write_Access = std::function<void(const Write_Function&)>;
|
||||
void configure_raw_access(Object& value, Lock& lock) {
|
||||
read_access_ = [&value, &lock](const Read_Function& function) {
|
||||
std::unique_lock guard(lock);
|
||||
function(value);
|
||||
};
|
||||
write_access_ = [&value, &lock](const Write_Function& function) {
|
||||
std::unique_lock guard(lock);
|
||||
function(value);
|
||||
};
|
||||
}
|
||||
Http_Response<Json> update_locked(Object& value, const Json& patch, const Request_Context& context) {
|
||||
Model original = Object_Adapter<Object>::snapshot(value);
|
||||
Model candidate = original;
|
||||
auto result = apply_model_json<Json>(candidate, patch, Write_Mode::update, false);
|
||||
if(!result.success) {
|
||||
return make_http_error<Json>(422, result);
|
||||
}
|
||||
bool transaction_started{};
|
||||
bool runtime_commit_started{};
|
||||
try {
|
||||
if(transaction_.prepare) {
|
||||
transaction_started = true;
|
||||
transaction_.prepare(candidate, context);
|
||||
}
|
||||
runtime_commit_started = true;
|
||||
Object_Adapter<Object>::commit(value, candidate);
|
||||
if(transaction_.commit) {
|
||||
transaction_started = true;
|
||||
transaction_.commit(candidate, context);
|
||||
}
|
||||
} catch(const Json_Assignment_Error& error) {
|
||||
rollback_after_failure_locked(value, original, context, runtime_commit_started, transaction_started);
|
||||
return make_http_error<Json>(422, error.result());
|
||||
} catch(const Field_Validation_Error& error) {
|
||||
rollback_after_failure_locked(value, original, context, runtime_commit_started, transaction_started);
|
||||
return make_http_error<Json>(422, object_commit_error<Object>(error));
|
||||
} catch(const std::exception& error) {
|
||||
const std::string rollback_error = rollback_after_failure_locked(value, original, context, runtime_commit_started, transaction_started);
|
||||
return make_http_error<Json>(500, rollback_error.empty() ? error.what() : std::string(error.what()) + "; rollback failed: " + rollback_error);
|
||||
} catch(...) {
|
||||
const std::string rollback_error = rollback_after_failure_locked(value, original, context, runtime_commit_started, transaction_started);
|
||||
return make_http_error<Json>(500, rollback_error.empty() ? "unknown server error" : "unknown server error; rollback failed: " + rollback_error);
|
||||
}
|
||||
return make_http_success<Json>(to_frontend_json<Json>(value), std::move(result.message));
|
||||
}
|
||||
template <class Function>
|
||||
static Http_Response<Json> safe_response(Function&& function) noexcept {
|
||||
try {
|
||||
return std::forward<Function>(function)();
|
||||
} catch(const Json_Assignment_Error& error) {
|
||||
return make_http_error<Json>(422, error.result());
|
||||
} catch(const Field_Validation_Error& error) {
|
||||
return make_http_error<Json>(422, object_commit_error<Object>(error));
|
||||
} catch(const std::exception& error) {
|
||||
return make_http_error<Json>(500, error.what());
|
||||
} catch(...) {
|
||||
return make_http_error<Json>(500, "unknown server error");
|
||||
}
|
||||
}
|
||||
std::string rollback_after_failure_locked(Object& value, const Model& original, const Request_Context& context, bool restore_runtime, bool transaction_started) noexcept {
|
||||
std::string message;
|
||||
if(restore_runtime) {
|
||||
try {
|
||||
Object_Adapter<Object>::commit(value, original);
|
||||
} catch(const std::exception& error) {
|
||||
message = error.what();
|
||||
} catch(...) {
|
||||
message = "runtime object rollback failed";
|
||||
}
|
||||
}
|
||||
if(transaction_started && transaction_.rollback) {
|
||||
try {
|
||||
transaction_.rollback(original, context);
|
||||
} catch(const std::exception& error) {
|
||||
if(!message.empty()) {
|
||||
message += "; ";
|
||||
}
|
||||
message += error.what();
|
||||
} catch(...) {
|
||||
if(!message.empty()) {
|
||||
message += "; ";
|
||||
}
|
||||
message += "external rollback failed";
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
std::string path_;
|
||||
Transaction transaction_;
|
||||
std::shared_ptr<Lock> owned_lock_;
|
||||
Read_Access read_access_;
|
||||
Write_Access write_access_;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user