1use thiserror::Error;
2
3use ahri_tre_types::EncryptionMode;
4
5#[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
85pub(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}