use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
pub type BatchId = String;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum BatchStatus {
Created,
Running,
Completed,
Failed,
Cancelled,
}
impl fmt::Display for BatchStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
BatchStatus::Created => "Created",
BatchStatus::Running => "Running",
BatchStatus::Completed => "Completed",
BatchStatus::Failed => "Failed",
BatchStatus::Cancelled => "Cancelled",
};
write!(f, "{}", name)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum BatchType {
Vertex,
Edge,
Mixed,
}
impl fmt::Display for BatchType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
BatchType::Vertex => "vertex",
BatchType::Edge => "edge",
BatchType::Mixed => "mixed",
};
write!(f, "{}", name)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum BatchItemType {
Vertex,
Edge,
Update,
Delete,
}
impl fmt::Display for BatchItemType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
BatchItemType::Vertex => "vertex",
BatchItemType::Edge => "edge",
BatchItemType::Update => "update",
BatchItemType::Delete => "delete",
};
write!(f, "{}", name)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
#[serde(tag = "type")]
pub enum BatchItem {
#[serde(rename = "vertex")]
Vertex(VertexData),
#[serde(rename = "edge")]
Edge(EdgeData),
#[serde(rename = "update_vertex")]
UpdateVertex(VertexData),
#[serde(rename = "update_edge")]
UpdateEdgeData(UpdateEdgeData),
#[serde(rename = "delete_vertex")]
DeleteVertex(DeleteVertexData),
#[serde(rename = "delete_edge")]
DeleteEdgeData(DeleteEdgeData),
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct VertexData {
pub vid: serde_json::Value,
pub tag: String,
#[serde(default)]
pub properties: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct EdgeData {
pub edge_type: String,
pub src_vid: serde_json::Value,
pub dst_vid: serde_json::Value,
#[serde(default)]
pub properties: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct UpdateEdgeData {
pub edge_type: String,
pub src_vid: serde_json::Value,
pub dst_vid: serde_json::Value,
#[serde(default)]
pub rank: i64,
#[serde(default)]
pub properties: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct DeleteVertexData {
pub vid: serde_json::Value,
#[serde(default)]
pub tag_names: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct DeleteEdgeData {
pub edge_type: String,
pub src_vid: serde_json::Value,
pub dst_vid: serde_json::Value,
#[serde(default)]
pub rank: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct CreateBatchRequest {
pub space_id: u64,
pub batch_type: BatchType,
#[serde(default = "default_batch_size")]
pub batch_size: usize,
}
fn default_batch_size() -> usize {
1000
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct CreateBatchResponse {
pub batch_id: BatchId,
pub status: BatchStatus,
pub created_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct AddBatchItemsRequest {
pub items: Vec<BatchItem>,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct AddBatchItemsResponse {
pub accepted: usize,
#[serde(default)]
pub buffered: usize,
#[serde(default)]
pub total_buffered: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct ExecuteBatchResponse {
pub batch_id: BatchId,
pub status: BatchStatus,
pub result: BatchResultData,
#[serde(default)]
pub completed_at: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct BatchResultData {
pub vertices_inserted: usize,
pub edges_inserted: usize,
#[serde(default)]
pub vertices_updated: usize,
#[serde(default)]
pub edges_updated: usize,
#[serde(default)]
pub vertices_deleted: usize,
#[serde(default)]
pub edges_deleted: usize,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub errors: Vec<BatchErrorData>,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct BatchErrorData {
pub index: usize,
pub item_type: BatchItemType,
pub error: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct BatchStatusResponse {
pub batch_id: BatchId,
pub status: BatchStatus,
pub progress: BatchProgress,
pub created_at: String,
pub updated_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct BatchProgress {
pub total: usize,
pub processed: usize,
pub succeeded: usize,
pub failed: usize,
#[serde(default)]
pub buffered: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn batch_item_vertex_roundtrip() {
let item = BatchItem::Vertex(VertexData {
vid: serde_json::json!("v1"),
tag: "person".to_string(),
properties: HashMap::from([("name".to_string(), serde_json::json!("Alice"))]),
});
let json = serde_json::to_string(&item).unwrap();
assert!(json.contains(r#""type":"vertex""#), "json: {json}");
let back: BatchItem = serde_json::from_str(&json).unwrap();
match back {
BatchItem::Vertex(v) => {
assert_eq!(v.vid, serde_json::json!("v1"));
assert_eq!(v.tag, "person");
assert_eq!(v.properties.get("name"), Some(&serde_json::json!("Alice")));
}
other => panic!("expected vertex item, got {other:?}"),
}
}
#[test]
fn batch_item_edge_roundtrip() {
let item = BatchItem::Edge(EdgeData {
edge_type: "follows".to_string(),
src_vid: serde_json::json!("v1"),
dst_vid: serde_json::json!("v2"),
properties: HashMap::new(),
});
let json = serde_json::to_string(&item).unwrap();
let back: BatchItem = serde_json::from_str(&json).unwrap();
match back {
BatchItem::Edge(e) => assert_eq!(e.edge_type, "follows"),
other => panic!("expected edge item, got {other:?}"),
}
}
#[test]
fn batch_status_wire_names() {
assert_eq!(
serde_json::to_string(&BatchStatus::Completed).unwrap(),
r#""completed""#
);
assert_eq!(
serde_json::from_str::<BatchStatus>(r#""running""#).unwrap(),
BatchStatus::Running
);
assert_eq!(
serde_json::from_str::<BatchType>(r#""mixed""#).unwrap(),
BatchType::Mixed
);
}
#[test]
fn create_batch_request_roundtrip() {
let request = CreateBatchRequest {
space_id: 1,
batch_type: BatchType::Vertex,
batch_size: 100,
};
let json = serde_json::to_string(&request).unwrap();
let back: CreateBatchRequest = serde_json::from_str(&json).unwrap();
assert_eq!(back.space_id, 1);
assert_eq!(back.batch_type, BatchType::Vertex);
assert_eq!(back.batch_size, 100);
}
#[test]
fn execute_batch_response_roundtrip() {
let response = ExecuteBatchResponse {
batch_id: "b1".to_string(),
status: BatchStatus::Completed,
result: BatchResultData {
vertices_inserted: 2,
edges_inserted: 0,
vertices_updated: 0,
edges_updated: 0,
vertices_deleted: 0,
edges_deleted: 0,
errors: Vec::new(),
},
completed_at: Some("now".to_string()),
};
let json = serde_json::to_string(&response).unwrap();
let back: ExecuteBatchResponse = serde_json::from_str(&json).unwrap();
assert_eq!(back.result.vertices_inserted, 2);
assert_eq!(back.status, BatchStatus::Completed);
}
}