1mod change_coupling_types;
8mod types;
9
10pub use change_coupling_types::{ChangeCouplingConfigInput, CoChangeEventInput};
11pub use edge_weight::{edge_weight_key, split_edge_weight_key};
12pub use types::{
13 BoundaryDefInput, BoundarySpecInput, DependencyGraphInput, EdgeInput, LeidenConfigInput,
14 NodeInput, NormalizeNode, PatternInstanceInput, PatternLocationInput, PriorPartition,
15 QualityFunctionInput, ThresholdOverrideInput, ThresholdsInput,
16};
17mod edge_weight;
18
19use crate::error::AnalysisError;
20
21pub fn validate_node_id(s: &str) -> Result<(), AnalysisError> {
49 if s.is_empty() {
50 return Err(AnalysisError::InvalidNodeId {
51 id: s.to_string(),
52 reason: "must not be empty".to_string(),
53 });
54 }
55 if s.contains('\\') {
56 return Err(AnalysisError::InvalidNodeId {
57 id: s.to_string(),
58 reason: "must use forward slashes only".to_string(),
59 });
60 }
61 if s.starts_with("./") {
62 return Err(AnalysisError::InvalidNodeId {
63 id: s.to_string(),
64 reason: "must not start with './'".to_string(),
65 });
66 }
67 if s.ends_with('/') {
68 return Err(AnalysisError::InvalidNodeId {
69 id: s.to_string(),
70 reason: "must not end with '/'".to_string(),
71 });
72 }
73 if s.starts_with('/') {
74 return Err(AnalysisError::InvalidNodeId {
75 id: s.to_string(),
76 reason: "must not be an absolute path".to_string(),
77 });
78 }
79 for component in s.split('/') {
80 if component == ".." {
81 return Err(AnalysisError::InvalidNodeId {
82 id: s.to_string(),
83 reason: "must not contain '..' components".to_string(),
84 });
85 }
86 }
87 Ok(())
88}