use std::collections::BTreeMap;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use crate::error::{Result, RetrievalError};
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(tag = "kind", content = "id", rename_all = "snake_case")]
pub enum EntityRef {
External(String),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct LinkId(u64);
impl LinkId {
pub const NIL: Self = Self(0);
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Link {
pub id: LinkId,
pub source: EntityRef,
pub target: EntityRef,
pub relation: String,
pub properties: Option<BTreeMap<String, serde_json::Value>>,
}
impl Link {
pub fn new(
id: LinkId,
source: EntityRef,
target: EntityRef,
relation: impl Into<String>,
) -> Self {
Self {
id,
source,
target,
relation: relation.into(),
properties: None,
}
}
pub fn with_properties(
id: LinkId,
source: EntityRef,
target: EntityRef,
relation: impl Into<String>,
props: serde_json::Value,
) -> Self {
let properties = props
.as_object()
.map(|m| m.iter().map(|(k, v)| (k.clone(), v.clone())).collect());
Self {
id,
source,
target,
relation: relation.into(),
properties,
}
}
}
#[derive(Clone, Debug, Default)]
pub struct StorageContext {
pub namespace: String,
}
impl StorageContext {
pub fn new(namespace: impl Into<String>) -> Self {
Self {
namespace: namespace.into(),
}
}
}
#[async_trait]
pub trait LinkStore: Send + Sync {
async fn outgoing(&self, ctx: &StorageContext, entity: &EntityRef) -> Result<Vec<Link>>;
async fn incoming(&self, ctx: &StorageContext, entity: &EntityRef) -> Result<Vec<Link>>;
async fn link(
&self,
ctx: &StorageContext,
source: EntityRef,
target: EntityRef,
relation: &str,
properties: Option<serde_json::Value>,
) -> Result<Link>;
}
pub struct MockLinkStore {
links: parking_lot::Mutex<Vec<Link>>,
next_id: std::sync::atomic::AtomicU64,
}
impl MockLinkStore {
pub fn new() -> Self {
Self {
links: parking_lot::Mutex::new(Vec::new()),
next_id: std::sync::atomic::AtomicU64::new(1),
}
}
}
impl Default for MockLinkStore {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl LinkStore for MockLinkStore {
async fn outgoing(&self, _ctx: &StorageContext, entity: &EntityRef) -> Result<Vec<Link>> {
let links = self.links.lock();
Ok(links
.iter()
.filter(|l| &l.source == entity)
.cloned()
.collect())
}
async fn incoming(&self, _ctx: &StorageContext, entity: &EntityRef) -> Result<Vec<Link>> {
let links = self.links.lock();
Ok(links
.iter()
.filter(|l| &l.target == entity)
.cloned()
.collect())
}
async fn link(
&self,
_ctx: &StorageContext,
source: EntityRef,
target: EntityRef,
relation: &str,
properties: Option<serde_json::Value>,
) -> Result<Link> {
let id = self
.next_id
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let link = if let Some(props) = properties {
Link::with_properties(LinkId(id), source, target, relation, props)
} else {
Link::new(LinkId(id), source, target, relation)
};
self.links.lock().push(link.clone());
Ok(link)
}
}
pub fn test_context() -> StorageContext {
StorageContext::new("test")
}
#[allow(dead_code)]
pub(crate) fn graph_err(msg: impl std::fmt::Display) -> RetrievalError {
RetrievalError::GraphTraversal(msg.to_string())
}