1use crate::driver;
20use crate::{msg_rows, saga_rows, tcc_rows};
21use dtmrs_core::{BranchOp, GlobalStatus, SagaStep, TransType};
22use dtmrs_store::{Store, SubmitOutcome};
23use serde::Serialize;
24
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub enum ApiError {
27 BadRequest(String),
28 NotFound(String),
29 Conflict(String),
31 Internal(String),
32}
33
34impl ApiError {
35 pub fn message(&self) -> &str {
36 match self {
37 Self::BadRequest(m) | Self::NotFound(m) | Self::Conflict(m) | Self::Internal(m) => m,
38 }
39 }
40}
41
42impl std::fmt::Display for ApiError {
43 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 f.write_str(self.message())
45 }
46}
47
48pub type Result<T> = std::result::Result<T, ApiError>;
49
50fn internal(e: impl std::fmt::Display) -> ApiError {
51 ApiError::Internal(e.to_string())
52}
53
54#[derive(Debug, Clone, Serialize)]
55pub struct BranchView {
56 pub branch_id: String,
57 pub op: String,
58 pub url: String,
59 pub status: String,
60}
61
62#[derive(Debug, Clone, Serialize)]
63pub struct TransView {
64 pub gid: String,
65 pub trans_type: String,
66 pub status: String,
67 pub rollback_reason: String,
68 pub create_time: i64,
69 pub finish_time: Option<i64>,
70 pub branches: Vec<BranchView>,
71}
72
73#[derive(Debug, Clone, Default)]
75pub struct RegisterBranch {
76 pub gid: String,
77 pub branch_id: String,
78 pub confirm: String,
79 pub cancel: String,
80 pub r#try: String,
81 pub commit: String,
82 pub rollback: String,
83}
84
85#[derive(Clone)]
86pub struct Api {
87 pub store: Store,
88 inline: Option<crate::driver::Driver>,
91}
92
93impl Api {
94 pub fn new(store: Store) -> Self {
95 Self {
96 store,
97 inline: None,
98 }
99 }
100
101 pub fn with_inline_driver(mut self, d: crate::driver::Driver) -> Self {
124 self.inline = Some(d);
125 self
126 }
127
128 fn claim_for_inline(&self, g: &mut dtmrs_store::GlobalRow) -> bool {
130 let Some(d) = &self.inline else { return false };
131 g.owner = d.owner.clone();
132 g.next_cron_time = dtmrs_store::now() + d.lease;
133 true
134 }
135
136 async fn claim_and_submit(&self, gid: &str) -> Result<SubmitOutcome> {
141 let (owner, nct) = match &self.inline {
142 Some(d) => (d.owner.clone(), dtmrs_store::now() + d.lease),
143 None => (String::new(), dtmrs_store::now()),
144 };
145 self.store
146 .submit_prepared(gid, &owner, nct)
147 .await
148 .map_err(internal)
149 }
150
151 fn drive_detached(&self, g: dtmrs_store::GlobalRow) {
153 let Some(d) = self.inline.clone() else { return };
154 tokio::spawn(async move {
155 if let Err(e) = d.process(&g).await {
156 tracing::warn!(gid = %g.gid, error = %e, "提交后直接推进出错,等租约到期重试");
158 }
159 });
160 }
161
162 pub fn new_gid(&self) -> String {
165 use std::sync::atomic::{AtomicU64, Ordering};
166 static SEQ: AtomicU64 = AtomicU64::new(0);
167 let n = SEQ.fetch_add(1, Ordering::Relaxed);
168 format!("{}-{}", dtmrs_store::now(), n)
169 }
170
171 pub async fn submit(&self, gid: &str, trans_type: &str, steps: &[SagaStep]) -> Result<()> {
176 if gid.is_empty() {
177 return Err(ApiError::BadRequest("gid 不能为空".into()));
178 }
179 let Some(tt) = TransType::parse(trans_type) else {
180 return Err(ApiError::BadRequest("未知 trans_type".into()));
181 };
182
183 match tt {
184 TransType::Saga => {
185 if steps.is_empty() {
186 return match self.claim_and_submit(gid).await? {
190 SubmitOutcome::Advanced(g) => {
191 self.drive_detached(*g);
192 Ok(())
193 }
194 SubmitOutcome::Already => Ok(()),
195 SubmitOutcome::Missing => {
196 Err(ApiError::BadRequest("saga 的 steps 不能为空".into()))
197 }
198 };
199 }
200 let (mut g, branches) = saga_rows(gid, steps);
201 let claimed = self.claim_for_inline(&mut g);
204 if self
209 .store
210 .create_global(&g, &branches)
211 .await
212 .map_err(internal)?
213 {
214 if claimed {
215 self.drive_detached(g);
216 }
217 return Ok(());
218 }
219 if let SubmitOutcome::Advanced(g) = self.claim_and_submit(gid).await? {
223 self.drive_detached(*g);
224 }
225 Ok(())
226 }
227 TransType::Tcc | TransType::Msg | TransType::Xa => {
228 match self.claim_and_submit(gid).await? {
233 SubmitOutcome::Advanced(g) => {
237 self.drive_detached(*g);
238 Ok(())
239 }
240 SubmitOutcome::Already => Ok(()),
242 SubmitOutcome::Missing => {
243 Err(ApiError::BadRequest("tcc/xa/msg 要先调 prepare".into()))
244 }
245 }
246 }
247 TransType::Workflow => Err(ApiError::BadRequest(
251 "workflow 模式只能在嵌入式形态下提交(步骤是进程内的函数,不是 URL)".into(),
252 )),
253 }
254 }
255
256 pub async fn prepare(
258 &self,
259 gid: &str,
260 trans_type: &str,
261 actions: &[String],
262 query_prepared: &str,
263 grace_secs: Option<i64>,
264 ) -> Result<()> {
265 if gid.is_empty() {
266 return Err(ApiError::BadRequest("gid 不能为空".into()));
267 }
268 match TransType::parse(trans_type) {
269 Some(TransType::Msg) => {
270 if actions.is_empty() {
271 return Err(ApiError::BadRequest("msg 的 actions 不能为空".into()));
272 }
273 if query_prepared.is_empty() {
274 return Err(ApiError::BadRequest(
277 "msg 必须提供 query_prepared,否则崩溃后无法决断".into(),
278 ));
279 }
280 let (g, br) = msg_rows(gid, actions, query_prepared, grace_secs.unwrap_or(10));
281 self.store.create_global(&g, &br).await.map_err(internal)?;
282 Ok(())
283 }
284 Some(tt @ (TransType::Tcc | TransType::Xa)) => {
285 let mut g = tcc_rows(gid);
286 g.trans_type = tt;
287 self.store.create_global(&g, &[]).await.map_err(internal)?;
288 Ok(())
289 }
290 _ => Err(ApiError::BadRequest(
291 "prepare 支持 tcc / xa / msg;saga 直接 submit".into(),
292 )),
293 }
294 }
295
296 pub async fn register_branch(&self, r: &RegisterBranch) -> Result<()> {
300 if r.gid.is_empty() || r.branch_id.is_empty() {
301 return Err(ApiError::BadRequest("gid / branch_id 不能为空".into()));
302 }
303 if !driver::is_canonical_branch_id(&r.branch_id) {
317 return Err(ApiError::BadRequest(format!(
318 "branch_id \"{}\" 格式不对:必须是从 01 开始、至少两位补零的十进制序号\
319 (01、02 …… 99、100),且不超过 {}",
320 r.branch_id,
321 driver::MAX_BRANCH_INDEX + 1
322 )));
323 }
324 let tt = match self.store.get_global(&r.gid).await {
325 Ok(Some(g)) if matches!(g.status, GlobalStatus::Aborting) || g.status.is_final() => {
339 return Err(ApiError::Conflict(format!(
340 "事务处于 {} 状态,不能再登记分支(登记后的一阶段将无人收尾)",
341 g.status.as_str()
342 )));
343 }
344 Ok(Some(g)) => g.trans_type,
345 Ok(None) => return Err(ApiError::NotFound("gid 不存在,先 prepare".into())),
346 Err(e) => return Err(internal(e)),
347 };
348
349 let mut ops = Vec::new();
350 match tt {
351 TransType::Tcc => {
352 if r.confirm.is_empty() || r.cancel.is_empty() {
353 return Err(ApiError::BadRequest(
354 "tcc 分支必须提供 confirm 和 cancel".into(),
355 ));
356 }
357 ops.push((BranchOp::Confirm, r.confirm.clone()));
358 ops.push((BranchOp::Cancel, r.cancel.clone()));
359 if !r.r#try.is_empty() {
360 ops.push((BranchOp::Try, r.r#try.clone()));
361 }
362 }
363 TransType::Xa => {
364 if r.commit.is_empty() || r.rollback.is_empty() {
365 return Err(ApiError::BadRequest(
367 "xa 分支必须提供 commit 和 rollback".into(),
368 ));
369 }
370 ops.push((BranchOp::Commit, r.commit.clone()));
371 ops.push((BranchOp::Rollback, r.rollback.clone()));
372 }
373 _ => return Err(ApiError::BadRequest("只有 tcc 和 xa 需要登记分支".into())),
374 }
375
376 match self
387 .store
388 .register_branch(&r.gid, &r.branch_id, &ops)
389 .await
390 .map_err(internal)?
391 {
392 dtmrs_store::RegisterOutcome::Registered => Ok(()),
393 dtmrs_store::RegisterOutcome::Conflict { op, existing } => {
394 Err(ApiError::Conflict(format!(
395 "branch_id \"{}\" 的 {} 已经登记成 {existing},不能改成另一个地址。\
396 每个分支要用**各自**的分支号(01、02、03…),重号会让后面那个分支\
397 没人 confirm/cancel,资源永久泄漏",
398 r.branch_id,
399 op.as_str()
400 )))
401 }
402 }
403 }
404
405 pub async fn abort(&self, gid: &str) -> Result<()> {
407 match self.store.get_global(gid).await {
408 Ok(Some(g)) if !g.status.is_final() => {
409 self.store
410 .set_global_status(gid, GlobalStatus::Aborting, g.trans_type, "调用方主动中止")
411 .await
412 .map_err(internal)?;
413 let _ = self.store.schedule_now(gid).await;
414 Ok(())
415 }
416 Ok(Some(_)) => Err(ApiError::Conflict("事务已终结,无法中止".into())),
417 Ok(None) => Err(ApiError::NotFound("gid 不存在".into())),
418 Err(e) => Err(internal(e)),
419 }
420 }
421
422 pub async fn retry(&self, gid: &str) -> Result<()> {
427 match self.store.get_global(gid).await {
428 Ok(Some(g)) if !g.status.is_final() => {
429 self.store.schedule_now(gid).await.map_err(internal)?;
430 Ok(())
431 }
432 Ok(Some(_)) => Err(ApiError::Conflict("事务已终结,无需重试".into())),
433 Ok(None) => Err(ApiError::NotFound("gid 不存在".into())),
434 Err(e) => Err(internal(e)),
435 }
436 }
437
438 pub async fn query(&self, gid: &str) -> Result<TransView> {
439 let g = self
440 .store
441 .get_global(gid)
442 .await
443 .map_err(internal)?
444 .ok_or_else(|| ApiError::NotFound("gid 不存在".into()))?;
445 let branches = self.store.list_branches(gid).await.map_err(internal)?;
446 Ok(TransView {
447 gid: g.gid,
448 trans_type: g.trans_type.to_string(),
449 status: g.status.as_str().into(),
450 rollback_reason: g.rollback_reason,
451 create_time: g.create_time,
452 finish_time: g.finish_time,
453 branches: branches
454 .into_iter()
455 .map(|b| BranchView {
456 branch_id: b.branch_id,
457 op: b.op.as_str().into(),
458 url: b.url,
459 status: b.status.as_str().into(),
460 })
461 .collect(),
462 })
463 }
464
465 pub async fn list_recent(&self, limit: i64) -> Vec<TransView> {
466 self.store
467 .list_recent(limit)
468 .await
469 .unwrap_or_default()
470 .into_iter()
471 .map(|g| TransView {
472 gid: g.gid,
473 trans_type: g.trans_type.to_string(),
474 status: g.status.as_str().into(),
475 rollback_reason: g.rollback_reason,
476 create_time: g.create_time,
477 finish_time: g.finish_time,
478 branches: Vec::new(),
479 })
480 .collect()
481 }
482}