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, output: &BarbOutput) -> ureq::Request { output.req( bfile.method_as_string(), self.context.substitute(&bfile.url()), ); let mut req = ureq::request( bfile.method_as_string().as_str(), self.context.substitute(&bfile.url()).as_str(), ); for header in bfile.headers() { let hdr_val = self.context.substitute(header.value()); req = req.set(header.name(), hdr_val.as_str()); output.req_hdr(header.name().to_string(), hdr_val); } output.end_req(); 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( &mut self, bfile: &BarbFile, output: &BarbOutput, filter: &Option, skip_filters: bool ) -> Result<(), String> { let response = self.run(&bfile, self.make_req(&bfile, output))?; //let response = executor.execute(&bfile, &output)?; 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::new(false, false, false, false, false, false); let req = executor.make_req(&bfile, &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::new(false, false, false, false, false, false); let req = executor.make_req(&bfile, &output); assert_eq!( req.header_names(), vec![String::from("foo"), String::from("bar")] ); assert_eq!(req.header("foo"), Some("Bar")); assert_eq!(req.header("bar"), Some("Baz")); } }