Skip to main content

klasp_core/
source.rs

1//! `CheckSource` — abstraction over a runnable check.
2//!
3//! Design: [docs/design.md §3.2]. v0.1 ships exactly one impl (`Shell`,
4//! living in the `klasp` binary). The trait exists at v0.1 so v0.2's named
5//! recipes (`pre_commit`, `fallow`, `pytest`, `cargo`) and v0.3's
6//! subprocess plugins land as new impls without touching the trait.
7//!
8//! **Lifetime note**: `source_id(&self) -> &str` returns a `&str` tied to
9//! `&self`, *not* `&'static str`. v0.3 subprocess plugins have IDs derived
10//! from binary filenames discovered at runtime — a `'static` lifetime
11//! cannot represent that. This is the v0.3 plugin commitment locked in
12//! at v0.1. See issue [klasp#1] for the explicit callout.
13
14use std::path::PathBuf;
15
16use crate::config::CheckConfig;
17use crate::trigger::GitEvent;
18use crate::verdict::Verdict;
19
20/// Typed error for `CheckSource::run`. Covers runtime-level failures only;
21/// semantic failures (lint hits, test failures) ride inside `Verdict::Fail`.
22/// Use `CheckSourceError::Other` for impl-specific errors that don't fit the
23/// predefined variants.
24#[derive(Debug, thiserror::Error)]
25pub enum CheckSourceError {
26    #[error("failed to spawn check process: {source}")]
27    Spawn {
28        #[source]
29        source: std::io::Error,
30    },
31
32    #[error("check produced unparseable output: {0}")]
33    Output(String),
34
35    #[error("check exceeded {secs}s timeout")]
36    Timeout { secs: u64 },
37
38    #[error(transparent)]
39    Other(#[from] Box<dyn std::error::Error + Send + Sync>),
40}
41
42/// Snapshot of repo metadata passed to every check execution.
43///
44/// `base_ref` is the merge-base ref between `HEAD` and the upstream tracking
45/// branch — the "branch divergence point" diff-aware tools (`pre-commit
46/// run --from-ref`, `fallow audit --base`) want. The gate runtime exposes it
47/// to shell checks via the `KLASP_BASE_REF` env var; sources that talk to
48/// other check tools (named recipes in v0.2, subprocess plugins in v0.3) read
49/// it directly off this struct.
50///
51/// Falls back to `HEAD~1` when no upstream is configured (a fresh checkout,
52/// a detached HEAD, or a branch that has never been pushed). The fallback is
53/// best-effort — diff-aware tools that don't recognise the ref simply lint
54/// the whole tree, which is the same behaviour they'd have without klasp.
55///
56/// `staged_files` carries the absolute paths of files in the current group's
57/// scope when running in monorepo mode (i.e. the subset of staged files that
58/// belong to the `klasp.toml` group that owns this invocation). An **empty
59/// Vec means "no scoping; the check sees the whole repo"** — this is the
60/// back-compat value used by the single-config fallback path and by callers
61/// that do not dispatch per-group. Per-source consumption of `staged_files`
62/// for fine-grained scoping is deferred to issue #34 (rayon / named recipes);
63/// the field is present now so that data is available to checks without a
64/// further struct-breaking change.
65#[derive(Debug, Clone)]
66pub struct RepoState {
67    pub root: PathBuf,
68    pub git_event: GitEvent,
69    pub base_ref: String,
70    /// Staged files scoped to this group's `klasp.toml`. Empty = whole-repo
71    /// (single-config fallback or unscoped callers).
72    pub staged_files: Vec<PathBuf>,
73}
74
75/// Outcome of a single `CheckSource::run` invocation.
76#[derive(Debug, Clone)]
77pub struct CheckResult {
78    pub source_id: String,
79    pub check_name: String,
80    pub verdict: Verdict,
81    pub raw_stdout: Option<String>,
82    pub raw_stderr: Option<String>,
83}
84
85/// Object-safe trait. Implementations are stored as `Box<dyn CheckSource>`
86/// in the source registry.
87pub trait CheckSource: Send + Sync {
88    /// Stable identifier for this source (e.g. `"shell"`, `"pre_commit"`,
89    /// `"plugin:klasp-plugin-foo"`). Tied to `&self` lifetime — see module
90    /// docs for why this isn't `&'static str`.
91    fn source_id(&self) -> &str;
92
93    /// Pre-flight check: does this source know how to handle the given
94    /// `CheckConfig`? Used by the registry to dispatch a check to the
95    /// right source.
96    fn supports_config(&self, config: &CheckConfig) -> bool;
97
98    /// Execute the check and return a structured result.
99    ///
100    /// Errors here are runtime failures (process spawn errors, malformed
101    /// output) — semantic failures (lint hits, test failures) ride inside
102    /// `Verdict::Fail`. Use `CheckSourceError::Other` for impl-specific
103    /// errors that don't fit the predefined variants.
104    fn run(&self, config: &CheckConfig, state: &RepoState)
105        -> Result<CheckResult, CheckSourceError>;
106}