1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
use crate::errors::FirestoreError;
use crate::FirestoreInstant;
use crate::{FirestoreConsistencySelector, FirestoreRequestOptions, FirestoreWriteResult};
use rsb_derive::Builder;
use std::time::Duration;
/// Options for configuring a Firestore transaction.
///
/// These options control the behavior of a transaction, such as its mode (read-only or read-write)
/// and consistency requirements for read-only transactions.
#[derive(Debug, Eq, PartialEq, Clone, Builder)]
pub struct FirestoreTransactionOptions {
/// The mode of the transaction (e.g., read-only, read-write).
/// Defaults to [`FirestoreTransactionMode::ReadWrite`].
#[default = "FirestoreTransactionMode::ReadWrite"]
pub mode: FirestoreTransactionMode,
/// How many times a transaction that failed transiently is retried after its first
/// attempt. Defaults to 4, which means at most 5 attempts in total.
#[default = "4"]
pub max_retries: usize,
/// An optional cap on how long a transaction keeps retrying, counted from its first
/// failure, on top of [`max_retries`](Self::max_retries). A retry runs only if its delay,
/// the backoff's or a callback's `retry_after`, ends within the cap, so zero means no retry.
/// `None`, the default, sets no cap.
pub retry_max_elapsed_time: Option<Duration>,
/// Concurrent transaction mode
pub concurrent_mode:
Option<gcloud_sdk::google::firestore::v1::transaction_options::ConcurrencyMode>,
/// Request options (e.g. request tags) attached to the `BeginTransaction`,
/// `Commit` and `Rollback` requests of this transaction.
pub request_options: Option<FirestoreRequestOptions>,
}
impl Default for FirestoreTransactionOptions {
fn default() -> Self {
Self {
mode: FirestoreTransactionMode::ReadWrite,
max_retries: 4,
retry_max_elapsed_time: None,
concurrent_mode: None,
request_options: None,
}
}
}
impl TryFrom<FirestoreTransactionOptions>
for gcloud_sdk::google::firestore::v1::TransactionOptions
{
type Error = FirestoreError;
fn try_from(options: FirestoreTransactionOptions) -> Result<Self, Self::Error> {
match options.mode {
FirestoreTransactionMode::ReadOnly => {
Ok(gcloud_sdk::google::firestore::v1::TransactionOptions {
mode: Some(
gcloud_sdk::google::firestore::v1::transaction_options::Mode::ReadOnly(
gcloud_sdk::google::firestore::v1::transaction_options::ReadOnly {
consistency_selector: None,
},
),
),
})
}
FirestoreTransactionMode::ReadOnlyWithConsistency(ref selector) => {
Ok(gcloud_sdk::google::firestore::v1::TransactionOptions {
mode: Some(
gcloud_sdk::google::firestore::v1::transaction_options::Mode::ReadOnly(
gcloud_sdk::google::firestore::v1::transaction_options::ReadOnly {
consistency_selector: Some(selector.try_into()?),
},
),
),
})
}
FirestoreTransactionMode::ReadWrite => {
Ok(gcloud_sdk::google::firestore::v1::TransactionOptions {
mode: Some(
gcloud_sdk::google::firestore::v1::transaction_options::Mode::ReadWrite(
gcloud_sdk::google::firestore::v1::transaction_options::ReadWrite {
retry_transaction: vec![],
concurrency_mode: options.concurrent_mode.map_or_else(
|| gcloud_sdk::google::firestore::v1::transaction_options::ConcurrencyMode::Unspecified.into(),
|m| m.into(),
),
},
),
),
})
}
FirestoreTransactionMode::ReadWriteRetry(tid) => {
Ok(gcloud_sdk::google::firestore::v1::TransactionOptions {
mode: Some(
gcloud_sdk::google::firestore::v1::transaction_options::Mode::ReadWrite(
gcloud_sdk::google::firestore::v1::transaction_options::ReadWrite {
retry_transaction: tid,
concurrency_mode: options.concurrent_mode.map_or_else(
|| gcloud_sdk::google::firestore::v1::transaction_options::ConcurrencyMode::Unspecified.into(),
|m| m.into(),
),
},
),
),
})
}
}
}
}
/// Defines the mode of a Firestore transaction.
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum FirestoreTransactionMode {
/// A read-only transaction.
///
/// In this mode, only read operations are allowed. The transaction will use
/// strong consistency by default, reading the latest version of data.
ReadOnly,
/// A read-write transaction.
///
/// This is the default mode. Both read and write operations are allowed.
/// Firestore ensures atomicity for all operations within the transaction.
ReadWrite,
/// A read-only transaction with a specific consistency requirement.
///
/// Allows specifying how data should be read, for example, at a particular
/// A read-write transaction.src/db/transaction_models.rs:36:28, at a particular
/// point in time using [`FirestoreConsistencySelector::ReadTime`].
ReadOnlyWithConsistency(FirestoreConsistencySelector),
/// A read-write transaction that attempts to retry a previous transaction.
///
/// This is used internally by the client when retrying a transaction that
/// failed due to contention or other transient issues. The `FirestoreTransactionId`
/// is the ID of the transaction to retry.
ReadWriteRetry(FirestoreTransactionId),
}
/// A type alias for Firestore transaction IDs.
/// Transaction IDs are represented as a vector of bytes.
pub type FirestoreTransactionId = Vec<u8>;
/// Represents the response from committing a Firestore transaction.
#[derive(Debug, PartialEq, Clone, Builder)]
pub struct FirestoreTransactionResponse {
/// A list of results for each write operation performed within the transaction.
/// Each [`FirestoreWriteResult`] provides information about a specific write,
/// such as its update time.
pub write_results: Vec<FirestoreWriteResult>,
/// The time at which the transaction was committed.
/// This is `None` if the transaction was read-only or did not involve writes.
pub commit_time: Option<FirestoreInstant>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_transaction_options_default_request_options() {
assert!(FirestoreTransactionOptions::default()
.request_options
.is_none());
let options = FirestoreTransactionOptions::new()
.with_request_options(FirestoreRequestOptions::from_tags(["checkout"]));
assert_eq!(
options.request_options,
Some(FirestoreRequestOptions::from_tags(["checkout"]))
);
}
#[test]
fn default_and_new_agree() {
assert_eq!(
FirestoreTransactionOptions::default(),
FirestoreTransactionOptions::new()
);
assert_eq!(FirestoreTransactionOptions::new().max_retries, 4);
}
}