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
58impl ReadTxnPolicy {
59 pub fn try_new(retry_limit: u32, time_out: Duration) -> Result<Self, TransactionPolicyError> {
61 validate_policy_limits(retry_limit, time_out)?;
62 Ok(Self {
63 retry_limit,
64 time_out,
65 })
66 }
67
68 pub const fn default() -> Self {
70 Self {
71 retry_limit: 2,
72 time_out: Duration::from_millis(500),
73 }
74 }
75
76 pub const fn retry_limit(&self) -> u32 {
78 self.retry_limit
79 }
80
81 pub const fn time_out(&self) -> Duration {
83 self.time_out
84 }
85
86 pub const fn to_transact_option(self) -> foundationdb::TransactOption {
88 foundationdb::TransactOption {
89 retry_limit: Some(self.retry_limit),
90 time_out: Some(self.time_out),
91 is_idempotent: true,
92 }
93 }
94}
95
96impl WriteTxnPolicy {
97 pub fn try_new(retry_limit: u32, time_out: Duration) -> Result<Self, TransactionPolicyError> {
99 validate_policy_limits(retry_limit, time_out)?;
100 Ok(Self {
101 retry_limit,
102 time_out,
103 })
104 }
105
106 pub const fn default() -> Self {
108 Self {
109 retry_limit: 3,
110 time_out: Duration::from_secs(5),
111 }
112 }
113
114 pub const fn retry_limit(&self) -> u32 {
116 self.retry_limit
117 }
118
119 pub const fn time_out(&self) -> Duration {
121 self.time_out
122 }
123
124 pub const fn to_transact_option(self) -> foundationdb::TransactOption {
126 foundationdb::TransactOption {
127 retry_limit: Some(self.retry_limit),
128 time_out: Some(self.time_out),
129 is_idempotent: false,
130 }
131 }
132}
133
134pub fn idempotent_read_option() -> foundationdb::TransactOption {
139 ReadTxnPolicy::default().to_transact_option()
140}
141
142pub fn mutation_option() -> foundationdb::TransactOption {
155 WriteTxnPolicy::default().to_transact_option()
156}
157
158fn validate_policy_limits(
159 retry_limit: u32,
160 time_out: Duration,
161) -> Result<(), TransactionPolicyError> {
162 if !(MIN_RETRY_LIMIT..=MAX_RETRY_LIMIT).contains(&retry_limit) {
163 return Err(TransactionPolicyError::InvalidRetryLimit {
164 min: MIN_RETRY_LIMIT,
165 max: MAX_RETRY_LIMIT,
166 value: retry_limit,
167 });
168 }
169
170 let time_out_ms = u64::try_from(time_out.as_millis()).map_err(|_| {
171 TransactionPolicyError::InvalidTimeout {
172 min_ms: MIN_TIMEOUT_MS,
173 max_ms: MAX_TIMEOUT_MS,
174 value_ms: MAX_TIMEOUT_MS.saturating_add(1),
175 }
176 })?;
177
178 if !(MIN_TIMEOUT_MS..=MAX_TIMEOUT_MS).contains(&time_out_ms) {
179 return Err(TransactionPolicyError::InvalidTimeout {
180 min_ms: MIN_TIMEOUT_MS,
181 max_ms: MAX_TIMEOUT_MS,
182 value_ms: time_out_ms,
183 });
184 }
185
186 Ok(())
187}
188
189#[cfg(test)]
190#[path = "transaction_tests.rs"]
191mod tests;