Skip to main content

reallyme_foundationdb_kit/fdb/
error.rs

1// SPDX-FileCopyrightText: 2026 ReallyMe LLC
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Typed FoundationDB infrastructure errors.
5
6use std::num::NonZeroUsize;
7
8use thiserror::Error;
9
10use crate::fdb::tenant_name::FoundationDbTenantName;
11
12/// Top-level FoundationDB infrastructure error.
13///
14/// Higher-level kits and apps should wrap or compose this error rather than
15/// returning it directly from public business surfaces.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
17pub enum FdbError {
18    /// Validated connector configuration could not be constructed.
19    #[error("foundationdb configuration error: {reason}")]
20    Config {
21        /// Stable configuration failure reason.
22        reason: ConfigErrorReason,
23    },
24    /// FoundationDB client or database setup failed.
25    #[error("foundationdb setup error: {reason}")]
26    Setup {
27        /// Stable setup failure reason.
28        reason: FdbSetupErrorReason,
29    },
30    /// A low-level query primitive failed.
31    #[error("foundationdb query error: {reason}")]
32    Query {
33        /// Stable query failure reason.
34        reason: FdbQueryErrorReason,
35    },
36    /// Connector health verification failed.
37    #[error("foundationdb health check failed: {reason}")]
38    Health {
39        /// Stable health failure reason.
40        reason: FdbHealthErrorReason,
41    },
42    /// Tenant access, metadata, or administration failed.
43    #[error("foundationdb tenant error: {reason}")]
44    Tenant {
45        /// Stable tenant failure reason.
46        reason: TenantErrorReason,
47    },
48}
49
50/// Tenant-related failures.
51///
52/// `TenantNotProvisioned` is the production fail-closed signal: the kit will
53/// not create tenants implicitly. Operators must provision tenants via the
54/// admin helpers (under the `tenant-admin` feature) or `fdbcli`.
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
56pub enum TenantErrorReason {
57    /// The requested tenant has not been explicitly provisioned.
58    #[error("tenant `{tenant}` is not provisioned in foundationdb")]
59    NotProvisioned {
60        /// Requested logical tenant.
61        tenant: FoundationDbTenantName,
62    },
63    /// Tenant metadata could not be queried.
64    #[error("tenant lookup failed for `{tenant}`")]
65    LookupFailed {
66        /// Requested logical tenant.
67        tenant: FoundationDbTenantName,
68    },
69    /// FoundationDB could not create a tenant-scoped handle.
70    #[error("tenant `{tenant}` could not be opened")]
71    OpenFailed {
72        /// Requested logical tenant.
73        tenant: FoundationDbTenantName,
74    },
75    /// The requested create operation targeted an existing tenant.
76    #[error("tenant `{tenant}` already exists")]
77    AlreadyExists {
78        /// Requested logical tenant.
79        tenant: FoundationDbTenantName,
80    },
81    /// An operator-only tenant lifecycle operation failed.
82    #[error("tenant administration failed for `{tenant}`")]
83    AdministrationFailed {
84        /// Requested logical tenant.
85        tenant: FoundationDbTenantName,
86    },
87    /// Metadata repair and tenant deletion refuse application data that remains.
88    #[error("tenant `{tenant}` must be empty for this administration operation")]
89    RepairRequiresEmptyTenant {
90        /// Tenant that was not empty.
91        tenant: FoundationDbTenantName,
92    },
93    /// Tenant metadata is missing required key(s).
94    #[error("tenant `{tenant}` metadata is missing required field `{field:?}")]
95    MetadataMissing {
96        /// Tenant whose metadata could not be read.
97        tenant: FoundationDbTenantName,
98        /// Missing metadata field.
99        field: TenantMetadataField,
100    },
101    /// Tenant metadata is malformed and cannot be decoded.
102    #[error("tenant `{tenant}` metadata field `{field:?}` is malformed")]
103    MetadataMalformed {
104        /// Tenant whose metadata is malformed.
105        tenant: FoundationDbTenantName,
106        /// Malformed metadata field.
107        field: TenantMetadataField,
108    },
109    /// Tenant metadata schema version is not supported by this binary.
110    #[error("tenant `{tenant}` metadata schema version `{schema_version}` is unsupported")]
111    SchemaVersionUnsupported {
112        /// Tenant with incompatible schema version.
113        tenant: FoundationDbTenantName,
114        /// Encoded schema version value.
115        schema_version: u32,
116    },
117}
118
119/// Stable tenant metadata fields used in typed failures.
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub enum TenantMetadataField {
122    /// Tenant schema version.
123    SchemaVersion,
124    /// Tenant creation timestamp.
125    CreatedAt,
126}
127
128/// Validated configuration fields owned by this kit.
129#[derive(Debug, Clone, Copy, PartialEq, Eq)]
130pub enum ConfigField {
131    /// Prefix used to derive environment-variable names.
132    EnvironmentPrefix,
133    /// FoundationDB runtime API version.
134    ApiVersion,
135    /// FoundationDB cluster file path.
136    ClusterFile,
137    /// Connector readiness timeout.
138    HealthCheckTimeoutMillis,
139}
140
141/// Stable configuration validation failure reasons.
142#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
143pub enum ConfigErrorReason {
144    /// A numeric environment value could not be parsed.
145    #[error("invalid integer value in {field:?}")]
146    InvalidInteger {
147        /// Invalid configuration field.
148        field: ConfigField,
149    },
150    /// A numeric value fell outside its safe operating range.
151    #[error("value out of bounds in {field:?}")]
152    ValueOutOfBounds {
153        /// Invalid configuration field.
154        field: ConfigField,
155    },
156    /// A cluster-file path was empty, oversized, or contained a null byte.
157    #[error("fdb cluster file path is not valid")]
158    InvalidClusterFilePath,
159    /// A configured cluster file was absent, unreadable, or not a file.
160    #[error("fdb cluster file path is missing or cannot be read")]
161    MissingClusterFile,
162    /// A required configuration value was empty.
163    #[error("configuration value is empty")]
164    Empty,
165    /// An environment value was not valid Unicode.
166    #[error("configuration value in {field:?} is not valid unicode")]
167    InvalidEncoding {
168        /// Invalid configuration field.
169        field: ConfigField,
170    },
171    /// An environment prefix was not uppercase ASCII with digits/underscores.
172    #[error("environment prefix is invalid")]
173    InvalidEnvironmentPrefix,
174}
175
176/// Stable FoundationDB client and database setup failure reasons.
177#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
178pub enum FdbSetupErrorReason {
179    /// The process already initialized the singleton FoundationDB client.
180    #[error("foundationdb client is already initialized in this process")]
181    ClientAlreadyInitialized,
182    /// The selected runtime API is incompatible with the linked client.
183    #[error("api version not supported")]
184    ApiVersionUnsupported,
185    /// The FoundationDB network thread could not be booted.
186    #[error("network bootstrap failed")]
187    NetworkBootFailed,
188    /// A database handle could not be opened.
189    #[error("database open failed")]
190    DatabaseOpenFailed,
191    /// The upstream binding panicked while initializing its global client.
192    #[error("foundationdb client initialization panicked")]
193    ClientInitializationPanicked,
194}
195
196/// Stable, low-cardinality connector health failure reasons.
197#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
198pub enum FdbHealthErrorReason {
199    /// The client network thread did not process a local no-op.
200    #[error("client network thread is unavailable")]
201    NetworkUnavailable,
202    /// A read version could not be obtained from the cluster.
203    #[error("cluster is unavailable")]
204    ClusterUnavailable,
205    /// The complete health probe exceeded its configured deadline.
206    #[error("health check deadline exceeded")]
207    DeadlineExceeded,
208}
209
210/// Stable query and value-decoding failure reasons.
211#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
212pub enum FdbQueryErrorReason {
213    /// A transaction operation failed.
214    #[error("transaction failed")]
215    TransactionFailed,
216    /// A FoundationDB tuple could not be decoded.
217    #[error("tuple decode failed")]
218    TupleDecodeFailed,
219    /// A tuple did not match the required domain shape.
220    #[error("tuple shape invalid")]
221    TupleShapeInvalid,
222    /// A stored value could not be decoded.
223    #[error("value decode failed")]
224    ValueDecodeFailed,
225    /// Checked arithmetic or numeric conversion overflowed.
226    #[error("integer overflow")]
227    IntegerOverflow,
228    /// A requested page size was zero or outside policy.
229    #[error("page size invalid")]
230    InvalidPageSize,
231    /// A prefix could not produce a bounded exclusive range end.
232    #[error("range prefix invalid")]
233    InvalidRangePrefix,
234    /// A tenant metadata key could not be constructed.
235    #[error("metadata key construction invalid")]
236    MetadataKeyInvalid,
237}
238
239/// Result type returned by FoundationDB kit operations.
240pub type FdbResult<T> = Result<T, FdbError>;
241
242/// Validates a non-zero page size and returns it as a typed value.
243pub fn non_zero_page_size(value: usize) -> FdbResult<NonZeroUsize> {
244    NonZeroUsize::new(value).ok_or(FdbError::Query {
245        reason: FdbQueryErrorReason::InvalidPageSize,
246    })
247}