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 params: &self.params,
74 artifacts: &self.artifacts,
75 roles: &self.roles,
76 materials: &self.materials,
77 }
78 }
79
80 #[must_use]
82 pub fn with_material(mut self, id: ArtifactId, artifact: ArtifactValue) -> Self {
83 self.artifacts.insert(id, artifact);
84 self
85 }
86
87 #[must_use]
89 pub fn resolve_role(&self, role_id: &RoleId) -> String {
90 resolve_role_in(&self.roles, role_id)
91 }
92}
93
94#[derive(Debug, Clone, Copy)]
99pub struct HandlerContext<'a> {
100 pub params: &'a HashMap<ParamId, serde_json::Value>,
102 pub artifacts: &'a HashMap<ArtifactId, ArtifactValue>,
104 pub roles: &'a HashMap<RoleId, String>,
106 pub materials: &'a HashMap<MaterialId, String>,
108}
109
110impl HandlerContext<'_> {
111 #[must_use]
113 pub fn resolve_role(&self, role_id: &RoleId) -> String {
114 resolve_role_in(self.roles, role_id)
115 }
116
117 #[must_use]
122 pub fn resolve_role_name(&self, role_name: &str) -> String {
123 self.resolve_role(&RoleId::new(role_name))
124 }
125
126 #[must_use]
128 pub fn get_artifact(&self, id: &ArtifactId) -> Option<&ArtifactValue> {
129 self.artifacts.get(id)
130 }
131
132 #[must_use]
135 pub fn resolve_material(&self, material_id: &MaterialId) -> Option<&str> {
136 self.materials.get(material_id).map(String::as_str)
137 }
138}
139
140#[derive(Debug)]
145pub struct StepResult {
146 pub outcome: StepOutcome,
148 pub artifacts: Vec<(ArtifactId, ArtifactValue)>,
150}
151
152impl StepResult {
153 pub fn completed(message: impl Into<String>) -> Self {
155 Self {
156 outcome: StepOutcome::Completed {
157 message: message.into(),
158 },
159 artifacts: Vec::new(),
160 }
161 }
162
163 pub fn completed_with_artifact(
165 message: impl Into<String>,
166 id: ArtifactId,
167 value: ArtifactValue,
168 ) -> Self {
169 Self {
170 outcome: StepOutcome::Completed {
171 message: message.into(),
172 },
173 artifacts: vec![(id, value)],
174 }
175 }
176
177 #[must_use]
179 pub fn with_artifact(mut self, id: ArtifactId, value: ArtifactValue) -> Self {
180 self.artifacts.push((id, value));
181 self
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn handler_context_is_read_only_view() {
191 let mut artifacts = HashMap::new();
192 artifacts.insert(
193 ArtifactId::new("test"),
194 ArtifactValue::Bytes(b"value".to_vec()),
195 );
196
197 let state = ExecutionState {
198 params: HashMap::new(),
199 roles: HashMap::new(),
200 materials: HashMap::new(),
201 dry_run: true,
202 artifacts,
203 };
204
205 let ctx = state.handler_context();
206
207 assert!(ctx.get_artifact(&ArtifactId::new("test")).is_some());
208 assert!(ctx.get_artifact(&ArtifactId::new("missing")).is_none());
209 }
210
211 #[test]
212 fn step_result_builder_pattern() {
213 let result = StepResult::completed("Done")
214 .with_artifact(ArtifactId::new("a"), ArtifactValue::Bytes(b"1".to_vec()))
215 .with_artifact(ArtifactId::new("b"), ArtifactValue::Bytes(b"2".to_vec()));
216
217 assert_eq!(result.artifacts.len(), 2);
218 }
219}