Skip to main content

helios_persistence/search/
errors.rs

1//! Search-specific error types.
2//!
3//! This module provides error types for search parameter operations:
4//! - Loading and parsing SearchParameter resources
5//! - Registry operations
6//! - FHIRPath extraction
7//! - Value conversion
8//! - Reindexing operations
9
10use std::fmt;
11
12use serde::{Deserialize, Serialize};
13
14/// Error during SearchParameter loading.
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub enum LoaderError {
17    /// Invalid SearchParameter resource structure.
18    InvalidResource {
19        /// Description of what was invalid.
20        message: String,
21        /// URL of the problematic parameter, if known.
22        url: Option<String>,
23    },
24
25    /// Missing required field in SearchParameter.
26    MissingField {
27        /// Name of the missing field.
28        field: String,
29        /// URL of the parameter.
30        url: Option<String>,
31    },
32
33    /// Invalid FHIRPath expression in SearchParameter.
34    InvalidExpression {
35        /// The invalid expression.
36        expression: String,
37        /// Parser error message.
38        error: String,
39    },
40
41    /// Failed to read embedded parameters.
42    EmbeddedLoadFailed {
43        /// FHIR version attempted.
44        version: String,
45        /// Error message.
46        message: String,
47    },
48
49    /// Failed to read config file.
50    ConfigLoadFailed {
51        /// Path to the config file.
52        path: String,
53        /// Error message.
54        message: String,
55    },
56
57    /// Storage error when loading stored parameters.
58    StorageError {
59        /// Error message.
60        message: String,
61    },
62}
63
64impl fmt::Display for LoaderError {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        match self {
67            LoaderError::InvalidResource { message, url } => {
68                if let Some(url) = url {
69                    write!(f, "Invalid SearchParameter '{}': {}", url, message)
70                } else {
71                    write!(f, "Invalid SearchParameter: {}", message)
72                }
73            }
74            LoaderError::MissingField { field, url } => {
75                if let Some(url) = url {
76                    write!(
77                        f,
78                        "SearchParameter '{}' missing required field '{}'",
79                        url, field
80                    )
81                } else {
82                    write!(f, "SearchParameter missing required field '{}'", field)
83                }
84            }
85            LoaderError::InvalidExpression { expression, error } => {
86                write!(f, "Invalid FHIRPath expression '{}': {}", expression, error)
87            }
88            LoaderError::EmbeddedLoadFailed { version, message } => {
89                write!(
90                    f,
91                    "Failed to load embedded {} parameters: {}",
92                    version, message
93                )
94            }
95            LoaderError::ConfigLoadFailed { path, message } => {
96                write!(f, "Failed to load config from '{}': {}", path, message)
97            }
98            LoaderError::StorageError { message } => {
99                write!(f, "Storage error loading parameters: {}", message)
100            }
101        }
102    }
103}
104
105impl std::error::Error for LoaderError {}
106
107/// Error during registry operations.
108#[derive(Debug, Clone, Serialize, Deserialize)]
109pub enum RegistryError {
110    /// Parameter with this URL already exists.
111    DuplicateUrl {
112        /// The duplicate URL.
113        url: String,
114    },
115
116    /// Parameter not found in registry.
117    NotFound {
118        /// The URL or code that was not found.
119        identifier: String,
120    },
121
122    /// Invalid parameter definition.
123    InvalidDefinition {
124        /// Description of the problem.
125        message: String,
126    },
127
128    /// Registry is locked/read-only.
129    Locked {
130        /// Reason for the lock.
131        reason: String,
132    },
133}
134
135impl fmt::Display for RegistryError {
136    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137        match self {
138            RegistryError::DuplicateUrl { url } => {
139                write!(f, "SearchParameter with URL '{}' already exists", url)
140            }
141            RegistryError::NotFound { identifier } => {
142                write!(f, "SearchParameter '{}' not found", identifier)
143            }
144            RegistryError::InvalidDefinition { message } => {
145                write!(f, "Invalid SearchParameter definition: {}", message)
146            }
147            RegistryError::Locked { reason } => {
148                write!(f, "Registry is locked: {}", reason)
149            }
150        }
151    }
152}
153
154impl std::error::Error for RegistryError {}
155
156/// Error during value extraction.
157#[derive(Debug, Clone, Serialize, Deserialize)]
158pub enum ExtractionError {
159    /// FHIRPath evaluation failed.
160    EvaluationFailed {
161        /// The parameter name.
162        param_name: String,
163        /// The FHIRPath expression.
164        expression: String,
165        /// Error message.
166        error: String,
167    },
168
169    /// Value conversion failed.
170    ConversionFailed {
171        /// The parameter name.
172        param_name: String,
173        /// The expected type.
174        expected_type: String,
175        /// What was actually found.
176        actual_value: String,
177    },
178
179    /// Unsupported value type for indexing.
180    UnsupportedType {
181        /// The parameter name.
182        param_name: String,
183        /// The unsupported type.
184        value_type: String,
185    },
186
187    /// Resource is not a valid JSON object.
188    InvalidResource {
189        /// Description of the problem.
190        message: String,
191    },
192
193    /// FHIRPath expression evaluation error.
194    FhirPathError {
195        /// The FHIRPath expression that failed.
196        expression: String,
197        /// The error message from the evaluator.
198        message: String,
199    },
200
201    /// Generic value conversion error.
202    ConversionError {
203        /// Description of the conversion error.
204        message: String,
205    },
206}
207
208impl fmt::Display for ExtractionError {
209    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210        match self {
211            ExtractionError::EvaluationFailed {
212                param_name,
213                expression,
214                error,
215            } => {
216                write!(
217                    f,
218                    "Failed to evaluate '{}' for parameter '{}': {}",
219                    expression, param_name, error
220                )
221            }
222            ExtractionError::ConversionFailed {
223                param_name,
224                expected_type,
225                actual_value,
226            } => {
227                write!(
228                    f,
229                    "Cannot convert '{}' to {} for parameter '{}'",
230                    actual_value, expected_type, param_name
231                )
232            }
233            ExtractionError::UnsupportedType {
234                param_name,
235                value_type,
236            } => {
237                write!(
238                    f,
239                    "Unsupported value type '{}' for parameter '{}'",
240                    value_type, param_name
241                )
242            }
243            ExtractionError::InvalidResource { message } => {
244                write!(f, "Invalid resource: {}", message)
245            }
246            ExtractionError::FhirPathError {
247                expression,
248                message,
249            } => {
250                write!(f, "FHIRPath error evaluating '{}': {}", expression, message)
251            }
252            ExtractionError::ConversionError { message } => {
253                write!(f, "Conversion error: {}", message)
254            }
255        }
256    }
257}
258
259impl std::error::Error for ExtractionError {}
260
261/// Error during reindex operations.
262#[derive(Debug, Clone, Serialize, Deserialize)]
263pub enum ReindexError {
264    /// Reindex job not found.
265    JobNotFound {
266        /// The job ID.
267        job_id: String,
268    },
269
270    /// Reindex job already running.
271    AlreadyRunning {
272        /// The existing job ID.
273        existing_job_id: String,
274    },
275
276    /// Failed to process resource during reindex.
277    ProcessingFailed {
278        /// Resource type.
279        resource_type: String,
280        /// Resource ID.
281        resource_id: String,
282        /// Error message.
283        error: String,
284    },
285
286    /// Storage error during reindex.
287    StorageError {
288        /// Error message.
289        message: String,
290    },
291
292    /// Reindex was cancelled.
293    Cancelled {
294        /// Job ID that was cancelled.
295        job_id: String,
296    },
297}
298
299impl fmt::Display for ReindexError {
300    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301        match self {
302            ReindexError::JobNotFound { job_id } => {
303                write!(f, "Reindex job '{}' not found", job_id)
304            }
305            ReindexError::AlreadyRunning { existing_job_id } => {
306                write!(
307                    f,
308                    "Reindex already running with job ID '{}'",
309                    existing_job_id
310                )
311            }
312            ReindexError::ProcessingFailed {
313                resource_type,
314                resource_id,
315                error,
316            } => {
317                write!(
318                    f,
319                    "Failed to reindex {}/{}: {}",
320                    resource_type, resource_id, error
321                )
322            }
323            ReindexError::StorageError { message } => {
324                write!(f, "Storage error during reindex: {}", message)
325            }
326            ReindexError::Cancelled { job_id } => {
327                write!(f, "Reindex job '{}' was cancelled", job_id)
328            }
329        }
330    }
331}
332
333impl std::error::Error for ReindexError {}
334
335#[cfg(test)]
336mod tests {
337    use super::*;
338
339    #[test]
340    fn test_loader_error_display() {
341        let err = LoaderError::MissingField {
342            field: "expression".to_string(),
343            url: Some("http://example.org/SearchParameter/test".to_string()),
344        };
345        assert!(err.to_string().contains("expression"));
346        assert!(err.to_string().contains("test"));
347    }
348
349    #[test]
350    fn test_registry_error_display() {
351        let err = RegistryError::DuplicateUrl {
352            url: "http://example.org/sp".to_string(),
353        };
354        assert!(err.to_string().contains("already exists"));
355    }
356
357    #[test]
358    fn test_extraction_error_display() {
359        let err = ExtractionError::EvaluationFailed {
360            param_name: "name".to_string(),
361            expression: "Patient.name".to_string(),
362            error: "syntax error".to_string(),
363        };
364        assert!(err.to_string().contains("name"));
365        assert!(err.to_string().contains("Patient.name"));
366    }
367
368    #[test]
369    fn test_reindex_error_display() {
370        let err = ReindexError::ProcessingFailed {
371            resource_type: "Patient".to_string(),
372            resource_id: "123".to_string(),
373            error: "database error".to_string(),
374        };
375        assert!(err.to_string().contains("Patient/123"));
376    }
377}