voice_audio_multi/app/server/frontend.cpp
Fabio c36510c21e
Some checks failed
Linux Build / Linux cpu (push) Has been cancelled
Linux Build / Linux cpu-clang (push) Has been cancelled
Linux Build / Linux vulkan (push) Has been cancelled
mac-build / macOS CPU (push) Has been cancelled
Nix Build / Nix (cpu) (push) Has been cancelled
Nix Build / Nix (vulkan) (push) Has been cancelled
windows-build / Windows CPU (push) Has been cancelled
Initial import: audio.cpp with Orange Pi documentation
2026-11-04 19:05:22 +01:00

189 lines
7.2 KiB
C++

#include "frontend.h"
#include "engine/framework/debug/trace.h"
#include <stdexcept>
#include <string>
#include <utility>
namespace minitts::server {
namespace {
bool contract_value_matches(std::string_view lhs, std::string_view rhs) {
return lhs == frontend_contracts::any || rhs == frontend_contracts::any || lhs == rhs;
}
bool contract_route_overlaps(std::string_view lhs, std::string_view rhs) {
return contract_value_matches(lhs, rhs);
}
bool validate_pre_contracts(
std::string_view previous_module,
const FrontendPreContract & previous,
std::string_view next_module,
const FrontendPreContract & next) {
if (!contract_route_overlaps(previous.method, next.method) ||
!contract_route_overlaps(previous.path, next.path)) {
return false;
}
if (!contract_value_matches(previous.request_out, next.request_in)) {
throw std::runtime_error(
"incompatible frontend pre-processing order: " + std::string(previous_module) +
" outputs request state '" + std::string(previous.request_out) +
"', but " + std::string(next_module) +
" expects '" + std::string(next.request_in) + "'");
}
return true;
}
bool validate_post_contracts(
std::string_view previous_module,
const FrontendPostContract & previous,
std::string_view next_module,
const FrontendPostContract & next) {
if (!contract_route_overlaps(previous.method, next.method) ||
!contract_route_overlaps(previous.path, next.path)) {
return false;
}
if (!contract_value_matches(previous.response_out, next.response_in)) {
throw std::runtime_error(
"incompatible frontend post-processing order: " + std::string(previous_module) +
" outputs response state '" + std::string(previous.response_out) +
"', but " + std::string(next_module) +
" expects '" + std::string(next.response_in) + "'");
}
return true;
}
} // namespace
#include "server_frontend_module_declarations.inc"
std::optional<FrontendPreContract> ServerFrontendModule::pre_contract() const {
return std::nullopt;
}
std::optional<FrontendPostContract> ServerFrontendModule::post_contract() const {
return std::nullopt;
}
void ServerFrontendModule::pre_process(ServerFrontendContext & context, ServerFrontendRequest & request) {
(void) context;
(void) request;
}
void ServerFrontendModule::post_process(ServerFrontendContext & context, ServerFrontendResponse & response) {
(void) context;
(void) response;
}
void ServerFrontendRegistry::add(ServerFrontendModuleFactory factory) {
if (factory == nullptr) {
throw std::runtime_error("server frontend registration requires a module factory");
}
auto module = factory();
if (!module) {
throw std::runtime_error("server frontend module factory returned null");
}
const auto pre = module->pre_contract();
const auto post = module->post_contract();
for (auto it = modules_.rbegin(); it != modules_.rend(); ++it) {
if (pre.has_value()) {
if (const auto previous = (*it)->pre_contract()) {
if (validate_pre_contracts((*it)->name(), *previous, module->name(), *pre)) {
break;
}
}
}
}
for (auto it = modules_.rbegin(); it != modules_.rend(); ++it) {
if (post.has_value()) {
if (const auto previous = (*it)->post_contract()) {
if (validate_post_contracts((*it)->name(), *previous, module->name(), *post)) {
break;
}
}
}
}
modules_.push_back(std::move(module));
}
void ServerFrontendRegistry::add_listener(std::string name, ServerFrontendListenerFactory factory) {
if (name.empty()) {
throw std::runtime_error("server frontend listener registration requires a name");
}
if (factory == nullptr) {
throw std::runtime_error("server frontend listener registration requires a factory");
}
if (!listeners_.emplace(std::move(name), factory).second) {
throw std::runtime_error("duplicate server frontend listener registration");
}
}
bool ServerFrontendRegistry::empty() const { return modules_.empty(); }
HttpResponse ServerFrontendRegistry::handle(ServerFrontendContext & context, const HttpRequest & request) const {
ServerFrontendRequest frontend_request{request, std::nullopt};
if (engine::debug::log_enabled()) {
const auto content_type = request.headers.find("content-type");
engine::debug::log_message(
"[SERVER_FRONTEND_DEBUG] frontend.enter method=" + request.method +
" path=" + request.path +
" content_type=" +
(content_type == request.headers.end() ? std::string("<none>") : content_type->second) +
" body_bytes=" + std::to_string(request.body.size()));
}
for (const auto & module : modules_) {
if (engine::debug::log_enabled()) {
engine::debug::log_message(
"[SERVER_FRONTEND_DEBUG] frontend.pre.begin module=" + std::string(module->name()) +
" path=" + frontend_request.request.path +
" body_bytes=" + std::to_string(frontend_request.request.body.size()));
}
module->pre_process(context, frontend_request);
if (engine::debug::log_enabled()) {
const auto content_type = frontend_request.request.headers.find("content-type");
engine::debug::log_message(
"[SERVER_FRONTEND_DEBUG] frontend.pre.end module=" + std::string(module->name()) +
" path=" + frontend_request.request.path +
" content_type=" +
(content_type == frontend_request.request.headers.end() ? std::string("<none>") : content_type->second) +
" body_bytes=" + std::to_string(frontend_request.request.body.size()) +
" short_circuit=" + (frontend_request.response.has_value() ? "true" : "false"));
}
if (frontend_request.response.has_value()) {
return std::move(*frontend_request.response);
}
}
auto core_response = context.forward_to_core(frontend_request.request);
if (engine::debug::log_enabled()) {
engine::debug::log_message(
"[SERVER_FRONTEND_DEBUG] frontend.core_response status=" + std::to_string(core_response.status) +
" content_type=" + core_response.content_type +
" body_bytes=" + std::to_string(core_response.body.size()));
}
ServerFrontendResponse frontend_response{request, frontend_request.request, std::move(core_response)};
for (const auto & module : modules_) {
module->post_process(context, frontend_response);
}
return std::move(frontend_response.response);
}
std::unique_ptr<ServerFrontendListener> ServerFrontendRegistry::make_listener(std::string_view name) const {
const auto it = listeners_.find(std::string(name));
if (it == listeners_.end()) {
throw std::runtime_error("server frontend listener is not available in this build: " + std::string(name));
}
return it->second();
}
void register_static_server_frontends(ServerFrontendRegistry & registry) {
(void) registry;
#include "server_frontend_module_registrations.inc"
}
} // namespace minitts::server