nextest_runner/config/
helpers.rs1use camino::{Utf8Component, Utf8PathBuf};
5use serde::{
6 Deserialize,
7 de::{Error, Unexpected},
8};
9
10pub(super) fn deserialize_relative_path<'de, D>(deserializer: D) -> Result<Utf8PathBuf, D::Error>
14where
15 D: serde::Deserializer<'de>,
16{
17 let s = Utf8PathBuf::deserialize(deserializer)?;
18 for component in s.components() {
19 match component {
20 Utf8Component::Normal(_) | Utf8Component::CurDir => {}
21 Utf8Component::RootDir | Utf8Component::Prefix(_) | Utf8Component::ParentDir => {
22 return Err(D::Error::invalid_value(
23 Unexpected::Str(s.as_str()),
24 &"a relative path with no parent components",
25 ));
26 }
27 }
28 }
29
30 Ok(s)
31}
32
33#[cfg(test)]
34mod tests {
35 use super::*;
36 use color_eyre::eyre::{Context, Result, bail};
37 use serde::de::IntoDeserializer;
38
39 #[test]
40 fn test_deserialize_relative_path() -> Result<()> {
41 let valid = &["foo", "foo/bar", "foo/./bar", "./foo/bar", "."];
42
43 let invalid = &[
44 "/foo/bar",
45 "foo/../bar",
46 "../foo/bar",
47 #[cfg(windows)]
48 "C:\\foo\\bar",
49 #[cfg(windows)]
50 "C:foo",
51 #[cfg(windows)]
52 "\\\\?\\C:\\foo\\bar",
53 ];
54
55 for &input in valid {
56 let path = de_relative_path(input.into_deserializer())
57 .wrap_err_with(|| format!("error deserializing valid path {input:?}: error"))?;
58 assert_eq!(path, Utf8PathBuf::from(input), "path matches: {path:?}");
59 }
60
61 for &input in invalid {
62 let error = match de_relative_path(input.into_deserializer()) {
63 Ok(path) => bail!("successfully deserialized an invalid path: {:?}", path),
64 Err(error) => error,
65 };
66 assert_eq!(
67 error.to_string(),
68 format!(
69 "invalid value: string {input:?}, expected a relative path with no parent components"
70 )
71 );
72 }
73
74 Ok(())
75 }
76
77 fn de_relative_path<'de, D>(deserializer: D) -> Result<Utf8PathBuf, D::Error>
79 where
80 D: serde::Deserializer<'de, Error = serde::de::value::Error>,
81 {
82 deserialize_relative_path(deserializer)
83 }
84}