Skip to main content

akar_function/scalar/
path.rs

1use crate::registry::*;
2use akar_common::types::Value;
3
4// ==================== Path ====================
5
6pub(crate) fn evaluate_path(op: PathOp, args: &[Value]) -> Result<Value, String> {
7    match op {
8        PathOp::Nodes => {
9            let path = &args[0];
10            match path {
11                Value::Struct(fields) => {
12                    // Look for "_nodes" field in struct
13                    if let Some((_, nodes_val)) = fields.iter().find(|(k, _)| k == "_nodes") {
14                        Ok(nodes_val.clone())
15                    } else if let Some((_, first)) = fields.first() {
16                        // Fallback: return first field (usually nodes list)
17                        Ok(first.clone())
18                    } else {
19                        Ok(Value::Null)
20                    }
21                }
22                Value::List(_) => Ok(path.clone()),
23                _ => Err(format!("NODES() requires a path/recursive rel, got {:?}", path)),
24            }
25        }
26        PathOp::Rels => {
27            let path = &args[0];
28            match path {
29                Value::Struct(fields) => {
30                    if let Some((_, rels_val)) = fields.iter().find(|(k, _)| k == "_rels") {
31                        Ok(rels_val.clone())
32                    } else if fields.len() >= 2 {
33                        Ok(fields[1].1.clone())
34                    } else {
35                        Ok(Value::Null)
36                    }
37                }
38                _ => Err(format!("RELS() requires a path/recursive rel, got {:?}", path)),
39            }
40        }
41        PathOp::Length => {
42            let path = &args[0];
43            match path {
44                Value::List(items) => Ok(Value::Int64(items.len() as i64)),
45                Value::Struct(fields) => {
46                    // Count entries in _rels or _nodes minus 1
47                    if let Some((_, Value::List(rels))) = fields.iter().find(|(k, _)| k == "_rels") {
48                        Ok(Value::Int64(rels.len() as i64))
49                    } else {
50                        Ok(Value::Int64(0))
51                    }
52                }
53                _ => Err(format!("LENGTH() requires a path/recursive rel, got {:?}", path)),
54            }
55        }
56        PathOp::Properties => {
57            let path = &args[0];
58            match path {
59                Value::Struct(fields) => {
60                    let mut props: Vec<(Value, Value)> = Vec::new();
61                    for (key, val) in fields {
62                        if key != "_nodes" && key != "_rels" && key != "_src" && key != "_dst" {
63                            props.push((Value::String(key.clone()), val.clone()));
64                        }
65                    }
66                    Ok(Value::Map(props))
67                }
68                Value::List(items) => {
69                    let mut all_props: Vec<(Value, Value)> = Vec::new();
70                    for (i, item) in items.iter().enumerate() {
71                        all_props.push((Value::Int64(i as i64), item.clone()));
72                    }
73                    Ok(Value::Map(all_props))
74                }
75                _ => Err(format!("PROPERTIES() requires a path, got {:?}", path)),
76            }
77        }
78        PathOp::IsTrail => {
79            let path = &args[0];
80            let result = match path {
81                Value::Struct(fields) => {
82                    if let Some((_, Value::List(rels))) = fields.iter().find(|(k, _)| k == "_rels") {
83                        !has_duplicates(rels)
84                    } else {
85                        true
86                    }
87                }
88                Value::List(items) => !has_duplicates(items),
89                _ => true,
90            };
91            Ok(Value::Bool(result))
92        }
93        PathOp::IsAcyclic => {
94            let path = &args[0];
95            let result = match path {
96                Value::Struct(fields) => {
97                    if let Some((_, Value::List(nodes))) = fields.iter().find(|(k, _)| k == "_nodes") {
98                        !has_duplicates(nodes)
99                    } else {
100                        true
101                    }
102                }
103                Value::List(items) => !has_duplicates(items),
104                _ => true,
105            };
106            Ok(Value::Bool(result))
107        }
108    }
109}
110
111/// Check if a list of Values has any duplicates (by equality).
112fn has_duplicates(items: &[Value]) -> bool {
113    for i in 0..items.len() {
114        for j in (i + 1)..items.len() {
115            if items[i] == items[j] {
116                return true;
117            }
118        }
119    }
120    false
121}
122
123/// Generate a random UUID v4 string.
124pub(crate) fn evaluate_uuid(_args: &[Value]) -> Result<Value, String> {
125    use std::time::{SystemTime, UNIX_EPOCH};
126    // Simple UUID v4 generation without external crate dependency
127    let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default();
128    let mut seed = now.as_nanos() as u64;
129    // Simple PRNG (xorshift64*)
130    seed ^= seed >> 12;
131    seed ^= seed << 25;
132    seed ^= seed >> 27;
133    let r1 = seed.wrapping_mul(0x2545F4914F6CDD1Du64);
134    seed ^= seed >> 12;
135    seed ^= seed << 25;
136    seed ^= seed >> 27;
137    let r2 = seed.wrapping_mul(0x2545F4914F6CDD1Du64);
138
139    // Format as UUID v4: 8-4-4-4-12 hex digits
140    let time_low = (r1 & 0xFFFFFFFF) as u32;
141    let time_mid = ((r1 >> 32) & 0xFFFF) as u16;
142    let time_hi_and_version = (((r1 >> 48) & 0x0FFF) | 0x4000) as u16; // version 4
143    let clock_seq = ((r2 & 0x3FFF) | 0x8000) as u16; // variant 1
144    let node_low = ((r2 >> 14) & 0xFFFFFFFF) as u32;
145    let node_hi = ((r2 >> 46) & 0xFFFF) as u16;
146
147    Ok(Value::String(format!(
148        "{:08x}-{:04x}-{:04x}-{:04x}-{:04x}{:08x}",
149        time_low, time_mid, time_hi_and_version, clock_seq, node_hi, node_low
150    )))
151}