Skip to main content

eggserve_core/path/
rejected.rs

1use std::fmt;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
4pub enum PathRejection {
5    Empty,
6    #[allow(dead_code)]
7    TooLong,
8    UnsupportedUriForm,
9    MalformedPercentEncoding,
10    InvalidUtf8,
11    NulByte,
12    #[allow(dead_code)]
13    AbsolutePath,
14    ParentComponent,
15    CurrentComponent,
16    SeparatorAmbiguity,
17    DotfileDenied,
18    WindowsPrefixDenied,
19    WindowsReservedNameDenied,
20    WindowsAlternateStreamDenied,
21    SymlinkDenied,
22    RootEscapeDenied,
23}
24
25impl fmt::Display for PathRejection {
26    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27        match self {
28            PathRejection::Empty => write!(f, "empty path"),
29            PathRejection::TooLong => write!(f, "path too long"),
30            PathRejection::UnsupportedUriForm => write!(f, "unsupported URI form"),
31            PathRejection::MalformedPercentEncoding => {
32                write!(f, "malformed percent encoding")
33            }
34            PathRejection::InvalidUtf8 => write!(f, "invalid UTF-8"),
35            PathRejection::NulByte => write!(f, "NUL byte in path"),
36            PathRejection::AbsolutePath => write!(f, "absolute path"),
37            PathRejection::ParentComponent => write!(f, "parent component (..)"),
38            PathRejection::CurrentComponent => write!(f, "current component (.)"),
39            PathRejection::SeparatorAmbiguity => write!(f, "separator ambiguity"),
40            PathRejection::DotfileDenied => write!(f, "dotfile denied"),
41            PathRejection::WindowsPrefixDenied => write!(f, "Windows prefix denied"),
42            PathRejection::WindowsReservedNameDenied => {
43                write!(f, "Windows reserved name denied")
44            }
45            PathRejection::WindowsAlternateStreamDenied => {
46                write!(f, "Windows alternate stream denied")
47            }
48            PathRejection::SymlinkDenied => write!(f, "symlink denied by policy"),
49            PathRejection::RootEscapeDenied => write!(f, "canonical path escapes root"),
50        }
51    }
52}
53
54impl std::error::Error for PathRejection {}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    #[test]
61    fn display_all_variants() {
62        let cases = &[
63            (PathRejection::Empty, "empty path"),
64            (PathRejection::TooLong, "path too long"),
65            (PathRejection::UnsupportedUriForm, "unsupported URI form"),
66            (
67                PathRejection::MalformedPercentEncoding,
68                "malformed percent encoding",
69            ),
70            (PathRejection::InvalidUtf8, "invalid UTF-8"),
71            (PathRejection::NulByte, "NUL byte in path"),
72            (PathRejection::AbsolutePath, "absolute path"),
73            (PathRejection::ParentComponent, "parent component (..)"),
74            (PathRejection::CurrentComponent, "current component (.)"),
75            (PathRejection::SeparatorAmbiguity, "separator ambiguity"),
76            (PathRejection::DotfileDenied, "dotfile denied"),
77            (PathRejection::WindowsPrefixDenied, "Windows prefix denied"),
78            (
79                PathRejection::WindowsReservedNameDenied,
80                "Windows reserved name denied",
81            ),
82            (
83                PathRejection::WindowsAlternateStreamDenied,
84                "Windows alternate stream denied",
85            ),
86            (PathRejection::SymlinkDenied, "symlink denied by policy"),
87            (
88                PathRejection::RootEscapeDenied,
89                "canonical path escapes root",
90            ),
91        ];
92        for (rejection, expected) in cases {
93            assert_eq!(rejection.to_string(), *expected);
94        }
95    }
96
97    #[test]
98    fn is_error() {
99        let err: &dyn std::error::Error = &PathRejection::Empty;
100        assert_eq!(err.to_string(), "empty path");
101    }
102}