refactor(settings): resolve workflow settings

This commit is contained in:
Bryan Helmkamp 2026-04-10 07:12:29 -04:00
parent 48c5737bd5
commit e7d890bbf5
No known key found for this signature in database
7 changed files with 96 additions and 12 deletions

View file

@ -16,7 +16,8 @@ pub use fabro_util::path::expand_tilde;
pub use home::Home;
pub use resolve::{
ResolveError, resolve_cli, resolve_cli_from_file, resolve_project, resolve_project_from_file,
resolve_run, resolve_run_from_file, resolve_server, resolve_server_from_file,
resolve_run, resolve_run_from_file, resolve_server, resolve_server_from_file, resolve_workflow,
resolve_workflow_from_file,
};
pub use storage::{RunScratch, ServerState, Storage};

View file

@ -12,11 +12,10 @@ use serde::Serialize;
use crate::config::ConfigLayer;
use crate::run;
use crate::{resolve_project_from_file, resolve_run_from_file};
use crate::{resolve_project_from_file, resolve_run_from_file, resolve_workflow_from_file};
use fabro_types::settings::SettingsFile;
const CONFIG_FILENAME: &str = "fabro.toml";
const RUN_GRAPH_FILE: &str = "workflow.fabro";
#[derive(Clone, Debug)]
pub struct WorkflowPathResolution {
pub resolved_workflow_path: PathBuf,
@ -91,13 +90,10 @@ pub fn resolve_workflow_path(
if path.extension().is_some_and(|ext| ext == "toml") {
match run::load_run_config(&path) {
Ok(cfg) => {
let graph = cfg
.as_v2()
.workflow
.as_ref()
.and_then(|w| w.graph.as_deref())
.unwrap_or(RUN_GRAPH_FILE);
let dot_path = run::resolve_graph_path(&path, graph);
let workflow = resolve_workflow_from_file(cfg.as_v2()).map_err(|errors| {
anyhow::anyhow!("Failed to resolve workflow settings: {errors:?}")
})?;
let dot_path = run::resolve_graph_path(&path, &workflow.graph);
Ok(WorkflowPathResolution {
resolved_workflow_path: path.clone(),
dot_path,

View file

@ -3,9 +3,10 @@ mod error;
mod project;
mod run;
mod server;
mod workflow;
use fabro_types::settings::{
CliSettings, ProjectSettings, RunSettings, ServerSettings, SettingsFile,
CliSettings, ProjectSettings, RunSettings, ServerSettings, SettingsFile, WorkflowSettings,
};
pub use cli::resolve_cli;
@ -13,6 +14,7 @@ pub use error::ResolveError;
pub use project::resolve_project;
pub use run::resolve_run;
pub use server::resolve_server;
pub use workflow::resolve_workflow;
pub fn resolve_cli_from_file(file: &SettingsFile) -> Result<CliSettings, Vec<ResolveError>> {
let mut errors = Vec::new();
@ -60,6 +62,19 @@ pub fn resolve_run_from_file(file: &SettingsFile) -> Result<RunSettings, Vec<Res
}
}
pub fn resolve_workflow_from_file(
file: &SettingsFile,
) -> Result<WorkflowSettings, Vec<ResolveError>> {
let mut errors = Vec::new();
let layer = file.workflow.as_ref().cloned().unwrap_or_default();
let resolved = resolve_workflow(&layer, &mut errors);
if errors.is_empty() {
Ok(resolved)
} else {
Err(errors)
}
}
pub(crate) fn require_interp(
value: Option<&fabro_types::settings::InterpString>,
path: &str,

View file

@ -0,0 +1,20 @@
use fabro_types::settings::workflow::{WorkflowLayer, WorkflowSettings};
use super::ResolveError;
const DEFAULT_WORKFLOW_GRAPH: &str = "workflow.fabro";
pub fn resolve_workflow(
layer: &WorkflowLayer,
_errors: &mut Vec<ResolveError>,
) -> WorkflowSettings {
WorkflowSettings {
name: layer.name.clone(),
description: layer.description.clone(),
graph: layer
.graph
.clone()
.unwrap_or_else(|| DEFAULT_WORKFLOW_GRAPH.to_string()),
metadata: layer.metadata.clone(),
}
}

View file

@ -0,0 +1,43 @@
use fabro_config::resolve_workflow_from_file;
use fabro_types::settings::SettingsFile;
#[test]
fn resolves_workflow_defaults_from_empty_settings() {
let settings = SettingsFile::default();
let workflow = resolve_workflow_from_file(&settings).expect("empty settings should resolve");
assert_eq!(workflow.graph, "workflow.fabro");
assert!(workflow.name.is_none());
assert!(workflow.description.is_none());
assert!(workflow.metadata.is_empty());
}
#[test]
fn resolves_workflow_graph_and_metadata() {
let settings: SettingsFile = fabro_config::ConfigLayer::parse(
r#"
_version = 1
[workflow]
name = "Ship"
description = "Primary flow"
graph = "graphs/ship.dot"
[workflow.metadata]
tier = "gold"
"#,
)
.expect("fixture should parse")
.into();
let workflow = resolve_workflow_from_file(&settings).expect("workflow settings should resolve");
assert_eq!(workflow.name.as_deref(), Some("Ship"));
assert_eq!(workflow.description.as_deref(), Some("Primary flow"));
assert_eq!(workflow.graph, "graphs/ship.dot");
assert_eq!(
workflow.metadata.get("tier").map(String::as_str),
Some("gold")
);
}

View file

@ -57,4 +57,4 @@ pub use size::{ParseSizeError, Size};
pub use splice_array::{SPLICE_MARKER, SpliceArray, SpliceArrayError};
pub use tree::{ParseError, SettingsFile, parse_settings_file};
pub use version::{CURRENT_VERSION, SchemaVersion, VersionError, validate_version};
pub use workflow::WorkflowLayer;
pub use workflow::{WorkflowLayer, WorkflowSettings};

View file

@ -7,6 +7,15 @@ use std::collections::HashMap;
use serde::{Deserialize, Serialize};
/// A structurally resolved `[workflow]` view for consumers.
#[derive(Debug, Clone, Default, PartialEq)]
pub struct WorkflowSettings {
pub name: Option<String>,
pub description: Option<String>,
pub graph: String,
pub metadata: HashMap<String, String>,
}
/// A sparse `[workflow]` layer as it appears in a single settings file.
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]