Skip to main content

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}