konnektoren_core/challenges/
task_pattern.rs

1use rand::seq::SliceRandom;
2use rand::thread_rng;
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5use std::fmt;
6use std::ops::RangeInclusive;
7
8#[derive(Debug, Clone, PartialEq, JsonSchema)]
9#[serde(rename_all = "snake_case")]
10pub enum TaskPattern {
11    /// Exact number of tasks
12    Exact(usize),
13    /// Range of task indices
14    Range(RangeInclusive<i32>),
15    /// Random selection with optional range
16    Random(usize, Option<RangeInclusive<i32>>),
17}
18
19impl TaskPattern {
20    pub fn parse(s: &str) -> Result<Self, String> {
21        if let Ok(n) = s.parse::<usize>() {
22            return Ok(TaskPattern::Exact(n));
23        }
24
25        if s.contains(':') {
26            let parts: Vec<&str> = s.split(':').collect();
27            if parts.len() != 2 {
28                return Err("Invalid random pattern".to_string());
29            }
30            let n = parts[0]
31                .parse::<usize>()
32                .map_err(|_| "Invalid number of tasks".to_string())?;
33            if parts[1] == "random" {
34                return Ok(TaskPattern::Random(n, None));
35            }
36            let range = Self::parse_range(parts[1])?;
37            return Ok(TaskPattern::Random(n, Some(range)));
38        }
39
40        Self::parse_range(s).map(TaskPattern::Range)
41    }
42
43    fn parse_range(s: &str) -> Result<RangeInclusive<i32>, String> {
44        let parts: Vec<&str> = s.split("..").collect();
45        match parts.len() {
46            1 => {
47                let n = parts[0]
48                    .parse::<i32>()
49                    .map_err(|_| "Invalid range".to_string())?;
50                Ok(n..=n)
51            }
52            2 => {
53                let start = parts[0].parse::<i32>().unwrap_or(i32::MIN);
54                let end = parts[1].parse::<i32>().unwrap_or(i32::MAX);
55                if start > end {
56                    return Err("Invalid range".to_string());
57                }
58                Ok(start..=end)
59            }
60            _ => Err("Invalid range format".to_string()),
61        }
62    }
63
64    pub fn select_items<T: Clone>(&self, items: &[T]) -> Vec<T> {
65        match self {
66            TaskPattern::Exact(n) => items.iter().take(*n).cloned().collect(),
67            TaskPattern::Range(range) => {
68                let start = *range.start().max(&0) as usize;
69                let end = *range.end().min(&(items.len() as i32 - 1)) as usize;
70                items[start..=end].to_vec()
71            }
72            TaskPattern::Random(n, range_opt) => {
73                let range = range_opt
74                    .as_ref()
75                    .map(|r| {
76                        let start = *r.start().max(&0) as usize;
77                        let end = *r.end().min(&(items.len() as i32 - 1)) as usize;
78                        start..=end
79                    })
80                    .unwrap_or(0..=items.len() - 1);
81
82                let mut rng = thread_rng();
83                items[range]
84                    .choose_multiple(&mut rng, *n)
85                    .cloned()
86                    .collect()
87            }
88        }
89    }
90
91    pub fn len(&self) -> usize {
92        match self {
93            TaskPattern::Exact(n) => *n,
94            TaskPattern::Range(range) => {
95                let start = *range.start().max(&0) as usize;
96                let end = *range.end() as usize;
97                end.saturating_sub(start) + 1
98            }
99            TaskPattern::Random(n, _) => *n,
100        }
101    }
102
103    pub fn is_empty(&self) -> bool {
104        match self {
105            TaskPattern::Exact(n) => *n == 0,
106            TaskPattern::Range(range) => range.is_empty(),
107            TaskPattern::Random(n, _) => *n == 0,
108        }
109    }
110}
111
112impl fmt::Display for TaskPattern {
113    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114        match self {
115            TaskPattern::Exact(n) => write!(f, "{}", n),
116            TaskPattern::Range(range) => {
117                if *range.start() == i32::MIN {
118                    write!(f, "..={}", range.end())
119                } else if *range.end() == i32::MAX {
120                    write!(f, "{}..=", range.start())
121                } else {
122                    write!(f, "{}..={}", range.start(), range.end())
123                }
124            }
125            TaskPattern::Random(n, Some(range)) => {
126                if *range.start() == i32::MIN {
127                    write!(f, "{}:..={}", n, range.end())
128                } else if *range.end() == i32::MAX {
129                    write!(f, "{}:{}..=", n, range.start())
130                } else {
131                    write!(f, "{}:{}..={}", n, range.start(), range.end())
132                }
133            }
134            TaskPattern::Random(n, None) => write!(f, "{}:random", n),
135        }
136    }
137}
138
139impl Serialize for TaskPattern {
140    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
141    where
142        S: serde::Serializer,
143    {
144        match self {
145            TaskPattern::Exact(n) => serializer.serialize_str(&n.to_string()),
146            TaskPattern::Range(range) => {
147                serializer.serialize_str(&format!("{}..{}", range.start(), range.end()))
148            }
149            TaskPattern::Random(n, Some(range)) => {
150                serializer.serialize_str(&format!("{}:{}..{}", n, range.start(), range.end()))
151            }
152            TaskPattern::Random(n, None) => serializer.serialize_str(&format!("{}:random", n)),
153        }
154    }
155}
156
157impl<'de> Deserialize<'de> for TaskPattern {
158    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
159    where
160        D: serde::Deserializer<'de>,
161    {
162        let s = String::deserialize(deserializer)?;
163        TaskPattern::parse(&s).map_err(serde::de::Error::custom)
164    }
165}
166
167impl From<usize> for TaskPattern {
168    fn from(value: usize) -> Self {
169        TaskPattern::Exact(value)
170    }
171}
172
173impl From<String> for TaskPattern {
174    fn from(s: String) -> Self {
175        TaskPattern::parse(&s).unwrap_or(TaskPattern::Exact(0))
176    }
177}
178
179impl<'a> From<&'a str> for TaskPattern {
180    fn from(s: &'a str) -> Self {
181        TaskPattern::parse(s).unwrap_or(TaskPattern::Exact(0))
182    }
183}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188
189    #[test]
190    fn test_parse_exact() {
191        assert_eq!(TaskPattern::parse("10"), Ok(TaskPattern::Exact(10)));
192    }
193
194    #[test]
195    fn test_parse_range() {
196        assert_eq!(TaskPattern::parse("1..10"), Ok(TaskPattern::Range(1..=10)));
197        assert_eq!(
198            TaskPattern::parse("..10"),
199            Ok(TaskPattern::Range(i32::MIN..=10))
200        );
201        assert_eq!(
202            TaskPattern::parse("10.."),
203            Ok(TaskPattern::Range(10..=i32::MAX))
204        );
205    }
206
207    #[test]
208    fn test_parse_random() {
209        assert_eq!(
210            TaskPattern::parse("5:random"),
211            Ok(TaskPattern::Random(5, None))
212        );
213        assert_eq!(
214            TaskPattern::parse("5:1..10"),
215            Ok(TaskPattern::Random(5, Some(1..=10)))
216        );
217    }
218
219    #[test]
220    fn test_select_items() {
221        let items: Vec<i32> = (1..=20).collect();
222
223        // Test Exact
224        let pattern = TaskPattern::Exact(5);
225        assert_eq!(pattern.select_items(&items).len(), 5);
226
227        // Test Range
228        let pattern = TaskPattern::Range(5..=10);
229        assert_eq!(pattern.select_items(&items), vec![6, 7, 8, 9, 10, 11]);
230
231        // Test Random
232        let pattern = TaskPattern::Random(5, None);
233        assert_eq!(pattern.select_items(&items).len(), 5);
234
235        let pattern = TaskPattern::Random(5, Some(1..=10));
236        let selected = pattern.select_items(&items);
237        assert_eq!(selected.len(), 5);
238        assert!(selected.iter().all(|&x| (2..=11).contains(&x)));
239    }
240
241    #[test]
242    fn test_serialize() {
243        let pattern = TaskPattern::Exact(5);
244        assert_eq!(serde_json::to_string(&pattern).unwrap(), "\"5\"");
245
246        let pattern = TaskPattern::Range(5..=10);
247        assert_eq!(serde_json::to_string(&pattern).unwrap(), "\"5..10\"");
248
249        let pattern = TaskPattern::Random(5, Some(1..=10));
250        assert_eq!(serde_json::to_string(&pattern).unwrap(), "\"5:1..10\"");
251
252        let pattern = TaskPattern::Random(5, None);
253        assert_eq!(serde_json::to_string(&pattern).unwrap(), "\"5:random\"");
254    }
255
256    #[test]
257    fn test_deserialize() {
258        let pattern: TaskPattern = serde_json::from_str("\"5\"").unwrap();
259        assert_eq!(pattern, TaskPattern::Exact(5));
260
261        let pattern: TaskPattern = serde_json::from_str("\"5..10\"").unwrap();
262        assert_eq!(pattern, TaskPattern::Range(5..=10));
263
264        let pattern: TaskPattern = serde_json::from_str("\"5:1..10\"").unwrap();
265        assert_eq!(pattern, TaskPattern::Random(5, Some(1..=10)));
266
267        let pattern: TaskPattern = serde_json::from_str("\"5:random\"").unwrap();
268        assert_eq!(pattern, TaskPattern::Random(5, None));
269    }
270
271    #[test]
272    fn test_from_usize() {
273        let pattern: TaskPattern = 5.into();
274        assert_eq!(pattern, TaskPattern::Exact(5));
275    }
276
277    #[test]
278    fn test_from_string() {
279        let pattern: TaskPattern = "5".to_string().into();
280        assert_eq!(pattern, TaskPattern::Exact(5));
281    }
282
283    #[test]
284    fn test_from_str() {
285        let pattern: TaskPattern = "5".into();
286        assert_eq!(pattern, TaskPattern::Exact(5));
287    }
288
289    #[test]
290    fn test_invalid_pattern() {
291        assert_eq!(
292            TaskPattern::parse("invalid"),
293            Err("Invalid range".to_string())
294        );
295    }
296
297    #[test]
298    fn test_invalid_range() {
299        assert_eq!(
300            TaskPattern::parse("10..5"),
301            Err("Invalid range".to_string())
302        );
303    }
304
305    #[test]
306    fn test_invalid_random() {
307        assert_eq!(
308            TaskPattern::parse("5:10..5"),
309            Err("Invalid range".to_string())
310        );
311    }
312
313    #[test]
314    fn test_invalid_random_count() {
315        assert_eq!(
316            TaskPattern::parse("5:random:10"),
317            Err("Invalid random pattern".to_string())
318        );
319    }
320
321    #[test]
322    fn test_invalid_random_range() {
323        assert_eq!(
324            TaskPattern::parse("5:10..5"),
325            Err("Invalid range".to_string())
326        );
327    }
328
329    #[test]
330    fn test_invalid_random_range_count() {
331        assert_eq!(
332            TaskPattern::parse("5:10..5:10"),
333            Err("Invalid random pattern".to_string())
334        );
335    }
336
337    #[test]
338    fn test_invalid_patterns() {
339        assert!(TaskPattern::parse("invalid").is_err());
340        assert!(TaskPattern::parse("10..5").is_err());
341        assert!(TaskPattern::parse("5:10..5").is_err());
342    }
343}