reallyme_foundationdb_kit/fdb/
transaction.rs1use std::time::Duration;
7use thiserror::Error;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
11pub enum TransactionPolicyError {
12 #[error("retry limit must be between {min} and {max}")]
14 InvalidRetryLimit {
15 min: u32,
17 max: u32,
19 value: u32,
21 },
22 #[error("transaction timeout must be between {min_ms}ms and {max_ms}ms")]
24 InvalidTimeout {
25 min_ms: u64,
27 max_ms: u64,
29 value_ms: u64,
31 },
32}
33
34const MIN_RETRY_LIMIT: u32 = 1;
35const MAX_RETRY_LIMIT: u32 = 16;
36const MIN_TIMEOUT_MS: u64 = 1;
37const MAX_TIMEOUT_MS: u64 = 60_000;
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub struct ReadTxnPolicy {
44 retry_limit: u32,
45 time_out: Duration,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub struct WriteTxnPolicy {
54 retry_limit: u32,
55 time_out: Duration,
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum TenantTransactionPolicy {
61 Read(ReadTxnPolicy),
63 Write(WriteTxnPolicy),
65}
66
67impl TenantTransactionPolicy {
68 pub(crate) const fn to_transact_option(self) -> foundationdb::TransactOption {
69 match self {
70 Self::Read(policy) => policy.to_transact_option(),
71 Self::Write(policy) => policy.to_transact_option(),
72 }
73 }
74}
75
76impl ReadTxnPolicy {
77 pub fn try_new(retry_limit: u32, time_out: Duration) -> Result<Self, TransactionPolicyError> {
79 validate_policy_limits(retry_limit, time_out)?;
80 Ok(Self {
81 retry_limit,
82 time_out,
83 })
84 }
85
86 pub const fn default() -> Self {
88 Self {
89 retry_limit: 2,
90 time_out: Duration::from_millis(500),
91 }
92 }
93
94 pub const fn retry_limit(&self) -> u32 {
96 self.retry_limit
97 }
98
99 pub const fn time_out(&self) -> Duration {
101 self.time_out
102 }
103
104 pub const fn to_transact_option(self) -> foundationdb::TransactOption {
106 foundationdb::TransactOption {
107 retry_limit: Some(self.retry_limit),
108 time_out: Some(self.time_out),
109 is_idempotent: true,
110 }
111 }
112}
113
114impl WriteTxnPolicy {
115 pub fn try_new(retry_limit: u32, time_out: Duration) -> Result<Self, TransactionPolicyError> {
117 validate_policy_limits(retry_limit, time_out)?;
118 Ok(Self {
119 retry_limit,
120 time_out,
121 })
122 }
123
124 pub const fn default() -> Self {
126 Self {
127 retry_limit: 3,
128 time_out: Duration::from_secs(5),
129 }
130 }
131
132 pub const fn retry_limit(&self) -> u32 {
134 self.retry_limit
135 }
136
137 pub const fn time_out(&self) -> Duration {
139 self.time_out
140 }
141
142 pub const fn to_transact_option(self) -> foundationdb::TransactOption {
144 foundationdb::TransactOption {
145 retry_limit: Some(self.retry_limit),
146 time_out: Some(self.time_out),
147 is_idempotent: false,
148 }
149 }
150}
151
152pub fn idempotent_read_option() -> foundationdb::TransactOption {
157 ReadTxnPolicy::default().to_transact_option()
158}
159
160pub fn mutation_option() -> foundationdb::TransactOption {
173 WriteTxnPolicy::default().to_transact_option()
174}
175
176fn validate_policy_limits(
177 retry_limit: u32,
178 time_out: Duration,
179) -> Result<(), TransactionPolicyError> {
180 if !(MIN_RETRY_LIMIT..=MAX_RETRY_LIMIT).contains(&retry_limit) {
181 return Err(TransactionPolicyError::InvalidRetryLimit {
182 min: MIN_RETRY_LIMIT,
183 max: MAX_RETRY_LIMIT,
184 value: retry_limit,
185 });
186 }
187
188 let time_out_ms = u64::try_from(time_out.as_millis()).map_err(|_| {
189 TransactionPolicyError::InvalidTimeout {
190 min_ms: MIN_TIMEOUT_MS,
191 max_ms: MAX_TIMEOUT_MS,
192 value_ms: MAX_TIMEOUT_MS.saturating_add(1),
193 }
194 })?;
195
196 if !(MIN_TIMEOUT_MS..=MAX_TIMEOUT_MS).contains(&time_out_ms) {
197 return Err(TransactionPolicyError::InvalidTimeout {
198 min_ms: MIN_TIMEOUT_MS,
199 max_ms: MAX_TIMEOUT_MS,
200 value_ms: time_out_ms,
201 });
202 }
203
204 Ok(())
205}
206
207#[cfg(test)]
208#[path = "transaction_tests.rs"]
209mod tests;