tpnote_lib/
error.rs

1//! Custom error types.
2
3use std::io;
4use std::path::PathBuf;
5use thiserror::Error;
6
7/// The error `InvalidFrontMatterYaml` prints the front matter section of the
8/// note file. This constant limits the number of text lines that are printed.
9pub const FRONT_MATTER_ERROR_MAX_LINES: usize = 20;
10
11/// Error related to the clipboard or `stdin` input stream.
12#[derive(Debug, Error, PartialEq)]
13pub enum InputStreamError {
14    /// Remedy: Prepend HTML input data with `<!DOCTYPE html>` or `<html>`.
15    /// with a doc type other than `<!DOCTYPE html>`.
16    #[error(
17        "The HTML input stream starts with a doctype other than\n\
18         \"<!DOCTYPE html>\":\n\
19         {html}"
20    )]
21    NonHtmlDoctype { html: String },
22}
23
24/// Configuration file related filesystem and syntax errors.
25#[derive(Debug, Error)]
26pub enum FileError {
27    /// Remedy: delete all files in configuration file directory.
28    #[error(
29        "Can not find unused filename in directory:\n\
30        \t{directory:?}\n\
31        (only `COPY_COUNTER_MAX` copies are allowed)."
32    )]
33    NoFreeFileName { directory: PathBuf },
34
35    #[error(transparent)]
36    Io(#[from] std::io::Error),
37
38    #[error(transparent)]
39    Serialize(#[from] toml::ser::Error),
40
41    #[error(transparent)]
42    Deserialize(#[from] toml::de::Error),
43}
44
45/// Configuration file related semantic errors.
46#[derive(Debug, Error, Clone, PartialEq)]
47pub enum LibCfgError {
48    /// CfgVal can only be deserialized with data whose root element
49    /// is a `Value::Table`.
50    /// This should not happen. Please file a bug report.
51    #[error("Input data root must be a `Value::Table`")]
52    CfgValInputIsNotTable,
53
54    /// Remedy: Choose another scheme.
55    #[error(
56        "Configuration file error in section:\n\
57        \t[[scheme]]\n\
58        \tscheme_default = \"{scheme_name}\"\n\
59        No scheme found. Available configured schemes:\n\
60        {schemes}
61        "
62    )]
63    SchemeNotFound {
64        scheme_name: String,
65        schemes: String,
66    },
67
68    /// Remedy: Choose another `sort_tag.extra_separator` character.
69    #[error(
70        "Configuration file error in section:\n\
71        \t[[scheme]]\n\
72        \tname = \"{scheme_name}\"
73        \t[scheme.filename]\n\
74        \tsort_tag.extra_separator=\"{extra_separator}\"\n\
75        must not be one of `sort_tag_extra_chars=\"{sort_tag_extra_chars}\"`,\n\
76        `0..9`, `a..z` or `{dot_file_marker}`."
77    )]
78    SortTagExtraSeparator {
79        scheme_name: String,
80        dot_file_marker: char,
81        sort_tag_extra_chars: String,
82        extra_separator: String,
83    },
84
85    /// Remedy: Choose another `extension_default` out of
86    /// `extensions[..].0`.
87    #[error(
88        "Configuration file error in section:\n\
89        \t[[scheme]]\n\
90        \tname = \"{scheme_name}\"
91        \t[scheme.filename]\n\
92        \t`extension_default=\"{extension_default}\"\n\
93        must not be one of:`\n\
94        \t{extensions}."
95    )]
96    ExtensionDefault {
97        scheme_name: String,
98        extension_default: String,
99        extensions: String,
100    },
101
102    /// Remedy: Insert `sort_tag.separator` in `sort_tag.extra_chars`.
103    #[error(
104        "Configuration file error in section:\n\
105        \t[[scheme]]\n\
106        \tname = \"{scheme_name}\"
107        \t[scheme.filename]\n\
108        All characters in `sort_tag.separator=\"{separator}\"\n\
109        must be in the set `sort_tag.extra_chars=\"{chars}\"`,\n\
110        or in `0..9`, `a..z``\n\
111        must NOT start with `{dot_file_marker}`."
112    )]
113    SortTagSeparator {
114        scheme_name: String,
115        dot_file_marker: char,
116        chars: String,
117        separator: String,
118    },
119
120    /// Remedy: Choose a `copy_counter.extra_separator` in the set.
121    #[error(
122        "Configuration file error in section:\n\
123        \t[[scheme]]\n\
124        \tname = \"{scheme_name}\"
125        \t[scheme.filename]\n\
126        `copy_counter.extra_separator=\"{extra_separator}\"`\n\
127        must be one of: \"{chars}\""
128    )]
129    CopyCounterExtraSeparator {
130        scheme_name: String,
131        chars: String,
132        extra_separator: String,
133    },
134
135    /// Remedy: check the configuration file variable `tmpl.filter.assert_preconditions`.
136    #[error("choose one of: `IsDefined`, `IsString`, `IsNumber`, `IsStringOrNumber`, `IsBool`, `IsValidSortTag`")]
137    ParseAssertPrecondition,
138
139    /// Remedy: check the configuration file variable `arg_default.export_link_rewriting`.
140    #[error("choose one of: `off`, `short` or `long`")]
141    ParseLocalLinkKind,
142
143    /// Remedy: check the ISO 639-1 codes in the configuration variable
144    /// `tmpl.filter.get_lang` and make sure that they are supported, by
145    /// checking `tpnote -V`.
146    #[error(
147        "The ISO 639-1 language subtag `{language_code}`\n\
148         in the configuration file variable\n\
149         `tmpl.filter.get_lang` or in the environment\n\
150         variable `TPNOTE_LANG_DETECTION` is not supported.\n\
151         All listed codes must be part of the set:\n\
152         {all_langs}."
153    )]
154    ParseLanguageCode {
155        language_code: String,
156        all_langs: String,
157    },
158
159    /// Remedy: add one more ISO 639-1 code in the configuration variable
160    /// `tmpl.filter.get_lang` (or in `TPNOTE_LANG_DETECTION`) and make
161    /// sure that the code is supported, by checking `tpnote -V`.
162    #[error(
163        "Not enough languages to choose from.\n\
164         The list of ISO 639-1 language subtags\n\
165         currently contains only one item: `{language_code}`.\n\
166         Add one more language to the configuration \n\
167         file variable `tmpl.filter.get_lang` or to the\n\
168         environment variable `TPNOTE_LANG_DETECTION`\n\
169         to prevent this error from occurring."
170    )]
171    NotEnoughLanguageCodes { language_code: String },
172
173    /// Remedy: correct the variable by choosing one the available themes.
174    #[error(
175        "Configuration file error in section `[tmp_html]` in line:\n\
176        \t{var} = \"{value}\"\n\
177        The theme must be one of the following set:\n\
178        {available}"
179    )]
180    HighlightingThemeName {
181        var: String,
182        value: String,
183        available: String,
184    },
185
186    #[error(transparent)]
187    Deserialize(#[from] toml::de::Error),
188}
189
190#[derive(Debug, Error)]
191/// Error type returned form methods in or related to the `note` module.
192pub enum NoteError {
193    /// Remedy: make sure, that a file starting with `path` exists.
194    #[error("<NONE FOUND: {path}...>")]
195    CanNotExpandShorthandLink { path: String },
196
197    /// Remedy: report this error. It should not happen.
198    #[error("Can not prepend header. File has one already: \n{existing_header}")]
199    CannotPrependHeader { existing_header: String },
200
201    #[error(transparent)]
202    File(#[from] FileError),
203
204    /// Remedy: Choose another scheme.
205    #[error(
206        "Invalid header variable value: no scheme `{scheme_val}` found.\n\
207         \t---\n\
208         \t{scheme_key}: {scheme_val}\n\
209         \t---\n\n\
210        Available schemes in configuration file:\n\
211        {schemes}
212        "
213    )]
214    SchemeNotFound {
215        scheme_val: String,
216        scheme_key: String,
217        schemes: String,
218    },
219
220    /// Remedy: remove invalid characters.
221    #[error(
222        "The `sort_tag` header variable contains invalid\n\
223         character(s):\n\n\
224         \t---\n\
225         \tsort_tag: {sort_tag}\n\
226         \t---\n\n\
227         Only the characters: \"{sort_tag_extra_chars}\", `0..9`\n\
228         and `a..z` (maximum {filename_sort_tag_letters_in_succession_max} in \
229         succession) are allowed."
230    )]
231    FrontMatterFieldIsInvalidSortTag {
232        sort_tag: String,
233        sort_tag_extra_chars: String,
234        filename_sort_tag_letters_in_succession_max: u8,
235    },
236
237    /// Remedy: choose another sort-tag.
238    #[error(
239        "This `sort_tag` header variable is a sequential sort-tag:\n\
240         \t---\n\
241         \tsort_tag: {sort_tag}\n\
242         \t---\n\n\
243         A file with this sort-tag exists already on disk:\n\n\
244         \t`{existing_file}`\n\n\
245         For sequential sort-tags no duplicates are allowed.\n\
246         Please choose another sort-tag.
247    "
248    )]
249    FrontMatterFieldIsDuplicateSortTag {
250        sort_tag: String,
251        existing_file: String,
252    },
253
254    /// Remedy: index the compound type?
255    #[error(
256        "The type of the front matter field `{field_name}:`\n\
257         must not be a compound type. Use a simple type, \n\
258         i.e. `String`, `Number` or `Bool` instead. Example:\n\
259         \n\
260         \t~~~~~~~~~~~~~~\n\
261         \t---\n\
262         \t{field_name}: My simple type\n\
263         \t---\n\
264         \tsome text\n\
265         \t~~~~~~~~~~~~~~"
266    )]
267    FrontMatterFieldIsCompound { field_name: String },
268
269    /// Remedy: try to enclose with quotes.
270    #[error(
271        "The (sub)type of the front matter field `{field_name}:`\n\
272         must be a non empty `String`. Example:\n\
273         \n\
274         \t~~~~~~~~~~~~~~\n\
275         \t---\n\
276         \t{field_name}: My string\n\
277         \t---\n\
278         \tsome text\n\
279         \t~~~~~~~~~~~~~~"
280    )]
281    FrontMatterFieldIsEmptyString { field_name: String },
282
283    /// Remedy: try to remove possible quotes.
284    #[error(
285        "The (sub)type of the front matter field `{field_name}:`\n\
286         must be `Bool`. Example:\n\
287         \n\
288         \t~~~~~~~~~~~~~~\n\
289         \t---\n\
290         \t{field_name}: false\n\
291         \t---\n\
292         \tsome text\n\
293         \t~~~~~~~~~~~~~~\n\
294         \n\
295         Hint: try to remove possible quotes."
296    )]
297    FrontMatterFieldIsNotBool { field_name: String },
298
299    /// Remedy: try to remove possible quotes.
300    #[error(
301        "The (sub)type of the front matter field `{field_name}:`\n\
302         must be `Number`. Example:\n\
303         \n\
304         \t~~~~~~~~~~~~~~\n\
305         \t---\n\
306         \t{field_name}: 142\n\
307         \t---\n\
308         \tsome text\n\
309         \t~~~~~~~~~~~~~~\n\
310         \n\
311         Hint: try to remove possible quotes."
312    )]
313    FrontMatterFieldIsNotNumber { field_name: String },
314
315    /// Remedy: try to enclose with quotes.
316    #[error(
317        "The (sub)type of the front matter field `{field_name}:`\n\
318         must be `String`. Example:\n\
319         \n\
320         \t~~~~~~~~~~~~~~\n\
321         \t---\n\
322         \t{field_name}: My string\n\
323         \t---\n\
324         \tsome text\n\
325         \t~~~~~~~~~~~~~~\n\
326         \n\
327         Hint: try to enclose with quotes."
328    )]
329    FrontMatterFieldIsNotString { field_name: String },
330
331    /// Remedy: correct the front matter variable `file_ext`.
332    #[error(
333        "The file extension:\n\
334        \t---\n\
335        \tfile_ext: {extension}\n\
336        \t---\n\
337        is not registered as Tp-Note file in\n\
338        your configuration file:\n\
339        \t{extensions}\n\
340        \n\
341        Choose one of the listed above or add more extensions to the\n\
342        `filename.extensions` variable in your configuration file."
343    )]
344    FrontMatterFieldIsNotTpnoteExtension {
345        extension: String,
346        extensions: String,
347    },
348
349    /// Remedy: add the missing field in the note's front matter.
350    #[error(
351        "The document is missing a `{field_name}:`\n\
352         field in its front matter:\n\
353         \n\
354         \t~~~~~~~~~~~~~~\n\
355         \t---\n\
356         \t{field_name}: \"My note\"\n\
357         \t---\n\
358         \tsome text\n\
359         \t~~~~~~~~~~~~~~\n\
360         \n\
361         Please correct the front matter if this is\n\
362         supposed to be a Tp-Note file. Ignore otherwise."
363    )]
364    FrontMatterFieldMissing { field_name: String },
365
366    /// Remedy: check front matter delimiters `----`.
367    #[error(
368        "The document (or template) has no front matter\n\
369         section. Is one `---` missing?\n\n\
370         \t~~~~~~~~~~~~~~\n\
371         \t---\n\
372         \t{compulsory_field}: My note\n\
373         \t---\n\
374         \tsome text\n\
375         \t~~~~~~~~~~~~~~\n\
376         \n\
377         Please correct the front matter if this is\n\
378         supposed to be a Tp-Note file. Ignore otherwise."
379    )]
380    FrontMatterMissing { compulsory_field: String },
381
382    /// Remedy: check YAML syntax in the note's front matter.
383    #[error(
384        "Can not parse front matter:\n\
385         \n\
386         {front_matter}\
387         \n\
388         {source_error}"
389    )]
390    InvalidFrontMatterYaml {
391        front_matter: String,
392        source_error: serde_yaml::Error,
393    },
394
395    /// Remedy: check YAML syntax in the input stream's front matter.
396    #[error(
397        "Invalid YAML field(s) in the {tmpl_var} input\n\
398        stream data found:\n\
399        {source_str}"
400    )]
401    InvalidInputYaml {
402        tmpl_var: String,
403        source_str: String,
404    },
405
406    /// Remedy: check HTML syntax in the input stream data.
407    #[error(
408        "Invalid HTML in the input stream data found:\n\
409        {source_str}"
410    )]
411    InvalidHtml { source_str: String },
412
413    /// Remedy: reconfigure `scheme.filename.extensions.1`.
414    #[error(
415        "Filter `html_to_markup` is disabled for this \n\
416        `extension_default` in table `scheme.filename.extensions.1`."
417    )]
418    HtmlToMarkupDisabled,
419
420    /// Remedy: correct link path.
421    #[error("<INVALID: {path}>")]
422    InvalidLocalPath { path: String },
423
424    #[error(transparent)]
425    Io(#[from] std::io::Error),
426
427    #[error(transparent)]
428    ParseLanguageCode(#[from] LibCfgError),
429
430    /// Remedy: check the file permission of the note file.
431    #[error("Can not read file:\n\t {path:?}\n{source}")]
432    Read { path: PathBuf, source: io::Error },
433
434    /// Remedy: check reStructuredText syntax.
435    #[error("Can not parse reStructuredText input:\n{msg}")]
436    #[cfg(feature = "renderer")]
437    RstParse { msg: String },
438
439    /// Remedy: restart with `--debug trace`.
440    #[error(
441        "Tera error:\n\
442         {source}"
443    )]
444    Tera {
445        #[from]
446        source: tera::Error,
447    },
448
449    /// Remedy: check the syntax of the Tera template in the configuration file.
450    #[error(
451        "Tera template error in configuration file\n\
452        variable \"{template_str}\":\n {source_str}"
453    )]
454    TeraTemplate {
455        source_str: String,
456        template_str: String,
457    },
458
459    #[error(transparent)]
460    Utf8Conversion {
461        #[from]
462        source: core::str::Utf8Error,
463    },
464}
465
466/// Macro to construct a `NoteError::TeraTemplate from a `Tera::Error` .
467#[macro_export]
468macro_rules! note_error_tera_template {
469    ($e:ident, $t:expr) => {
470        NoteError::TeraTemplate {
471            source_str: std::error::Error::source(&$e)
472                .unwrap_or(&tera::Error::msg(""))
473                .to_string()
474                // Remove useless information.
475                .trim_end_matches("in context while rendering '__tera_one_off'")
476                .to_string(),
477            template_str: $t,
478        }
479    };
480}