1use icu_properties::props::{IdContinue, IdStart};
18use icu_properties::CodePointSetData;
19
20pub const MIN_GRAPH_NAME_LEN: usize = 3;
22pub const MAX_GRAPH_NAME_LEN: usize = 63;
24pub const MIN_LABEL_NAME_LEN: usize = 1;
26pub const MAX_LABEL_NAME_LEN: usize = 63;
28
29pub const VERTEX_DEFAULT_LABEL_NAME: &str = "_ag_label_vertex";
31pub const EDGE_DEFAULT_LABEL_NAME: &str = "_ag_label_edge";
33
34fn is_id_start(c: char) -> bool {
35 c == '_' || CodePointSetData::new::<IdStart>().contains(c)
36}
37
38fn is_id_continue(c: char) -> bool {
39 CodePointSetData::new::<IdContinue>().contains(c)
40}
41
42#[must_use]
46pub fn is_valid_graph_name(name: &str) -> bool {
47 if name.len() < MIN_GRAPH_NAME_LEN || name.len() > MAX_GRAPH_NAME_LEN {
48 return false;
49 }
50 let mut chars = name.chars();
51 let Some(first) = chars.next() else {
52 return false;
53 };
54 if !is_id_start(first) {
55 return false;
56 }
57 let rest: Vec<char> = chars.collect();
58 let Some((last, middle)) = rest.split_last() else {
61 return false;
62 };
63 middle
64 .iter()
65 .all(|c| is_id_continue(*c) || *c == '.' || *c == '-')
66 && is_id_continue(*last)
67}
68
69#[must_use]
72pub fn is_valid_label_name(name: &str) -> bool {
73 if name.len() < MIN_LABEL_NAME_LEN || name.len() > MAX_LABEL_NAME_LEN {
74 return false;
75 }
76 let mut chars = name.chars();
77 chars.next().is_some_and(is_id_start) && chars.all(is_id_continue)
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 #[test]
85 fn graph_names_follow_age_rules() {
86 assert!(is_valid_graph_name("demo"));
87 assert!(is_valid_graph_name("_g1"));
88 assert!(is_valid_graph_name("g_c1"));
89 assert!(is_valid_graph_name("my.graph-2"));
90 assert!(is_valid_graph_name("한글그래프"));
91 assert!(!is_valid_graph_name("g1"), "shorter than three bytes");
92 assert!(!is_valid_graph_name("1abc"), "must not start with a digit");
93 assert!(!is_valid_graph_name("abc."), "must not end with a dot");
94 assert!(!is_valid_graph_name("abc-"), "must not end with a dash");
95 assert!(!is_valid_graph_name("a b"), "no spaces");
96 assert!(
97 !is_valid_graph_name(&"x".repeat(64)),
98 "longer than 63 bytes"
99 );
100 assert!(is_valid_graph_name(&"x".repeat(63)));
101 }
102
103 #[test]
104 fn label_names_follow_age_rules() {
105 assert!(is_valid_label_name("Person"));
106 assert!(is_valid_label_name("_"));
107 assert!(is_valid_label_name("v1"));
108 assert!(is_valid_label_name("KNOWS"));
109 assert!(is_valid_label_name(VERTEX_DEFAULT_LABEL_NAME));
110 assert!(!is_valid_label_name(""));
111 assert!(!is_valid_label_name("1v"));
112 assert!(!is_valid_label_name("has-dash"));
113 assert!(!is_valid_label_name("has.dot"));
114 assert!(!is_valid_label_name(&"x".repeat(64)));
115 }
116}