1use crate::{msg_rows, saga_rows, tcc_rows};
20use dtmrs_core::{BranchOp, GlobalStatus, SagaStep, TransType};
21use dtmrs_store::Store;
22use serde::Serialize;
23
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub enum ApiError {
26 BadRequest(String),
27 NotFound(String),
28 Conflict(String),
30 Internal(String),
31}
32
33impl ApiError {
34 pub fn message(&self) -> &str {
35 match self {
36 Self::BadRequest(m) | Self::NotFound(m) | Self::Conflict(m) | Self::Internal(m) => m,
37 }
38 }
39}
40
41impl std::fmt::Display for ApiError {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 f.write_str(self.message())
44 }
45}
46
47pub type Result<T> = std::result::Result<T, ApiError>;
48
49fn internal(e: impl std::fmt::Display) -> ApiError {
50 ApiError::Internal(e.to_string())
51}
52
53#[derive(Debug, Clone, Serialize)]
54pub struct BranchView {
55 pub branch_id: String,
56 pub op: String,
57 pub url: String,
58 pub status: String,
59}
60
61#[derive(Debug, Clone, Serialize)]
62pub struct TransView {
63 pub gid: String,
64 pub trans_type: String,
65 pub status: String,
66 pub rollback_reason: String,
67 pub create_time: i64,
68 pub finish_time: Option<i64>,
69 pub branches: Vec<BranchView>,
70}
71
72#[derive(Debug, Clone, Default)]
74pub struct RegisterBranch {
75 pub gid: String,
76 pub branch_id: String,
77 pub confirm: String,
78 pub cancel: String,
79 pub r#try: String,
80 pub commit: String,
81 pub rollback: String,
82}
83
84#[derive(Clone)]
85pub struct Api {
86 pub store: Store,
87}
88
89impl Api {
90 pub fn new(store: Store) -> Self {
91 Self { store }
92 }
93
94 pub fn new_gid(&self) -> String {
97 use std::sync::atomic::{AtomicU64, Ordering};
98 static SEQ: AtomicU64 = AtomicU64::new(0);
99 let n = SEQ.fetch_add(1, Ordering::Relaxed);
100 format!("{}-{}", dtmrs_store::now(), n)
101 }
102
103 pub async fn submit(&self, gid: &str, trans_type: &str, steps: &[SagaStep]) -> Result<()> {
108 if gid.is_empty() {
109 return Err(ApiError::BadRequest("gid 不能为空".into()));
110 }
111 let Some(tt) = TransType::parse(trans_type) else {
112 return Err(ApiError::BadRequest("未知 trans_type".into()));
113 };
114
115 if let Ok(Some(g)) = self.store.get_global(gid).await {
117 if g.status == GlobalStatus::Prepared {
118 self.store
119 .set_global_status(gid, GlobalStatus::Submitted, "")
120 .await
121 .map_err(internal)?;
122 let _ = self.store.schedule_now(gid).await;
123 }
124 return Ok(());
126 }
127
128 match tt {
129 TransType::Saga => {
130 if steps.is_empty() {
131 return Err(ApiError::BadRequest("saga 的 steps 不能为空".into()));
132 }
133 let (g, branches) = saga_rows(gid, steps);
134 self.store
135 .create_global(&g, &branches)
136 .await
137 .map_err(internal)?;
138 Ok(())
139 }
140 TransType::Tcc | TransType::Msg | TransType::Xa => {
141 Err(ApiError::BadRequest("tcc/xa/msg 要先调 prepare".into()))
142 }
143 TransType::Workflow => Err(ApiError::BadRequest(
147 "workflow 模式只能在嵌入式形态下提交(步骤是进程内的函数,不是 URL)".into(),
148 )),
149 }
150 }
151
152 pub async fn prepare(
154 &self,
155 gid: &str,
156 trans_type: &str,
157 actions: &[String],
158 query_prepared: &str,
159 grace_secs: Option<i64>,
160 ) -> Result<()> {
161 if gid.is_empty() {
162 return Err(ApiError::BadRequest("gid 不能为空".into()));
163 }
164 match TransType::parse(trans_type) {
165 Some(TransType::Msg) => {
166 if actions.is_empty() {
167 return Err(ApiError::BadRequest("msg 的 actions 不能为空".into()));
168 }
169 if query_prepared.is_empty() {
170 return Err(ApiError::BadRequest(
173 "msg 必须提供 query_prepared,否则崩溃后无法决断".into(),
174 ));
175 }
176 let (g, br) = msg_rows(gid, actions, query_prepared, grace_secs.unwrap_or(10));
177 self.store.create_global(&g, &br).await.map_err(internal)?;
178 Ok(())
179 }
180 Some(tt @ (TransType::Tcc | TransType::Xa)) => {
181 let mut g = tcc_rows(gid);
182 g.trans_type = tt;
183 self.store.create_global(&g, &[]).await.map_err(internal)?;
184 Ok(())
185 }
186 _ => Err(ApiError::BadRequest(
187 "prepare 支持 tcc / xa / msg;saga 直接 submit".into(),
188 )),
189 }
190 }
191
192 pub async fn register_branch(&self, r: &RegisterBranch) -> Result<()> {
196 if r.gid.is_empty() || r.branch_id.is_empty() {
197 return Err(ApiError::BadRequest("gid / branch_id 不能为空".into()));
198 }
199 let tt = match self.store.get_global(&r.gid).await {
200 Ok(Some(g)) => g.trans_type,
201 Ok(None) => return Err(ApiError::NotFound("gid 不存在,先 prepare".into())),
202 Err(e) => return Err(internal(e)),
203 };
204
205 let mut ops = Vec::new();
206 match tt {
207 TransType::Tcc => {
208 if r.confirm.is_empty() || r.cancel.is_empty() {
209 return Err(ApiError::BadRequest(
210 "tcc 分支必须提供 confirm 和 cancel".into(),
211 ));
212 }
213 ops.push((BranchOp::Confirm, r.confirm.clone()));
214 ops.push((BranchOp::Cancel, r.cancel.clone()));
215 if !r.r#try.is_empty() {
216 ops.push((BranchOp::Try, r.r#try.clone()));
217 }
218 }
219 TransType::Xa => {
220 if r.commit.is_empty() || r.rollback.is_empty() {
221 return Err(ApiError::BadRequest(
223 "xa 分支必须提供 commit 和 rollback".into(),
224 ));
225 }
226 ops.push((BranchOp::Commit, r.commit.clone()));
227 ops.push((BranchOp::Rollback, r.rollback.clone()));
228 }
229 _ => return Err(ApiError::BadRequest("只有 tcc 和 xa 需要登记分支".into())),
230 }
231
232 self.store
233 .register_branch(&r.gid, &r.branch_id, &ops)
234 .await
235 .map_err(internal)
236 }
237
238 pub async fn abort(&self, gid: &str) -> Result<()> {
240 match self.store.get_global(gid).await {
241 Ok(Some(g)) if !g.status.is_final() => {
242 self.store
243 .set_global_status(gid, GlobalStatus::Aborting, "调用方主动中止")
244 .await
245 .map_err(internal)?;
246 let _ = self.store.schedule_now(gid).await;
247 Ok(())
248 }
249 Ok(Some(_)) => Err(ApiError::Conflict("事务已终结,无法中止".into())),
250 Ok(None) => Err(ApiError::NotFound("gid 不存在".into())),
251 Err(e) => Err(internal(e)),
252 }
253 }
254
255 pub async fn query(&self, gid: &str) -> Result<TransView> {
256 let g = self
257 .store
258 .get_global(gid)
259 .await
260 .map_err(internal)?
261 .ok_or_else(|| ApiError::NotFound("gid 不存在".into()))?;
262 let branches = self.store.list_branches(gid).await.map_err(internal)?;
263 Ok(TransView {
264 gid: g.gid,
265 trans_type: g.trans_type.to_string(),
266 status: g.status.as_str().into(),
267 rollback_reason: g.rollback_reason,
268 create_time: g.create_time,
269 finish_time: g.finish_time,
270 branches: branches
271 .into_iter()
272 .map(|b| BranchView {
273 branch_id: b.branch_id,
274 op: b.op.as_str().into(),
275 url: b.url,
276 status: b.status.as_str().into(),
277 })
278 .collect(),
279 })
280 }
281
282 pub async fn list_recent(&self, limit: i64) -> Vec<TransView> {
283 self.store
284 .list_recent(limit)
285 .await
286 .unwrap_or_default()
287 .into_iter()
288 .map(|g| TransView {
289 gid: g.gid,
290 trans_type: g.trans_type.to_string(),
291 status: g.status.as_str().into(),
292 rollback_reason: g.rollback_reason,
293 create_time: g.create_time,
294 finish_time: g.finish_time,
295 branches: Vec::new(),
296 })
297 .collect()
298 }
299}