1use std::collections::HashMap;
2use std::path::Path;
3
4use crate::{config, ConfigError, FloeResult, RunOptions, ValidateOptions};
5
6mod context;
7pub(crate) mod entity;
8mod file;
9pub(crate) mod normalize;
10mod output;
11mod reporting;
12
13use context::RunContext;
14use entity::{run_entity, EntityRunResult};
15
16pub(super) const MAX_RESOLVED_INPUTS: usize = 50;
17
18#[derive(Debug, Clone)]
19pub struct RunOutcome {
20 pub run_id: String,
21 pub report_base_path: String,
22 pub entity_outcomes: Vec<EntityOutcome>,
23}
24
25#[derive(Debug, Clone)]
26pub struct EntityOutcome {
27 pub report: crate::report::RunReport,
28 pub file_timings_ms: Vec<Option<u64>>,
29}
30
31pub(crate) fn validate_entities(
32 config: &config::RootConfig,
33 selected: &[String],
34) -> FloeResult<()> {
35 let missing: Vec<String> = selected
36 .iter()
37 .filter(|name| !config.entities.iter().any(|entity| &entity.name == *name))
38 .cloned()
39 .collect();
40
41 if !missing.is_empty() {
42 return Err(Box::new(ConfigError(format!(
43 "entities not found: {}",
44 missing.join(", ")
45 ))));
46 }
47 Ok(())
48}
49
50pub fn run(config_path: &Path, options: RunOptions) -> FloeResult<RunOutcome> {
51 let validate_options = ValidateOptions {
52 entities: options.entities.clone(),
53 };
54 crate::validate(config_path, validate_options)?;
55
56 let context = RunContext::new(config_path, &options)?;
57 if !options.entities.is_empty() {
58 validate_entities(&context.config, &options.entities)?;
59 }
60
61 let mut entity_outcomes = Vec::new();
62 let mut abort_run = false;
63 let mut s3_clients = HashMap::new();
64 for entity in &context.config.entities {
65 let EntityRunResult {
66 outcome,
67 abort_run: aborted,
68 } = run_entity(&context, &mut s3_clients, entity)?;
69 entity_outcomes.push(outcome);
70 abort_run = abort_run || aborted;
71 if abort_run {
72 break;
73 }
74 }
75
76 Ok(RunOutcome {
77 run_id: context.run_id.clone(),
78 report_base_path: context.report_base_path.clone(),
79 entity_outcomes,
80 })
81}