use super::RunnerConfig;
use super::levels::capped_level;
use crate::snippets::session::ValidationSession;
use crate::snippets::types::{Snippet, SnippetStatus, ValidationLevel, ValidationResult};
use crate::snippets::validators::ValidatorRegistry;
use std::collections::HashMap;
use std::path::PathBuf;
const REPORTED_ARTIFACT_LIMIT: usize = 4;
pub(super) struct ArtifactPreflight {
unsatisfiable: HashMap<String, MissingArtifacts>,
}
struct MissingArtifacts {
language: crate::snippets::types::Language,
paths: Vec<PathBuf>,
}
impl ArtifactPreflight {
pub(super) fn inspect(
snippets: &[Snippet],
registry: &ValidatorRegistry,
config: &RunnerConfig,
sessions: &HashMap<String, ValidationSession>,
) -> Self {
if config.level == ValidationLevel::Syntax {
return Self {
unsatisfiable: HashMap::new(),
};
}
let claimed = claimed_snippet_counts(snippets, registry, config, sessions);
let mut unsatisfiable = HashMap::new();
for (fingerprint, (session, snippet_count)) in claimed {
let Some(validator) = registry.get(session.language) else {
continue;
};
let paths = validator.missing_session_artifacts(session, config.level);
if paths.is_empty() {
continue;
}
report(session, config.level, snippet_count, &paths);
unsatisfiable.insert(
fingerprint,
MissingArtifacts {
language: session.language,
paths,
},
);
}
Self { unsatisfiable }
}
pub(super) fn skipped_result(
&self,
snippet: &Snippet,
config: &RunnerConfig,
session: Option<&ValidationSession>,
effective: ValidationLevel,
) -> Option<ValidationResult> {
if effective == ValidationLevel::Syntax {
return None;
}
let missing = self.unsatisfiable.get(session?.fingerprint.as_str())?;
let mut result = super::result(
snippet,
SnippetStatus::Unavailable,
config.level,
config.level,
Some(message(missing, effective)),
0,
);
result.unresolved_dependency = true;
result.preflight_skipped = true;
Some(result)
}
pub(super) fn is_unsatisfiable(&self, session: Option<&ValidationSession>, effective: ValidationLevel) -> bool {
effective != ValidationLevel::Syntax
&& session.is_some_and(|session| self.unsatisfiable.contains_key(session.fingerprint.as_str()))
}
}
fn claimed_snippet_counts<'a>(
snippets: &[Snippet],
registry: &ValidatorRegistry,
config: &RunnerConfig,
sessions: &'a HashMap<String, ValidationSession>,
) -> HashMap<String, (&'a ValidationSession, usize)> {
let mut claimed: HashMap<String, (&ValidationSession, usize)> = HashMap::new();
for snippet in snippets {
let Some(session) = super::session_for(snippet, sessions) else {
continue;
};
let Some(validator) = registry.get(snippet.language) else {
continue;
};
if capped_level(snippet, config, validator) == ValidationLevel::Syntax {
continue;
}
let entry = claimed.entry(session.fingerprint.clone()).or_insert((session, 0));
entry.1 += 1;
}
claimed
}
fn report(session: &ValidationSession, level: ValidationLevel, snippet_count: usize, paths: &[PathBuf]) {
tracing::warn!(
language = %session.language,
level = %level,
snippet_count,
missing_artifacts = %rendered(paths),
working_directory = %session.working_directory.display(),
"skipping snippet validation for this session: the build artifacts its manifest points at do not exist, so \
every snippet would fail for that one reason -- run `alef build` first (or pass \
--skip-snippet-validation for a generate-only run); these snippets are reported unvalidated, not passed"
);
}
fn message(missing: &MissingArtifacts, effective: ValidationLevel) -> String {
format!(
"could not validate at {effective}: skipped before spawning a {} toolchain because this session's build \
artifacts do not exist ({}) -- run `alef build` first",
missing.language,
rendered(&missing.paths)
)
}
fn rendered(paths: &[PathBuf]) -> String {
let listed = paths
.iter()
.take(REPORTED_ARTIFACT_LIMIT)
.map(|path| path.display().to_string())
.collect::<Vec<_>>()
.join(", ");
match paths.len().checked_sub(REPORTED_ARTIFACT_LIMIT) {
Some(remainder) if remainder > 0 => format!("{listed}, +{remainder} more"),
_ => listed,
}
}