vdsl_sync/application/sdk.rs
1//! SyncStore SDK — vdsl-syncの外部向けAPI面。
2//!
3//! MCP・Lua Bridge等のインターフェース層はこのtraitのみに依存する。
4//! scan→delta→plan→execute等の内部パイプラインは一切漏れない。
5//!
6//! # 設計方針
7//!
8//! - **UseCase完結**: `sync()`一発で全工程が内部完結
9//! - **CRUD + Query**: Firestore的なput/get/list/delete + status/errors/pending
10//! - **進捗**: `status()` でDB SELECTベースのサマリーを取得(Observer不要)
11//! - **型安全**: 戻り型は全てSDK専用型。旧Store/SyncFacadeの型に依存しない
12
13use std::path::Path;
14
15use async_trait::async_trait;
16
17use super::topology_store::TopologyFileView;
18use crate::application::error::SyncError;
19use crate::domain::file_type::FileType;
20use crate::domain::fingerprint::FileFingerprint;
21use crate::domain::location::{LocationId, SyncSummary};
22use crate::domain::view::{ErrorEntry, PendingEntry};
23use crate::infra::backend::ProgressFn;
24
25// =============================================================================
26// SDK Result types
27// =============================================================================
28
29/// sync/sync_route の結果。
30///
31/// 旧BatchResult/SyncResult/FacadeSyncResultを統合した単一型。
32/// Serialize対応でMCP層がそのまま返せる。
33#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
34pub struct SyncReport {
35 /// スキャンで検出されたファイル数。
36 pub scanned: usize,
37 /// スキャン時の非致命的エラー(個別ファイル読み取り失敗等)。
38 pub scan_errors: Vec<SyncReportError>,
39 /// 計画で作成されたTransfer数。
40 pub transfers_created: usize,
41 /// 実行で成功した転送数。
42 pub transferred: usize,
43 /// 実行で失敗した転送数。
44 pub failed: usize,
45 /// 転送失敗詳細。
46 pub errors: Vec<SyncReportError>,
47 /// 検出されたコンフリクト。
48 ///
49 /// 複数Locationで同一ファイルが異なる内容に更新された場合に報告される。
50 /// コンフリクトのあるファイルのUpdate転送は実行されない。
51 #[serde(skip_serializing_if = "Vec::is_empty")]
52 pub conflicts: Vec<SyncReportConflict>,
53}
54
55/// コンフリクト報告。SDK面の型。
56///
57/// domain::distribute::ConflictEntry から変換して使用する。
58#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
59pub struct SyncReportConflict {
60 pub file_id: String,
61 pub path: String,
62 /// コンフリクトしているLocation群。
63 pub locations: Vec<String>,
64}
65
66impl From<&crate::domain::distribute::ConflictEntry> for SyncReportConflict {
67 fn from(c: &crate::domain::distribute::ConflictEntry) -> Self {
68 Self {
69 file_id: c.topology_file_id().to_string(),
70 path: c.relative_path().to_string(),
71 locations: c
72 .variants()
73 .iter()
74 .map(|v| v.location_id().to_string())
75 .collect(),
76 }
77 }
78}
79
80/// 転送失敗の詳細。
81#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
82pub struct SyncReportError {
83 pub path: String,
84 pub error: String,
85}
86
87/// put() の結果。
88///
89/// TopologyPutResultをre-exportせず、SDK面として安定させる。
90#[derive(Debug, serde::Serialize)]
91pub struct PutReport {
92 /// 登録/更新されたファイルID。
93 pub file_id: String,
94 /// 新規登録 = true、更新 = false。
95 pub is_new: bool,
96 /// 作成されたTransfer数。
97 pub transfers_created: usize,
98}
99
100// =============================================================================
101// SDK Trait
102// =============================================================================
103
104/// vdsl-syncの外部向けAPI。
105///
106/// インターフェース層(MCP, Lua Bridge, CLI)はこのtraitのみに依存する。
107/// 内部実装(TopologyStore, TransferEngine, scan, delta等)は一切公開しない。
108#[async_trait]
109pub trait SyncStoreSdk: Send + Sync {
110 // =========================================================================
111 // UseCase — 同期操作
112 // =========================================================================
113
114 /// 全体同期: scan→delta→plan→execute 一括。
115 async fn sync(&self) -> Result<SyncReport, SyncError>;
116
117 /// 単一ルート同期。
118 async fn sync_route(
119 &self,
120 src: &LocationId,
121 dest: &LocationId,
122 ) -> Result<SyncReport, SyncError>;
123
124 // =========================================================================
125 // Command — ファイル操作
126 // =========================================================================
127
128 /// ファイル登録。
129 async fn put(
130 &self,
131 path: &str,
132 file_type: FileType,
133 fingerprint: FileFingerprint,
134 origin: &LocationId,
135 embedded_id: Option<String>,
136 ) -> Result<PutReport, SyncError>;
137
138 /// ファイル削除。削除されたTransfer数を返す。
139 async fn delete(&self, path: &str) -> Result<usize, SyncError>;
140
141 // =========================================================================
142 // Query — 読み取り
143 // =========================================================================
144
145 /// ファイル取得。
146 async fn get(&self, path: &str) -> Result<Option<TopologyFileView>, SyncError>;
147
148 /// ファイル一覧。
149 async fn list(
150 &self,
151 file_type: Option<FileType>,
152 limit: Option<usize>,
153 ) -> Result<Vec<TopologyFileView>, SyncError>;
154
155 /// ロケーション別同期サマリー。
156 async fn status(&self) -> Result<SyncSummary, SyncError>;
157
158 /// エラー一覧。
159 async fn errors(&self) -> Result<Vec<ErrorEntry>, SyncError>;
160
161 /// 転送待ち一覧。
162 async fn pending(&self, dest: &LocationId) -> Result<Vec<PendingEntry>, SyncError>;
163
164 // =========================================================================
165 // Topology — 読み取り専用
166 // =========================================================================
167
168 /// 登録済みロケーション一覧。
169 fn locations(&self) -> Vec<LocationId>;
170
171 /// 全エッジ `(src, dest)` 一覧。
172 fn all_edges(&self) -> Vec<(LocationId, LocationId)>;
173
174 /// ローカルファイルルート。
175 fn local_root(&self) -> Option<&Path>;
176
177 /// Set a progress callback for reporting phase/chunk progress.
178 ///
179 /// Called by `SyncTaskManager` before spawning sync.
180 /// The callback receives human-readable messages like:
181 /// - `"phase: scan (1200 files)"`
182 /// - `"push: chunk 5/22 (500/2111)"`
183 ///
184 /// Default: no-op.
185 fn set_progress_callback(&self, _callback: Option<ProgressFn>) {}
186}