1use crate::Error;
2use serde::{Deserialize, Serialize};
3
4pub type Id = String;
5
6#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Hash, Eq, Default)]
7pub struct WorkflowId(Id);
8
9#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Hash, Eq, Default)]
10pub struct JobId(Id, Id);
11
12#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Hash, Eq, Default)]
13pub struct StepId(Id, Id, usize);
14
15impl WorkflowId {
16 pub fn new(id: impl Into<String>) -> Self {
17 WorkflowId(id.into())
18 }
19
20 pub fn inner(&self) -> Id {
21 self.0.clone()
22 }
23}
24
25impl JobId {
26 pub fn new(workflow_id: impl Into<String>, job_id: impl Into<String>) -> Self {
27 JobId(workflow_id.into(), job_id.into())
28 }
29
30 pub fn workflow_id(&self) -> WorkflowId {
31 WorkflowId(self.0.clone())
32 }
33
34 pub fn job_key(&self) -> Id {
35 self.1.clone()
36 }
37}
38
39impl StepId {
40 pub fn new(workflow_id: impl Into<String>, job_id: impl Into<String>, step_id: usize) -> Self {
41 StepId(workflow_id.into(), job_id.into(), step_id)
42 }
43
44 pub fn workflow_id(&self) -> WorkflowId {
45 WorkflowId(self.0.clone())
46 }
47
48 pub fn job_id(&self) -> JobId {
49 JobId(self.0.clone(), self.1.clone())
50 }
51
52 pub fn job_key(&self) -> Id {
53 self.1.clone()
54 }
55
56 pub fn step_number(&self) -> usize {
57 self.2
58 }
59}
60
61impl std::fmt::Display for WorkflowId {
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 write!(f, "{}", self.0)
64 }
65}
66
67impl std::fmt::Display for JobId {
68 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69 write!(f, "{}/{}", self.0, self.1)
70 }
71}
72
73impl std::fmt::Display for StepId {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 write!(f, "{}/{}/{}", self.0, self.1, self.2)
76 }
77}
78
79impl TryFrom<&str> for WorkflowId {
80 type Error = Error;
81
82 fn try_from(value: &str) -> Result<Self, Self::Error> {
83 if value.is_empty() {
84 Err(Error::internal_runtime_error("WorkflowId cannot be empty"))
85 } else {
86 Ok(WorkflowId(value.to_string()))
87 }
88 }
89}
90
91impl TryFrom<&str> for JobId {
92 type Error = Error;
93
94 fn try_from(value: &str) -> Result<Self, Self::Error> {
95 let parts: Vec<&str> = value.split('/').collect();
96 if parts.len() != 2 {
97 Err(Error::internal_runtime_error(
98 "JobId must be in the format of <workflow_id>/<job_key>",
99 ))
100 } else {
101 Ok(JobId(parts[0].to_string(), parts[1].to_string()))
102 }
103 }
104}
105
106impl TryFrom<&str> for StepId {
107 type Error = Error;
108
109 fn try_from(value: &str) -> Result<Self, Self::Error> {
110 let parts: Vec<&str> = value.split('/').collect();
111 if parts.len() != 3 {
112 Err(Error::internal_runtime_error(
113 "StepId must be in the format of <workflow_id>/<job_key>/<step_number>",
114 ))
115 } else {
116 let step_number = parts[2]
117 .parse::<usize>()
118 .map_err(|_| Error::internal_runtime_error("Step number must be a number"))?;
119 Ok(StepId(
120 parts[0].to_string(),
121 parts[1].to_string(),
122 step_number,
123 ))
124 }
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn test_workflow_id() {
134 let workflow_id = WorkflowId::new("test");
135 assert_eq!(workflow_id, WorkflowId("test".to_string()));
136 }
137
138 #[test]
139 fn test_job_id() {
140 let job_id = JobId::new("workflow", "job");
141 assert_eq!(job_id, JobId("workflow".to_string(), "job".to_string()));
142 assert_eq!(job_id.workflow_id(), WorkflowId("workflow".to_string()));
143 assert_eq!(job_id.job_key(), "job".to_string());
144 }
145
146 #[test]
147 fn test_step_id() {
148 let step_id = StepId::new("workflow", "job", 1);
149 assert_eq!(
150 step_id,
151 StepId("workflow".to_string(), "job".to_string(), 1)
152 );
153 assert_eq!(step_id.workflow_id(), WorkflowId("workflow".to_string()));
154 assert_eq!(
155 step_id.job_id(),
156 JobId("workflow".to_string(), "job".to_string())
157 );
158 assert_eq!(step_id.job_key(), "job".to_string());
159 assert_eq!(step_id.step_number(), 1);
160 }
161
162 #[test]
163 fn test_workflow_id_to_string() {
164 let workflow_id = WorkflowId::new("test");
165 assert_eq!(workflow_id.to_string(), "test".to_string());
166 }
167
168 #[test]
169 fn test_job_id_to_string() {
170 let job_id = JobId::new("workflow", "job");
171 assert_eq!(job_id.to_string(), "workflow/job".to_string());
172 }
173
174 #[test]
175 fn test_step_id_to_string() {
176 let step_id = StepId::new("workflow", "job", 1);
177 assert_eq!(step_id.to_string(), "workflow/job/1".to_string());
178 }
179
180 #[test]
181 fn test_workflow_id_try_from() {
182 let workflow_id = WorkflowId::try_from("test").unwrap();
183 assert_eq!(workflow_id, WorkflowId("test".to_string()));
184 }
185
186 #[test]
187 fn test_job_id_try_from() {
188 let job_id = JobId::try_from("workflow/job").unwrap();
189 assert_eq!(job_id, JobId("workflow".to_string(), "job".to_string()));
190 }
191
192 #[test]
193 fn test_step_id_try_from() {
194 let step_id = StepId::try_from("workflow/job/1").unwrap();
195 assert_eq!(
196 step_id,
197 StepId("workflow".to_string(), "job".to_string(), 1)
198 );
199 }
200
201 #[test]
202 fn test_workflow_id_try_from_empty() {
203 let workflow_id = WorkflowId::try_from("");
204 assert!(workflow_id.is_err());
205 }
206
207 #[test]
208 fn test_job_id_try_from_empty() {
209 let job_id = JobId::try_from("");
210 assert!(job_id.is_err());
211 }
212
213 #[test]
214 fn test_step_id_try_from_empty() {
215 let step_id = StepId::try_from("");
216 assert!(step_id.is_err());
217 }
218
219 #[test]
220 fn test_job_id_try_from_invalid() {
221 let job_id = JobId::try_from("workflow");
222 assert!(job_id.is_err());
223 }
224
225 #[test]
226 fn test_step_id_try_from_invalid() {
227 let step_id = StepId::try_from("workflow/job");
228 assert!(step_id.is_err());
229 }
230}