pub fn escape(value: &str) -> String {
format!(
"\"{}\"",
value
.chars()
.flat_map(|c| match c {
'"' => vec!['\\', '"'],
'\n' => vec!['\\', 'n'],
'\r' => vec!['\\', 'r'],
'\t' => vec!['\\', 't'],
'\\' => vec!['\\', '\\'],
_ => vec![c],
})
.collect::<String>()
)
}
pub fn parse(value: &str) -> Result<String, String> {
let mut chars = value.chars();
if value.len() < 2 || chars.next() != Some('\"') || chars.last() != Some('\"') {
return Err(format!(
"Could not find starting or ending double quote in: {}",
value
));
}
let basic = value[1..value.len() - 1].chars();
let mut result: String = "".to_string();
enum State {
Normal,
Escaped,
}
let mut state = State::Normal;
for c in basic {
match state {
State::Normal => match c {
'\\' => state = State::Escaped,
_ => result += &format!("{}", c),
},
State::Escaped => {
match c {
'"' => result += "\"",
'n' => result += "\n",
'r' => result += "\r",
't' => result += "\t",
'\\' => result += "\\",
_ => return Err(format!("Expected escape symbol, found: {}", c)),
};
state = State::Normal;
}
}
}
if let State::Escaped = state {
return Err("Expected input to be closed, found escape token: \\".to_string());
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_escape_and_parse_string() {
let escaped = escape("foo\"\n\r\t\\bar");
let parsed = parse(&escaped).unwrap();
assert_eq!("foo\"\n\r\t\\bar", parsed);
assert_eq!("\"foo\\\"\\n\\r\\t\\\\bar\"", escaped);
}
#[test]
fn test_wrong_escape() {
assert_eq!(true, parse("\"\\w\"").is_err());
}
#[test]
fn test_attempt_parse_without_opening_quote() {
assert_eq!(true, parse("a\"").is_err());
}
#[test]
fn test_attempt_parse_without_closing_quote() {
assert_eq!(true, parse("\"a").is_err());
}
#[test]
fn test_did_not_end_escape() {
assert_eq!(true, parse("\"\\\"").is_err());
}
}