use crate::entity::EntityId;
use crate::project::ProjectId;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use ulid::Ulid;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct RelationId(pub Ulid);
impl RelationId {
pub fn new() -> Self {
Self(Ulid::new())
}
}
impl Default for RelationId {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Display for RelationId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Direction {
Outgoing,
Incoming,
#[default]
Both,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Relation {
pub id: RelationId,
pub project_id: ProjectId,
pub from_id: EntityId,
pub from_name: String,
pub to_id: EntityId,
pub to_name: String,
pub relation_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub weight: Option<f64>,
#[serde(default)]
pub metadata: HashMap<String, serde_json::Value>,
pub created_at: DateTime<Utc>,
}
impl Relation {
pub fn new(
project_id: ProjectId,
from_id: EntityId,
from_name: impl Into<String>,
to_id: EntityId,
to_name: impl Into<String>,
relation_type: impl Into<String>,
) -> Self {
Self {
id: RelationId::new(),
project_id,
from_id,
from_name: from_name.into(),
to_id,
to_name: to_name.into(),
relation_type: relation_type.into(),
weight: None,
metadata: HashMap::new(),
created_at: Utc::now(),
}
}
pub fn from_names(
project_id: ProjectId,
from_name: impl Into<String>,
to_name: impl Into<String>,
relation_type: impl Into<String>,
) -> Self {
Self {
id: RelationId::new(),
project_id,
from_id: EntityId::new(),
from_name: from_name.into(),
to_id: EntityId::new(),
to_name: to_name.into(),
relation_type: relation_type.into(),
weight: None,
metadata: HashMap::new(),
created_at: Utc::now(),
}
}
pub fn with_weight(mut self, weight: f64) -> Self {
self.weight = Some(weight);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NewRelation {
pub from: String,
pub to: String,
pub relation_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub weight: Option<f64>,
#[serde(default)]
pub metadata: HashMap<String, serde_json::Value>,
}
impl NewRelation {
pub fn new(
from: impl Into<String>,
to: impl Into<String>,
relation_type: impl Into<String>,
) -> Self {
Self {
from: from.into(),
to: to.into(),
relation_type: relation_type.into(),
weight: None,
metadata: HashMap::new(),
}
}
pub fn with_weight(mut self, weight: f64) -> Self {
self.weight = Some(weight);
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_relation_creation() {
let project_id = ProjectId::new();
let from_id = EntityId::new();
let to_id = EntityId::new();
let relation = Relation::new(
project_id,
from_id,
"John_Smith",
to_id,
"Google",
"works_at",
);
assert_eq!(relation.from_name, "John_Smith");
assert_eq!(relation.to_name, "Google");
assert_eq!(relation.relation_type, "works_at");
assert!(relation.weight.is_none());
}
#[test]
fn test_relation_with_weight() {
let project_id = ProjectId::new();
let from_id = EntityId::new();
let to_id = EntityId::new();
let relation = Relation::new(
project_id,
from_id,
"John_Smith",
to_id,
"Jane_Doe",
"mentors",
)
.with_weight(0.8);
assert_eq!(relation.weight, Some(0.8));
}
}