sbatch_rs/dependency/mod.rs
1//! This module contains the `Dependency` enum and related types.
2
3use std::collections::BTreeSet;
4use thiserror::Error;
5
6mod dependency_type;
7pub use dependency_type::{DependencyType, DependencyTypeError};
8
9/// Sbatch dependency representation
10///
11/// Represents the different types of dependencies that can be used in a Slurm job script.
12/// See <https://slurm.schedmd.com/sbatch.html> for more information.
13///
14/// - `And(Vec<DependencyType>)`: The job can start after all of the specified dependencies have been met.
15/// - `Or(Vec<DependencyType>)`: The job can start after any of the specified dependencies have been met.
16///
17/// # Examples
18///
19/// ```
20/// use sbatch_rs::{Dependency, DependencyType};
21///
22/// // Create a new `And` dependency
23/// let dependency = Dependency::new_and()
24/// .push(DependencyType::After("123".to_string())).unwrap() // Add an `After` dependency
25/// .push_after_time_delay("456", "10").unwrap() // Add an `AfterTimeDelay` dependency
26/// .build().unwrap(); // Build the dependency string
27///
28/// // Check that the dependency string is correct
29/// assert_eq!(dependency, "after:123,after:456+10");
30/// ```
31#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
32pub enum Dependency {
33 And(Vec<DependencyType>),
34 Or(Vec<DependencyType>),
35}
36
37/// Represents an error that can occur when working with the `Dependency` enum.
38/// This error is used to indicate that a `Dependency` value is invalid.
39///
40/// - `NoDependencies`: Indicates that no dependencies were provided.
41/// - `DependencyTypeError`: Indicates that a `DependencyType` value is invalid.
42#[derive(Debug, Error)]
43pub enum DependencyError {
44 #[error("No dependencies provided")]
45 NoDependencies,
46 #[error("Dependency type error: {0}")]
47 DependencyTypeError(#[from] dependency_type::DependencyTypeError),
48}
49
50// Helper functions for the `Dependency` enum
51impl Dependency {
52 // Helper function to get the separator for the dependency string.
53 fn separator(&self) -> &str {
54 match self {
55 Dependency::And(_) => ",",
56 Dependency::Or(_) => "?",
57 }
58 }
59
60 // Helper function to get the dependencies vector.
61 fn dependencies(&self) -> &Vec<DependencyType> {
62 match &self {
63 Dependency::And(dependencies) => dependencies,
64 Dependency::Or(dependencies) => dependencies,
65 }
66 }
67}
68
69// Interface functions for the `Dependency` enum
70impl Dependency {
71 /// Create a new `And` dependency.
72 ///
73 /// # Returns
74 ///
75 /// This function returns a new `Dependency` enum with an `And` variant.
76 ///
77 /// # Examples
78 ///
79 /// ```
80 /// use sbatch_rs::Dependency;
81 ///
82 /// // Create a new `And` dependency
83 /// let dependency = Dependency::new_and();
84 /// ```
85 pub fn new_and() -> Self {
86 Dependency::And(Vec::new())
87 }
88
89 /// Create a new `Or` dependency.
90 ///
91 /// # Returns
92 ///
93 /// This function returns a new `Dependency` enum with an `Or` variant.
94 ///
95 /// # Examples
96 ///
97 /// ```
98 /// use sbatch_rs::Dependency;
99 ///
100 /// // Create a new `Or` dependency
101 /// let dependency = Dependency::new_or();
102 /// ```
103 pub fn new_or() -> Self {
104 Dependency::Or(Vec::new())
105 }
106
107 /// Add a dependency to the `Dependency` enum.
108 ///
109 /// # Arguments
110 ///
111 /// * `dependency` - A `DependencyType` value to add to the `Dependency` enum.
112 ///
113 /// # Returns
114 ///
115 /// This function returns a mutable reference to the `Dependency` enum.
116 ///
117 /// # Errors
118 ///
119 /// This function returns a `DependencyError` if the dependency is invalid.
120 ///
121 /// # Examples
122 ///
123 /// ```
124 /// use sbatch_rs::{Dependency, DependencyType};
125 ///
126 /// // Create a new `And` dependency
127 /// let mut dependency = Dependency::new_and();
128 ///
129 /// // Add an `After` dependency using the enum variant
130 /// dependency.push(DependencyType::After("123".to_string())).unwrap();
131 ///
132 /// // Build the dependency string
133 /// let dependency_str = dependency.build().unwrap();
134 /// assert_eq!(dependency_str, "after:123");
135 /// ```
136 pub fn push(&mut self, dependency: DependencyType) -> Result<&mut Self, DependencyError> {
137 // Validate the dependency
138 dependency.validate()?;
139
140 // Add the dependency to the vector
141 match self {
142 Dependency::And(dependencies) => dependencies.push(dependency),
143 Dependency::Or(dependencies) => dependencies.push(dependency),
144 }
145 Ok(self)
146 }
147
148 /// Add an `After` dependency to the `Dependency` enum.
149 ///
150 /// # Arguments
151 ///
152 /// * `job_id` - The job ID to add as a dependency.
153 ///
154 /// # Returns
155 ///
156 /// This function returns a mutable reference to the `Dependency` enum.
157 ///
158 /// # Errors
159 ///
160 /// This function returns a `DependencyError` if the dependency is invalid.
161 ///
162 /// # Examples
163 ///
164 /// ```
165 /// use sbatch_rs::Dependency;
166 ///
167 /// let dependency = Dependency::new_and()
168 /// .push_after("123").unwrap()
169 /// .build().unwrap();
170 /// assert_eq!(dependency, "after:123");
171 ///
172 /// ```
173 pub fn push_after(&mut self, job_id: impl ToString) -> Result<&mut Self, DependencyError> {
174 self.push(DependencyType::After(job_id.to_string()))
175 }
176
177 /// Add an `AfterTimeDelay` dependency to the `Dependency` enum.
178 ///
179 /// # Arguments
180 ///
181 /// * `job_id` - The job ID to add as a dependency.
182 /// * `time_delay` - The time delay to add as a dependency.
183 ///
184 /// # Returns
185 ///
186 /// This function returns a mutable reference to the `Dependency` enum.
187 ///
188 /// # Errors
189 ///
190 /// This function returns a `DependencyError` if the dependency is invalid.
191 ///
192 /// # Examples
193 ///
194 /// ```
195 /// use sbatch_rs::Dependency;
196 ///
197 /// let dependency = Dependency::new_and()
198 /// .push_after_time_delay("123", "10").unwrap()
199 /// .build().unwrap();
200 /// assert_eq!(dependency, "after:123+10");
201 /// ```
202 pub fn push_after_time_delay(
203 &mut self,
204 job_id: impl ToString,
205 time_delay: impl ToString,
206 ) -> Result<&mut Self, DependencyError> {
207 self.push(DependencyType::AfterTimeDelay(
208 job_id.to_string(),
209 time_delay.to_string(),
210 ))
211 }
212
213 /// Add an `AfterAny` dependency to the `Dependency` enum.
214 ///
215 /// # Arguments
216 ///
217 /// * `job_id` - The job ID to add as a dependency.
218 ///
219 /// # Returns
220 ///
221 /// This function returns a mutable reference to the `Dependency` enum.
222 ///
223 /// # Errors
224 ///
225 /// This function returns a `DependencyError` if the dependency is invalid.
226 ///
227 /// # Examples
228 ///
229 /// ```
230 /// use sbatch_rs::Dependency;
231 ///
232 /// let dependency = Dependency::new_and()
233 /// .push_after_any("123").unwrap()
234 /// .build().unwrap();
235 /// assert_eq!(dependency, "afterany:123");
236 /// ```
237 pub fn push_after_any(&mut self, job_id: impl ToString) -> Result<&mut Self, DependencyError> {
238 self.push(DependencyType::AfterAny(job_id.to_string()))
239 }
240
241 /// Add an `AfterBurstBuffer` dependency to the `Dependency` enum.
242 ///
243 /// # Arguments
244 ///
245 /// * `job_id` - The job ID to add as a dependency.
246 ///
247 /// # Returns
248 ///
249 /// This function returns a mutable reference to the `Dependency` enum.
250 ///
251 /// # Errors
252 ///
253 /// This function returns a `DependencyError` if the dependency is invalid.
254 ///
255 /// # Examples
256 ///
257 /// ```
258 /// use sbatch_rs::Dependency;
259 ///
260 /// let dependency = Dependency::new_and()
261 /// .push_after_burst_buffer("123").unwrap()
262 /// .build().unwrap();
263 /// assert_eq!(dependency, "afterburstbuffer:123");
264 ///
265 /// ```
266 pub fn push_after_burst_buffer(
267 &mut self,
268 job_id: impl ToString,
269 ) -> Result<&mut Self, DependencyError> {
270 self.push(DependencyType::AfterBurstBuffer(job_id.to_string()))
271 }
272
273 /// Add an `AfterCorr` dependency to the `Dependency` enum.
274 ///
275 /// # Arguments
276 ///
277 /// * `job_id` - The job ID to add as a dependency.
278 ///
279 /// # Returns
280 ///
281 /// This function returns a mutable reference to the `Dependency` enum.
282 ///
283 /// # Errors
284 ///
285 /// This function returns a `DependencyError` if the dependency is invalid.
286 ///
287 /// # Examples
288 ///
289 /// ```
290 /// use sbatch_rs::Dependency;
291 ///
292 /// let dependency = Dependency::new_and()
293 /// .push_after_corr("123").unwrap()
294 /// .build().unwrap();
295 /// assert_eq!(dependency, "aftercorr:123");
296 /// ```
297 pub fn push_after_corr(&mut self, job_id: impl ToString) -> Result<&mut Self, DependencyError> {
298 self.push(DependencyType::AfterCorr(job_id.to_string()))
299 }
300
301 /// Add an `AfterNotOk` dependency to the `Dependency` enum.
302 ///
303 /// # Arguments
304 ///
305 /// * `job_id` - The job ID to add as a dependency.
306 ///
307 /// # Returns
308 ///
309 /// This function returns a mutable reference to the `Dependency` enum.
310 ///
311 /// # Errors
312 ///
313 /// This function returns a `DependencyError` if the dependency is invalid.
314 ///
315 /// # Examples
316 ///
317 /// ```
318 /// use sbatch_rs::Dependency;
319 ///
320 /// let dependency = Dependency::new_and()
321 /// .push_after_not_ok("123").unwrap()
322 /// .build().unwrap();
323 /// assert_eq!(dependency, "afternotok:123");
324 /// ```
325 pub fn push_after_not_ok(
326 &mut self,
327 job_id: impl ToString,
328 ) -> Result<&mut Self, DependencyError> {
329 self.push(DependencyType::AfterNotOk(job_id.to_string()))
330 }
331
332 /// Add an `AfterOk` dependency to the `Dependency` enum.
333 ///
334 /// # Arguments
335 ///
336 /// * `job_id` - The job ID to add as a dependency.
337 ///
338 /// # Returns
339 ///
340 /// This function returns a mutable reference to the `Dependency` enum.
341 ///
342 /// # Errors
343 ///
344 /// This function returns a `DependencyError` if the dependency is invalid.
345 ///
346 /// # Examples
347 ///
348 /// ```
349 /// use sbatch_rs::Dependency;
350 ///
351 /// let dependency = Dependency::new_and()
352 /// .push_after_ok("123").unwrap()
353 /// .build().unwrap();
354 /// assert_eq!(dependency, "afterok:123");
355 /// ```
356 pub fn push_after_ok(&mut self, job_id: &str) -> Result<&mut Self, DependencyError> {
357 self.push(DependencyType::AfterOk(job_id.to_string()))
358 }
359
360 /// Add a `Singleton` dependency to the `Dependency` enum.
361 ///
362 /// # Returns
363 ///
364 /// This function returns a mutable reference to the `Dependency` enum.
365 ///
366 /// # Errors
367 ///
368 /// Note that the `Singleton` dependency type does not require any arguments.
369 /// Currently, this function does not return any errors but may be updated in the future.
370 ///
371 /// # Examples
372 ///
373 /// ```
374 /// use sbatch_rs::Dependency;
375 ///
376 /// let dependency = Dependency::new_and()
377 /// .push_singleton().unwrap()
378 /// .build().unwrap();
379 /// assert_eq!(dependency, "singleton");
380 /// ```
381 pub fn push_singleton(&mut self) -> Result<&mut Self, DependencyError> {
382 self.push(DependencyType::Singleton)
383 }
384
385 /// Build the dependency string.
386 ///
387 /// # Returns
388 ///
389 /// This function returns a `String` containing the dependency string.
390 ///
391 /// # Errors
392 ///
393 /// This function returns a `DependencyError` if the dependency is invalid.
394 /// The `NoDependencies` error is returned if no dependencies were provided.
395 /// The `DependencyTypeError` error is returned if a dependency is invalid.
396 ///
397 /// # Examples
398 ///
399 /// ```
400 /// use sbatch_rs::{Dependency, DependencyType};
401 ///
402 /// // Create a new `And` dependency
403 /// let mut dependency = Dependency::new_and();
404 ///
405 /// // Add an `After` dependency using the enum variant
406 /// dependency.push(DependencyType::After("123".to_string())).unwrap();
407 ///
408 /// // Add a `AfterTimeDelay` dependency using the helper function
409 /// dependency.push_after_time_delay("456", "10").unwrap();
410 ///
411 /// // Build the dependency string
412 /// let dependency_str = dependency.build().unwrap();
413 /// assert_eq!(dependency_str, "after:123,after:456+10");
414 /// ```
415 pub fn build(&self) -> Result<String, DependencyError> {
416 // Check if there are any dependencies
417 if self.dependencies().is_empty() {
418 return Err(DependencyError::NoDependencies);
419 }
420
421 // Validate the dependencies
422 for dependency in self.dependencies() {
423 dependency.validate()?;
424 }
425
426 // Convert the dependencies to a single string
427 Ok(self
428 .dependencies()
429 .iter()
430 .map(|d| d.to_string())
431 .collect::<BTreeSet<_>>()
432 .into_iter()
433 .collect::<Vec<_>>()
434 .join(self.separator()))
435 }
436}