rested 0.9.2

Language/Interpreter for easily defining and running requests to an http server.
Documentation
pub use crate::parser::ast::RequestMethod;

use crate::{
    error::ColoredMetaError,
    error_meta::ToContextualError,
    interpreter::{
        ir::{self, *},
        ureq_runner::UreqRun,
    },
};
use string_utils::*;

use std::error::Error;

use tracing::{error, info};

impl<'source> ir::Program<'source> {
    pub fn run_ureq(self, request_names: Option<&[String]>) {
        Runner::new(self, Box::new(UreqRun)).run(request_names)
    }
}

use colored::Colorize;
pub trait RunStrategy {
    fn run_request(&mut self, request: &Request) -> std::result::Result<String, Box<dyn Error>>;
}

struct Runner<'source> {
    program: ir::Program<'source>,
    strategy: Box<dyn RunStrategy>,
}

impl<'source> Runner<'source> {
    pub fn new(program: ir::Program<'source>, strategy: Box<dyn RunStrategy>) -> Self {
        Self { program, strategy }
    }

    pub fn run(&mut self, request_names: Option<&[String]>) {
        let requests = self.program.items.iter().filter(|r| {
            match (&request_names, r.name.as_deref().unwrap_or(&r.request.url)) {
                (None, _) => true,
                (Some(desired), name) => desired.iter().any(|n| n == name),
            }
        });

        for RequestItem {
            span,
            request,
            dbg,
            log_destination,
            ..
        } in requests
        {
            info!(
                "sending {} request to {}",
                request.method.to_string().yellow().bold(),
                request.url.bold()
            );

            if *dbg {
                info!(" \u{21B3} with request data:");
                eprintln!("{}", indent_lines(&format!("{:#?}", request), 6));

                eprintln!(
                    "{}",
                    indent_lines(
                        &format!(
                            "Body: {}",
                            request.body.clone().unwrap_or("(no body)".to_string())
                        ),
                        6
                    )
                );
            }

            let res = match self.strategy.run_request(request) {
                Ok(res) => res,
                Err(error) => {
                    error!(
                        "{:#}",
                        ColoredMetaError(
                            &error::RunError(error.to_string())
                                .to_contextual_error(*span, self.program.source)
                        )
                    );
                    continue;
                }
            };

            if let Some(log_destination) = log_destination {
                match log_destination {
                    LogDestination::File(file_path) => match log(&res, file_path) {
                        Ok(_) => {
                            info!("{}", format!("saved response to {:?}", file_path).blue());
                        }
                        Err(error) => {
                            error!(
                                "{:#}",
                                ColoredMetaError(
                                    &error::RunError(error.to_string())
                                        .to_contextual_error(*span, self.program.source)
                                )
                            )
                        }
                    },
                }
            }

            println!("{}", indent_lines(&res, 4));
        }
    }
}

mod error {
    use std::error::Error;

    use crate::error_meta::ToContextualError;

    #[derive(Debug, Clone)]
    pub struct RunError(pub String);

    impl Error for RunError {}
    impl std::fmt::Display for RunError {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            self.0.fmt(f)
        }
    }

    impl ToContextualError for RunError {}
}

mod string_utils {
    use std::{
        fs,
        io::{self, Write},
    };

    pub fn indent_lines(string: &str, indent: u8) -> std::string::String {
        string
            .lines()
            .map(|line| (" ".repeat(indent as usize) + line))
            .collect::<Vec<_>>()
            .join("\n")
    }

    pub fn log(content: &str, to_file: &std::path::PathBuf) -> std::io::Result<()> {
        if let Some(dir_path) = to_file.parent() {
            fs::create_dir_all(dir_path)?
        };

        let file = fs::File::options()
            .truncate(true)
            .write(true)
            .create(true)
            .open(to_file)?;

        let mut w = io::BufWriter::new(file);

        write!(w, "{content}")
    }
}