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}