1use mant_protocol::ScopeTextError;
3use std::{error::Error, fmt, path::PathBuf};
4
5#[derive(Debug, Clone, PartialEq, Eq)]
7pub enum LoadError {
8 NativeBackendUnavailable {
10 tldr_topic: Option<String>,
12 },
13 EmptyName,
15 InvalidManualSection,
17 TldrManualSection {
19 section: String,
21 },
22 InvalidSource,
24 ConflictingSourceSelectors,
26 EmptyMarkdownPath,
28 UnsupportedInputFormat {
30 path: String,
32 },
33 InvalidSelector {
35 field: &'static str,
37 error: ScopeTextError,
39 },
40 Markdown {
42 path: String,
44 detail: String,
46 },
47 EmptyMarkdown {
49 label: String,
51 },
52 Registry {
54 detail: String,
56 },
57 Manual(ManualLoadError),
59 ManualWithTldr {
61 error: ManualLoadError,
63 topic: String,
65 },
66 TldrNotFound {
68 topic: String,
70 },
71 Tldr {
73 topic: String,
75 detail: String,
77 },
78 NoReadableContent {
80 name: String,
82 },
83}
84
85#[derive(Debug, Clone, PartialEq, Eq)]
87pub enum ManualLoadError {
88 NotFound {
90 name: String,
92 detail: String,
94 },
95 Parse {
97 name: String,
99 detail: String,
101 },
102 Empty {
104 name: String,
106 path: PathBuf,
108 diagnostics: Vec<String>,
110 },
111}
112
113impl fmt::Display for LoadError {
114 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
115 match self {
116 Self::NativeBackendUnavailable { tldr_topic } => {
117 formatter.write_str("native manual loading requires the 'roff' feature")?;
118 if let Some(topic) = tldr_topic {
119 write!(
120 formatter,
121 "\nhint: a tldr entry is available; run `mant {topic} --tldr`"
122 )?;
123 }
124 Ok(())
125 }
126 Self::EmptyName => formatter.write_str("name must not be empty"),
127 Self::InvalidManualSection => formatter.write_str(
128 "manual section must be a conventional number or the single letter 'l' or 'n'",
129 ),
130 Self::TldrManualSection { section } => write!(
131 formatter,
132 "manual section '{section}' does not identify a command quick reference; tldr supports section families 1 and 8"
133 ),
134 Self::InvalidSource => formatter.write_str("document source must not be empty"),
135 Self::ConflictingSourceSelectors => formatter.write_str(
136 "document source cannot be combined with a manual section or manual-only policy",
137 ),
138 Self::EmptyMarkdownPath => formatter.write_str("Markdown path must not be empty"),
139 Self::UnsupportedInputFormat { path } => write!(
140 formatter,
141 "could not infer the input format for '{path}'; use --input-format markdown or roff"
142 ),
143 Self::InvalidSelector { field, error } => {
144 write!(formatter, "{field} {}", selector_error_message(*error))
145 }
146 Self::Markdown { path, detail } => {
147 write!(
148 formatter,
149 "could not load Markdown document '{path}': {detail}"
150 )
151 }
152 Self::EmptyMarkdown { label } => {
153 write!(
154 formatter,
155 "Markdown document '{label}' has no readable content"
156 )
157 }
158 Self::Registry { detail } => formatter.write_str(detail),
159 Self::Manual(error) => error.fmt(formatter),
160 Self::ManualWithTldr { error, topic } => {
161 error.fmt(formatter)?;
162 write!(
163 formatter,
164 "\nhint: a tldr entry is available; run `mant {topic} --tldr`"
165 )
166 }
167 Self::TldrNotFound { topic } => {
168 write!(formatter, "no tldr quick reference was found for '{topic}'")
169 }
170 Self::Tldr { topic, detail } => {
171 write!(formatter, "could not load tldr entry '{topic}': {detail}")
172 }
173 Self::NoReadableContent { name } => {
174 write!(
175 formatter,
176 "no readable document content was found for '{name}'"
177 )
178 }
179 }
180 }
181}
182
183impl Error for LoadError {
184 fn source(&self) -> Option<&(dyn Error + 'static)> {
185 match self {
186 Self::Manual(error) | Self::ManualWithTldr { error, .. } => Some(error),
187 _ => None,
188 }
189 }
190}
191
192fn selector_error_message(error: ScopeTextError) -> String {
193 match error {
194 ScopeTextError::Empty => "must not be empty".to_owned(),
195 ScopeTextError::ControlCharacter => "must not contain control characters".to_owned(),
196 ScopeTextError::TooLong { maximum } => {
197 format!("must not exceed {maximum} Unicode scalar values")
198 }
199 }
200}
201
202impl fmt::Display for ManualLoadError {
203 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
204 match self {
205 Self::NotFound { name, detail } => {
206 write!(formatter, "could not load manual '{name}': {detail}")
207 }
208 Self::Parse { name, detail } => write!(
209 formatter,
210 "could not load manual '{name}': manual source: {detail}"
211 ),
212 Self::Empty {
213 name,
214 path,
215 diagnostics,
216 } => {
217 write!(
218 formatter,
219 "could not load manual '{name}': libmandoc parsed {} but produced no readable sections",
220 path.display()
221 )?;
222 if !diagnostics.is_empty() {
223 write!(formatter, "; diagnostics: {}", diagnostics.join("; "))?;
224 }
225 Ok(())
226 }
227 }
228 }
229}
230
231impl Error for ManualLoadError {}