Skip to content

Instantly share code, notes, and snippets.

@matthewjberger
Last active September 26, 2024 19:31
Show Gist options
  • Save matthewjberger/9d516b640f8b986df049f4548b63c68a to your computer and use it in GitHub Desktop.
Save matthewjberger/9d516b640f8b986df049f4548b63c68a to your computer and use it in GitHub Desktop.
Use regex to extract parts of sections of MQTT-like topic strings
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