use crate::barbfile::{BarbFile, BarbFilter}; use crate::output::BarbOutput; use regex::Regex; use std::collections::HashMap; use ureq; use ureq::Error as UreqError; pub struct Context { vars: HashMap, } impl Context { pub fn new(vars: I) -> Context where I: Iterator, { let mut tmp_vars = HashMap::new(); tmp_vars.extend(vars); Context { vars: tmp_vars } } #[cfg(test)] pub fn empty() -> Context { Context { vars: HashMap::new(), } } #[cfg(test)] fn key_str(&self, key: &String) -> String { // Use for reference / utility? format!("{{{}}}", key) } pub fn substitute(&self, string: &String) -> String { let mut buffer = string.clone(); let re = Regex::new(r"\{(?P[A-Za-z0-9_]+)(?::-(?P.*))?\}").unwrap(); for var in re.captures_iter(string) { let key = String::from(&var["key"]); let value = String::from( var.name("value") .map(|m| m.as_str()) .or(Some(&String::from(""))) .unwrap(), ); buffer = buffer.replace( // Since this iterates over matches, having match 0 is guaranteed. So the // `unwrap()` operation here is safe. var.get(0).unwrap().as_str(), self.vars.get(&key).or(Some(&value)).unwrap(), ); } buffer } } pub struct Executor { context: Context, } impl Executor { pub fn new(context: Context) -> Executor { Executor { context } } fn make_req( &self, bfile: &BarbFile, headers: Vec<(String, String)>, output: &BarbOutput, ) -> ureq::Request { let mut req = ureq::request( bfile.method_as_string().as_str(), self.context.substitute(&bfile.url()).as_str(), ); let mut final_headers: HashMap = HashMap::new(); for header in bfile.headers() { let hdr_val = self.context.substitute(header.value()); final_headers.insert(String::from(header.name()), hdr_val); } for header in headers { final_headers.insert(header.0, header.1); } for (hdr_name, hdr_val) in final_headers.iter() { req = req.set(hdr_name.as_str(), hdr_val.as_str()); output.req_hdr(String::from(hdr_name), String::from(hdr_val)); } req } fn apply_filters( &mut self, bfile: &BarbFile, body: String, arg_filter: &Option, ) -> Result { if let Some(filter) = arg_filter { return Ok(BarbFilter::from_path(filter.to_string()).apply(&body)?); } else if bfile.filters().len() > 0 { let mut end_body: String = body.clone(); for filter in bfile.filters().iter() { if let Some(name) = filter.name() { self.context.vars.insert(name.clone(), filter.apply(&body)?); } else { end_body = filter.apply(&body)?; } } return Ok(end_body); } Ok(String::from(body)) } fn run(&self, bfile: &BarbFile, req: ureq::Request) -> Result { let resp = match bfile.method().takes_body() { true => match bfile.body() { Some(body) => req.send_string(body.as_str()), None => req.call(), }, false => req.call(), }; match resp { Ok(resp) => Ok(resp), Err(UreqError::Status(_, resp)) => Ok(resp), Err(UreqError::Transport(transp)) => Err(String::from(transp.to_string())), } } pub fn execute_dep(&mut self, bfile: &BarbFile, output: &BarbOutput) -> Result<(), String> { let req = self.make_req(&bfile, vec![], output); let method = String::from(req.method()); let url = String::from(req.url()); let response = self.run(&bfile, req)?; output.req_dep(method, url, response.status(), response.status_text()); output.end_req(); self.apply_filters(&bfile, response.into_string().unwrap(), &None)?; Ok(()) } pub fn execute( &mut self, bfile: &BarbFile, output: &BarbOutput, filter: &Option, skip_filters: bool, headers: Vec<(String, String)>, ) -> Result<(), String> { let req = self.make_req(&bfile, headers, output); output.req(String::from(req.method()), String::from(req.url())); output.end_req(); let response = self.run(&bfile, req)?; output.status(response.status(), response.status_text()); for header_name in response.headers_names() { output.resp_hdr( header_name.to_string(), response.header(header_name.as_str()).unwrap(), ); } output.end_resp_hdr(); if !skip_filters { output.body(self.apply_filters(&bfile, response.into_string().unwrap(), filter)?); } else { output.body(response.into_string().unwrap()); } Ok(()) } } #[cfg(test)] mod tests_context { use super::*; fn have_vars() -> Vec<(String, String)> { return vec![ (String::from("foo"), String::from("bar")), (String::from("bar"), String::from("baz")), ]; } #[test] fn test_context_key_str() { let ctx = Context::empty(); assert_eq!(ctx.key_str(&String::from("foo")), String::from("{foo}")); } #[test] fn test_context_substitute() { let vars = have_vars(); let ctx = Context::new(vars.into_iter()); assert_eq!( ctx.substitute(&String::from("blah blah {foo} blah")), String::from("blah blah bar blah") ); } #[test] fn test_context_substitute_multi() { let vars = have_vars(); let ctx = Context::new(vars.into_iter()); assert_eq!( ctx.substitute(&String::from("blah {foo} {bar} blah")), String::from("blah bar baz blah") ); assert_eq!( ctx.substitute(&String::from("blah {bar} {foo} blah")), String::from("blah baz bar blah") ); } #[test] fn test_context_substitute_missing() { let vars: Vec<(String, String)> = vec![]; let ctx = Context::new(vars.into_iter()); assert_eq!( ctx.substitute(&String::from("blah blah {foo} blah")), String::from("blah blah blah") ); } #[test] fn test_context_substitute_default() { let vars: Vec<(String, String)> = vec![]; let ctx = Context::new(vars.into_iter()); assert_eq!( ctx.substitute(&String::from("blah blah {chewie:-wookie} blah")), String::from("blah blah wookie blah") ); } #[test] fn test_context_substitute_value_with_default() { let vars: Vec<(String, String)> = vec![(String::from("chewie"), String::from("han"))]; let ctx = Context::new(vars.into_iter()); assert_eq!( ctx.substitute(&String::from("blah blah {chewie:-wookie} blah")), String::from("blah blah han blah") ); } #[test] fn test_context_substitute_empty_default() { let vars: Vec<(String, String)> = vec![]; let ctx = Context::new(vars.into_iter()); assert_eq!( ctx.substitute(&String::from("blah blah {chewie:-} blah")), String::from("blah blah blah") ); } } #[cfg(test)] mod tests_make_req { use super::*; use std::str::FromStr; #[test] fn test_make_req_simple_get() { let executor = Executor::new(Context::empty()); let bfile = BarbFile::from_str("#GET^http://foo.bar\n\n").unwrap(); let output = BarbOutput::quiet(); let req = executor.make_req(&bfile, vec![], &output); assert_eq!(req.url(), "http://foo.bar"); assert_eq!(req.method(), "GET"); } #[test] fn test_make_req_simple_headers() { let executor = Executor::new(Context::empty()); let bfile = BarbFile::from_str("#GET^http://foo.bar\n#Foo: Bar\n#Bar: Baz\n\n").unwrap(); let output = BarbOutput::quiet(); let req = executor.make_req(&bfile, vec![], &output); let mut header_names = req.header_names(); header_names.sort(); assert_eq!(header_names, vec![String::from("bar"), String::from("foo")]); assert_eq!(req.header("foo"), Some("Bar")); assert_eq!(req.header("bar"), Some("Baz")); } #[test] fn test_make_req_headers_override() { let executor = Executor::new(Context::empty()); let bfile = BarbFile::from_str("#GET^http://foo.bar\n#Foo: Bar\n#Bar: Baz\n\n").unwrap(); let output = BarbOutput::quiet(); let req = executor.make_req( &bfile, vec![(String::from("Foo"), String::from("Qux"))], &output, ); let mut header_names = req.header_names(); header_names.sort(); assert_eq!(header_names, vec![String::from("bar"), String::from("foo")]); assert_eq!(req.header("foo"), Some("Qux")); assert_eq!(req.header("bar"), Some("Baz")); } }