|
1 | | -use anyhow::{Context, Result, anyhow}; |
| 1 | +use anyhow::{Context, Result, anyhow, bail}; |
2 | 2 | use base64::Engine; |
3 | 3 | use base64::engine::general_purpose::STANDARD; |
4 | 4 |
|
@@ -28,9 +28,59 @@ pub fn from_base64(input: impl AsRef<str>) -> Result<String> { |
28 | 28 | ) |
29 | 29 | } |
30 | 30 |
|
| 31 | +pub fn element<T: Clone, I: TryInto<i64>>(list: impl AsRef<[T]>, index: I) -> Result<T> { |
| 32 | + let index = index |
| 33 | + .try_into() |
| 34 | + .map_err(|_| anyhow!("Failed to convert list index to i64"))?; |
| 35 | + if index < 0 { |
| 36 | + bail!("List index cannot be negative: {index}"); |
| 37 | + } |
| 38 | + let index = usize::try_from(index).context("Failed to convert list index to usize")?; |
| 39 | + let list_ref = list.as_ref(); |
| 40 | + list_ref.get(index).cloned().ok_or_else(|| { |
| 41 | + anyhow!( |
| 42 | + "List index {index} is out of bounds for length {}", |
| 43 | + list_ref.len() |
| 44 | + ) |
| 45 | + }) |
| 46 | +} |
| 47 | + |
| 48 | +pub fn join<T: AsRef<str>>(separator: impl AsRef<str>, list: impl AsRef<[T]>) -> String { |
| 49 | + list.as_ref() |
| 50 | + .iter() |
| 51 | + .map(AsRef::as_ref) |
| 52 | + .collect::<Vec<_>>() |
| 53 | + .join(separator.as_ref()) |
| 54 | +} |
| 55 | + |
| 56 | +pub fn length<T>(list: impl AsRef<[T]>) -> i64 { |
| 57 | + i64::try_from(list.as_ref().len()).expect("List length exceeds i64::MAX") |
| 58 | +} |
| 59 | + |
| 60 | +pub fn split(separator: impl AsRef<str>, text: impl AsRef<str>) -> Vec<String> { |
| 61 | + text.as_ref() |
| 62 | + .split(separator.as_ref()) |
| 63 | + .map(ToOwned::to_owned) |
| 64 | + .collect() |
| 65 | +} |
| 66 | + |
| 67 | +pub fn single_or_none<T: Clone>(list: impl AsRef<[T]>) -> Result<Option<T>> { |
| 68 | + let list_ref = list.as_ref(); |
| 69 | + if list_ref.is_empty() { |
| 70 | + return Ok(None); |
| 71 | + } |
| 72 | + if list_ref.len() != 1 { |
| 73 | + bail!( |
| 74 | + "singleOrNone expected input list to have at most one element, got {}", |
| 75 | + list_ref.len() |
| 76 | + ); |
| 77 | + } |
| 78 | + Ok(Some(list_ref[0].clone())) |
| 79 | +} |
| 80 | + |
31 | 81 | #[cfg(test)] |
32 | 82 | mod tests { |
33 | | - use super::{cwd, from_base64, to_base64}; |
| 83 | + use super::{cwd, element, from_base64, join, length, single_or_none, split, to_base64}; |
34 | 84 |
|
35 | 85 | #[test] |
36 | 86 | fn to_base64_encodes_known_text() { |
@@ -92,4 +142,60 @@ mod tests { |
92 | 142 | assert_eq!(from_str, "YWJj"); |
93 | 143 | assert_eq!(from_str, from_bytes); |
94 | 144 | } |
| 145 | + |
| 146 | + #[test] |
| 147 | + fn element_returns_item_for_valid_index() { |
| 148 | + let values = vec!["a".to_string(), "b".to_string(), "c".to_string()]; |
| 149 | + assert_eq!(element(&values, 1).unwrap(), "b"); |
| 150 | + } |
| 151 | + |
| 152 | + #[test] |
| 153 | + fn element_returns_error_for_negative_index() { |
| 154 | + let values = vec!["a", "b"]; |
| 155 | + let error = element(&values, -1).unwrap_err().to_string(); |
| 156 | + assert!(error.contains("cannot be negative")); |
| 157 | + } |
| 158 | + |
| 159 | + #[test] |
| 160 | + fn element_returns_error_for_out_of_bounds_index() { |
| 161 | + let values = vec!["a", "b"]; |
| 162 | + let error = element(&values, 2).unwrap_err().to_string(); |
| 163 | + assert!(error.contains("out of bounds")); |
| 164 | + } |
| 165 | + |
| 166 | + #[test] |
| 167 | + fn join_joins_values_with_separator() { |
| 168 | + let values = vec!["a".to_string(), "b".to_string(), "c".to_string()]; |
| 169 | + assert_eq!(join("|", &values), "a|b|c"); |
| 170 | + } |
| 171 | + |
| 172 | + #[test] |
| 173 | + fn length_returns_i64_length() { |
| 174 | + let values = vec!["x", "y", "z"]; |
| 175 | + assert_eq!(length(&values), 3_i64); |
| 176 | + } |
| 177 | + |
| 178 | + #[test] |
| 179 | + fn split_returns_segments() { |
| 180 | + assert_eq!(split("-", "a-b-c"), vec!["a", "b", "c"]); |
| 181 | + } |
| 182 | + |
| 183 | + #[test] |
| 184 | + fn single_or_none_returns_none_for_empty_list() { |
| 185 | + let values: Vec<String> = vec![]; |
| 186 | + assert_eq!(single_or_none(&values).unwrap(), None); |
| 187 | + } |
| 188 | + |
| 189 | + #[test] |
| 190 | + fn single_or_none_returns_some_for_single_item_list() { |
| 191 | + let values = vec!["only".to_string()]; |
| 192 | + assert_eq!(single_or_none(&values).unwrap(), Some("only".to_string())); |
| 193 | + } |
| 194 | + |
| 195 | + #[test] |
| 196 | + fn single_or_none_returns_error_for_multi_item_list() { |
| 197 | + let values = vec!["a", "b"]; |
| 198 | + let error = single_or_none(&values).unwrap_err().to_string(); |
| 199 | + assert!(error.contains("at most one element")); |
| 200 | + } |
95 | 201 | } |
0 commit comments