1use std::collections::HashMap;
19
20use rite_model::{ArtifactId, MaterialId, ParamId, RoleId, StepOutcome};
21
22use crate::actions::ArtifactValue;
23
24#[derive(Debug)]
31pub struct ExecutionState {
32 pub params: HashMap<ParamId, serde_json::Value>,
34 pub roles: HashMap<RoleId, String>,
36 pub materials: HashMap<MaterialId, String>,
38 pub dry_run: bool,
40 pub artifacts: HashMap<ArtifactId, ArtifactValue>,
42}
43
44fn resolve_role_in(roles: &HashMap<RoleId, String>, role_id: &RoleId) -> String {
45 roles
46 .get(role_id)
47 .cloned()
48 .unwrap_or_else(|| role_id.as_str().to_string())
49}
50
51impl ExecutionState {
52 #[must_use]
54 pub fn new(
55 params: HashMap<ParamId, serde_json::Value>,
56 roles: HashMap<RoleId, String>,
57 materials: HashMap<MaterialId, String>,
58 dry_run: bool,
59 ) -> Self {
60 Self {
61 params,
62 roles,
63 materials,
64 dry_run,
65 artifacts: HashMap::new(),
66 }
67 }
68
69 #[must_use]
71 pub fn handler_context(&self) -> HandlerContext<'_> {
72 HandlerContext {
73 dry_run: self.dry_run,
74 params: &self.params,
75 artifacts: &self.artifacts,
76 roles: &self.roles,
77 materials: &self.materials,
78 }
79 }
80
81 #[must_use]
83 pub fn with_material(mut self, id: ArtifactId, artifact: ArtifactValue) -> Self {
84 self.artifacts.insert(id, artifact);
85 self
86 }
87
88 #[must_use]
90 pub fn resolve_role(&self, role_id: &RoleId) -> String {
91 resolve_role_in(&self.roles, role_id)
92 }
93}
94
95#[derive(Debug, Clone, Copy)]
100pub struct HandlerContext<'a> {
101 pub dry_run: bool,
103 pub params: &'a HashMap<ParamId, serde_json::Value>,
105 pub artifacts: &'a HashMap<ArtifactId, ArtifactValue>,
107 pub roles: &'a HashMap<RoleId, String>,
109 pub materials: &'a HashMap<MaterialId, String>,
111}
112
113impl HandlerContext<'_> {
114 #[must_use]
116 pub fn resolve_role(&self, role_id: &RoleId) -> String {
117 resolve_role_in(self.roles, role_id)
118 }
119
120 #[must_use]
125 pub fn resolve_role_name(&self, role_name: &str) -> String {
126 self.resolve_role(&RoleId::new(role_name))
127 }
128
129 #[must_use]
131 pub fn get_artifact(&self, id: &ArtifactId) -> Option<&ArtifactValue> {
132 self.artifacts.get(id)
133 }
134
135 #[must_use]
138 pub fn resolve_material(&self, material_id: &MaterialId) -> Option<&str> {
139 self.materials.get(material_id).map(String::as_str)
140 }
141}
142
143#[derive(Debug)]
148pub struct StepResult {
149 pub outcome: StepOutcome,
151 pub artifacts: Vec<(ArtifactId, ArtifactValue)>,
153}
154
155impl StepResult {
156 pub fn completed(message: impl Into<String>) -> Self {
158 Self {
159 outcome: StepOutcome::Completed {
160 message: message.into(),
161 },
162 artifacts: Vec::new(),
163 }
164 }
165
166 pub fn completed_with_artifact(
168 message: impl Into<String>,
169 id: ArtifactId,
170 value: ArtifactValue,
171 ) -> Self {
172 Self {
173 outcome: StepOutcome::Completed {
174 message: message.into(),
175 },
176 artifacts: vec![(id, value)],
177 }
178 }
179
180 #[must_use]
182 pub fn with_artifact(mut self, id: ArtifactId, value: ArtifactValue) -> Self {
183 self.artifacts.push((id, value));
184 self
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191
192 #[test]
193 fn handler_context_is_read_only_view() {
194 let mut artifacts = HashMap::new();
195 artifacts.insert(
196 ArtifactId::new("test"),
197 ArtifactValue::Bytes(b"value".to_vec()),
198 );
199
200 let state = ExecutionState {
201 params: HashMap::new(),
202 roles: HashMap::new(),
203 materials: HashMap::new(),
204 dry_run: true,
205 artifacts,
206 };
207
208 let ctx = state.handler_context();
209
210 assert!(ctx.dry_run);
211 assert!(ctx.get_artifact(&ArtifactId::new("test")).is_some());
212 assert!(ctx.get_artifact(&ArtifactId::new("missing")).is_none());
213 }
214
215 #[test]
216 fn step_result_builder_pattern() {
217 let result = StepResult::completed("Done")
218 .with_artifact(ArtifactId::new("a"), ArtifactValue::Bytes(b"1".to_vec()))
219 .with_artifact(ArtifactId::new("b"), ArtifactValue::Bytes(b"2".to_vec()));
220
221 assert_eq!(result.artifacts.len(), 2);
222 }
223}