feat: add configurable UCE error pages

This commit is contained in:
udo
2026-06-12 08:59:09 +00:00
parent b957a2373b
commit 941f5aea08
12 changed files with 352 additions and 1 deletions
+107 -1
View File
@@ -18,7 +18,7 @@ const char* UCE_CLI_SYMBOL = "__uce_cli";
const char* UCE_SERVE_HTTP_SYMBOL = "__uce_serve_http";
const char* UCE_ONCE_SYMBOL = "__uce_once";
const char* UCE_INIT_SYMBOL = "__uce_init";
const u64 UCE_UNIT_ABI_VERSION = 4;
const u64 UCE_UNIT_ABI_VERSION = 5;
struct SharedUnitFilesystemState
{
@@ -995,6 +995,15 @@ SharedUnit* compiler_load_shared_unit(Request* context, String file_name, String
printf("%s\n", display_messages.c_str());
if(compiler_can_write_response(context) && context->stats.invoke_count == 1)
{
DTree error_info;
error_info["type"] = "compiler_error";
error_info["status"] = su->compile_status;
error_info["source"] = su->file_name;
error_info["compiler_output"] = su->compiler_messages;
error_info["generated_cpp"] = compiler_generated_cpp_path(su);
error_info["details"] = display_messages;
if(compiler_render_error_page(context, "page_compiler_error", 500, "UCE Unit Compile Error", error_info))
return(0);
context->set_status(500, "UCE Unit Compile Error");
context->header["Content-Type"] = "text/plain; charset=utf-8";
}
@@ -1739,9 +1748,106 @@ bool compiler_invoke_component(Request* context, String file_name, String render
));
}
String compiler_error_page_unit(Request* context, String config_key)
{
if(!context || !context->server)
return("");
String configured = trim(first(
context->server->config[config_key],
context->server->config[to_upper(config_key)]
));
if(configured == "")
return("");
// Resolve relative to where the server was started, not the volatile
// per-unit working directory (which is also stale right after a fault).
String resolved = compiler_resolve_unit_path(context, configured, process_start_directory());
if(resolved == "" || !file_exists(resolved))
return("");
return(resolved);
}
bool compiler_render_error_page(Request* context, String config_key, s32 status_code, String status_reason, DTree error_info)
{
if(!compiler_can_write_response(context))
return(false);
if(!context->server || context->resources.error_page_active)
return(false);
String unit_file = compiler_error_page_unit(context, config_key);
if(unit_file == "")
return(false);
String previous_response_code = context->response_code;
s32 previous_status = context->flags.status;
String previous_content_type = context->header["Content-Type"];
context->resources.error_page_active = true;
context->call["error"] = error_info;
context->set_status(status_code, status_reason);
context->header["Content-Type"] = first(context->server->config["CONTENT_TYPE"], "text/html; charset=utf-8");
ob_start();
String error_message = "";
bool rendered = compiler_invoke_render(context, unit_file, "render", &error_message);
String html = ob_get_close();
context->resources.error_page_active = false;
if(!rendered)
{
printf("(!) configured %s page %s failed to render: %s\n", config_key.c_str(), unit_file.c_str(), trim(error_message).c_str());
context->response_code = previous_response_code;
context->flags.status = previous_status;
context->header["Content-Type"] = previous_content_type;
context->call.remove("error");
return(false);
}
print(html);
return(true);
}
bool compiler_unit_compile_pending(Request* context, String file_name)
{
String normalized = compiler_normalize_unit_path(context, file_name);
if(normalized == "" || !file_exists(normalized))
return(false);
if(compiler_reusable_cached_unit(context, normalized, false, false))
return(false);
SharedUnit probe;
setup_unit_paths(context, &probe, normalized);
auto state = inspect_shared_unit_filesystem(context, &probe);
if(!shared_unit_compile_check(state).needs_compile)
return(false);
// A persisted, still-current failure is a compiler error, not a build in
// progress: let the load path surface it.
if(compiler_failure_retry_deferred(context, &probe, state))
return(false);
return(true);
}
void compiler_invoke(Request* context, String file_name)
{
printf("(i) compiler_invoke file %s\n", file_name.c_str());
// Entry-unit hook for the configured "compiling" page: when the unit needs
// a (re)build that the proactive compiler can deliver, answer immediately
// instead of blocking the worker on a synchronous compile.
if(context && context->stats.invoke_count == 0 &&
!context->resources.is_cli && !context->resources.error_page_active &&
config_bool("PROACTIVE_COMPILE_ENABLED", true) &&
compiler_error_page_unit(context, "page_compiling") != "")
{
String resolved = compiler_resolve_unit_path(context, file_name, "");
if(resolved != "" && compiler_unit_compile_pending(context, resolved))
{
compiler_track_known_unit(context, resolved);
DTree error_info;
error_info["type"] = "compiling";
error_info["source"] = resolved;
if(compiler_render_error_page(context, "page_compiling", 503, "UCE Unit Compiling", error_info))
return;
}
}
String error_message = "";
if(!compiler_invoke_render(context, file_name, "render", &error_message) && error_message != "")
{