Skip to main content

vibe_graph_ops/
responses.rs

1//! Response DTOs for operations.
2//!
3//! Each response type contains all the data produced by an operation,
4//! making it easy to consume from CLI, REST API, or programmatically.
5
6use std::path::PathBuf;
7use std::time::SystemTime;
8
9use serde::{Deserialize, Serialize};
10use vibe_graph_core::{GitChangeSnapshot, SourceCodeGraph};
11
12use crate::project::Project;
13use crate::store::Manifest;
14use crate::workspace::WorkspaceInfo;
15
16/// Response from a sync operation.
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct SyncResponse {
19    /// The synced project.
20    pub project: Project,
21
22    /// Workspace information.
23    pub workspace: WorkspaceInfo,
24
25    /// Path where the project was saved or cloned.
26    pub path: PathBuf,
27
28    /// Whether a new snapshot was created.
29    pub snapshot_created: Option<PathBuf>,
30
31    /// Detected git remote (for single repos).
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub remote: Option<String>,
34}
35
36impl SyncResponse {
37    /// Get total file count.
38    pub fn file_count(&self) -> usize {
39        self.project.total_sources()
40    }
41
42    /// Get total repository count.
43    pub fn repo_count(&self) -> usize {
44        self.project.repositories.len()
45    }
46}
47
48/// Response from a graph build operation.
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct GraphResponse {
51    /// The built graph.
52    pub graph: SourceCodeGraph,
53
54    /// Path where the graph was saved.
55    pub saved_path: PathBuf,
56
57    /// Additional output path (if requested).
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub output_path: Option<PathBuf>,
60
61    /// Whether the graph was loaded from cache.
62    pub from_cache: bool,
63}
64
65impl GraphResponse {
66    /// Get node count.
67    pub fn node_count(&self) -> usize {
68        self.graph.node_count()
69    }
70
71    /// Get edge count.
72    pub fn edge_count(&self) -> usize {
73        self.graph.edge_count()
74    }
75}
76
77/// Response from a status operation.
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct StatusResponse {
80    /// Workspace information.
81    pub workspace: WorkspaceInfo,
82
83    /// Whether the .self store exists.
84    pub store_exists: bool,
85
86    /// Manifest info (if store exists).
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub manifest: Option<Manifest>,
89
90    /// Number of snapshots available.
91    pub snapshot_count: usize,
92
93    /// Total size of .self directory.
94    pub store_size: u64,
95
96    /// List of repository names (for multi-repo workspaces).
97    #[serde(default)]
98    pub repositories: Vec<String>,
99}
100
101impl StatusResponse {
102    /// Check if the project has been synced.
103    pub fn is_synced(&self) -> bool {
104        self.store_exists && self.manifest.is_some()
105    }
106
107    /// Get time since last sync.
108    pub fn time_since_sync(&self) -> Option<std::time::Duration> {
109        self.manifest
110            .as_ref()
111            .and_then(|m| m.last_sync.elapsed().ok())
112    }
113}
114
115/// Response from a load operation.
116#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct LoadResponse {
118    /// The loaded project.
119    pub project: Project,
120
121    /// Manifest info.
122    pub manifest: Manifest,
123}
124
125/// Response from a compose operation.
126#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct ComposeResponse {
128    /// The composed content.
129    pub content: String,
130
131    /// Output path (if saved to file).
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub output_path: Option<PathBuf>,
134
135    /// The project that was composed.
136    pub project_name: String,
137
138    /// Number of files included.
139    pub file_count: usize,
140}
141
142/// Response from a clean operation.
143#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct CleanResponse {
145    /// Path that was cleaned.
146    pub path: PathBuf,
147
148    /// Whether any files were actually removed.
149    pub cleaned: bool,
150}
151
152/// Response from a git changes operation.
153#[derive(Debug, Clone, Serialize, Deserialize)]
154pub struct GitChangesResponse {
155    /// The git changes snapshot.
156    pub changes: GitChangeSnapshot,
157
158    /// Number of files with changes.
159    pub change_count: usize,
160}
161
162/// Summary of an operation for API responses.
163#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct OperationSummary {
165    /// Whether the operation succeeded.
166    pub success: bool,
167
168    /// Operation type.
169    pub operation: String,
170
171    /// Duration in milliseconds.
172    pub duration_ms: u64,
173
174    /// Timestamp when operation completed.
175    pub timestamp: SystemTime,
176
177    /// Optional message.
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub message: Option<String>,
180}
181
182impl OperationSummary {
183    /// Create a success summary.
184    pub fn success(operation: impl Into<String>, duration_ms: u64) -> Self {
185        Self {
186            success: true,
187            operation: operation.into(),
188            duration_ms,
189            timestamp: SystemTime::now(),
190            message: None,
191        }
192    }
193
194    /// Create a failure summary.
195    pub fn failure(operation: impl Into<String>, message: impl Into<String>) -> Self {
196        Self {
197            success: false,
198            operation: operation.into(),
199            duration_ms: 0,
200            timestamp: SystemTime::now(),
201            message: Some(message.into()),
202        }
203    }
204
205    /// Add a message to the summary.
206    pub fn with_message(mut self, message: impl Into<String>) -> Self {
207        self.message = Some(message.into());
208        self
209    }
210}