capsula_core/
hook.rs

1use crate::error::{CapsulaError, CapsulaResult};
2use clap::ValueEnum;
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Serialize, Deserialize)]
6#[serde(rename_all = "lowercase")]
7pub enum HookPhase {
8    Pre,
9    Post,
10}
11
12#[derive(Debug, Clone)]
13pub struct RuntimeParams {
14    pub phase: HookPhase,
15    // TODO: Make it non-optional by making struct for each command
16    pub run_dir: Option<std::path::PathBuf>,
17    pub project_root: std::path::PathBuf,
18}
19
20pub trait Hook {
21    type Output: super::captured::Captured;
22    type Config: Serialize + for<'de> Deserialize<'de>;
23    fn id(&self) -> String;
24    fn config(&self) -> &Self::Config;
25    fn config_as_json(&self) -> Result<serde_json::Value, serde_json::Error> {
26        serde_json::to_value(self.config())
27    }
28    fn run(&self, params: &RuntimeParams) -> CapsulaResult<Self::Output>;
29}
30
31/// Engine-facing trait (object-safe, heterogenous)
32pub trait HookErased: Send + Sync {
33    fn id(&self) -> String;
34    fn config_as_json_erased(&self) -> Result<serde_json::Value, serde_json::Error>;
35    fn run_erased(
36        &self,
37        parmas: &RuntimeParams,
38    ) -> Result<Box<dyn super::captured::Captured>, CapsulaError>;
39}
40
41impl<T> HookErased for T
42where
43    T: Hook + Send + Sync + 'static,
44{
45    fn id(&self) -> String {
46        <T as Hook>::id(self)
47    }
48    fn config_as_json_erased(&self) -> Result<serde_json::Value, serde_json::Error> {
49        self.config_as_json()
50    }
51
52    fn run_erased(
53        &self,
54        params: &RuntimeParams,
55    ) -> Result<Box<dyn super::captured::Captured>, CapsulaError> {
56        let out = <T as Hook>::run(self, params)?;
57        Ok(Box::new(out))
58    }
59}
60
61/// Factory trait for creating hooks from configuration
62pub trait HookFactory: Send + Sync {
63    /// The type key this factory handles (e.g., "cwd", "git", "file")
64    fn key(&self) -> &'static str;
65
66    /// Create a hook instance from JSON configuration
67    fn create_hook(
68        &self,
69        config: &serde_json::Value,
70        project_root: &std::path::Path,
71    ) -> CapsulaResult<Box<dyn HookErased>>;
72}