nextest_runner/config/
helpers.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
// Copyright (c) The nextest Contributors
// SPDX-License-Identifier: MIT OR Apache-2.0

use camino::{Utf8Component, Utf8PathBuf};
use serde::{
    de::{Error, Unexpected},
    Deserialize,
};

/// Deserializes a well-formed relative path.
///
/// Returns an error on absolute paths, and on other kinds of relative paths.
pub(super) fn deserialize_relative_path<'de, D>(deserializer: D) -> Result<Utf8PathBuf, D::Error>
where
    D: serde::Deserializer<'de>,
{
    let s = Utf8PathBuf::deserialize(deserializer)?;
    for component in s.components() {
        match component {
            Utf8Component::Normal(_) | Utf8Component::CurDir => {}
            Utf8Component::RootDir | Utf8Component::Prefix(_) | Utf8Component::ParentDir => {
                return Err(D::Error::invalid_value(
                    Unexpected::Str(s.as_str()),
                    &"a relative path with no parent components",
                ));
            }
        }
    }

    Ok(s)
}

#[cfg(test)]
mod tests {
    use super::*;
    use color_eyre::eyre::{bail, Context, Result};
    use serde::de::IntoDeserializer;

    #[test]
    fn test_deserialize_relative_path() -> Result<()> {
        let valid = &["foo", "foo/bar", "foo/./bar", "./foo/bar", "."];

        let invalid = &[
            "/foo/bar",
            "foo/../bar",
            "../foo/bar",
            #[cfg(windows)]
            "C:\\foo\\bar",
            #[cfg(windows)]
            "C:foo",
            #[cfg(windows)]
            "\\\\?\\C:\\foo\\bar",
        ];

        for &input in valid {
            let path = de_relative_path(input.into_deserializer())
                .wrap_err_with(|| format!("error deserializing valid path {input:?}: error"))?;
            assert_eq!(path, Utf8PathBuf::from(input), "path matches: {path:?}");
        }

        for &input in invalid {
            let error = match de_relative_path(input.into_deserializer()) {
                Ok(path) => bail!("successfully deserialized an invalid path: {:?}", path),
                Err(error) => error,
            };
            assert_eq!(
                error.to_string(),
                format!(
                    "invalid value: string {input:?}, expected a relative path with no parent components"
                )
            );
        }

        Ok(())
    }

    // Required for type inference.
    fn de_relative_path<'de, D>(deserializer: D) -> Result<Utf8PathBuf, D::Error>
    where
        D: serde::Deserializer<'de, Error = serde::de::value::Error>,
    {
        deserialize_relative_path(deserializer)
    }
}