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}