1use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::fmt;
11
12pub type BatchId = String;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)]
17#[serde(rename_all = "snake_case")]
18pub enum BatchStatus {
19 Created,
21 Running,
23 Completed,
25 Failed,
27 Cancelled,
29}
30
31impl fmt::Display for BatchStatus {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 let name = match self {
34 BatchStatus::Created => "Created",
35 BatchStatus::Running => "Running",
36 BatchStatus::Completed => "Completed",
37 BatchStatus::Failed => "Failed",
38 BatchStatus::Cancelled => "Cancelled",
39 };
40 write!(f, "{}", name)
41 }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)]
46#[serde(rename_all = "snake_case")]
47pub enum BatchType {
48 Vertex,
50 Edge,
52 Mixed,
54}
55
56impl fmt::Display for BatchType {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 let name = match self {
59 BatchType::Vertex => "vertex",
60 BatchType::Edge => "edge",
61 BatchType::Mixed => "mixed",
62 };
63 write!(f, "{}", name)
64 }
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)]
69#[serde(rename_all = "snake_case")]
70pub enum BatchItemType {
71 Vertex,
73 Edge,
75 Update,
77 Delete,
79}
80
81impl fmt::Display for BatchItemType {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 let name = match self {
84 BatchItemType::Vertex => "vertex",
85 BatchItemType::Edge => "edge",
86 BatchItemType::Update => "update",
87 BatchItemType::Delete => "delete",
88 };
89 write!(f, "{}", name)
90 }
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
95#[serde(tag = "type")]
96pub enum BatchItem {
97 #[serde(rename = "vertex")]
98 Vertex(VertexData),
99 #[serde(rename = "edge")]
100 Edge(EdgeData),
101 #[serde(rename = "update_vertex")]
102 UpdateVertex(VertexData),
103 #[serde(rename = "update_edge")]
104 UpdateEdgeData(UpdateEdgeData),
105 #[serde(rename = "delete_vertex")]
106 DeleteVertex(DeleteVertexData),
107 #[serde(rename = "delete_edge")]
108 DeleteEdgeData(DeleteEdgeData),
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
113pub struct VertexData {
114 pub vid: serde_json::Value,
116 pub tag: String,
118 #[serde(default)]
120 pub properties: HashMap<String, serde_json::Value>,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
125pub struct EdgeData {
126 pub edge_type: String,
128 pub src_vid: serde_json::Value,
130 pub dst_vid: serde_json::Value,
132 #[serde(default)]
134 pub properties: HashMap<String, serde_json::Value>,
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
139pub struct UpdateEdgeData {
140 pub edge_type: String,
142 pub src_vid: serde_json::Value,
144 pub dst_vid: serde_json::Value,
146 #[serde(default)]
148 pub rank: i64,
149 #[serde(default)]
151 pub properties: HashMap<String, serde_json::Value>,
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
156pub struct DeleteVertexData {
157 pub vid: serde_json::Value,
159 #[serde(default)]
161 pub tag_names: Vec<String>,
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
166pub struct DeleteEdgeData {
167 pub edge_type: String,
169 pub src_vid: serde_json::Value,
171 pub dst_vid: serde_json::Value,
173 #[serde(default)]
175 pub rank: i64,
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
180pub struct CreateBatchRequest {
181 pub space_id: u64,
183 pub batch_type: BatchType,
185 #[serde(default = "default_batch_size")]
187 pub batch_size: usize,
188}
189
190fn default_batch_size() -> usize {
191 1000
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
196pub struct CreateBatchResponse {
197 pub batch_id: BatchId,
199 pub status: BatchStatus,
201 pub created_at: String,
203}
204
205#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
207pub struct AddBatchItemsRequest {
208 pub items: Vec<BatchItem>,
210}
211
212#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
214pub struct AddBatchItemsResponse {
215 pub accepted: usize,
217 #[serde(default)]
219 pub buffered: usize,
220 #[serde(default)]
222 pub total_buffered: usize,
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
227pub struct ExecuteBatchResponse {
228 pub batch_id: BatchId,
230 pub status: BatchStatus,
232 pub result: BatchResultData,
234 #[serde(default)]
236 pub completed_at: Option<String>,
237}
238
239#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
241pub struct BatchResultData {
242 pub vertices_inserted: usize,
244 pub edges_inserted: usize,
246 #[serde(default)]
248 pub vertices_updated: usize,
249 #[serde(default)]
251 pub edges_updated: usize,
252 #[serde(default)]
254 pub vertices_deleted: usize,
255 #[serde(default)]
257 pub edges_deleted: usize,
258 #[serde(default, skip_serializing_if = "Vec::is_empty")]
260 pub errors: Vec<BatchErrorData>,
261}
262
263#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
265pub struct BatchErrorData {
266 pub index: usize,
268 pub item_type: BatchItemType,
270 pub error: String,
272}
273
274#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
276pub struct BatchStatusResponse {
277 pub batch_id: BatchId,
279 pub status: BatchStatus,
281 pub progress: BatchProgress,
283 pub created_at: String,
285 pub updated_at: String,
287}
288
289#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
291pub struct BatchProgress {
292 pub total: usize,
294 pub processed: usize,
296 pub succeeded: usize,
298 pub failed: usize,
300 #[serde(default)]
302 pub buffered: usize,
303}
304
305#[cfg(test)]
306mod tests {
307 use super::*;
308
309 #[test]
310 fn batch_item_vertex_roundtrip() {
311 let item = BatchItem::Vertex(VertexData {
312 vid: serde_json::json!("v1"),
313 tag: "person".to_string(),
314 properties: HashMap::from([("name".to_string(), serde_json::json!("Alice"))]),
315 });
316 let json = serde_json::to_string(&item).unwrap();
317 assert!(json.contains(r#""type":"vertex""#), "json: {json}");
318 let back: BatchItem = serde_json::from_str(&json).unwrap();
319 match back {
320 BatchItem::Vertex(v) => {
321 assert_eq!(v.vid, serde_json::json!("v1"));
322 assert_eq!(v.tag, "person");
323 assert_eq!(v.properties.get("name"), Some(&serde_json::json!("Alice")));
324 }
325 other => panic!("expected vertex item, got {other:?}"),
326 }
327 }
328
329 #[test]
330 fn batch_item_edge_roundtrip() {
331 let item = BatchItem::Edge(EdgeData {
332 edge_type: "follows".to_string(),
333 src_vid: serde_json::json!("v1"),
334 dst_vid: serde_json::json!("v2"),
335 properties: HashMap::new(),
336 });
337 let json = serde_json::to_string(&item).unwrap();
338 let back: BatchItem = serde_json::from_str(&json).unwrap();
339 match back {
340 BatchItem::Edge(e) => assert_eq!(e.edge_type, "follows"),
341 other => panic!("expected edge item, got {other:?}"),
342 }
343 }
344
345 #[test]
346 fn batch_status_wire_names() {
347 assert_eq!(
348 serde_json::to_string(&BatchStatus::Completed).unwrap(),
349 r#""completed""#
350 );
351 assert_eq!(
352 serde_json::from_str::<BatchStatus>(r#""running""#).unwrap(),
353 BatchStatus::Running
354 );
355 assert_eq!(
356 serde_json::from_str::<BatchType>(r#""mixed""#).unwrap(),
357 BatchType::Mixed
358 );
359 }
360
361 #[test]
362 fn create_batch_request_roundtrip() {
363 let request = CreateBatchRequest {
364 space_id: 1,
365 batch_type: BatchType::Vertex,
366 batch_size: 100,
367 };
368 let json = serde_json::to_string(&request).unwrap();
369 let back: CreateBatchRequest = serde_json::from_str(&json).unwrap();
370 assert_eq!(back.space_id, 1);
371 assert_eq!(back.batch_type, BatchType::Vertex);
372 assert_eq!(back.batch_size, 100);
373 }
374
375 #[test]
376 fn execute_batch_response_roundtrip() {
377 let response = ExecuteBatchResponse {
378 batch_id: "b1".to_string(),
379 status: BatchStatus::Completed,
380 result: BatchResultData {
381 vertices_inserted: 2,
382 edges_inserted: 0,
383 vertices_updated: 0,
384 edges_updated: 0,
385 vertices_deleted: 0,
386 edges_deleted: 0,
387 errors: Vec::new(),
388 },
389 completed_at: Some("now".to_string()),
390 };
391 let json = serde_json::to_string(&response).unwrap();
392 let back: ExecuteBatchResponse = serde_json::from_str(&json).unwrap();
393 assert_eq!(back.result.vertices_inserted, 2);
394 assert_eq!(back.status, BatchStatus::Completed);
395 }
396}