use std::{borrow::Cow, collections::HashMap, fmt, num::ParseIntError};
use nom::{
bytes::complete::take_while1,
character::complete::{anychar, char as chr, digit1},
combinator::map_res,
error::ErrorKind,
};
use super::response::ResponseLine;
type NomErr<'a> = nom::Err<(&'a str, ErrorKind)>;
#[derive(Debug, Clone)]
pub struct ParseError(pub String);
impl From<NomErr<'_>> for ParseError {
fn from(err: NomErr<'_>) -> Self {
ParseError(err.to_string())
}
}
impl From<ParseIntError> for ParseError {
fn from(err: ParseIntError) -> Self {
ParseError(err.to_string())
}
}
impl std::error::Error for ParseError {}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "ParseError({})", self.0)
}
}
pub fn response_line(line: &str) -> Result<ResponseLine, ParseError> {
let mut parser = map_res(digit1, |code: &str| code.parse::<u16>());
let (rest, code) = parser(line)?;
let (rest, ch) = anychar(rest)?;
if ![' ', '-', '+'].contains(&ch) {
return Err(ParseError(format!(
"Unexpected response character '{ch}'. Expected ' ', '-' or '+'."
)));
}
Ok(ResponseLine {
has_more: ['-', '+'].contains(&ch),
is_multiline: ch == '+',
code,
value: rest.to_owned(),
})
}
pub fn multi_key_value(line: &str) -> Result<HashMap<Cow<'_, str>, Vec<Cow<'_, str>>>, ParseError> {
let parts = line.split(' ');
let mut kv = HashMap::new();
for part in parts {
let (identifier, value) = key_value(part)?;
kv.insert(identifier, value);
}
Ok(kv)
}
pub fn key_value(line: &str) -> Result<(Cow<'_, str>, Vec<Cow<'_, str>>), ParseError> {
let (rest, identifier) = take_while1(|ch| ch != '=')(line)?;
let (rest, _) = chr('=')(rest)?;
let lines = rest.lines();
let parts = lines
.filter(|s| !s.is_empty())
.flat_map(|line| line.split('"').filter(|part| !part.trim().is_empty()).map(Cow::from))
.collect();
Ok((identifier.trim().into(), parts))
}
#[cfg(test)]
mod test {
#[test]
fn key_value() {
let (key, values) = super::key_value("greeting=\"hello\" \"world 🌎\"").unwrap();
assert_eq!(key, "greeting");
assert_eq!(values, &["hello", "world 🌎"]);
}
}