Skip to main content

ahri_tre_lake/
error.rs

1use thiserror::Error;
2
3use ahri_tre_types::EncryptionMode;
4
5/// Errors produced while attaching DuckLake catalogs or moving lake files.
6#[derive(Debug, Error)]
7pub enum LakeError {
8    #[error("Query dependencies or syntax are not admissible")]
9    QueryNotAdmissible,
10    #[error("Governance ledger evidence is unavailable or conflicts with the retained event")]
11    GovernanceEvidenceConflict,
12    #[error("Dataset output ownership is unavailable")]
13    OutputAuthority(#[from] ahri_tre_core::CoreError),
14    #[error("Dataset output version is already occupied")]
15    DatasetOutputOccupied,
16
17    #[error("{0}")]
18    Scratch(#[from] crate::ScratchError),
19    #[error("lake root must not be empty")]
20    EmptyLakeRoot,
21    #[error("{kind} credential must not be empty")]
22    EmptyCredential { kind: &'static str },
23    #[error("credential material is not valid UTF-8")]
24    InvalidCredentialEncoding,
25    #[error("filesystem Datastore prefix is not canonical")]
26    InvalidDatastorePrefix,
27    #[error("DuckLake catalog identity is incomplete")]
28    IncompleteCatalogIdentity,
29    #[error("object-storage namespace identity is invalid")]
30    InvalidObjectStorageIdentity,
31    #[error("Datastore Lake namespace is not empty")]
32    NamespaceNotEmpty,
33    #[error("object-storage authentication is incompatible with the selected provider")]
34    IncompatibleStorageAuthentication,
35    #[error("ambient object-storage authority {name} is not permitted")]
36    AmbientStorageAuthority { name: String },
37    #[error("configured workload-identity source {kind} is unavailable")]
38    MissingWorkloadIdentitySource { kind: &'static str },
39    #[error("configured workload identity could not supply credentials")]
40    WorkloadIdentityUnavailable,
41    #[error("connection-scoped credential projection failed")]
42    CredentialProjectionFailed,
43    #[error("custom catalog TLS trust can only be projected while opening a connection")]
44    CustomTlsRequiresOpen,
45    #[error("DuckLake persisted data path does not match the Datastore binding")]
46    PersistedDataPathMismatch,
47    #[error("DuckLake supports server-managed encryption only, got {0:?}")]
48    UnsupportedEncryptionMode(EncryptionMode),
49    #[error("DuckLake metadata value for {key} is invalid: {value}")]
50    InvalidMetadataValue { key: &'static str, value: String },
51    #[error("invalid datafile source filename: {path}")]
52    InvalidFileName { path: String },
53    #[error("invalid datafile metadata for {field}: {message}")]
54    InvalidDataFileMetadata {
55        field: &'static str,
56        message: String,
57    },
58    #[error("staged datafile already exists: {storage_relative_path}")]
59    DataFileAlreadyExists { storage_relative_path: String },
60    #[error(
61        "lake operation failed and physical rollback also failed: operation={operation}; rollback={rollback}"
62    )]
63    PhysicalRollback { operation: String, rollback: String },
64    #[error("lake filesystem operation failed: {0}")]
65    Io(#[from] std::io::Error),
66    #[error("lake cryptographic operation failed")]
67    Crypto(#[from] openssl::error::ErrorStack),
68    #[error("Arrow IPC operation failed: {0}")]
69    Arrow(#[from] arrow_schema::ArrowError),
70    #[error("tabular operation failed: {0}")]
71    Tabular(#[from] ahri_tre_tabular::TabularError),
72    #[error("DuckDB operation failed: {0}")]
73    DuckDb(#[from] duckdb::Error),
74}
75
76impl LakeError {
77    pub fn ambient_authority_name(&self) -> Option<&str> {
78        match self {
79            Self::AmbientStorageAuthority { name } => Some(name),
80            _ => None,
81        }
82    }
83}
84
85/// Classify typed adapter evidence before any error is projected into App text.
86pub(crate) fn finish_dataset_observation<T>(
87    span: Option<ahri_tre_observability::OperationSpan>,
88    result: &Result<T, LakeError>,
89    evidence: &[ahri_tre_observability::Measurement],
90) {
91    use ahri_tre_observability::{FailureCategory, Outcome};
92    let Some(span) = span else { return };
93    let (outcome, category) = match result {
94        Ok(_) => (Outcome::Success, None),
95        Err(LakeError::Io(error)) if error.kind() == std::io::ErrorKind::TimedOut => {
96            (Outcome::Timeout, Some(FailureCategory::Storage))
97        }
98        Err(error) => (
99            Outcome::Unavailable,
100            Some(match error {
101                LakeError::Io(_) | LakeError::Scratch(_) | LakeError::InvalidFileName { .. } => {
102                    FailureCategory::Storage
103                }
104                LakeError::DuckDb(_) | LakeError::Arrow(_) | LakeError::Tabular(_) => {
105                    FailureCategory::Catalog
106                }
107                LakeError::OutputAuthority(_) => FailureCategory::Metadata,
108                LakeError::PhysicalRollback { .. } => FailureCategory::Cleanup,
109                _ => FailureCategory::Adapter,
110            }),
111        ),
112    };
113    span.finish_with_evidence(outcome, category, evidence);
114}