use std::fmt;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use uuid::Uuid;
use khive_types::EdgeRelation;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct LinkId(pub Uuid);
impl From<Uuid> for LinkId {
fn from(u: Uuid) -> Self {
Self(u)
}
}
impl From<LinkId> for Uuid {
fn from(l: LinkId) -> Uuid {
l.0
}
}
impl fmt::Display for LinkId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Edge {
pub id: LinkId,
pub namespace: String,
pub source_id: Uuid,
pub target_id: Uuid,
pub relation: EdgeRelation,
pub weight: f64,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub deleted_at: Option<DateTime<Utc>>,
pub metadata: Option<Value>,
pub target_backend: Option<String>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum Direction {
#[default]
Out,
In,
Both,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct TimeRange {
pub start: Option<DateTime<Utc>>,
pub end: Option<DateTime<Utc>>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[serde(try_from = "EdgeFilterRaw")]
pub struct EdgeFilter {
pub ids: Vec<LinkId>,
pub source_ids: Vec<Uuid>,
pub target_ids: Vec<Uuid>,
pub relations: Vec<EdgeRelation>,
pub min_weight: Option<f64>,
pub max_weight: Option<f64>,
pub created_at: Option<TimeRange>,
}
#[derive(Deserialize, Default)]
struct EdgeFilterRaw {
#[serde(default)]
ids: Vec<LinkId>,
#[serde(default)]
source_ids: Vec<Uuid>,
#[serde(default)]
target_ids: Vec<Uuid>,
#[serde(default)]
relations: Vec<EdgeRelation>,
min_weight: Option<f64>,
max_weight: Option<f64>,
created_at: Option<TimeRange>,
}
impl TryFrom<EdgeFilterRaw> for EdgeFilter {
type Error = String;
fn try_from(raw: EdgeFilterRaw) -> Result<Self, Self::Error> {
let ef = Self {
ids: raw.ids,
source_ids: raw.source_ids,
target_ids: raw.target_ids,
relations: raw.relations,
min_weight: raw.min_weight,
max_weight: raw.max_weight,
created_at: raw.created_at,
};
ef.validate()?;
Ok(ef)
}
}
impl EdgeFilter {
pub fn validate(&self) -> Result<(), String> {
if let Some(w) = self.min_weight {
if !w.is_finite() {
return Err(format!("EdgeFilter: min_weight is non-finite ({w})"));
}
}
if let Some(w) = self.max_weight {
if !w.is_finite() {
return Err(format!("EdgeFilter: max_weight is non-finite ({w})"));
}
}
if let (Some(lo), Some(hi)) = (self.min_weight, self.max_weight) {
if lo > hi {
return Err(format!("EdgeFilter: min_weight ({lo}) > max_weight ({hi})"));
}
}
Ok(())
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum EdgeSortField {
CreatedAt,
Weight,
Relation,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum SortDirection {
Asc,
Desc,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SortOrder<F> {
pub field: F,
pub direction: SortDirection,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NeighborQuery {
pub direction: Direction,
pub relations: Option<Vec<EdgeRelation>>,
pub limit: Option<u32>,
pub min_weight: Option<f64>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NeighborHit {
#[serde(rename = "id")]
pub node_id: Uuid,
pub edge_id: Uuid,
pub relation: EdgeRelation,
pub weight: f64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub kind: Option<String>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct TraversalOptions {
pub max_depth: usize,
pub direction: Direction,
pub relations: Option<Vec<EdgeRelation>>,
pub min_weight: Option<f64>,
pub limit: Option<u32>,
}
impl TraversalOptions {
pub fn new(max_depth: usize) -> Self {
Self {
max_depth,
..Default::default()
}
}
pub fn with_direction(mut self, d: Direction) -> Self {
self.direction = d;
self
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TraversalRequest {
pub roots: Vec<Uuid>,
pub options: TraversalOptions,
pub include_roots: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PathNode {
#[serde(rename = "id")]
pub node_id: Uuid,
pub via_edge: Option<Uuid>,
pub depth: usize,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub kind: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GraphPath {
pub root_id: Uuid,
pub nodes: Vec<PathNode>,
pub total_weight: f64,
}