fallow_engine/
project_config.rs1use std::path::{Path, PathBuf};
4
5use fallow_config::{FallowConfig, ProductionAnalysis, ResolvedConfig, WorkspaceDiagnostic};
6use fallow_types::output_format::OutputFormat;
7
8use crate::{EngineError, EngineResult, engine_error};
9
10#[derive(Debug)]
12pub struct ProjectConfig {
13 pub config: ResolvedConfig,
14 pub path: Option<PathBuf>,
15 pub workspace_diagnostics: Vec<WorkspaceDiagnostic>,
16}
17
18#[derive(Debug, Clone, Copy)]
20pub struct ProjectConfigOptions {
21 pub output: OutputFormat,
22 pub no_cache: bool,
23 pub threads: usize,
24 pub production_override: Option<bool>,
25 pub quiet: bool,
26 pub analysis: ProductionAnalysis,
27}
28
29pub fn config_for_project(root: &Path, config_path: Option<&Path>) -> EngineResult<ProjectConfig> {
36 fallow_core::config_for_project(root, config_path)
37 .map(|(config, path)| ProjectConfig {
38 workspace_diagnostics: collect_workspace_diagnostics(&config),
39 config,
40 path,
41 })
42 .map_err(engine_error)
43}
44
45#[must_use]
47pub fn resolve_cache_max_size_bytes(config: &ResolvedConfig) -> usize {
48 fallow_core::resolve_cache_max_size_bytes(config)
49}
50
51pub fn default_project_config(root: &Path) -> ProjectConfig {
52 let threads = std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get);
53 ProjectConfig {
54 config: FallowConfig::default().resolve(
55 root.to_path_buf(),
56 OutputFormat::Human,
57 threads,
58 false,
59 true,
60 None,
61 ),
62 path: None,
63 workspace_diagnostics: Vec::new(),
64 }
65}
66
67pub fn config_for_project_analysis(
78 root: &Path,
79 config_path: Option<&Path>,
80 options: ProjectConfigOptions,
81) -> EngineResult<ProjectConfig> {
82 let user_config = load_user_config(root, config_path)?;
83 let loaded_user_config = user_config.is_some();
84 let (mut config, path) = match user_config {
85 Some((config, path)) => (config, Some(path)),
86 None => (
87 FallowConfig {
88 production: options.production_override.unwrap_or(false).into(),
89 ..FallowConfig::default()
90 },
91 None,
92 ),
93 };
94
95 if loaded_user_config {
96 let production = options
97 .production_override
98 .unwrap_or_else(|| config.production.for_analysis(options.analysis));
99 config.production = production.into();
100 }
101 validate_config(root, &config)?;
102 let resolved = config.resolve(
103 root.to_path_buf(),
104 options.output,
105 options.threads,
106 options.no_cache,
107 options.quiet,
108 None,
109 );
110 Ok(ProjectConfig {
111 workspace_diagnostics: collect_workspace_diagnostics(&resolved),
112 config: resolved,
113 path,
114 })
115}
116
117fn collect_workspace_diagnostics(config: &ResolvedConfig) -> Vec<WorkspaceDiagnostic> {
118 fallow_config::discover_workspaces_with_diagnostics(&config.root, &config.ignore_patterns)
119 .map(|(_, diagnostics)| diagnostics)
120 .unwrap_or_default()
121}
122
123fn load_user_config(
124 root: &Path,
125 config_path: Option<&Path>,
126) -> EngineResult<Option<(FallowConfig, PathBuf)>> {
127 if let Some(path) = config_path {
128 let config = FallowConfig::load(path)
129 .map_err(|err| EngineError::new(format!("invalid config: {err:#}")))?;
130 return Ok(Some((config, path.to_path_buf())));
131 }
132 FallowConfig::find_and_load(root)
133 .map_err(|err| EngineError::new(format!("invalid config: {err}")))
134}
135
136fn validate_config(root: &Path, config: &FallowConfig) -> EngineResult<()> {
137 fallow_config::discover_and_validate_external_plugins(root, &config.plugins)
138 .map_err(|errors| joined_config_errors("invalid external plugin definition", &errors))?;
139 config
140 .validate_resolved_boundaries(root)
141 .map_err(|errors| joined_config_errors("invalid boundary configuration", &errors))?;
142 fallow_config::load_rule_packs(root, &config.rule_packs)
143 .map_err(|errors| joined_config_errors("invalid rule pack", &errors))?;
144 Ok(())
145}
146
147fn joined_config_errors(label: &str, errors: &[impl ToString]) -> EngineError {
148 let joined = errors
149 .iter()
150 .map(ToString::to_string)
151 .collect::<Vec<_>>()
152 .join("\n - ");
153 EngineError::new(format!("{label}:\n - {joined}"))
154}