urbit_http_api/
graph.rs

1use crate::error::{Result, UrbitAPIError};
2use chrono::prelude::*;
3use json::{object, JsonValue};
4use regex::Regex;
5
6/// Struct which represents a graph in Graph Store
7/// as a list of Nodes.
8#[derive(Clone, Debug)]
9pub struct Graph {
10    /// List of nodes structured as a graph with children
11    pub nodes: Vec<Node>,
12}
13
14/// Struct which represents a node in a graph in Graph Store
15#[derive(Clone, Debug)]
16pub struct Node {
17    pub index: String,
18    pub author: String,
19    pub time_sent: u64,
20    pub signatures: Vec<Signature>,
21    pub contents: NodeContents,
22    pub hash: Option<String>,
23    pub children: Vec<Node>,
24}
25
26/// Struct which represents the contents inside of a node
27#[derive(Debug, Clone)]
28pub struct NodeContents {
29    pub content_list: Vec<JsonValue>,
30}
31
32/// Struct which represents a signature inside of a node
33#[derive(Debug, Clone)]
34pub struct Signature {
35    signature: String,
36    life: u64,
37    ship: String,
38}
39
40impl Graph {
41    /// Create a new `Graph`
42    pub fn new(nodes: Vec<Node>) -> Graph {
43        Graph { nodes: nodes }
44    }
45
46    /// Insert a `Node` into the top level of the `Graph`.
47    pub fn insert(&mut self, node: Node) {
48        self.nodes.push(node);
49    }
50
51    /// Convert from graph `JsonValue` to `Graph`
52    pub fn from_json(graph_json: JsonValue) -> Result<Graph> {
53        // Create a new empty graph to insert nodes into
54        let mut graph = Graph::new(vec![]);
55        // Create a list of nodes all stripped of child associations
56        let mut childless_nodes = vec![];
57        // Get the graph inner json
58        let mut graph_text = format!("{}", graph_json["graph-update"]["add-graph"]["graph"]);
59        if graph_text == "null" {
60            graph_text = format!("{}", graph_json["graph-update"]["add-nodes"]["nodes"]);
61        }
62
63        // Check if the graph is valid but simply has no nodes
64        if graph_text == "{}" {
65            return Ok(Graph::new(vec![]));
66        }
67
68        // Create regex to capture each node json
69        let re = Regex::new(r#"\d+":(.+?children":).+?"#)
70            .map_err(|_| UrbitAPIError::FailedToCreateGraphFromJSON)?;
71        // For each capture group, create a childless node
72        for capture in re.captures_iter(&graph_text) {
73            // Get the node json string without it's children
74            let node_string = capture
75                .get(1)
76                .ok_or(UrbitAPIError::FailedToCreateGraphFromJSON)?
77                .as_str()
78                .to_string()
79                + r#"null}"#;
80            let json = json::parse(&node_string)
81                .map_err(|_| UrbitAPIError::FailedToCreateGraphNodeFromJSON)?;
82            // Skipping nodes which do not have a proper `post` value
83            if json["post"].is_string() {
84                continue;
85            }
86            // Finish processing node
87            let processed_node_opt = Node::from_json(&json);
88            if processed_node_opt.is_err() {
89                println!("Failed to process graph node: \n{}", node_string);
90            }
91            childless_nodes.push(processed_node_opt?);
92        }
93
94        // Check if failed to extract nodes from json via Regex
95        if childless_nodes.len() == 0 {
96            return Err(UrbitAPIError::FailedToCreateGraphFromJSON);
97        }
98
99        // Create a placeholder node that accumulates all of the children
100        // before being added to the graph
101        let mut building_node = childless_nodes[0].clone();
102        // Insert all of the childless nodes into the graph
103        // under the correct parent.
104        for i in 1..childless_nodes.len() {
105            if building_node.is_ancestor(&childless_nodes[i]) {
106                // Add the child into the deepest depth possible and update building_node
107                building_node = building_node.add_child(&childless_nodes[i]);
108            } else {
109                // Insert the finished `building_node` into the graph
110                graph.insert(building_node.clone());
111                building_node = childless_nodes[i].clone();
112            }
113        }
114        // Add the final created `building_node` from the last
115        // iteration of the for loop.
116        graph.insert(building_node.clone());
117
118        // Return the finished graph
119        Ok(graph)
120    }
121
122    // Converts to `JsonValue`
123    pub fn to_json(&self) -> JsonValue {
124        let nodes_json: Vec<JsonValue> = self.nodes.iter().map(|n| n.to_json()).collect();
125        object! {
126                            "graph-update": {
127                                "add-graph": {
128                                    "graph": nodes_json,
129        }
130                }
131                        }
132    }
133}
134
135impl Node {
136    // Create a new `Node`
137    pub fn new(
138        index: String,
139        author: String,
140        time_sent: u64,
141        signatures: Vec<Signature>,
142        contents: NodeContents,
143        hash: Option<String>,
144    ) -> Node {
145        Node {
146            index: index,
147            author: author,
148            time_sent: time_sent,
149            signatures: signatures,
150            contents: contents,
151            hash: hash,
152            children: vec![],
153        }
154    }
155
156    /// Extract the node's final section (after the last `/`) of the index
157    pub fn index_tail(&self) -> String {
158        let split_index: Vec<&str> = self.index.split("/").collect();
159        split_index[split_index.len() - 1].to_string()
160    }
161
162    /// Extract the `Node`'s parent's index (if parent exists)
163    pub fn parent_index(&self) -> Option<String> {
164        let rev_index = self.index.chars().rev().collect::<String>();
165        let split_index: Vec<&str> = rev_index.splitn(2, "/").collect();
166        // Error check
167        if split_index.len() < 2 {
168            return None;
169        }
170
171        let parent_index = split_index[1].chars().rev().collect::<String>();
172
173        Some(parent_index)
174    }
175
176    /// Check if a self is the direct parent of another `Node`.
177    pub fn is_parent(&self, potential_child: &Node) -> bool {
178        if let Some(index) = potential_child.parent_index() {
179            return self.index == index;
180        }
181        false
182    }
183
184    /// Check if self is a parent (direct or indirect) of another `Node`
185    pub fn is_ancestor(&self, potential_child: &Node) -> bool {
186        let pc_split_index: Vec<&str> = potential_child.index.split("/").collect();
187        let parent_split_index: Vec<&str> = self.index.split("/").collect();
188
189        // Verify the parent has a smaller split index than child
190        if parent_split_index.len() > pc_split_index.len() {
191            return false;
192        }
193
194        // Check if every index split part of the parent is part of
195        // the child
196        let mut matching = false;
197        for n in 0..parent_split_index.len() {
198            matching = parent_split_index[n] == pc_split_index[n]
199        }
200
201        // Return if parent index is fully part of the child index
202        matching
203    }
204
205    /// Creates a copy of self and searches through the children to find
206    /// the deepest depth which the `new_child` can be placed.
207    pub fn add_child(&self, new_child: &Node) -> Node {
208        let mut new_self = self.clone();
209        for i in 0..self.children.len() {
210            let child = &new_self.children[i];
211            if child.is_parent(new_child) {
212                new_self.children[i].children.push(new_child.clone());
213                return new_self;
214            } else if child.is_ancestor(new_child) {
215                new_self.children[i] = child.add_child(new_child);
216                return new_self;
217            }
218        }
219        new_self.children.push(new_child.clone());
220        new_self
221    }
222    /// Formats the `time_sent` field to be human readable date-time in UTC
223    pub fn time_sent_formatted(&self) -> String {
224        let unix_time = self.time_sent as i64 / 1000;
225        let date_time: DateTime<Utc> =
226            DateTime::from_utc(NaiveDateTime::from_timestamp(unix_time, 0), Utc);
227        let new_date = date_time.format("%Y-%m-%d %H:%M:%S");
228        format!("{}", new_date)
229    }
230
231    /// Converts to `JsonValue`
232    /// creates a json object with one field: key = the node's full index path, value = json representation of node
233    pub fn to_json(&self) -> JsonValue {
234        let mut node_json = object!();
235        node_json[self.index.clone()] = self.to_json_value();
236        node_json
237    }
238
239    /// Converts to `JsonValue`
240    /// json representation of a node
241    fn to_json_value(&self) -> JsonValue {
242        let mut children = object!();
243        for child in &self.children {
244            children[child.index_tail()] = child.to_json_value();
245        }
246
247        let result_json = object! {
248                        "post": {
249                            "author": self.author.clone(),
250                            "index": self.index.clone(),
251                            "time-sent": self.time_sent,
252                            "contents": self.contents.to_json(),
253                            "hash": null,
254                            "signatures": []
255                        },
256                        "children": children
257        };
258        result_json
259    }
260
261    /// Convert from node `JsonValue` which is wrapped up in a few wrapper fields
262    /// into a `Node`, with children if they exist.
263    pub fn from_graph_update_json(wrapped_json: &JsonValue) -> Result<Node> {
264        let dumped = wrapped_json["graph-update"]["add-nodes"]["nodes"].dump();
265        let split: Vec<&str> = dumped.splitn(2, ":").collect();
266        if split.len() <= 1 {
267            return Err(UrbitAPIError::FailedToCreateGraphNodeFromJSON);
268        }
269
270        let mut inner_string = split[1].to_string();
271        inner_string.remove(inner_string.len() - 1);
272
273        let inner_json = json::parse(&inner_string)
274            .map_err(|_| UrbitAPIError::FailedToCreateGraphNodeFromJSON)?;
275
276        Self::from_json(&inner_json)
277    }
278
279    /// Convert from straight node `JsonValue` to `Node`
280    pub fn from_json(json: &JsonValue) -> Result<Node> {
281        // Process all of the json fields
282        let children = json["children"].clone();
283        let post_json = json["post"].clone();
284
285        let index = post_json["index"]
286            .as_str()
287            .ok_or(UrbitAPIError::FailedToCreateGraphNodeFromJSON)?;
288        let author = post_json["author"]
289            .as_str()
290            .ok_or(UrbitAPIError::FailedToCreateGraphNodeFromJSON)?;
291        let time_sent = post_json["time-sent"]
292            .as_u64()
293            .ok_or(UrbitAPIError::FailedToCreateGraphNodeFromJSON)?;
294
295        // Convert array JsonValue to vector for contents
296        let mut json_contents = vec![];
297        for content in post_json["contents"].members() {
298            json_contents.push(content.clone());
299        }
300        let contents = NodeContents::from_json(json_contents);
301
302        // Wrap hash in an Option for null case
303        let hash = match post_json["contents"]["hash"].is_null() {
304            true => None,
305            false => Some(
306                post_json["contents"]["hash"]
307                    .as_str()
308                    .ok_or(UrbitAPIError::FailedToCreateGraphNodeFromJSON)?
309                    .to_string(),
310            ),
311        };
312
313        // Convert array JsonValue to vector of Signatures
314        let mut signatures = vec![];
315        for signature in post_json["signatures"].members() {
316            let sig = Signature {
317                signature: signature["signature"]
318                    .as_str()
319                    .ok_or(UrbitAPIError::FailedToCreateGraphNodeFromJSON)?
320                    .to_string(),
321                life: signature["life"]
322                    .as_u64()
323                    .ok_or(UrbitAPIError::FailedToCreateGraphNodeFromJSON)?,
324                ship: signature["ship"]
325                    .as_str()
326                    .ok_or(UrbitAPIError::FailedToCreateGraphNodeFromJSON)?
327                    .to_string(),
328            };
329
330            signatures.push(sig);
331        }
332
333        // children
334        let mut node_children: Vec<Node> = vec![];
335        if let JsonValue::Object(o) = children {
336            for (_, val) in o.iter() {
337                if let Ok(child_node) = Node::from_json(val) {
338                    node_children.push(child_node);
339                }
340            }
341        }
342
343        Ok(Node {
344            index: index.to_string(),
345            author: author.to_string(),
346            time_sent: time_sent,
347            signatures: signatures,
348            contents: contents,
349            hash: hash,
350            children: node_children,
351        })
352    }
353}
354
355/// Methods for `NodeContents`
356impl NodeContents {
357    /// Create a new empty `NodeContents`
358    pub fn new() -> NodeContents {
359        NodeContents {
360            content_list: vec![],
361        }
362    }
363
364    /// Check if `NodeContents` is empty
365    pub fn is_empty(&self) -> bool {
366        self.content_list.len() == 0
367    }
368
369    /// Appends text to the end of the list of contents
370    pub fn add_text(&self, text: &str) -> NodeContents {
371        let formatted = object! {
372            "text": text
373        };
374        self.add_to_contents(formatted)
375    }
376
377    /// Appends a url to the end of the list of contents
378    pub fn add_url(&self, url: &str) -> NodeContents {
379        let formatted = object! {
380            "url": url
381        };
382        self.add_to_contents(formatted)
383    }
384
385    /// Appends a mention to another @p/ship to the end of the list of contents
386    pub fn add_mention(&self, referenced_ship: &str) -> NodeContents {
387        let formatted = object! {
388            "mention": referenced_ship
389        };
390        self.add_to_contents(formatted)
391    }
392
393    /// Appends a code block to the end of the list of contents
394    pub fn add_code(&self, expression: &str, output: &str) -> NodeContents {
395        let formatted = object! {
396            "code": {
397                "expression": expression,
398                "output": [[output]]
399            }
400        };
401        self.add_to_contents(formatted)
402    }
403
404    /// Create a `NodeContents` from a list of `JsonValue`s
405    pub fn from_json(json_contents: Vec<JsonValue>) -> NodeContents {
406        NodeContents {
407            content_list: json_contents,
408        }
409    }
410
411    /// Convert the `NodeContents` into a json array in a `JsonValue`
412    pub fn to_json(&self) -> JsonValue {
413        self.content_list.clone().into()
414    }
415
416    /// Convert the `NodeContents` into a single `String` that is formatted
417    /// for human reading.
418    pub fn to_formatted_string(&self) -> String {
419        let mut result = "".to_string();
420        for item in &self.content_list {
421            // Convert item into text
422            let text = Self::extract_content_text(item);
423            result = result + " " + text.trim();
424        }
425        result
426    }
427
428    /// Converts the `NodeContents` into a `String` that is formatted
429    /// for human reading, which is then split at every whitespace.
430    /// Useful for parsing a message.
431    pub fn to_formatted_words(&self) -> Vec<String> {
432        let formatted_string = self.to_formatted_string();
433        formatted_string
434            .split_whitespace()
435            .map(|s| s.to_string())
436            .collect()
437    }
438
439    /// Extracts content from a content list item `JsonValue`
440    fn extract_content_text(json: &JsonValue) -> String {
441        let mut result = "  ".to_string();
442        if !json["text"].is_empty() {
443            result = json["text"].dump();
444        } else if !json["url"].is_empty() {
445            result = json["url"].dump();
446        } else if !json["mention"].is_empty() {
447            result = json["mention"].dump();
448            result.remove(0);
449            result.remove(result.len() - 1);
450            return format!("~{}", result);
451        } else if !json["code"].is_empty() {
452            result = json["code"].dump();
453        }
454        result.remove(0);
455        result.remove(result.len() - 1);
456        result
457    }
458
459    /// Internal method to append `JsonValue` to the end of the list of contents
460    fn add_to_contents(&self, json: JsonValue) -> NodeContents {
461        let mut contents = self.content_list.clone();
462        contents.append(&mut vec![json]);
463        NodeContents {
464            content_list: contents,
465        }
466    }
467}