Skip to main content

reallyme_foundationdb_kit/fdb/
error.rs

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