#pragma once #include "adminive/amis.hpp" #include "adminive/lock.hpp" #include "adminive/managed.hpp" #include #include #include #include #include #include #include namespace adminive { struct Request_Context { std::string user; std::string request_id; std::string remote_address; std::map attributes; }; template struct Http_Response { int status{200}; Json body{}; }; template Json make_http_result(int status, std::string message, Data&& data) { Json result = json_object(); json_set(result, "status", status); json_set(result, "msg", std::move(message)); json_set(result, "data", std::forward(data)); return result; } template Http_Response make_http_success(Json data, std::string message = {}) { return Http_Response{200, make_http_result(0, std::move(message), std::move(data))}; } template Http_Response make_http_error(int status, const Update_Result& update) { Json result = make_http_result(status, update.message, json_object()); Json errors = json_object(); Json field_errors = json_object(); 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{status, std::move(result)}; } template Http_Response make_http_error(int status, std::string message) { return Http_Response{status, make_http_result(status, std::move(message), json_object())}; } template struct Resource_Transaction { using Prepare_Function = std::function; using Commit_Function = std::function; using Rollback_Function = std::function; Prepare_Function prepare; Commit_Function commit; Rollback_Function rollback; }; template Resource_Transaction make_resource_transaction(Function&& function) { Resource_Transaction result; result.commit = std::forward(function); return result; } template requires Writable_Adapted_Object && std::copy_constructible> class Resource_Service { public: using Object = std::remove_cvref_t; using Model = Object_Model_Type; using Transaction = Resource_Transaction; 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(); configure_raw_access(value, *owned_lock_); } } template explicit Resource_Service(Managed_Value& 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 requires std::same_as::value_type, Object> && (!std::is_const_v) explicit Resource_Service(Managed_Field 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) mutable { value.write([&](Object& object) { function(object); }); }; } const std::string& path() const noexcept { return path_; } Json view_schema() const { return to_view_json(describe_edit_view()); } Json amis_schema() const { Json result; const auto view = describe_edit_view(); read_access_([&](const Object& value) { result = to_amis_form_schema(value, view, path_ + "/data"); }); return result; } Http_Response descriptor_response() const noexcept { return safe_response([&] { return make_http_success(to_descriptor_json()); }); } Http_Response view_response() const noexcept { return safe_response([&] { return make_http_success(view_schema()); }); } Http_Response data_response() const noexcept { return safe_response([&] { Http_Response response; read_access_([&](const Object& value) { response = make_http_success(to_frontend_json(value)); }); return response; }); } Http_Response amis_response() const noexcept { return safe_response([&] { return make_http_success(amis_schema()); }); } Http_Response update_response(std::string_view body, const Request_Context& context = {}) noexcept { return safe_response([&] { Json patch; try { patch = parse_json(body); } catch(const std::exception& error) { return make_http_error(400, error.what()); } Http_Response response; write_access_([&](Object& value) { response = update_locked(value, patch, context); }); return response; }); } private: using Read_Function = std::function; using Write_Function = std::function; using Read_Access = std::function; using Write_Access = std::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 update_locked(Object& value, const Json& patch, const Request_Context& context) { Model original = Object_Adapter::snapshot(value); Model candidate = original; auto result = apply_model_json(candidate, patch, Write_Mode::update, false); if(!result.success) { return make_http_error(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::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(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(422, object_commit_error(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(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(500, rollback_error.empty() ? "unknown server error" : "unknown server error; rollback failed: " + rollback_error); } return make_http_success(to_frontend_json(value), std::move(result.message)); } template static Http_Response safe_response(Function&& function) noexcept { try { return std::forward(function)(); } catch(const Json_Assignment_Error& error) { return make_http_error(422, error.result()); } catch(const Field_Validation_Error& error) { return make_http_error(422, object_commit_error(error)); } catch(const std::exception& error) { return make_http_error(500, error.what()); } catch(...) { return make_http_error(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::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 owned_lock_; Read_Access read_access_; Write_Access write_access_; }; }