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}