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.as_ref().and_then(|m| {
110            m.last_sync
111                .elapsed()
112                .ok()
113        })
114    }
115}
116
117/// Response from a load operation.
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct LoadResponse {
120    /// The loaded project.
121    pub project: Project,
122
123    /// Manifest info.
124    pub manifest: Manifest,
125}
126
127/// Response from a compose operation.
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct ComposeResponse {
130    /// The composed content.
131    pub content: String,
132
133    /// Output path (if saved to file).
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub output_path: Option<PathBuf>,
136
137    /// The project that was composed.
138    pub project_name: String,
139
140    /// Number of files included.
141    pub file_count: usize,
142}
143
144/// Response from a clean operation.
145#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct CleanResponse {
147    /// Path that was cleaned.
148    pub path: PathBuf,
149
150    /// Whether any files were actually removed.
151    pub cleaned: bool,
152}
153
154/// Response from a git changes operation.
155#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct GitChangesResponse {
157    /// The git changes snapshot.
158    pub changes: GitChangeSnapshot,
159
160    /// Number of files with changes.
161    pub change_count: usize,
162}
163
164/// Summary of an operation for API responses.
165#[derive(Debug, Clone, Serialize, Deserialize)]
166pub struct OperationSummary {
167    /// Whether the operation succeeded.
168    pub success: bool,
169
170    /// Operation type.
171    pub operation: String,
172
173    /// Duration in milliseconds.
174    pub duration_ms: u64,
175
176    /// Timestamp when operation completed.
177    pub timestamp: SystemTime,
178
179    /// Optional message.
180    #[serde(skip_serializing_if = "Option::is_none")]
181    pub message: Option<String>,
182}
183
184impl OperationSummary {
185    /// Create a success summary.
186    pub fn success(operation: impl Into<String>, duration_ms: u64) -> Self {
187        Self {
188            success: true,
189            operation: operation.into(),
190            duration_ms,
191            timestamp: SystemTime::now(),
192            message: None,
193        }
194    }
195
196    /// Create a failure summary.
197    pub fn failure(operation: impl Into<String>, message: impl Into<String>) -> Self {
198        Self {
199            success: false,
200            operation: operation.into(),
201            duration_ms: 0,
202            timestamp: SystemTime::now(),
203            message: Some(message.into()),
204        }
205    }
206
207    /// Add a message to the summary.
208    pub fn with_message(mut self, message: impl Into<String>) -> Self {
209        self.message = Some(message.into());
210        self
211    }
212}
213