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    /// Tenant metadata is missing required key(s).
88    #[error("tenant `{tenant}` metadata is missing required field `{field:?}")]
89    MetadataMissing {
90        /// Tenant whose metadata could not be read.
91        tenant: FoundationDbTenantName,
92        /// Missing metadata field.
93        field: TenantMetadataField,
94    },
95    /// Tenant metadata is malformed and cannot be decoded.
96    #[error("tenant `{tenant}` metadata field `{field:?}` is malformed")]
97    MetadataMalformed {
98        /// Tenant whose metadata is malformed.
99        tenant: FoundationDbTenantName,
100        /// Malformed metadata field.
101        field: TenantMetadataField,
102    },
103    /// Tenant metadata schema version is not supported by this binary.
104    #[error("tenant `{tenant}` metadata schema version `{schema_version}` is unsupported")]
105    SchemaVersionUnsupported {
106        /// Tenant with incompatible schema version.
107        tenant: FoundationDbTenantName,
108        /// Encoded schema version value.
109        schema_version: u32,
110    },
111}
112
113/// Stable tenant metadata fields used in typed failures.
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115pub enum TenantMetadataField {
116    /// Tenant schema version.
117    SchemaVersion,
118    /// Tenant creation timestamp.
119    CreatedAt,
120}
121
122/// Validated configuration fields owned by this kit.
123#[derive(Debug, Clone, Copy, PartialEq, Eq)]
124pub enum ConfigField {
125    /// Prefix used to derive environment-variable names.
126    EnvironmentPrefix,
127    /// FoundationDB runtime API version.
128    ApiVersion,
129    /// FoundationDB cluster file path.
130    ClusterFile,
131    /// Connector readiness timeout.
132    HealthCheckTimeoutMillis,
133}
134
135/// Stable configuration validation failure reasons.
136#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
137pub enum ConfigErrorReason {
138    /// A numeric environment value could not be parsed.
139    #[error("invalid integer value in {field:?}")]
140    InvalidInteger {
141        /// Invalid configuration field.
142        field: ConfigField,
143    },
144    /// A numeric value fell outside its safe operating range.
145    #[error("value out of bounds in {field:?}")]
146    ValueOutOfBounds {
147        /// Invalid configuration field.
148        field: ConfigField,
149    },
150    /// A cluster-file path was empty, oversized, or contained a null byte.
151    #[error("fdb cluster file path is not valid")]
152    InvalidClusterFilePath,
153    /// A configured cluster file was absent, unreadable, or not a file.
154    #[error("fdb cluster file path is missing or cannot be read")]
155    MissingClusterFile,
156    /// A required configuration value was empty.
157    #[error("configuration value is empty")]
158    Empty,
159    /// An environment value was not valid Unicode.
160    #[error("configuration value in {field:?} is not valid unicode")]
161    InvalidEncoding {
162        /// Invalid configuration field.
163        field: ConfigField,
164    },
165    /// An environment prefix was not uppercase ASCII with digits/underscores.
166    #[error("environment prefix is invalid")]
167    InvalidEnvironmentPrefix,
168}
169
170/// Stable FoundationDB client and database setup failure reasons.
171#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
172pub enum FdbSetupErrorReason {
173    /// The process already initialized the singleton FoundationDB client.
174    #[error("foundationdb client is already initialized in this process")]
175    ClientAlreadyInitialized,
176    /// The selected runtime API is incompatible with the linked client.
177    #[error("api version not supported")]
178    ApiVersionUnsupported,
179    /// The FoundationDB network thread could not be booted.
180    #[error("network bootstrap failed")]
181    NetworkBootFailed,
182    /// A database handle could not be opened.
183    #[error("database open failed")]
184    DatabaseOpenFailed,
185    /// The upstream binding panicked while initializing its global client.
186    #[error("foundationdb client initialization panicked")]
187    ClientInitializationPanicked,
188}
189
190/// Stable, low-cardinality connector health failure reasons.
191#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
192pub enum FdbHealthErrorReason {
193    /// The client network thread did not process a local no-op.
194    #[error("client network thread is unavailable")]
195    NetworkUnavailable,
196    /// A read version could not be obtained from the cluster.
197    #[error("cluster is unavailable")]
198    ClusterUnavailable,
199    /// The complete health probe exceeded its configured deadline.
200    #[error("health check deadline exceeded")]
201    DeadlineExceeded,
202}
203
204/// Stable query and value-decoding failure reasons.
205#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
206pub enum FdbQueryErrorReason {
207    /// A transaction operation failed.
208    #[error("transaction failed")]
209    TransactionFailed,
210    /// A FoundationDB tuple could not be decoded.
211    #[error("tuple decode failed")]
212    TupleDecodeFailed,
213    /// A tuple did not match the required domain shape.
214    #[error("tuple shape invalid")]
215    TupleShapeInvalid,
216    /// A stored value could not be decoded.
217    #[error("value decode failed")]
218    ValueDecodeFailed,
219    /// Checked arithmetic or numeric conversion overflowed.
220    #[error("integer overflow")]
221    IntegerOverflow,
222    /// A requested page size was zero or outside policy.
223    #[error("page size invalid")]
224    InvalidPageSize,
225    /// A prefix could not produce a bounded exclusive range end.
226    #[error("range prefix invalid")]
227    InvalidRangePrefix,
228    /// A tenant metadata key could not be constructed.
229    #[error("metadata key construction invalid")]
230    MetadataKeyInvalid,
231}
232
233/// Result type returned by FoundationDB kit operations.
234pub type FdbResult<T> = Result<T, FdbError>;
235
236/// Validates a non-zero page size and returns it as a typed value.
237pub fn non_zero_page_size(value: usize) -> FdbResult<NonZeroUsize> {
238    NonZeroUsize::new(value).ok_or(FdbError::Query {
239        reason: FdbQueryErrorReason::InvalidPageSize,
240    })
241}