use serde::{Deserialize, Serialize};
use serde_json::json;
use serde_json::value::Value;
use std::collections::HashMap;
use std::ops::{Deref, DerefMut};
use typed_graph::*;
type WeightId = u64;
#[derive(Debug, Clone)]
pub struct Weight(Value);
impl Deref for Weight {
type Target = Value;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for Weight {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl From<Value> for Weight {
fn from(value: Value) -> Self {
Weight(value)
}
}
impl Weight {
fn get_id_from_json(&self) -> u64 {
self.as_object()
.and_then(|obj| obj.get("id"))
.and_then(|ty| ty.as_u64())
.unwrap_or_default()
}
fn set_id_for_json(&mut self, id: u64) {
self.as_object_mut()
.and_then(|obj| obj.insert("id".to_string(), id.into()));
}
fn get_type_from_json(&self) -> &str {
self.as_object()
.and_then(|obj| obj.get("type"))
.and_then(|ty| ty.as_str())
.unwrap_or_default()
}
}
impl Id<WeightId> for Weight {
fn get_id(&self) -> WeightId {
self.get_id_from_json()
}
fn set_id(&mut self, new_id: WeightId) {
self.set_id_for_json(new_id)
}
}
impl Typed for Weight {
type Type = String;
fn get_type(&self) -> Self::Type {
self.get_type_from_json().to_string()
}
}
impl PartialEq<String> for Weight {
fn eq(&self, other: &String) -> bool {
self.get_type_from_json() == other
}
}
impl NodeExt<WeightId> for Weight {}
impl EdgeExt<WeightId> for Weight {}
#[derive(Serialize, Deserialize, Clone, Debug)]
struct JsonSchema {
version: String,
nodes: Vec<String>,
edges: Vec<(String, String, String)>,
}
impl SchemaExt<WeightId, WeightId> for JsonSchema {
type E = Weight;
type N = Weight;
fn name(&self) -> String {
self.version.clone()
}
fn allow_node(&self, node_ty: <Self::N as Typed>::Type) -> Result<(), DisAllowedNode> {
if self.nodes.contains(&node_ty) {
Ok(())
} else {
Err(DisAllowedNode::InvalidType)
}
}
fn allow_edge(
&self,
_outgoing_edge_count: usize,
_incoming_edge_count: usize,
edge_ty: <Self::E as Typed>::Type,
source: <Self::N as Typed>::Type,
target: <Self::N as Typed>::Type,
) -> Result<(), DisAllowedEdge> {
let endpoint = (edge_ty, source, target);
if self.edges.contains(&endpoint) {
Ok(())
} else {
Err(DisAllowedEdge::InvalidType)
}
}
}
impl MigrateSchema<WeightId, WeightId, JsonSchema> for JsonSchema {
fn update_edge(
&self,
new_schema: &JsonSchema,
edge: Self::E,
) -> SchemaResult<Option<Weight>, u64, u64, JsonSchema> {
let edge_type = edge.get_type();
let is_allowed = new_schema.edges.iter().any(|(ty, _, _)| &edge_type == ty);
Ok(is_allowed.then(|| edge))
}
fn update_node(
&self,
new_schema: &JsonSchema,
node: Self::N,
) -> SchemaResult<Option<Weight>, u64, u64, JsonSchema> {
let node_type = node.get_type();
let is_allowed = new_schema.nodes.iter().any(|ty| &node_type == ty);
Ok(is_allowed.then(|| node))
}
fn update_edge_type(
&self,
new_schema: &JsonSchema,
edge_type: <Self::E as Typed>::Type,
) -> Option<<Weight as Typed>::Type> {
let is_allowed = new_schema.edges.iter().any(|(ty, _, _)| &edge_type == ty);
is_allowed.then(|| edge_type)
}
fn update_node_type(
&self,
new_schema: &JsonSchema,
node_type: <Self::N as Typed>::Type,
) -> Option<<Weight as Typed>::Type> {
let is_allowed = new_schema.nodes.iter().any(|ty| &node_type == ty);
is_allowed.then(|| node_type)
}
}
impl Migration<WeightId, WeightId, JsonSchema> for JsonSchema {
type Handler = DefaultMigrationHandler;
}
fn main() -> SchemaResult<(), WeightId, WeightId, JsonSchema> {
let schemav0 = JsonSchema {
version: "V0".to_string(),
nodes: vec!["A".to_string(), "B".to_string(), "C".to_string()],
edges: vec![
("AB".to_string(), "A".to_string(), "B".to_string()),
("BC".to_string(), "B".to_string(), "C".to_string()),
("CA".to_string(), "C".to_string(), "A".to_string()),
("CC".to_string(), "C".to_string(), "C".to_string()),
],
};
let mut gv0 = TypedGraph::new(schemav0);
let a_id = gv0.add_node(json!({"id": 0, "type": "A"}))?;
let b_id = gv0.add_node(json!({"id": 1, "type": "B"}))?;
let c_id = gv0.add_node(json!({"id": 2, "type": "C"}))?;
let ab_id = gv0.add_edge(a_id, b_id, json!({"id": 0, "type": "AB"}))?;
let bc_id = gv0.add_edge(b_id, c_id, json!({"id": 1, "type": "BC"}))?;
let ca_id = gv0.add_edge(c_id, a_id, json!({"id": 2, "type": "CA"}))?;
let new_node_id = gv0.add_node(json!({"id": 2, "type": "D"}));
assert!(new_node_id.is_err());
println!("Adding node D");
println!("{:?}", new_node_id);
let new_edge_id = gv0.add_edge(c_id, a_id, json!({"id": 2, "type": "AB"}));
assert!(new_edge_id.is_err());
println!("Adding edge AC");
println!("{:?}", new_edge_id);
let dublicate_edge_id = gv0.add_edge(a_id, b_id, json!({"id": 3, "type": "AB"}))?;
gv0.remove_edge(dublicate_edge_id)?;
let dublicate_edge_id = gv0.add_edge(c_id, c_id, json!({"id": 3, "type": "CC"}))?;
gv0.remove_edge(dublicate_edge_id)?;
let a = gv0.remove_node(a_id)?;
assert_eq!(gv0.has_edge(ab_id), false);
assert_eq!(gv0.has_edge(ca_id), false);
gv0.add_node(a)?;
gv0.add_edge(a_id, b_id, json!({"id": 0, "type": "AB"}))?;
gv0.add_edge(c_id, a_id, json!({"id": 2, "type": "CA"}))?;
let a_outgoing: Vec<_> = gv0.get_outgoing(a_id)?.collect();
let b_incoming: Vec<_> = gv0.get_incoming(b_id)?.collect();
assert_eq!(a_outgoing.len(), 1);
assert_eq!(b_incoming.len(), 1);
let a_outgoing_edge = &a_outgoing[0];
let b_incoming_edge = &b_incoming[0];
assert_eq!(a_outgoing_edge.get_source(), a_id);
assert_eq!(a_outgoing_edge.get_target(), b_id);
assert_eq!(b_incoming_edge.get_source(), a_id);
assert_eq!(b_incoming_edge.get_target(), b_id);
let b_both: Vec<_> = gv0.get_incoming_and_outgoing(b_id)?.collect();
assert_eq!(b_both.len(), 2);
let edge0 = &b_both[0];
let edge1 = &b_both[1];
assert_eq!(edge0.get_inner(), b_id);
assert_eq!(edge1.get_inner(), b_id);
assert_ne!(edge0.get_outer(), edge1.get_outer());
fn longest_distance(
weight_id: WeightId,
g: &TypedGraph<WeightId, WeightId, JsonSchema>,
) -> Option<usize> {
g.get_node_safe(weight_id)?;
let mut visited: HashMap<_, usize> = HashMap::new();
let mut front = vec![(weight_id, 0)];
while let Some((front_id, distance)) = front.pop() {
if visited.contains_key(&front_id) {
continue;
}
visited.insert(front_id, distance);
for edge in g.get_incoming_and_outgoing(front_id).unwrap() {
front.push((edge.get_outer(), distance + 1));
}
}
visited.values().max().copied()
}
println!(
"Longest distance from {} is {:?}",
b_id,
longest_distance(b_id, &gv0)
);
let outer: Vec<_> = gv0
.get_outgoing(a_id)?
.filter_map(|id| gv0.get_outgoing(id.get_outer()).ok())
.flatten()
.filter_map(|id| gv0.get_outgoing(id.get_outer()).ok())
.flatten()
.filter_map(|id| gv0.get_outgoing(id.get_outer()).ok())
.flatten()
.collect();
assert_eq!(outer.len(), 1);
let outer_node = outer[0].get_id();
assert_eq!(outer_node, a_id);
fn move_forward<'a>(
n: &'a Weight,
gv0: &'a TypedGraph<u64, u64, JsonSchema>,
) -> SchemaResult<impl Iterator<Item = (String, &'a Weight)>, WeightId, WeightId, JsonSchema>
{
Ok(gv0.get_outgoing(n.get_id())?.map(|e| {
(
e.get_weight().get_type(),
gv0.get_node(e.get_outer()).unwrap(),
)
}))
}
let outer: Vec<_> = gv0
.get_node(a_id)?
.to_walker(&gv0)?
.progress(move_forward)
.progress(move_forward)
.progress(move_forward)
.many()?;
assert_eq!(outer.len(), 1);
let outer_node = outer[0].get_id();
assert_eq!(outer_node, a_id);
fn update_state(mut state: Vec<String>, addition: String) -> Vec<String> {
state.push(addition);
state
}
let outer: Vec<_> = gv0
.get_node(a_id)?
.to_walker(&gv0)?
.set_state(Vec::<String>::new())
.progress_with_state(move_forward, update_state)
.progress_with_state(move_forward, update_state)
.progress_with_state(move_forward, update_state)
.many_with_state()?;
assert_eq!(outer.len(), 1);
let walker_target = &outer[0];
assert_eq!(walker_target.val.get_id(), a_id);
assert_eq!(walker_target.state, vec!["AB", "BC", "CA"]);
let schemav1 = JsonSchema {
version: "V0".to_string(),
nodes: vec!["A".to_string(), "C".to_string()],
edges: vec![
("CA".to_string(), "C".to_string(), "A".to_string()),
("CC".to_string(), "C".to_string(), "C".to_string()),
],
};
let gv1 = gv0.migrate(schemav1, &DefaultMigrationHandler)?;
assert_eq!(gv1.has_node(b_id), false);
assert_eq!(gv1.has_edge(ab_id), false);
assert_eq!(gv1.has_edge(bc_id), false);
assert_eq!(gv1.has_node(a_id), true);
assert_eq!(gv1.has_node(c_id), true);
assert_eq!(gv1.has_edge(ca_id), true);
Ok(())
}