arminius_lib_message_bus/
subject.rs1use std::str::FromStr;
15
16use serde::{Deserialize, Serialize};
17
18#[derive(Debug, thiserror::Error, Serialize, Deserialize, PartialEq)]
20pub enum TokenEscapeError {
21 #[error("Unknown escape code in token {0}")]
24 UnknownEscapeCode(char),
25 #[error("Last character of escaped token is backslash")]
28 LastCharIsBackslash,
29}
30
31#[derive(Debug)]
34pub struct EscapingSubject(pub Vec<String>);
35
36impl EscapingSubject {
37 #[must_use]
39 pub fn new(tokens: Vec<String>) -> Self {
40 EscapingSubject(tokens)
41 }
42
43 #[must_use]
46 pub fn to_subject_string(&self) -> String {
47 self.0.iter().map(|token| escape_token(token)).collect::<Vec<_>>().join(".")
48 }
49}
50
51impl FromStr for EscapingSubject {
52 type Err = TokenEscapeError;
53
54 fn from_str(subject: &str) -> Result<Self, Self::Err> {
55 Ok(Self(subject.split('.').map(unescape_token).collect::<Result<Vec<_>, _>>()?))
56 }
57}
58
59const ESCAPE_CHAR: char = '\\';
61
62const ESCAPES: &[(char, char)] = &[
69 ('.', 'd'), ('*', 'a'), ('>', 'g'), (ESCAPE_CHAR, 'b'), ];
74
75pub fn escape_token(token: &str) -> String {
78 let mut str = String::with_capacity(token.len() * 2);
79 for char in token.chars() {
80 match ESCAPES.iter().find(|(literal, _)| *literal == char) {
81 Some((_, code)) => {
82 str.push(ESCAPE_CHAR);
83 str.push(*code);
84 }
85 None => str.push(char),
86 }
87 }
88 str
89}
90
91pub fn unescape_token(escaped_token: &str) -> Result<String, TokenEscapeError> {
93 let mut token = String::with_capacity(escaped_token.len());
94 let mut chars = escaped_token.chars();
95 while let Some(char) = chars.next() {
96 if char == ESCAPE_CHAR {
97 let code = chars.next().ok_or(TokenEscapeError::LastCharIsBackslash)?;
98 let (literal, _) = ESCAPES
99 .iter()
100 .find(|(_, escape_code)| *escape_code == code)
101 .ok_or(TokenEscapeError::UnknownEscapeCode(code))?;
102 token.push(*literal);
103 } else {
104 token.push(char);
105 }
106 }
107 Ok(token)
108}
109
110#[cfg(test)]
111mod tests;