Skip to main content

mant_engine/mandoc/
error.rs

1//! Product-level failures while loading and parsing one native manual.
2
3use std::{
4    fmt,
5    path::{Path, PathBuf},
6};
7
8use libmandoc_rs::ParseError;
9
10/// Stable category for a native manual failure.
11#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12pub enum ManualErrorKind {
13    /// Source bytes could not be read.
14    Read,
15    /// Compressed source bytes could not be decoded.
16    Decompression,
17    /// A configured resource bound was exceeded.
18    Limit,
19    /// A path or redirect crossed the approved manual tree.
20    UnsafePath,
21    /// A native alias redirect was invalid or could not be resolved.
22    Redirect,
23    /// libmandoc rejected or could not represent the source.
24    Parse,
25}
26
27/// Failure produced by `ManT`'s source policy or by the underlying roff parser.
28#[derive(Clone, Debug, Eq, PartialEq)]
29pub enum ManualError {
30    /// Filesystem read failure.
31    Read {
32        /// Original source path.
33        path: PathBuf,
34        /// Stable human-readable detail.
35        message: String,
36    },
37    /// Top-level decompression failure.
38    Decompression {
39        /// Original source path.
40        path: PathBuf,
41        /// Stable human-readable detail.
42        message: String,
43    },
44    /// Input resource limit violation.
45    Limit {
46        /// Original source path.
47        path: PathBuf,
48        /// Stable human-readable detail.
49        message: String,
50    },
51    /// Manual-tree containment policy violation.
52    UnsafePath {
53        /// Rejected source or target path.
54        path: PathBuf,
55        /// Stable human-readable detail.
56        message: String,
57    },
58    /// Invalid or unresolved `.so` alias page.
59    Redirect {
60        /// Alias source path.
61        path: PathBuf,
62        /// Stable human-readable detail.
63        message: String,
64    },
65    /// Failure reported by the low-level libmandoc binding.
66    Parse(ParseError),
67}
68
69impl ManualError {
70    pub(super) fn read(path: &Path, message: impl Into<String>) -> Self {
71        Self::Read {
72            path: path.to_path_buf(),
73            message: message.into(),
74        }
75    }
76
77    pub(super) fn decompression(path: &Path, message: impl Into<String>) -> Self {
78        Self::Decompression {
79            path: path.to_path_buf(),
80            message: message.into(),
81        }
82    }
83
84    pub(super) fn limit(path: &Path, message: impl Into<String>) -> Self {
85        Self::Limit {
86            path: path.to_path_buf(),
87            message: message.into(),
88        }
89    }
90
91    pub(super) fn unsafe_path(path: &Path, message: impl Into<String>) -> Self {
92        Self::UnsafePath {
93            path: path.to_path_buf(),
94            message: message.into(),
95        }
96    }
97
98    pub(super) fn redirect(path: &Path, message: impl Into<String>) -> Self {
99        Self::Redirect {
100            path: path.to_path_buf(),
101            message: message.into(),
102        }
103    }
104
105    #[must_use]
106    /// Return the stable failure category.
107    pub const fn kind(&self) -> ManualErrorKind {
108        match self {
109            Self::Read { .. } => ManualErrorKind::Read,
110            Self::Decompression { .. } => ManualErrorKind::Decompression,
111            Self::Limit { .. } => ManualErrorKind::Limit,
112            Self::UnsafePath { .. } => ManualErrorKind::UnsafePath,
113            Self::Redirect { .. } => ManualErrorKind::Redirect,
114            Self::Parse(_) => ManualErrorKind::Parse,
115        }
116    }
117
118    #[must_use]
119    /// Return the original caller-facing source path.
120    pub fn path(&self) -> &Path {
121        match self {
122            Self::Read { path, .. }
123            | Self::Decompression { path, .. }
124            | Self::Limit { path, .. }
125            | Self::UnsafePath { path, .. }
126            | Self::Redirect { path, .. } => path,
127            Self::Parse(error) => &error.path,
128        }
129    }
130
131    #[must_use]
132    /// Return the stable human-readable detail.
133    pub fn message(&self) -> &str {
134        match self {
135            Self::Read { message, .. }
136            | Self::Decompression { message, .. }
137            | Self::Limit { message, .. }
138            | Self::UnsafePath { message, .. }
139            | Self::Redirect { message, .. } => message,
140            Self::Parse(error) => &error.message,
141        }
142    }
143}
144
145impl From<ParseError> for ManualError {
146    fn from(error: ParseError) -> Self {
147        Self::Parse(error)
148    }
149}
150
151impl fmt::Display for ManualError {
152    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
153        write!(formatter, "{}: {}", self.path().display(), self.message())
154    }
155}
156
157impl std::error::Error for ManualError {
158    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
159        match self {
160            Self::Parse(error) => Some(error),
161            Self::Read { .. }
162            | Self::Decompression { .. }
163            | Self::Limit { .. }
164            | Self::UnsafePath { .. }
165            | Self::Redirect { .. } => None,
166        }
167    }
168}