http_file_parser 0.1.1

A Rust-based parser for .http files, parsing HTTP request data using Pest and making parsed requests.
Documentation
use pest::error::Error as PestError;
use pest::iterators::Pair;
use pest::Parser;
use pest_derive::Parser;
use std::collections::HashMap;
use std::fmt::Display;
use thiserror::Error;

#[derive(Parser)]
#[grammar = "grammar.pest"]
pub struct HttpParser;

#[derive(Debug, Error)]
pub enum ParseError {
    #[error("Parsing error: {0}")]
    PestError(#[from] Box<PestError<Rule>>),

    #[error("Unknown HTTP method: {0}")]
    UnknownHttpMethod(String),
}

#[derive(Debug, Clone)]
pub enum HttpMethod {
    Get,
    Post,
    Put,
    Delete,
}

impl Display for HttpMethod {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(match self {
            Self::Get => "GET",
            Self::Post => "POST",
            Self::Put => "PUT",
            Self::Delete => "DELETE",
        })
    }
}

impl HttpMethod {
    fn from_str(s: &str) -> Result<Self, ParseError> {
        match s {
            "GET" => Ok(Self::Get),
            "POST" => Ok(Self::Post),
            "PUT" => Ok(Self::Put),
            "DELETE" => Ok(Self::Delete),
            _ => Err(ParseError::UnknownHttpMethod(s.to_string())),
        }
    }
}

#[derive(Debug, Clone)]
pub struct HttpRequest {
    pub method: HttpMethod,
    pub url: String,
    pub version: String,
    pub headers: HashMap<String, String>,
    pub body: String,
}

impl HttpRequest {
    fn parse(pair: Pair<Rule>) -> Result<Self, ParseError> {
        let mut inner = pair.into_inner();
        let method = HttpMethod::from_str(inner.next().unwrap().as_str())?;
        let url = inner.next().unwrap().as_str().to_string();
        let version = inner.next().unwrap().as_str().to_string();
        let mut headers = HashMap::new();
        let mut body = String::new();

        for item in inner {
            match item.as_rule() {
                Rule::headers => {
                    for header in item.into_inner() {
                        let mut kv = header.into_inner();
                        headers.insert(
                            kv.next().unwrap().as_str().to_string(),
                            kv.next().unwrap().as_str().to_string(),
                        );
                    }
                }
                Rule::body => body = item.as_str().trim().to_string(),
                _ => (),
            }
        }

        Ok(HttpRequest {
            method,
            url,
            version,
            headers,
            body,
        })
    }
}

#[derive(Debug)]
pub struct HttpFile {
    pub requests: Vec<HttpRequest>,
}

impl HttpFile {
    fn parse(pair: Pair<Rule>) -> Result<Self, ParseError> {
        let requests = pair
            .into_inner()
            .filter(|item| item.as_rule() == Rule::request)
            .map(HttpRequest::parse)
            .collect::<Result<Vec<_>, _>>()?;

        Ok(HttpFile { requests })
    }
}

pub fn parse(input: &str) -> Result<HttpFile, ParseError> {
    let file = HttpParser::parse(Rule::http_file, input.trim_start())
        .expect("unable to parse")
        .next()
        .unwrap();

    HttpFile::parse(file)
}