Skip to main content

bids_core/
error.rs

1//! Error types for the BIDS crate ecosystem.
2//!
3//! All fallible operations across `bids-*` crates return [`Result<T>`], which
4//! uses [`BidsError`] as the error type. The error variants cover the full
5//! range of failure modes: filesystem I/O, JSON parsing, BIDS validation,
6//! entity resolution, query filtering, database operations, and path building.
7
8use thiserror::Error;
9
10/// A specialized `Result` type for BIDS operations.
11///
12/// This is defined as `std::result::Result<T, BidsError>` and is used
13/// throughout the `bids-*` crate ecosystem.
14pub type Result<T> = std::result::Result<T, BidsError>;
15
16/// Errors that can occur when working with BIDS datasets.
17///
18/// This enum covers all failure modes across the crate ecosystem, from
19/// low-level I/O errors to high-level BIDS validation failures. It implements
20/// `From<std::io::Error>` and `From<serde_json::Error>` for convenient
21/// error propagation with `?`.
22///
23/// # Example
24///
25/// ```
26/// use bids_core::BidsError;
27///
28/// let err = BidsError::validation("Missing required field");
29/// assert!(err.is_validation());
30/// assert!(!err.is_io());
31/// ```
32#[derive(Error, Debug)]
33#[non_exhaustive]
34pub enum BidsError {
35    #[error("IO error: {0}")]
36    Io(#[from] std::io::Error),
37
38    #[error("JSON error: {0}")]
39    Json(#[from] serde_json::Error),
40
41    #[error("BIDS validation error: {0}")]
42    Validation(String),
43
44    #[error("BIDS root does not exist: {0}")]
45    RootNotFound(String),
46
47    #[error("Missing dataset_description.json in project root")]
48    MissingDatasetDescription,
49
50    #[error("Missing mandatory field '{field}' in dataset_description.json")]
51    MissingMandatoryField { field: String },
52
53    #[error("Derivatives validation error: {0}")]
54    DerivativesValidation(String),
55
56    #[error("Entity error: {0}")]
57    Entity(String),
58
59    #[error("Invalid target entity: {0}")]
60    InvalidTarget(String),
61
62    #[error("No match found: {0}")]
63    NoMatch(String),
64
65    #[error("Invalid filter: {0}")]
66    InvalidFilter(String),
67
68    #[error("Conflicting values: {0}")]
69    ConflictingValues(String),
70
71    #[error("Database error: {0}")]
72    Database(String),
73
74    #[error("CSV error: {0}")]
75    Csv(String),
76
77    #[error("Config error: {0}")]
78    Config(String),
79
80    #[error("Path building error: {0}")]
81    PathBuilding(String),
82
83    #[error("File type error: {0}")]
84    FileType(String),
85
86    #[error("HTTP error: {0}")]
87    Http(String),
88
89    #[error("Data format error: {0}")]
90    DataFormat(String),
91
92    #[error("Network/download error: {0}")]
93    Network(String),
94
95    #[error("API error: {0}")]
96    Api(String),
97}
98
99impl BidsError {
100    /// Create a validation error with a message.
101    #[must_use]
102    pub fn validation(msg: impl Into<String>) -> Self {
103        Self::Validation(msg.into())
104    }
105
106    /// Create an entity error with a message.
107    #[must_use]
108    pub fn entity(msg: impl Into<String>) -> Self {
109        Self::Entity(msg.into())
110    }
111
112    /// Create a data format error with a message.
113    #[must_use]
114    pub fn data_format(msg: impl Into<String>) -> Self {
115        Self::DataFormat(msg.into())
116    }
117
118    /// Returns `true` if this is an I/O error.
119    #[must_use]
120    pub fn is_io(&self) -> bool {
121        matches!(self, Self::Io(_))
122    }
123
124    /// Returns `true` if this is a validation error.
125    #[must_use]
126    pub fn is_validation(&self) -> bool {
127        matches!(self, Self::Validation(_))
128    }
129
130    /// Returns `true` if this is a "not found" type error.
131    #[must_use]
132    pub fn is_not_found(&self) -> bool {
133        matches!(
134            self,
135            Self::RootNotFound(_) | Self::MissingDatasetDescription | Self::NoMatch(_)
136        )
137    }
138}