raphtory_api/core/input/
input_node.rs

1//! A data structure for representing input nodes in a temporal graph.
2//!
3//! Input nodes are used when first creating or accessing verticies by the user.
4//! This trait allows you to use a variety of types as input nodes, including
5//! `u64`, `&str`, and `String`.
6
7use crate::core::utils::hashing;
8
9const MAX_U64_BYTES: [u8; 20] = [
10    49, 56, 52, 52, 54, 55, 52, 52, 48, 55, 51, 55, 48, 57, 53, 53, 49, 54, 49, 53,
11];
12
13pub fn parse_u64_strict(input: &str) -> Option<u64> {
14    if input.len() > 20 {
15        // a u64 string has at most 20 bytes
16        return None;
17    }
18    let byte_0 = b'0';
19    let byte_1 = b'1';
20    let byte_9 = b'9';
21    let mut input_iter = input.bytes();
22    let first = input_iter.next()?;
23    if first == byte_0 {
24        return input_iter.next().is_none().then_some(0);
25    }
26
27    let mut check_max = input.len() == 20;
28    if check_max {
29        if !(byte_1..=MAX_U64_BYTES[0]).contains(&first) {
30            return None;
31        }
32    } else if !(byte_1..=byte_9).contains(&first) {
33        return None;
34    }
35
36    let mut result = (first - byte_0) as u64;
37    for (next_byte, max_byte) in input_iter.zip(MAX_U64_BYTES[1..].iter().copied()) {
38        if check_max {
39            if !(byte_0..=max_byte).contains(&next_byte) {
40                return None;
41            }
42            check_max = next_byte == max_byte;
43        } else if !(byte_0..=byte_9).contains(&next_byte) {
44            return None;
45        }
46        result = result * 10 + (next_byte - byte_0) as u64;
47    }
48    Some(result)
49}
50
51pub trait InputNode: Clone {
52    fn id(&self) -> u64;
53    fn id_str(&self) -> Option<&str>;
54}
55
56impl InputNode for u64 {
57    fn id(&self) -> u64 {
58        *self
59    }
60
61    fn id_str(&self) -> Option<&str> {
62        None
63    }
64}
65
66impl InputNode for &str {
67    fn id(&self) -> u64 {
68        parse_u64_strict(self).unwrap_or_else(|| hashing::calculate_hash(self))
69    }
70
71    fn id_str(&self) -> Option<&str> {
72        Some(self)
73    }
74}
75
76impl InputNode for String {
77    fn id(&self) -> u64 {
78        let s: &str = self;
79        s.id()
80    }
81
82    fn id_str(&self) -> Option<&str> {
83        Some(self)
84    }
85}
86
87#[cfg(test)]
88mod test {
89    use crate::core::input::input_node::{parse_u64_strict, InputNode};
90    use proptest::prelude::*;
91
92    #[test]
93    fn test_weird_num_edge_cases() {
94        assert_ne!("+3".id(), "3".id());
95        assert_eq!(3.id(), "3".id());
96        assert_ne!("00".id(), "0".id());
97        assert_eq!("0".id(), 0.id());
98    }
99
100    #[test]
101    fn test_u64_string_works() {
102        proptest!(|(n in any::<u64>())| {
103            assert_eq!(n.to_string().id(), n);
104        });
105    }
106
107    #[test]
108    fn test_if_str_parses_it_is_a_u64() {
109        proptest!(|(s in any::<String>())| {
110            let res = parse_u64_strict(&s);
111            if let Some(n) = res {
112                assert_eq!(n.to_string(), s)
113            } else {
114                assert_ne!(s.id().to_string(), s)
115            }
116        });
117    }
118}