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