simplify: dedupe render_resolve_errors across crates

Three byte-identical copies of `render_resolve_errors` had drifted into
`fabro-server/src/run_manifest.rs`, `fabro-workflow/src/operations/start.rs`,
and `fabro-workflow/src/operations/create.rs`. Each one folded a
`&[ResolveError]` into a semicolon-separated string for surfacing through
`anyhow!` / `Error::Precondition` envelopes.

Promote the helper to `fabro_config::render_resolve_errors` (it lives next
to `ResolveError`, the type it acts on) and rewrite the four call sites
in workflow ops plus the one in run_manifest to call the shared version.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-04-22 23:54:05 -04:00
parent a6e755c200
commit 2dfefe36e9
No known key found for this signature in database
5 changed files with 21 additions and 34 deletions

View file

@ -37,10 +37,10 @@ pub use load::{
};
pub use parse::{ParseError, parse_settings_layer};
pub use resolve::{
ResolveError, dev_token_auth_enabled, resolve_cli, resolve_cli_from_file, resolve_features,
resolve_features_from_file, resolve_project, resolve_project_from_file, resolve_run,
resolve_run_from_file, resolve_server, resolve_server_from_file, resolve_storage_root,
resolve_workflow, resolve_workflow_from_file,
ResolveError, dev_token_auth_enabled, render_resolve_errors, resolve_cli,
resolve_cli_from_file, resolve_features, resolve_features_from_file, resolve_project,
resolve_project_from_file, resolve_run, resolve_run_from_file, resolve_server,
resolve_server_from_file, resolve_storage_root, resolve_workflow, resolve_workflow_from_file,
};
use serde::de::DeserializeOwned;
pub use storage::{RunScratch, RuntimeDirectory, Storage};

View file

@ -100,6 +100,17 @@ pub fn resolve_workflow_from_file(
}
}
/// Render a list of [`ResolveError`]s as a single semicolon-separated message
/// suitable for surfacing through `anyhow!` / `Error::Precondition` / similar
/// human-facing error envelopes.
pub fn render_resolve_errors(errors: &[ResolveError]) -> String {
errors
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join("; ")
}
pub(crate) fn require_interp(
value: Option<&InterpString>,
path: &str,

View file

@ -361,7 +361,7 @@ async fn build_preflight_report(
&configured_providers,
);
let resolved_run = fabro_config::resolve_run_from_file(&materialized)
.map_err(|errors| anyhow!(render_resolve_errors(&errors)))?;
.map_err(|errors| anyhow!(fabro_config::render_resolve_errors(&errors)))?;
let server_settings = state.server_settings();
let github_integration = &server_settings.server.integrations.github;
let sandbox_provider = resolve_sandbox_provider(&resolved_run)?;
@ -701,14 +701,6 @@ fn resolve_model_provider(
}
}
fn render_resolve_errors(errors: &[fabro_config::ResolveError]) -> String {
errors
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join("; ")
}
fn runtime_daytona_config(settings: &DaytonaSettings) -> DaytonaConfig {
DaytonaConfig {
auto_stop_interval: settings.auto_stop_interval,

View file

@ -284,23 +284,15 @@ fn store_error(err: impl std::fmt::Display) -> Error {
Error::engine(err.to_string())
}
fn render_resolve_errors(errors: &[fabro_config::ResolveError]) -> String {
errors
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join("; ")
}
fn resolve_settings_tree(settings: &SettingsLayer) -> Result<ResolvedSettingsTree, Error> {
Ok(ResolvedSettingsTree {
server_storage_root: fabro_config::resolve_storage_root(settings),
project: fabro_config::resolve_project_from_file(settings)
.map_err(|errors| Error::Precondition(render_resolve_errors(&errors)))?,
.map_err(|errors| Error::Precondition(fabro_config::render_resolve_errors(&errors)))?,
workflow: fabro_config::resolve_workflow_from_file(settings)
.map_err(|errors| Error::Precondition(render_resolve_errors(&errors)))?,
.map_err(|errors| Error::Precondition(fabro_config::render_resolve_errors(&errors)))?,
run: fabro_config::resolve_run_from_file(settings)
.map_err(|errors| Error::Precondition(render_resolve_errors(&errors)))?,
.map_err(|errors| Error::Precondition(fabro_config::render_resolve_errors(&errors)))?,
})
}
@ -313,7 +305,7 @@ fn combined_labels(settings: &ResolvedSettingsTree) -> HashMap<String, String> {
fn validate_sandbox_provider(settings: &SettingsLayer) -> Result<(), Error> {
let resolved = fabro_config::resolve_run_from_file(settings)
.map_err(|errors| Error::Precondition(render_resolve_errors(&errors)))?;
.map_err(|errors| Error::Precondition(fabro_config::render_resolve_errors(&errors)))?;
resolved
.sandbox
.provider

View file

@ -310,7 +310,7 @@ impl RunSession {
.map_or((None, None), |(url, branch)| (Some(url), branch));
let resolved = fabro_config::resolve_run_from_file(settings)
.map_err(|errors| Error::Precondition(render_resolve_errors(&errors)))?;
.map_err(|errors| Error::Precondition(fabro_config::render_resolve_errors(&errors)))?;
let sandbox_provider = resolve_sandbox_provider(&resolved)?;
let sandbox_provider =
@ -518,14 +518,6 @@ fn resolve_fallback_chain(
Catalog::builtin().build_fallback_chain(provider, model, &by_provider)
}
fn render_resolve_errors(errors: &[fabro_config::ResolveError]) -> String {
errors
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join("; ")
}
fn runtime_mcp_server(settings: &ResolvedMcpServerSettings) -> McpServerSettings {
McpServerSettings {
name: settings.name.clone(),