capsula_core/
hook.rs

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