fhttp_core/execution/
execution_order.rs1use 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}