use std::matches; use std::str::FromStr; use std::{error::Error, fmt}; #[derive(Debug)] struct BarbParseError {} impl Error for BarbParseError {} impl fmt::Display for BarbParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Error parsing barb file") } } enum Method { GET, PUT, POST, PATCH, DELETE, } impl FromStr for Method { type Err = BarbParseError; fn from_str(s: &str) -> Result { match s { "GET" => Ok(Self::GET), "PUT" => Ok(Self::PUT), "POST" => Ok(Self::POST), "PATCH" => Ok(Self::PATCH), "DELETE" => Ok(Self::DELETE), _ => Err(BarbParseError {}), } } } #[derive(Debug)] struct Header { name: String, value: String, } impl fmt::Display for Header { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}: {}", self.name, self.value) } } struct BarbHeader { pub method: Method, pub url: String, pub headers: Vec
, pub filter: Option, } impl BarbHeader { fn new(method: Method, url: String, headers: Vec
, filter: Option) -> Self { BarbHeader { method: method, url: url, headers: headers, filter: filter, } } } struct BarbFile { header: BarbHeader, body: Option, } fn decode_url_line(line: &str) -> Result<(Method, String), BarbParseError> { let mut components = line[1..].split('^'); let meth = components.next().ok_or(BarbParseError {})?; let url = components.next().ok_or(BarbParseError {})?; return Ok((Method::from_str(meth)?, String::from(url))); } fn decode_header(line: &str) -> Result { let mut components = line[1..].split(':'); let header_name = components.next().ok_or(BarbParseError {})?; let header_val = components.next().ok_or(BarbParseError {})?.trim(); Ok(Header { name: String::from(header_name), value: String::from(header_val), }) } impl FromStr for BarbFile { type Err = BarbParseError; fn from_str(s: &str) -> Result { let mut lines = s.split('\n'); let (method, url) = decode_url_line(lines.next().ok_or(BarbParseError {})?)?; let mut headers: Vec
= vec![]; let mut filter = None; for line in lines { if !matches!(line.chars().nth(0), Some('#')) { break; // Reached the end of the header. } if let Some(_) = line.find(':') { headers.push(decode_header(line).unwrap()); } if let None = filter { filter = match &line[0..2] { "#|" => Some(String::from(&line[2..])), _ => None, } } } Ok(BarbFile { header: BarbHeader::new(method, url, headers, filter), body: None, }) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_method_from_str() { assert!(matches!(Method::from_str("GET").unwrap(), Method::GET)); assert!(matches!(Method::from_str("GET").unwrap(), Method::GET)); assert!(matches!(Method::from_str("PUT").unwrap(), Method::PUT)); assert!(matches!(Method::from_str("POST").unwrap(), Method::POST)); assert!(matches!(Method::from_str("PATCH").unwrap(), Method::PATCH)); assert!(matches!( Method::from_str("DELETE").unwrap(), Method::DELETE )); } #[test] fn test_decode_url_line() { let (method, url) = decode_url_line("#GET^http://blahblah").unwrap(); assert!(matches!(method, Method::GET)); assert_eq!(url, "http://blahblah"); } #[test] fn test_decode_header() { let hdr = decode_header("#Authorization: TOKEN 12345").unwrap(); assert_eq!(hdr.name, "Authorization"); assert_eq!(hdr.value, "TOKEN 12345"); } #[test] fn test_parse_barbfile() { let barbfile = BarbFile::from_str("#GET^https://blah.com/api/blah\n#Authorization: BLAH\n#|filtr\n") .unwrap(); assert_eq!(barbfile.body, None); assert!(matches!(barbfile.header.method, Method::GET)); assert_eq!(barbfile.header.url, "https://blah.com/api/blah"); assert_eq!(barbfile.header.filter, Some(String::from("filtr"))); println!("{:?}", barbfile.header.headers); assert_eq!(barbfile.header.headers.len(), 1); assert_eq!(barbfile.header.headers[0].name, "Authorization"); assert_eq!(barbfile.header.headers[0].value, "BLAH"); } }