use crate::error::{Result, UrbitAPIError};
use chrono::prelude::*;
use json::{object, JsonValue};
use regex::Regex;
#[derive(Clone, Debug)]
pub struct Graph {
pub nodes: Vec<Node>,
}
#[derive(Clone, Debug)]
pub struct Node {
pub index: String,
pub author: String,
pub time_sent: u64,
pub signatures: Vec<Signature>,
pub contents: NodeContents,
pub hash: Option<String>,
pub children: Vec<Node>,
}
#[derive(Debug, Clone)]
pub struct NodeContents {
pub content_list: Vec<JsonValue>,
}
#[derive(Debug, Clone)]
pub struct Signature {
signature: String,
life: u64,
ship: String,
}
impl Graph {
pub fn new(nodes: Vec<Node>) -> Graph {
Graph { nodes: nodes }
}
pub fn insert(&mut self, node: Node) {
self.nodes.push(node);
}
pub fn from_json(graph_json: JsonValue) -> Result<Graph> {
let mut graph = Graph::new(vec![]);
let mut childless_nodes = vec![];
let mut graph_text = format!("{}", graph_json["graph-update"]["add-graph"]["graph"]);
if graph_text == "null" {
graph_text = format!("{}", graph_json["graph-update"]["add-nodes"]["nodes"]);
}
if graph_text == "{}" {
return Ok(Graph::new(vec![]));
}
let re = Regex::new(r#"\d+":(.+?children":).+?"#)
.map_err(|_| UrbitAPIError::FailedToCreateGraphFromJSON)?;
for capture in re.captures_iter(&graph_text) {
let node_string = capture
.get(1)
.ok_or(UrbitAPIError::FailedToCreateGraphFromJSON)?
.as_str()
.to_string()
+ r#"null}"#;
let json = json::parse(&node_string)
.map_err(|_| UrbitAPIError::FailedToCreateGraphNodeFromJSON)?;
if json["post"].is_string() {
continue;
}
let processed_node_opt = Node::from_json(&json);
if processed_node_opt.is_err() {
println!("Failed to process graph node: \n{}", node_string);
}
childless_nodes.push(processed_node_opt?);
}
if childless_nodes.len() == 0 {
return Err(UrbitAPIError::FailedToCreateGraphFromJSON);
}
let mut building_node = childless_nodes[0].clone();
for i in 1..childless_nodes.len() {
if building_node.is_ancestor(&childless_nodes[i]) {
building_node = building_node.add_child(&childless_nodes[i]);
} else {
graph.insert(building_node.clone());
building_node = childless_nodes[i].clone();
}
}
graph.insert(building_node.clone());
Ok(graph)
}
pub fn to_json(&self) -> JsonValue {
let nodes_json: Vec<JsonValue> = self.nodes.iter().map(|n| n.to_json()).collect();
object! {
"graph-update": {
"add-graph": {
"graph": nodes_json,
}
}
}
}
}
impl Node {
pub fn new(
index: String,
author: String,
time_sent: u64,
signatures: Vec<Signature>,
contents: NodeContents,
hash: Option<String>,
) -> Node {
Node {
index: index,
author: author,
time_sent: time_sent,
signatures: signatures,
contents: contents,
hash: hash,
children: vec![],
}
}
pub fn index_tail(&self) -> String {
let split_index: Vec<&str> = self.index.split("/").collect();
split_index[split_index.len() - 1].to_string()
}
pub fn parent_index(&self) -> Option<String> {
let rev_index = self.index.chars().rev().collect::<String>();
let split_index: Vec<&str> = rev_index.splitn(2, "/").collect();
if split_index.len() < 2 {
return None;
}
let parent_index = split_index[1].chars().rev().collect::<String>();
Some(parent_index)
}
pub fn is_parent(&self, potential_child: &Node) -> bool {
if let Some(index) = potential_child.parent_index() {
return self.index == index;
}
false
}
pub fn is_ancestor(&self, potential_child: &Node) -> bool {
let pc_split_index: Vec<&str> = potential_child.index.split("/").collect();
let parent_split_index: Vec<&str> = self.index.split("/").collect();
if parent_split_index.len() > pc_split_index.len() {
return false;
}
let mut matching = false;
for n in 0..parent_split_index.len() {
matching = parent_split_index[n] == pc_split_index[n]
}
matching
}
pub fn add_child(&self, new_child: &Node) -> Node {
let mut new_self = self.clone();
for i in 0..self.children.len() {
let child = &new_self.children[i];
if child.is_parent(new_child) {
new_self.children[i].children.push(new_child.clone());
return new_self;
} else if child.is_ancestor(new_child) {
new_self.children[i] = child.add_child(new_child);
return new_self;
}
}
new_self.children.push(new_child.clone());
new_self
}
pub fn time_sent_formatted(&self) -> String {
let unix_time = self.time_sent as i64 / 1000;
let date_time: DateTime<Utc> =
DateTime::from_utc(NaiveDateTime::from_timestamp(unix_time, 0), Utc);
let new_date = date_time.format("%Y-%m-%d %H:%M:%S");
format!("{}", new_date)
}
pub fn to_json(&self) -> JsonValue {
let mut node_json = object!();
node_json[self.index.clone()] = self.to_json_value();
node_json
}
fn to_json_value(&self) -> JsonValue {
let mut children = object!();
for child in &self.children {
children[child.index_tail()] = child.to_json_value();
}
let result_json = object! {
"post": {
"author": self.author.clone(),
"index": self.index.clone(),
"time-sent": self.time_sent,
"contents": self.contents.to_json(),
"hash": null,
"signatures": []
},
"children": children
};
result_json
}
pub fn from_graph_update_json(wrapped_json: &JsonValue) -> Result<Node> {
let dumped = wrapped_json["graph-update"]["add-nodes"]["nodes"].dump();
let split: Vec<&str> = dumped.splitn(2, ":").collect();
if split.len() <= 1 {
return Err(UrbitAPIError::FailedToCreateGraphNodeFromJSON);
}
let mut inner_string = split[1].to_string();
inner_string.remove(inner_string.len() - 1);
let inner_json = json::parse(&inner_string)
.map_err(|_| UrbitAPIError::FailedToCreateGraphNodeFromJSON)?;
Self::from_json(&inner_json)
}
pub fn from_json(json: &JsonValue) -> Result<Node> {
let children = json["children"].clone();
let post_json = json["post"].clone();
let index = post_json["index"]
.as_str()
.ok_or(UrbitAPIError::FailedToCreateGraphNodeFromJSON)?;
let author = post_json["author"]
.as_str()
.ok_or(UrbitAPIError::FailedToCreateGraphNodeFromJSON)?;
let time_sent = post_json["time-sent"]
.as_u64()
.ok_or(UrbitAPIError::FailedToCreateGraphNodeFromJSON)?;
let mut json_contents = vec![];
for content in post_json["contents"].members() {
json_contents.push(content.clone());
}
let contents = NodeContents::from_json(json_contents);
let hash = match post_json["contents"]["hash"].is_null() {
true => None,
false => Some(
post_json["contents"]["hash"]
.as_str()
.ok_or(UrbitAPIError::FailedToCreateGraphNodeFromJSON)?
.to_string(),
),
};
let mut signatures = vec![];
for signature in post_json["signatures"].members() {
let sig = Signature {
signature: signature["signature"]
.as_str()
.ok_or(UrbitAPIError::FailedToCreateGraphNodeFromJSON)?
.to_string(),
life: signature["life"]
.as_u64()
.ok_or(UrbitAPIError::FailedToCreateGraphNodeFromJSON)?,
ship: signature["ship"]
.as_str()
.ok_or(UrbitAPIError::FailedToCreateGraphNodeFromJSON)?
.to_string(),
};
signatures.push(sig);
}
let mut node_children: Vec<Node> = vec![];
if let JsonValue::Object(o) = children {
for (_, val) in o.iter() {
if let Ok(child_node) = Node::from_json(val) {
node_children.push(child_node);
}
}
}
Ok(Node {
index: index.to_string(),
author: author.to_string(),
time_sent: time_sent,
signatures: signatures,
contents: contents,
hash: hash,
children: node_children,
})
}
}
impl NodeContents {
pub fn new() -> NodeContents {
NodeContents {
content_list: vec![],
}
}
pub fn is_empty(&self) -> bool {
self.content_list.len() == 0
}
pub fn add_text(&self, text: &str) -> NodeContents {
let formatted = object! {
"text": text
};
self.add_to_contents(formatted)
}
pub fn add_url(&self, url: &str) -> NodeContents {
let formatted = object! {
"url": url
};
self.add_to_contents(formatted)
}
pub fn add_mention(&self, referenced_ship: &str) -> NodeContents {
let formatted = object! {
"mention": referenced_ship
};
self.add_to_contents(formatted)
}
pub fn add_code(&self, expression: &str, output: &str) -> NodeContents {
let formatted = object! {
"code": {
"expression": expression,
"output": [[output]]
}
};
self.add_to_contents(formatted)
}
pub fn from_json(json_contents: Vec<JsonValue>) -> NodeContents {
NodeContents {
content_list: json_contents,
}
}
pub fn to_json(&self) -> JsonValue {
self.content_list.clone().into()
}
pub fn to_formatted_string(&self) -> String {
let mut result = "".to_string();
for item in &self.content_list {
let text = Self::extract_content_text(item);
result = result + " " + text.trim();
}
result
}
pub fn to_formatted_words(&self) -> Vec<String> {
let formatted_string = self.to_formatted_string();
formatted_string
.split_whitespace()
.map(|s| s.to_string())
.collect()
}
fn extract_content_text(json: &JsonValue) -> String {
let mut result = " ".to_string();
if !json["text"].is_empty() {
result = json["text"].dump();
} else if !json["url"].is_empty() {
result = json["url"].dump();
} else if !json["mention"].is_empty() {
result = json["mention"].dump();
result.remove(0);
result.remove(result.len() - 1);
return format!("~{}", result);
} else if !json["code"].is_empty() {
result = json["code"].dump();
}
result.remove(0);
result.remove(result.len() - 1);
result
}
fn add_to_contents(&self, json: JsonValue) -> NodeContents {
let mut contents = self.content_list.clone();
contents.append(&mut vec![json]);
NodeContents {
content_list: contents,
}
}
}