use serde::{Deserialize, Serialize};
use utoipa::{IntoParams, ToSchema};
use velesdb_core::api_types::serde_id;
#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct TraversalResultItem {
#[serde(serialize_with = "serde_id::serialize_id_as_string")]
#[cfg_attr(feature = "openapi", schema(value_type = String))]
pub target_id: u64,
pub depth: u32,
#[serde(serialize_with = "serde_id::serialize_ids_as_strings")]
#[cfg_attr(feature = "openapi", schema(schema_with = serde_id::ids_array_schema))]
pub path: Vec<u64>,
}
#[derive(Debug, Deserialize, IntoParams)]
pub struct EdgeQueryParams {
#[param(example = "KNOWS")]
pub label: Option<String>,
}
#[derive(Debug, Deserialize, ToSchema)]
pub struct TraverseRequest {
#[serde(deserialize_with = "serde_id::deserialize_id_from_string_or_number")]
#[cfg_attr(feature = "openapi", schema(schema_with = serde_id::id_input_schema))]
pub source: u64,
#[serde(default = "default_strategy")]
pub strategy: String,
#[serde(default = "default_max_depth")]
pub max_depth: u32,
#[serde(default = "default_limit")]
pub limit: usize,
#[serde(default)]
pub rel_types: Vec<String>,
}
fn default_strategy() -> String {
"bfs".to_string()
}
fn default_max_depth() -> u32 {
3
}
fn default_limit() -> usize {
100
}
#[derive(Debug, Serialize, ToSchema)]
pub struct TraverseResponse {
pub results: Vec<TraversalResultItem>,
pub has_more: bool,
pub stats: TraversalStats,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct TraversalStats {
pub visited: usize,
pub depth_reached: u32,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct DegreeResponse {
pub in_degree: usize,
pub out_degree: usize,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct EdgesResponse {
pub edges: Vec<EdgeResponse>,
pub count: usize,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct EdgeResponse {
#[serde(serialize_with = "serde_id::serialize_id_as_string")]
#[cfg_attr(feature = "openapi", schema(value_type = String))]
pub id: u64,
#[serde(serialize_with = "serde_id::serialize_id_as_string")]
#[cfg_attr(feature = "openapi", schema(value_type = String))]
pub source: u64,
#[serde(serialize_with = "serde_id::serialize_id_as_string")]
#[cfg_attr(feature = "openapi", schema(value_type = String))]
pub target: u64,
pub label: String,
pub properties: serde_json::Value,
}
#[derive(Debug, Deserialize, ToSchema)]
pub struct AddEdgeRequest {
#[serde(deserialize_with = "serde_id::deserialize_id_from_string_or_number")]
#[cfg_attr(feature = "openapi", schema(schema_with = serde_id::id_input_schema))]
pub id: u64,
#[serde(deserialize_with = "serde_id::deserialize_id_from_string_or_number")]
#[cfg_attr(feature = "openapi", schema(schema_with = serde_id::id_input_schema))]
pub source: u64,
#[serde(deserialize_with = "serde_id::deserialize_id_from_string_or_number")]
#[cfg_attr(feature = "openapi", schema(schema_with = serde_id::id_input_schema))]
pub target: u64,
pub label: String,
#[serde(default)]
pub properties: serde_json::Value,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct EdgeCountResponse {
pub count: usize,
}
#[derive(Debug, Deserialize, IntoParams)]
pub struct NodeEdgeQueryParams {
#[serde(default = "default_direction")]
#[param(example = "out")]
pub direction: String,
#[param(example = "KNOWS")]
pub label: Option<String>,
}
fn default_direction() -> String {
"out".to_string()
}
#[derive(Debug, Serialize, ToSchema)]
pub struct NodeListResponse {
#[serde(serialize_with = "serde_id::serialize_ids_as_strings")]
#[cfg_attr(feature = "openapi", schema(schema_with = serde_id::ids_array_schema))]
pub node_ids: Vec<u64>,
pub count: usize,
}
#[derive(Debug, Deserialize, ToSchema)]
pub struct UpsertNodePayloadRequest {
pub payload: serde_json::Value,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct NodePayloadResponse {
#[serde(serialize_with = "serde_id::serialize_id_as_string")]
#[cfg_attr(feature = "openapi", schema(value_type = String))]
pub node_id: u64,
pub payload: Option<serde_json::Value>,
}
#[derive(Debug, Deserialize, ToSchema)]
pub struct ParallelTraverseRequest {
#[serde(deserialize_with = "serde_id::deserialize_ids_from_string_or_number")]
#[cfg_attr(feature = "openapi", schema(schema_with = serde_id::ids_array_schema))]
pub sources: Vec<u64>,
#[serde(default = "default_max_depth")]
pub max_depth: u32,
#[serde(default = "default_limit")]
pub limit: usize,
#[serde(default)]
pub rel_types: Vec<String>,
}
#[derive(Debug, Deserialize, ToSchema)]
pub struct GraphSearchRequest {
pub vector: Vec<f32>,
#[serde(default = "default_graph_search_k")]
pub top_k: usize,
}
fn default_graph_search_k() -> usize {
10
}
#[derive(Debug, Serialize, ToSchema)]
pub struct GraphSearchResponse {
pub results: Vec<GraphSearchResultItem>,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct GraphSearchResultItem {
#[serde(serialize_with = "serde_id::serialize_id_as_string")]
#[cfg_attr(feature = "openapi", schema(value_type = String))]
pub id: u64,
pub score: f32,
pub payload: Option<serde_json::Value>,
}
#[derive(Debug, Deserialize, IntoParams)]
pub struct StreamTraverseParams {
#[serde(deserialize_with = "serde_id::deserialize_id_from_string_or_number")]
#[param(example = 123)]
pub start_node: u64,
#[serde(default = "default_algorithm")]
#[param(example = "bfs")]
pub algorithm: String,
#[serde(default = "default_stream_max_depth")]
#[param(example = 5)]
pub max_depth: u32,
#[serde(default = "default_stream_limit")]
#[param(example = 1000)]
pub limit: usize,
#[serde(default)]
#[param(example = "KNOWS,FOLLOWS")]
pub relationship_types: Option<String>,
}
fn default_algorithm() -> String {
"bfs".to_string()
}
fn default_stream_max_depth() -> u32 {
5
}
fn default_stream_limit() -> usize {
1000
}
#[derive(Debug, Serialize, ToSchema)]
pub struct StreamNodeEvent {
#[serde(serialize_with = "serde_id::serialize_id_as_string")]
#[cfg_attr(feature = "openapi", schema(value_type = String))]
pub id: u64,
pub depth: u32,
#[serde(serialize_with = "serde_id::serialize_ids_as_strings")]
#[cfg_attr(feature = "openapi", schema(schema_with = serde_id::ids_array_schema))]
pub path: Vec<u64>,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct StreamStatsEvent {
pub nodes_visited: usize,
pub elapsed_ms: u64,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct StreamDoneEvent {
pub total_nodes: usize,
pub max_depth_reached: u32,
pub elapsed_ms: u64,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct StreamErrorEvent {
pub error: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_traversal_path_serialized_as_strings() {
let above_safe = (1_u64 << 53) + 1; let item = TraversalResultItem {
target_id: 2,
depth: 1,
path: vec![above_safe],
};
let json = serde_json::to_value(&item).unwrap();
assert_eq!(json["path"], serde_json::json!(["9007199254740993"]));
}
#[test]
fn test_stream_node_event_path_serialized_as_strings() {
let above_safe = (1_u64 << 53) + 1;
let event = StreamNodeEvent {
id: 1,
depth: 1,
path: vec![above_safe],
};
let json = serde_json::to_value(&event).unwrap();
assert_eq!(json["path"], serde_json::json!(["9007199254740993"]));
}
#[test]
fn test_node_list_ids_serialized_as_strings() {
let above_safe = (1_u64 << 53) + 1;
let response = NodeListResponse {
node_ids: vec![1, above_safe],
count: 2,
};
let json = serde_json::to_value(&response).unwrap();
assert_eq!(
json["node_ids"],
serde_json::json!(["1", "9007199254740993"])
);
}
#[test]
fn test_parallel_sources_accepts_strings_and_numbers() {
let from_strings: ParallelTraverseRequest =
serde_json::from_value(serde_json::json!({ "sources": ["9007199254740993", "2"] }))
.expect("string sources must deserialize");
assert_eq!(from_strings.sources, vec![(1_u64 << 53) + 1, 2]);
let from_numbers: ParallelTraverseRequest =
serde_json::from_value(serde_json::json!({ "sources": [3, 4] }))
.expect("numeric sources must still deserialize");
assert_eq!(from_numbers.sources, vec![3, 4]);
}
}