vpr_core/
error.rs

1//! Error types for the VPR (Virtual Patient Record) system.
2//!
3//! This module defines the comprehensive error handling for all operations in the VPR core crate.
4//! The [`PatientError`] enum encompasses all possible failure modes across the system's functionality,
5//! including patient data management, Git versioning, cryptographic operations, and external integrations.
6//!
7//! # Error Categories
8//!
9//! Errors are organized into logical categories:
10//!
11//! - **Input Validation**: Invalid user inputs, malformed data, or constraint violations
12//! - **File System Operations**: Directory creation, file I/O, and storage management
13//! - **Serialization**: JSON and YAML encoding/decoding failures
14//! - **Git Operations**: Repository management, commits, signatures, and version control
15//! - **Cryptographic Operations**: ECDSA signing, key parsing, certificate validation
16//! - **Author Validation**: Commit author metadata and registration verification
17//! - **External Integrations**: OpenEHR system interactions
18//!
19//! # Error Handling Philosophy
20//!
21//! VPR follows defensive programming principles with comprehensive error handling:
22//!
23//! - **Fail Fast**: Invalid inputs and configuration are rejected early
24//! - **Detailed Diagnostics**: Errors include context and source information where possible
25//! - **Recovery Guidance**: Error messages are designed to be actionable for developers and operators
26//! - **Type Safety**: The [`PatientResult`] type alias provides consistent error propagation
27//!
28//! # Usage
29//!
30//! Most VPR operations return [`PatientResult<T>`] to indicate success or failure:
31//!
32//! ```rust,ignore
33//! use vpr_core::PatientResult;
34//!
35//! fn some_operation() -> PatientResult<String> {
36//!     // Operation that might fail
37//!     Ok("success".to_string())
38//! }
39//! ```
40//!
41//! Errors can be handled using standard Rust error handling patterns:
42//!
43//! ```rust,ignore
44//! match some_operation() {
45//!     Ok(result) => println!("Success: {}", result),
46//!     Err(PatientError::InvalidInput(msg)) => eprintln!("Invalid input: {}", msg),
47//!     Err(other) => eprintln!("Other error: {}", other),
48//! }
49//! ```
50
51#[allow(clippy::single_component_path_imports)]
52use serde_yaml;
53
54/// Comprehensive error type for all VPR operations.
55///
56/// This enum represents all possible failure modes in the VPR system, from basic I/O operations
57/// to complex cryptographic validation. Each variant includes relevant context and follows
58/// consistent naming and documentation patterns.
59///
60/// The error messages are designed to be informative for both developers debugging issues
61/// and operators maintaining production systems.
62#[derive(Debug, thiserror::Error)]
63pub enum PatientError {
64    #[error("invalid input: {0}")]
65    InvalidInput(String),
66    #[error("failed to create storage directory: {0}")]
67    StorageDirCreation(std::io::Error),
68    #[error("failed to create patient directory: {0}")]
69    PatientDirCreation(std::io::Error),
70    #[error(
71        "initialise failed and cleanup also failed (path: {path}): init={init_error}; cleanup={cleanup_error}",
72        path = path.display()
73    )]
74    CleanupAfterInitialiseFailed {
75        path: std::path::PathBuf,
76        #[source]
77        init_error: Box<PatientError>,
78        cleanup_error: std::io::Error,
79    },
80    #[error("failed to write patient file: {0}")]
81    FileWrite(std::io::Error),
82    #[error("failed to read patient file: {0}")]
83    FileRead(std::io::Error),
84    #[error("failed to serialize patient: {0}")]
85    Serialization(serde_json::Error),
86    #[error("failed to deserialize patient: {0}")]
87    Deserialization(serde_json::Error),
88    #[error("failed to serialize YAML: {0}")]
89    YamlSerialization(serde_yaml::Error),
90    #[error("failed to deserialize YAML: {0}")]
91    YamlDeserialization(serde_yaml::Error),
92
93    #[error("openEHR error: {0}")]
94    Openehr(#[from] openehr::OpenEhrError),
95    #[error("FHIR error: {0}")]
96    Fhir(#[from] fhir::FhirError),
97    #[error("UUID error: {0}")]
98    Uuid(#[from] vpr_uuid::UuidError),
99    #[error("failed to initialise git repository: {0}")]
100    GitInit(git2::Error),
101    #[error("failed to access git index: {0}")]
102    GitIndex(git2::Error),
103    #[error("failed to add file to git index: {0}")]
104    GitAdd(git2::Error),
105    #[error("failed to write git tree: {0}")]
106    GitWriteTree(git2::Error),
107    #[error("failed to find git tree: {0}")]
108    GitFindTree(git2::Error),
109    #[error("failed to create git signature: {0}")]
110    GitSignature(git2::Error),
111    #[error("failed to create initial git commit: {0}")]
112    GitCommit(git2::Error),
113    #[error("failed to parse PEM: {0}")]
114    PemParse(::pem::PemError),
115    #[error("failed to parse ECDSA private key: {0}")]
116    EcdsaPrivateKeyParse(Box<dyn std::error::Error + Send + Sync>),
117    #[error("failed to parse ECDSA public key/certificate: {0}")]
118    EcdsaPublicKeyParse(Box<dyn std::error::Error + Send + Sync>),
119    #[error("author certificate public key does not match signing key")]
120    AuthorCertificatePublicKeyMismatch,
121    #[error("invalid embedded commit signature payload")]
122    InvalidCommitSignaturePayload,
123    #[error("failed to sign: {0}")]
124    EcdsaSign(Box<dyn std::error::Error + Send + Sync>),
125    #[error("failed to create commit buffer: {0}")]
126    GitCommitBuffer(git2::Error),
127    #[error("failed to create signed commit: {0}")]
128    GitCommitSigned(git2::Error),
129    #[error("failed to convert commit buffer to string: {0}")]
130    CommitBufferToString(std::string::FromUtf8Error),
131    #[error("failed to open git repository: {0}")]
132    GitOpen(git2::Error),
133    #[error("failed to create/update git reference: {0}")]
134    GitReference(git2::Error),
135    #[error("failed to get git head: {0}")]
136    GitHead(git2::Error),
137    #[error("failed to set git head: {0}")]
138    GitSetHead(git2::Error),
139    #[error("failed to peel git commit: {0}")]
140    GitPeel(git2::Error),
141    #[error("invalid timestamp")]
142    InvalidTimestamp,
143
144    #[error("missing Author-Name")]
145    MissingAuthorName,
146    #[error("missing Author-Role")]
147    MissingAuthorRole,
148    #[error("invalid Author-Registration")]
149    InvalidAuthorRegistration,
150    #[error("author trailer keys are reserved")]
151    ReservedAuthorTrailerKey,
152
153    #[error("invalid Care-Location")]
154    InvalidCareLocation,
155    #[error("missing Care-Location")]
156    MissingCareLocation,
157    #[error("Care-Location trailer key is reserved")]
158    ReservedCareLocationTrailerKey,
159}
160
161/// Type alias for Results that can fail with [`PatientError`].
162///
163/// This is the standard return type for all VPR operations that may fail.
164/// Using this alias ensures consistent error handling across the codebase.
165pub type PatientResult<T> = std::result::Result<T, PatientError>;