Skip to main content

capsula_core/
hook.rs

1use crate::error::{CapsulaError, CapsulaResult};
2use crate::run::PreparedRun;
3use serde::{Deserialize, Serialize};
4use std::marker::PhantomData;
5
6#[derive(Debug, Clone, Default)]
7pub struct PreRun;
8#[derive(Debug, Clone, Default)]
9pub struct PostRun;
10
11pub trait PhaseMarker {}
12impl PhaseMarker for PreRun {}
13impl PhaseMarker for PostRun {}
14
15#[derive(Debug, Clone)]
16pub struct RuntimeParams<P: PhaseMarker> {
17    phase_marker: PhantomData<P>,
18}
19
20impl Default for RuntimeParams<PreRun> {
21    fn default() -> Self {
22        Self {
23            phase_marker: PhantomData,
24        }
25    }
26}
27
28impl Default for RuntimeParams<PostRun> {
29    fn default() -> Self {
30        Self {
31            phase_marker: PhantomData,
32        }
33    }
34}
35
36pub trait Hook<P: PhaseMarker>: Send + Sync {
37    /// The unique identifier for this hook type (e.g., "capture-cwd", "notify-slack")
38    const ID: &'static str;
39
40    type Output: super::captured::Captured + 'static;
41    type Config: Serialize + for<'de> Deserialize<'de>;
42
43    /// Create a hook instance from JSON configuration
44    fn from_config(
45        config: &serde_json::Value,
46        project_root: &std::path::Path,
47    ) -> CapsulaResult<Self>
48    where
49        Self: Sized;
50
51    fn config(&self) -> &Self::Config;
52    fn run(&self, metadata: &PreparedRun, params: &RuntimeParams<P>)
53    -> CapsulaResult<Self::Output>;
54}
55
56/// Engine-facing trait (object-safe, heterogenous)
57pub trait HookErased<P: PhaseMarker>: Send + Sync {
58    fn id(&self) -> String;
59    fn config_as_json(&self) -> Result<serde_json::Value, serde_json::Error>;
60    fn run(
61        &self,
62        metadata: &PreparedRun,
63        params: &RuntimeParams<P>,
64    ) -> Result<Box<dyn super::captured::Captured>, CapsulaError>;
65}
66
67impl<T, P> HookErased<P> for T
68where
69    T: Hook<P> + Send + Sync + 'static,
70    P: PhaseMarker,
71{
72    fn id(&self) -> String {
73        T::ID.to_string()
74    }
75
76    fn config_as_json(&self) -> Result<serde_json::Value, serde_json::Error> {
77        serde_json::to_value(<T as Hook<P>>::config(self))
78    }
79
80    fn run(
81        &self,
82        metadata: &PreparedRun,
83        params: &RuntimeParams<P>,
84    ) -> Result<Box<dyn super::captured::Captured>, CapsulaError> {
85        let out = <T as Hook<P>>::run(self, metadata, params)?;
86        Ok(Box::new(out))
87    }
88}