use std::collections::HashMap;
use std::path::{Path, PathBuf};
use crate::collection::Collection;
use crate::environment::Environment;
use crate::hurl::HurlEntry;
use crate::session::effective_env;
use super::flow::ReportFlow;
use super::validate::{self, Context, Diagnostic};
pub struct ReportRunInputs {
pub flow: ReportFlow,
pub entries: Vec<HurlEntry>,
pub base_vars: HashMap<String, String>,
pub named_envs: HashMap<String, HashMap<String, String>>,
pub root: Option<PathBuf>,
pub file_root: Option<PathBuf>,
}
pub(crate) fn flatten_env(env: &Environment) -> HashMap<String, String> {
env.vars
.iter()
.map(|v| (v.key.clone(), v.value.clone()))
.collect()
}
pub(crate) fn resolve_ref_path(report_path: Option<&Path>, cref: &str) -> PathBuf {
let p = Path::new(cref);
if p.is_absolute() {
return p.to_path_buf();
}
if let Some(dir) = report_path.and_then(|rp| rp.parent()) {
return dir.join(p);
}
p.to_path_buf()
}
pub(crate) fn paths_equal(a: &Path, b: &Path) -> bool {
match (a.canonicalize(), b.canonicalize()) {
(Ok(a), Ok(b)) => a == b,
_ => a == b,
}
}
pub fn report_base_dir(flow: &ReportFlow, report_path: Option<&Path>) -> (PathBuf, bool) {
if let Some(r) = flow.header.root()
&& !r.trim().is_empty()
{
return (resolve_ref_path(report_path, r), true);
}
if let Some(dir) = report_path.and_then(|p| p.parent()) {
return (dir.to_path_buf(), true);
}
(std::env::current_dir().unwrap_or_default(), false)
}
pub fn resolve_bound_collection(
collections: &[Collection],
flow: &ReportFlow,
report_path: Option<&Path>,
) -> Option<usize> {
let cref = flow.header.collection()?;
if cref.starts_with("git:") {
return None;
}
let target = resolve_ref_path(report_path, cref);
collections
.iter()
.position(|c| c.path.as_ref().is_some_and(|p| paths_equal(p, &target)) || c.name == cref)
}
fn base_var_names(
collections: &[Collection],
global_envs: &[Environment],
active_env_id: Option<u64>,
flow: &ReportFlow,
bound: Option<usize>,
) -> Option<Vec<String>> {
match (bound, flow.header.environment()) {
(_, Some(name)) => {
let name = name.trim();
global_envs
.iter()
.find(|e| e.name == name)
.map(|env| env.vars.iter().map(|v| v.key.clone()).collect())
}
(Some(ci), None) => Some(
effective_env(collections, global_envs, ci, active_env_id)
.map(|env| env.vars.iter().map(|v| v.key.clone()).collect())
.unwrap_or_default(),
),
(None, _) => None,
}
}
pub fn report_diagnostics(
collections: &[Collection],
global_envs: &[Environment],
active_env_id: Option<u64>,
flow: &ReportFlow,
report_path: Option<&Path>,
strings: &crate::i18n::Strings,
) -> Vec<Diagnostic> {
let bound = resolve_bound_collection(collections, flow, report_path);
let titles: Option<Vec<String>> = bound.map(|ci| {
collections[ci]
.entries
.iter()
.map(|e| e.title.clone())
.collect()
});
let fields: Option<Vec<(String, Vec<String>)>> = bound.map(|ci| {
collections[ci]
.entries
.iter()
.map(|e| {
(
e.title.clone(),
e.reports.iter().map(|(n, _)| n.clone()).collect(),
)
})
.collect()
});
let env_names: Vec<String> = global_envs.iter().map(|e| e.name.clone()).collect();
let (base_dir, anchored) = report_base_dir(flow, report_path);
let base_var_names = base_var_names(collections, global_envs, active_env_id, flow, bound);
let mut all_env_var_names: Vec<String> = global_envs
.iter()
.flat_map(|e| e.vars.iter().map(|v| v.key.clone()))
.collect();
all_env_var_names.sort();
all_env_var_names.dedup();
let request_entries_owned: Option<Vec<HurlEntry>> =
bound.map(|ci| collections[ci].entries.clone());
let ctx = Context {
request_titles: titles.as_deref(),
env_names: Some(&env_names),
request_fields: fields.as_deref(),
root: anchored.then_some(base_dir.as_path()),
base_var_names: base_var_names.as_deref(),
all_env_var_names: Some(&all_env_var_names),
request_entries: request_entries_owned.as_deref(),
strings,
};
validate::validate(flow, &ctx)
}
pub fn report_run_inputs(
collections: &[Collection],
global_envs: &[Environment],
active_env_id: Option<u64>,
flow: &ReportFlow,
report_path: Option<&Path>,
) -> Result<ReportRunInputs, RunInputError> {
let ci =
resolve_bound_collection(collections, flow, report_path).ok_or(RunInputError::Unbound)?;
let base_vars = match flow
.header
.environment()
.map(str::trim)
.filter(|e| !e.is_empty())
{
Some(name) => global_envs
.iter()
.find(|e| e.name == name)
.map(flatten_env)
.unwrap_or_default(),
None => effective_env(collections, global_envs, ci, active_env_id)
.map(|env| flatten_env(&env))
.unwrap_or_default(),
};
let named_envs = global_envs
.iter()
.map(|e| (e.name.clone(), flatten_env(e)))
.collect();
let report_dir = report_path.and_then(|p| p.parent()).map(Path::to_path_buf);
let root = match flow.header.root() {
Some(r) if !r.trim().is_empty() => Some(resolve_ref_path(report_path, r)),
_ => report_dir,
};
let file_root = collections[ci]
.path
.as_deref()
.and_then(|p| p.parent())
.map(Path::to_path_buf);
Ok(ReportRunInputs {
flow: flow.clone(),
entries: collections[ci].entries.clone(),
base_vars,
named_envs,
root,
file_root,
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RunInputError {
Unbound,
}