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