fhttp_core/execution/
execution_order.rs

1use anyhow::{anyhow, Result};
2use linked_hash_set::LinkedHashSet;
3
4use crate::path_utils::{CanonicalizedPathBuf, RelativePath};
5use crate::request_sources::variable_support::{get_env_vars, EnvVarOccurrence};
6use crate::Profile;
7use crate::RequestSource;
8
9pub fn plan_request_order(
10    initial_requests: Vec<RequestSource>,
11    profile: &Profile,
12) -> Result<LinkedHashSet<RequestSource>> {
13    let mut preprocessor_stack = vec![];
14    let mut requests_with_dependencies = LinkedHashSet::new();
15
16    for req in &initial_requests {
17        for path in get_env_vars_defined_through_requests(profile, req)? {
18            let req = RequestSource::from_file(&path, true)?;
19            preprocess_request(
20                req,
21                &mut requests_with_dependencies,
22                &mut preprocessor_stack,
23            )?;
24        }
25    }
26
27    for req in initial_requests {
28        preprocess_request(
29            req,
30            &mut requests_with_dependencies,
31            &mut preprocessor_stack,
32        )?;
33    }
34
35    Ok(requests_with_dependencies)
36}
37
38fn preprocess_request(
39    req: RequestSource,
40    all_requests: &mut LinkedHashSet<RequestSource>,
41    preprocessor_stack: &mut Vec<CanonicalizedPathBuf>,
42) -> Result<()> {
43    if all_requests.contains(&req) {
44        return Ok(());
45    }
46    if preprocessor_stack.contains(&req.source_path) {
47        return Err(anyhow!("cyclic dependency detected!"));
48    }
49    preprocessor_stack.push(req.source_path.clone());
50
51    for dep in req.unescaped_dependency_paths()? {
52        let dep = RequestSource::from_file(dep, true)?;
53        preprocess_request(dep, all_requests, preprocessor_stack)?;
54    }
55
56    preprocessor_stack.pop();
57    all_requests.insert(req);
58
59    Ok(())
60}
61
62fn get_env_vars_defined_through_requests(
63    profile: &Profile,
64    req: &RequestSource,
65) -> Result<Vec<CanonicalizedPathBuf>> {
66    let vars: Vec<EnvVarOccurrence> = get_env_vars(&req.text);
67    vars.into_iter()
68        .flat_map(|occ| profile.defined_through_request(occ.name))
69        .map(|path| profile.get_dependency_path(path.to_str().unwrap()))
70        .collect()
71}
72
73#[cfg(test)]
74mod tests {
75    use std::env;
76
77    use anyhow::Result;
78    use indoc::indoc;
79    use temp_dir::TempDir;
80
81    use crate::execution::execution_order::plan_request_order;
82    use crate::path_utils::canonicalize;
83    use crate::test_utils::write_test_file;
84    use crate::{Profile, RequestSource, ResponseStore};
85
86    #[test]
87    fn should_resolve_nested_dependencies() -> Result<()> {
88        let workdir = TempDir::new()?;
89        let r1 = write_test_file(&workdir, "1.http", r#"GET ${request("2.http")}"#)?;
90        let r2 = write_test_file(&workdir, "2.http", r#"GET ${request("3.http")}"#)?;
91        let r3 = write_test_file(&workdir, "3.http", r#"GET ${request("4.http")}"#)?;
92        let r4 = write_test_file(&workdir, "4.http", r#"GET ${request("5.http")}"#)?;
93        let r5 = write_test_file(&workdir, "5.http", r#"GET http://localhost"#)?;
94
95        let init_request = RequestSource::from_file(&r1, false)?;
96
97        let profile = Profile::empty(env::current_dir()?);
98        let mut response_store = ResponseStore::new();
99
100        let requests = vec![r1, r2, r3, r4, r5];
101        requests.iter().enumerate().for_each(|(i, r)| {
102            response_store.store(r.clone(), format!("{}", i));
103        });
104
105        let coll = plan_request_order(vec![init_request], &profile)?
106            .into_iter()
107            .map(|req| req.source_path)
108            .collect::<Vec<_>>();
109
110        let foo = requests.into_iter().rev().collect::<Vec<_>>();
111        assert_eq!(&coll, &foo);
112
113        Ok(())
114    }
115
116    #[test]
117    fn should_not_resolve_duplicate_dependencies() -> Result<()> {
118        let workdir = TempDir::new()?;
119        let r1 = write_test_file(&workdir, "1.http", r#"GET ${request("dependency.http")}"#)?;
120        let r2 = write_test_file(&workdir, "2.http", r#"GET ${request("dependency.http")}"#)?;
121        let dep = write_test_file(&workdir, "dependency.http", r#"GET http://localhost"#)?;
122
123        let req1 = RequestSource::from_file(&r1, false)?;
124        let req2 = RequestSource::from_file(&r2, false)?;
125
126        let profile = Profile::empty(env::current_dir().unwrap());
127        let mut response_store = ResponseStore::new();
128
129        response_store.store(dep.clone(), "");
130        let coll = plan_request_order(vec![req1, req2], &profile)?
131            .into_iter()
132            .map(|req| req.source_path)
133            .collect::<Vec<_>>();
134
135        assert_eq!(&coll, &[dep, r1, r2]);
136
137        Ok(())
138    }
139
140    #[test]
141    fn should_not_resolve_escaped_dependencies() -> Result<()> {
142        let workdir = TempDir::new()?;
143        let r1 = write_test_file(
144            &workdir,
145            "1.http",
146            indoc!(
147                r#"
148                GET server
149
150                \${request("4.http")}
151            "#
152            ),
153        )?;
154        let request = RequestSource::from_file(&r1, false)?;
155
156        let profile = Profile::empty(env::current_dir().unwrap());
157
158        let coll = plan_request_order(vec![request], &profile)?
159            .into_iter()
160            .map(|req| req.source_path)
161            .collect::<Vec<_>>();
162
163        assert_eq!(&coll, &[r1]);
164
165        Ok(())
166    }
167
168    #[test]
169    #[should_panic]
170    fn should_panic_on_cyclic_dependency() {
171        let workdir = TempDir::new().unwrap();
172        let r1 = &workdir.child("1.http");
173        let r2 = &workdir.child("2.http");
174        std::fs::File::create(r1).unwrap();
175        std::fs::File::create(r2).unwrap();
176
177        let r1 = canonicalize(r1).unwrap();
178        let r2 = canonicalize(r2).unwrap();
179
180        std::fs::write(
181            &r1,
182            format!(r#"GET ${{request("{}")}}"#, &r2.to_str()).as_bytes(),
183        )
184        .unwrap();
185        std::fs::write(
186            &r2,
187            format!(r#"GET ${{request("{}")}}"#, &r1.to_str()).as_bytes(),
188        )
189        .unwrap();
190
191        let req1 = RequestSource::from_file(&r1, false).unwrap();
192
193        plan_request_order(vec![req1], &Profile::empty(env::current_dir().unwrap())).unwrap();
194    }
195}