haproxy-config 0.4.1

Parse HAProxy configs and easily query it
Documentation
use std::borrow::Cow;
use std::io::BufWriter;
use std::path::PathBuf;

use ariadne::{Label, Report, ReportKind, Source};

use peg::error::ParseError;
use peg::str::LineCol;

#[derive(Debug)]
pub struct Error {
    pub inner: ParseError<LineCol>,
    pub source: String,
    pub path: Option<PathBuf>,
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let mut bytes = Vec::new();
        {
            let buf = BufWriter::new(&mut bytes);

            self.report()
                .write((self.path(), Source::from(&self.source)), buf)
                .unwrap();
        }

        let string = String::from_utf8(bytes).expect("source is utf8 thus report is utf8");
        f.write_str(&string)
    }
}

impl std::error::Error for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        None
    }
}

impl Error {
    pub fn with_path(mut self, path: impl Into<PathBuf>) -> Self {
        self.path = Some(path.into());
        self
    }

    pub fn report(&self) -> Report<(String, std::ops::Range<usize>)> {
        let offset = self.inner.location.offset;
        let msg = format!("expected {}", self.inner.expected);

        Report::build(ReportKind::Error, self.path(), offset)
            .with_message("parse error".to_string())
            .with_label(Label::new((self.path(), offset..offset)).with_message(msg))
            .finish()
    }

    fn path(&self) -> String {
        let path = self
            .path
            .as_ref()
            .map(|p| (*p).to_string_lossy())
            .map_or_else(|| "<unknown>".to_string(), Cow::into_owned);
        path
    }
}