Files
Adminive/backend/include/adminive/http.hpp
T
2026-08-07 08:39:15 +08:00

198 lines
8.8 KiB
C++

#pragma once
#include "adminive/amis.hpp"
#include "adminive/synchronized.hpp"
#include <exception>
#include <functional>
#include <map>
#include <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>())};
}
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::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(T& value, std::string path, Transaction transaction = {}, Lock* shared_lock = nullptr) : value_(value), path_(std::move(path)), transaction_(std::move(transaction)), shared_lock_(shared_lock) {}
explicit Resource_Service(Synchronized_Value<Object, Lock>& value, std::string path, Transaction transaction = {}) : Resource_Service(value.unsafe_value(), std::move(path), std::move(transaction), &value.mutex()) {}
template <class Root, auto Member>
requires (!std::is_const_v<Root>) && std::same_as<typename Synchronized_Member<Root, Member, Lock>::value_type, Object>
explicit Resource_Service(Synchronized_Member<Root, Member, Lock> value, std::string path, Transaction transaction = {}) : Resource_Service(value.unsafe_value(), std::move(path), std::move(transaction), &value.mutex()) {}
const std::string& path() const noexcept {
return path_;
}
Json amis_schema() const {
std::scoped_lock lock(resource_mutex());
return to_amis_form_schema<Json>(value_, path_ + "/data", describe<Model>().list_options().confirm_label);
}
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([&] {
std::scoped_lock lock(resource_mutex());
return make_http_success<Json>(to_frontend_json<Json>(value_));
});
}
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());
}
std::scoped_lock lock(resource_mutex());
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(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(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(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(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));
});
}
private:
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(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;
}
Lock& resource_mutex() const noexcept {
return shared_lock_ ? *shared_lock_ : lock_;
}
T& value_;
std::string path_;
Transaction transaction_;
Lock* shared_lock_{};
mutable Lock lock_{};
};
}