1use crate::error::{CapsulaError, CapsulaResult};
2use crate::run::PreparedRun;
3use serde::{Deserialize, Serialize};
4use std::marker::PhantomData;
5use std::path::PathBuf;
6
7#[derive(Debug, Clone, Default)]
8pub struct PreRun;
9#[derive(Debug, Clone, Default)]
10pub struct PostRun;
11
12pub trait PhaseMarker {
13 fn phase_name() -> &'static str;
15}
16impl PhaseMarker for PreRun {
17 fn phase_name() -> &'static str {
18 "pre"
19 }
20}
21impl PhaseMarker for PostRun {
22 fn phase_name() -> &'static str {
23 "post"
24 }
25}
26
27#[derive(Debug, Clone)]
28pub struct RuntimeParams<P: PhaseMarker> {
29 phase_marker: PhantomData<P>,
30 pub artifact_dir: Option<PathBuf>,
33}
34
35impl<P: PhaseMarker> RuntimeParams<P> {
36 #[must_use]
38 pub const fn new() -> Self {
39 Self {
40 phase_marker: PhantomData,
41 artifact_dir: None,
42 }
43 }
44
45 #[must_use]
47 pub const fn with_artifact_dir(artifact_dir: PathBuf) -> Self {
48 Self {
49 phase_marker: PhantomData,
50 artifact_dir: Some(artifact_dir),
51 }
52 }
53}
54
55impl<P: PhaseMarker> Default for RuntimeParams<P> {
56 fn default() -> Self {
57 Self::new()
58 }
59}
60
61pub trait Hook<P: PhaseMarker>: Send + Sync {
62 const ID: &'static str;
64
65 type Output: super::captured::Captured + 'static;
66 type Config: Serialize + for<'de> Deserialize<'de>;
67
68 fn from_config(
70 config: &serde_json::Value,
71 project_root: &std::path::Path,
72 ) -> CapsulaResult<Self>
73 where
74 Self: Sized;
75
76 fn config(&self) -> &Self::Config;
77 fn run(&self, metadata: &PreparedRun, params: &RuntimeParams<P>)
78 -> CapsulaResult<Self::Output>;
79
80 fn needs_artifact_dir(&self) -> bool {
85 false
86 }
87}
88
89pub trait HookErased<P: PhaseMarker>: Send + Sync {
91 fn id(&self) -> String;
92 fn config_as_json(&self) -> Result<serde_json::Value, serde_json::Error>;
93 fn run(
94 &self,
95 metadata: &PreparedRun,
96 params: &RuntimeParams<P>,
97 ) -> Result<Box<dyn super::captured::Captured>, CapsulaError>;
98 fn needs_artifact_dir(&self) -> bool;
99}
100
101impl<T, P> HookErased<P> for T
102where
103 T: Hook<P> + Send + Sync + 'static,
104 P: PhaseMarker,
105{
106 fn id(&self) -> String {
107 T::ID.to_string()
108 }
109
110 fn config_as_json(&self) -> Result<serde_json::Value, serde_json::Error> {
111 serde_json::to_value(<T as Hook<P>>::config(self))
112 }
113
114 fn run(
115 &self,
116 metadata: &PreparedRun,
117 params: &RuntimeParams<P>,
118 ) -> Result<Box<dyn super::captured::Captured>, CapsulaError> {
119 let out = <T as Hook<P>>::run(self, metadata, params)?;
120 Ok(Box::new(out))
121 }
122
123 fn needs_artifact_dir(&self) -> bool {
124 <T as Hook<P>>::needs_artifact_dir(self)
125 }
126}