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}