fhttp-core 1.3.1

core library for the fhttp tool
Documentation
use std::path::PathBuf;

use crate::{Config, FhttpError, path_utils, Request};
use crate::Profile;
use crate::request::variable_support::{VariableSupport, EnvVarOccurrence};
use crate::Result;

pub fn plan_request_order(
    initial_requests: Vec<Request>,
    profile: &Profile,
    config: &Config,
) -> Result<Vec<Request>> {
    let mut preprocessor_stack = vec![];
    let mut requests_with_dependencies = vec![];

    for req in &initial_requests {
        for path in get_env_vars_defined_through_requests(&profile, &req) {
            let req = Request::from_file(&path, true)?;
            preprocess_request(
                req,
                &mut requests_with_dependencies,
                &mut preprocessor_stack,
                &config
            )?;
        }
    }

    for req in initial_requests {
        preprocess_request(
            req,
            &mut requests_with_dependencies,
            &mut preprocessor_stack,
            &config
        )?;
    }

    Ok(requests_with_dependencies)
}

fn preprocess_request(
    req: Request,
    mut list: &mut Vec<Request>,
    mut preprocessor_stack: &mut Vec<PathBuf>,
    config: &Config
) -> Result<()> {
    if list.contains(&req) {
        return Ok(());
    }
    if preprocessor_stack.contains(&req.source_path) {
        return Err(FhttpError::new("cyclic dependency detected!"));
    }
    preprocessor_stack.push(req.source_path.clone());

    for dep in req.dependencies() {
        let dep = Request::from_file(&dep, true)?;
        preprocess_request(dep, &mut list, &mut preprocessor_stack, &config)?;
    }

    preprocessor_stack.pop();
    list.push(req);

    Ok(())
}

fn get_env_vars_defined_through_requests(
    profile: &Profile,
    req: &Request
) -> Vec<PathBuf> {
    let vars: Vec<EnvVarOccurrence> = req.get_env_vars();
    vars.into_iter()
        .map(|occ| profile.defined_through_request(occ.name))
        .filter(|it| it.is_some())
        .map(|it| it.unwrap())
        .map(|path| path_utils::get_dependency_path(profile.source_path(), path.to_str().unwrap()))
        .collect()
}

#[cfg(test)]
mod tests {
    use std::env;

    use crate::{Request, Profile, Config, Result, ResponseStore};
    use crate::test_utils::root;
    use crate::execution_order::plan_request_order;

    #[test]
    fn should_resolve_nested_dependencies() -> Result<()> {
        let root = root()
            .join("resources/test/requests/nested_dependencies");
        let init_path = root.join("1.http");

        let init_request = Request::from_file(&init_path, false)?;

        let profile = Profile::empty(env::current_dir().unwrap());
        let mut response_store = ResponseStore::new();
        let config = Config::default();

        for i in 2..=5 {
            let path = root.join(format!("{}.http", i));
            response_store.store(&path, &format!("{}", i));
        }

        let coll = plan_request_order(vec![init_request], &profile, &config)?
            .into_iter()
            .map(|req| req.source_path)
            .collect::<Vec<_>>();

        let foo = (1..=5).into_iter()
            .rev()
            .map(|i| root.join(format!("{}.http", i)))
            .collect::<Vec<_>>();
        assert_eq!(&coll, &foo);

        Ok(())
    }

    #[test]
    fn should_not_resolve_duplicate_dependencies() -> Result<()> {
        let root = root()
            .join("resources/test/requests/duplicate_dependencies");
        let path1 = root.join("1.http");
        let path2 = root.join("2.http");
        let dep_path = root.join("dependency.http");

        let req1 = Request::from_file(&path1, false)?;
        let req2 = Request::from_file(&path2, false)?;

        let profile = Profile::empty(env::current_dir().unwrap());
        let mut response_store = ResponseStore::new();
        let config = Config::default();

        response_store.store(&dep_path, "");
        let coll = plan_request_order(vec![req1, req2], &profile, &config)?
            .into_iter()
            .map(|req| req.source_path)
            .collect::<Vec<_>>();

        assert_eq!(&coll, &[dep_path, path1, path2]);

        Ok(())
    }

    #[test]
    #[should_panic]
    fn should_panic_on_cyclic_dependency() {
        let root = root()
            .join("resources/test/requests/cyclic_dependencies");
        let path1 = root.join("1.http");
        let req1 = Request::from_file(&path1, false).unwrap();

        plan_request_order(
            vec![req1],
            &Profile::empty(env::current_dir().unwrap()),
            &Config::default()
        ).unwrap();
    }
}