1use crate::{Error, ErrorKind, Result, ResultExt};
7use futures::future::BoxFuture;
8use std::{borrow::Cow, io, str::FromStr};
9use time::{format_description::well_known, OffsetDateTime};
10
11pub fn parse_date_time_opt(value: &str) -> Result<OffsetDateTime> {
13 OffsetDateTime::parse(value, &well_known::Rfc3339)
14 .with_context(ErrorKind::InvalidData, "failed to parse date-time")
15}
16
17pub fn parse_key_value<T>(value: &str) -> Result<(String, T)>
19where
20 T: FromStr,
21 Error: From<<T as FromStr>::Err>,
22{
23 let idx = value
24 .find("=")
25 .ok_or_else(|| format!("no '=' found in '{value}'"))?;
26 Ok((value[..idx].to_string(), value[idx + 1..].parse()?))
27}
28
29pub fn parse_key_value_opt<T>(value: &str) -> Result<(String, Option<T>)>
31where
32 T: FromStr,
33 Error: From<<T as FromStr>::Err>,
34{
35 if let Some(idx) = value.find("=") {
36 return Ok((value[..idx].to_string(), Some(value[idx + 1..].parse()?)));
37 }
38
39 Ok((value.to_string(), None))
40}
41
42pub async fn replace_expressions<W, F>(mut template: &str, w: &mut W, f: F) -> Result<()>
71where
72 W: io::Write,
73 F: Fn(&str) -> BoxFuture<'_, Result<String>>,
74{
75 const START: &str = "{{";
76 const START_LEN: usize = START.len();
77 const END: &str = "}}";
78 const END_LEN: usize = END.len();
79
80 while let Some(mut start) = template.find(START) {
81 let Some(mut end) = template[start + START_LEN..].find(END) else {
83 return Err(Error::with_message(
84 ErrorKind::InvalidData,
85 "missing closing '}}'",
86 ));
87 };
88 end += start + START_LEN;
89
90 w.write_all(&template.as_bytes()[..start])?;
91 start += START_LEN;
92
93 let id = template[start..end].trim();
94 let secret = f(id).await?;
95
96 w.write_all(secret.as_bytes())?;
97 end += END_LEN;
98
99 template = &template[end..];
100 }
101
102 w.write_all(template.as_bytes())?;
103 Ok(())
104}
105
106pub fn replace_vars<F>(input: &str, f: F) -> Result<Cow<'_, str>>
108where
109 F: Fn(&str) -> Result<String>,
110{
111 let mut cur = input;
112 let mut output = String::new();
113
114 while let Some(start) = cur.find('$') {
115 output += &cur[..start];
116 cur = &cur[start + 1..];
117
118 let mut end = cur.len();
119 for (i, c) in cur.char_indices() {
120 if !c.is_ascii_alphanumeric() && c != '_' {
121 end = i;
122 break;
123 }
124 }
125
126 let name = &cur[..end];
127 if !name.is_empty() {
128 output += &f(name)?;
129 }
130 cur = &cur[end..];
131 }
132
133 if output.is_empty() {
134 Ok(Cow::Borrowed(input))
135 } else {
136 output += cur;
137 Ok(Cow::Owned(output))
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use futures::FutureExt as _;
145
146 #[test]
147 fn test_parse_key_value() {
148 let kv = parse_key_value::<String>("key=value");
149 assert!(matches!(kv, Ok(kv) if kv.0 == "key" && kv.1 == "value"));
150
151 let kv = parse_key_value::<String>("key=value=other");
152 assert!(matches!(kv, Ok(kv) if kv.0 == "key" && kv.1 == "value=other"));
153
154 parse_key_value::<String>("key").expect_err("requires '='");
155
156 let k = parse_key_value::<i32>("key=1");
157 assert!(matches!(k, Ok(k) if k.0 == "key" && k.1 == 1));
158
159 parse_key_value::<i32>("key=value").expect_err("should not parse 'value' as i32");
160 }
161
162 #[test]
163 fn test_parse_key_value_opt() {
164 let kv = parse_key_value_opt::<String>("key=value");
165 assert!(matches!(kv, Ok(kv) if kv.0 == "key" && kv.1 == Some("value".into())));
166
167 let kv = parse_key_value_opt::<String>("key=value=other");
168 assert!(matches!(kv, Ok(kv) if kv.0 == "key" && kv.1 == Some("value=other".into())));
169
170 let k = parse_key_value_opt::<i32>("key");
171 assert!(matches!(k, Ok(k) if k.0 == "key" && k.1.is_none()));
172
173 parse_key_value_opt::<i32>("key=value").expect_err("should not parse 'value' as i32");
174 }
175
176 #[tokio::test]
177 async fn test_replace_expressions() {
178 let s = "Hello, {{ var }}!";
179 let mut buf = Vec::new();
180
181 replace_expressions(s, &mut buf, |v| {
182 assert_eq!(v, "var");
183 async { Ok(String::from("world")) }.boxed()
184 })
185 .await
186 .unwrap();
187 assert_eq!(String::from_utf8(buf).unwrap(), "Hello, world!");
188 }
189
190 #[tokio::test]
191 async fn replace_expressions_overlap() {
192 let s = "Hello, {{ {{var}} }}!";
193 let mut buf = Vec::new();
194
195 replace_expressions(s, &mut buf, |v| {
196 assert_eq!(v, "{{var");
197 async { Ok(String::from("world")) }.boxed()
198 })
199 .await
200 .unwrap();
201 assert_eq!(String::from_utf8(buf).unwrap(), "Hello, world }}!");
202 }
203
204 #[tokio::test]
205 async fn replace_expressions_missing_end() {
206 let s = "Hello, {{ var!";
207 let mut buf = Vec::new();
208
209 replace_expressions(s, &mut buf, |_| async { Ok(String::from("world")) }.boxed())
210 .await
211 .expect_err("missing end");
212 }
213
214 #[tokio::test]
215 async fn replace_expressions_missing_empty() {
216 let s = "";
217 let mut buf = Vec::new();
218
219 replace_expressions(s, &mut buf, |_| async { Ok(String::from("world")) }.boxed())
220 .await
221 .unwrap();
222 assert_eq!(String::from_utf8(buf).unwrap(), "");
223 }
224
225 #[tokio::test]
226 async fn replace_expressions_missing_no_template() {
227 let s = "Hello, world!";
228 let mut buf = Vec::new();
229
230 replace_expressions(s, &mut buf, |_| {
231 async { Ok(String::from("Ferris")) }.boxed()
232 })
233 .await
234 .unwrap();
235 assert_eq!(String::from_utf8(buf).unwrap(), "Hello, world!");
236 }
237
238 #[test]
239 fn replace_vars_borrowed() {
240 let s = "echo NONE";
241 let out = replace_vars(s, |name| {
242 assert_eq!(name, "VAR");
243 Ok(String::from("VALUE"))
244 })
245 .expect("replaces $VAR with VALUE");
246 assert!(matches!(out, Cow::Borrowed(out) if out == "echo NONE"));
247 }
248
249 #[test]
250 fn replace_vars_owned() {
251 let s = "echo $VAR";
252 let out = replace_vars(s, |name| {
253 assert_eq!(name, "VAR");
254 Ok(String::from("VALUE"))
255 })
256 .expect("replaces $VAR with VALUE");
257 assert!(matches!(out, Cow::Owned(out) if out == "echo VALUE"));
258 }
259
260 #[test]
261 fn replace_only_vars() {
262 let s = "$VAR";
263 let out = replace_vars(s, |name| {
264 assert_eq!(name, "VAR");
265 Ok(String::from("VALUE"))
266 })
267 .expect("replaces $VAR with VALUE");
268 assert!(matches!(out, Cow::Owned(out) if out == "VALUE"));
269 }
270
271 #[test]
272 fn replace_vars_errs() {
273 let s = "echo $VAR";
274 replace_vars(s, |name| {
275 assert_eq!(name, "VAR");
276 Err(Error::with_message(ErrorKind::Other, "test"))
277 })
278 .expect_err("expected error");
279 }
280
281 #[tokio::test]
282 async fn replace_expression_with_var() {
283 let s = "Hello, {{ $VAR }}!";
284 let mut buf = Vec::new();
285
286 replace_expressions(s, &mut buf, |expr| {
287 async move {
288 assert_eq!(expr, "$VAR");
289 replace_vars(expr, |var| {
290 assert_eq!(var, "VAR");
291 Ok(String::from("world"))
292 })
293 .map(Into::into)
294 }
295 .boxed()
296 })
297 .await
298 .expect("replaces $VAR with 'world'");
299 assert_eq!(String::from_utf8(buf).unwrap(), "Hello, world!");
300 }
301}