diff --git a/lib/crates/fabro-config/src/lib.rs b/lib/crates/fabro-config/src/lib.rs index af96fa58d..5b5fca329 100644 --- a/lib/crates/fabro-config/src/lib.rs +++ b/lib/crates/fabro-config/src/lib.rs @@ -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}; diff --git a/lib/crates/fabro-config/src/project.rs b/lib/crates/fabro-config/src/project.rs index b0aeb5be4..d6015a5e8 100644 --- a/lib/crates/fabro-config/src/project.rs +++ b/lib/crates/fabro-config/src/project.rs @@ -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, diff --git a/lib/crates/fabro-config/src/resolve/mod.rs b/lib/crates/fabro-config/src/resolve/mod.rs index 2df385ff0..f8e1c6288 100644 --- a/lib/crates/fabro-config/src/resolve/mod.rs +++ b/lib/crates/fabro-config/src/resolve/mod.rs @@ -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> { let mut errors = Vec::new(); @@ -60,6 +62,19 @@ pub fn resolve_run_from_file(file: &SettingsFile) -> Result Result> { + 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, diff --git a/lib/crates/fabro-config/src/resolve/workflow.rs b/lib/crates/fabro-config/src/resolve/workflow.rs new file mode 100644 index 000000000..18e0603b7 --- /dev/null +++ b/lib/crates/fabro-config/src/resolve/workflow.rs @@ -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, +) -> 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(), + } +} diff --git a/lib/crates/fabro-config/tests/resolve_workflow.rs b/lib/crates/fabro-config/tests/resolve_workflow.rs new file mode 100644 index 000000000..0ece928d8 --- /dev/null +++ b/lib/crates/fabro-config/tests/resolve_workflow.rs @@ -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") + ); +} diff --git a/lib/crates/fabro-types/src/settings/mod.rs b/lib/crates/fabro-types/src/settings/mod.rs index 9a5977d3d..14c6369b3 100644 --- a/lib/crates/fabro-types/src/settings/mod.rs +++ b/lib/crates/fabro-types/src/settings/mod.rs @@ -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}; diff --git a/lib/crates/fabro-types/src/settings/workflow.rs b/lib/crates/fabro-types/src/settings/workflow.rs index 8b0e986fd..42f8d38c7 100644 --- a/lib/crates/fabro-types/src/settings/workflow.rs +++ b/lib/crates/fabro-types/src/settings/workflow.rs @@ -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, + pub description: Option, + pub graph: String, + pub metadata: HashMap, +} + /// A sparse `[workflow]` layer as it appears in a single settings file. #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)]