nextest_runner/config/
helpers.rs

1// Copyright (c) The nextest Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use camino::{Utf8Component, Utf8PathBuf};
5use serde::{
6    Deserialize,
7    de::{Error, Unexpected},
8};
9
10/// Deserializes a well-formed relative path.
11///
12/// Returns an error on absolute paths, and on other kinds of relative paths.
13pub(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    // Required for type inference.
78    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}