Last active
September 26, 2024 19:31
-
-
Save matthewjberger/9d516b640f8b986df049f4548b63c68a to your computer and use it in GitHub Desktop.
Use regex to extract parts of sections of MQTT-like topic strings
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use regex::Regex; | |
use std::collections::HashMap; | |
#[derive(Debug)] | |
struct CaptureSpec { | |
name: String, | |
position: usize, | |
regex: Option<String>, | |
} | |
fn capture_from_topic(topic: &str, specs: &[CaptureSpec]) -> HashMap<String, String> { | |
let parts: Vec<&str> = topic.split('/').collect(); | |
let mut results = HashMap::new(); | |
for spec in specs { | |
let part = match parts.get(spec.position) { | |
Some(p) => *p, | |
None => continue, | |
}; | |
if let Some(regex_str) = &spec.regex { | |
let regex = match Regex::new(regex_str) { | |
Ok(r) => r, | |
Err(_) => continue, | |
}; | |
if let Some(captures) = regex.captures(part) { | |
if let Some(matched) = captures.get(1) { | |
results.insert(spec.name.clone(), matched.as_str().to_string()); | |
} | |
} | |
} else { | |
results.insert(spec.name.clone(), part.to_string()); | |
} | |
} | |
results | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[test] | |
fn test_capture_from_topic() { | |
let topic = "A-3/B/C/D"; | |
let specs = vec![ | |
CaptureSpec { | |
name: "first_number".to_string(), | |
position: 0, | |
regex: Some(r"A-([0-9])".to_string()), | |
}, | |
CaptureSpec { | |
name: "second_part".to_string(), | |
position: 1, | |
regex: None, | |
}, | |
CaptureSpec { | |
name: "fourth_part".to_string(), | |
position: 3, | |
regex: None, | |
}, | |
]; | |
let results = capture_from_topic(topic, &specs); | |
assert_eq!(results.get("first_number"), Some(&"3".to_string())); | |
assert_eq!(results.get("second_part"), Some(&"B".to_string())); | |
assert_eq!(results.get("fourth_part"), Some(&"D".to_string())); | |
} | |
#[test] | |
fn test_capture_from_topic_with_missing_parts() { | |
let topic = "A-5/B"; | |
let specs = vec![ | |
CaptureSpec { | |
name: "first_number".to_string(), | |
position: 0, | |
regex: Some(r"A-([0-9])".to_string()), | |
}, | |
CaptureSpec { | |
name: "second_part".to_string(), | |
position: 1, | |
regex: None, | |
}, | |
CaptureSpec { | |
name: "third_part".to_string(), | |
position: 2, | |
regex: None, | |
}, | |
]; | |
let results = capture_from_topic(topic, &specs); | |
assert_eq!(results.get("first_number"), Some(&"5".to_string())); | |
assert_eq!(results.get("second_part"), Some(&"B".to_string())); | |
assert_eq!(results.get("third_part"), None); | |
} | |
#[test] | |
fn test_capture_from_topic_with_invalid_regex() { | |
let topic = "X-3/Y/Z"; | |
let specs = vec![ | |
CaptureSpec { | |
name: "first_number".to_string(), | |
position: 0, | |
regex: Some(r"A-([0-9])".to_string()), | |
}, | |
CaptureSpec { | |
name: "second_part".to_string(), | |
position: 1, | |
regex: None, | |
}, | |
]; | |
let results = capture_from_topic(topic, &specs); | |
assert_eq!(results.get("first_number"), None); | |
assert_eq!(results.get("second_part"), Some(&"Y".to_string())); | |
} | |
#[test] | |
fn test_capture_from_topic_with_out_of_range_position() { | |
let topic = "A/B"; | |
let specs = vec![ | |
CaptureSpec { | |
name: "first_part".to_string(), | |
position: 0, | |
regex: None, | |
}, | |
CaptureSpec { | |
name: "out_of_range".to_string(), | |
position: 5, | |
regex: None, | |
}, | |
]; | |
let results = capture_from_topic(topic, &specs); | |
assert_eq!(results.get("first_part"), Some(&"A".to_string())); | |
assert_eq!(results.get("out_of_range"), None); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment