Skip to main content

asana_cli/
error.rs

1//! Typed error surface for the Asana CLI library.
2//!
3//! Per `REPO_INVARIANTS.md` (Error handling) the library exposes a `thiserror`
4//! enum with one variant per distinct failure mode and `#[from]` seams onto the
5//! domain error types that already live deeper in the crate ([`ApiError`],
6//! [`RichTextError`]). The binary edge (`main.rs`, `cli/`, `output/`) erases
7//! this type at the top of the call stack; library code never depends on the
8//! edge error crate.
9
10use std::path::PathBuf;
11
12use crate::api::error::ApiError;
13use crate::rich_text::RichTextError;
14
15/// All errors raised by the Asana CLI library layer.
16#[derive(Debug, thiserror::Error)]
17pub enum Error {
18    /// A filesystem operation failed.
19    #[error("I/O error: {0}")]
20    Io(#[from] std::io::Error),
21
22    /// A configuration or template TOML document failed to parse.
23    #[error("failed to parse TOML: {0}")]
24    TomlDeserialize(#[from] toml::de::Error),
25
26    /// Configuration could not be encoded to TOML for persistence.
27    #[error("failed to encode TOML: {0}")]
28    TomlSerialize(#[from] toml::ser::Error),
29
30    /// A user-supplied regular expression in a filter expression was invalid.
31    #[error("invalid filter regex: {0}")]
32    Regex(#[from] regex::Error),
33
34    /// An Asana API interaction failed.
35    #[error(transparent)]
36    Api(#[from] ApiError),
37
38    /// Rich-text preparation or validation failed.
39    #[error(transparent)]
40    RichText(#[from] RichTextError),
41
42    /// The platform's standard project directories could not be resolved.
43    #[error("{0}")]
44    ProjectDirs(String),
45
46    /// A filter expression was empty.
47    #[error("filter expression cannot be empty")]
48    EmptyFilter,
49
50    /// A filter expression did not match any supported operator syntax.
51    #[error(
52        "unable to parse filter expression '{0}'; \
53         expected syntax field=value | field!=value | field~regex | field:substring"
54    )]
55    UnparseableFilter(String),
56
57    /// A named saved-filter set could not be located on disk.
58    #[error("saved filter '{name}' not found in {path}")]
59    SavedFilterNotFound {
60        /// The requested filter set name.
61        name: String,
62        /// The directory searched.
63        path: PathBuf,
64    },
65
66    /// A named saved-filter set existed but contained no usable expressions.
67    #[error("saved filter '{0}' does not contain any expressions")]
68    EmptySavedFilter(String),
69
70    /// A filter set was asked to be saved without any expressions.
71    #[error("cannot save filter '{0}' without at least one filter expression")]
72    NoFilterExpressions(String),
73
74    /// A sort field name was not one of the supported values.
75    #[error("unsupported sort field '{0}'; supported values: name, created_at, modified_at")]
76    UnsupportedSortField(String),
77
78    /// A named project template could not be located.
79    #[error("template '{name}' not found; place templates in {search_dir}")]
80    TemplateNotFound {
81        /// The requested template identifier.
82        name: String,
83        /// The templates directory searched.
84        search_dir: PathBuf,
85    },
86
87    /// The global tracing subscriber could not be installed.
88    #[error("failed to initialise tracing: {0}")]
89    TracingInit(String),
90}
91
92/// Result type alias for the Asana CLI library, carrying the typed [`Error`].
93///
94/// The binary edge does not use this alias; it imports its own edge-level
95/// `Result` directly and collapses [`Error`] via `?` (the edge error crate
96/// converts any `std::error::Error`).
97pub type Result<T> = std::result::Result<T, Error>;