diff options
author | Guillaume Pasquet <dev@etenil.net> | 2022-02-26 09:38:34 +0000 |
---|---|---|
committer | Guillaume Pasquet <dev@etenil.net> | 2022-02-27 23:01:01 +0000 |
commit | 8a5c6db3159cdc9f350f9083ae7d190c050b76be (patch) | |
tree | 9651a05d61db7e51f447d8da3f7accdd6a9f20e5 | |
parent | cc2839e870c45d284d45fb835ddc940bec8c4975 (diff) |
Working implementation with list of filters.
-rw-r--r-- | Cargo.lock | 27 | ||||
-rw-r--r-- | Cargo.toml | 3 | ||||
-rw-r--r-- | src/barbfile.rs | 98 | ||||
-rw-r--r-- | src/executor.rs | 58 | ||||
-rw-r--r-- | src/main.rs | 45 | ||||
-rw-r--r-- | test_api/test_api_get_filter.barb | 3 |
6 files changed, 170 insertions, 64 deletions
@@ -9,6 +9,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] name = "ansi_term" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -44,6 +53,7 @@ dependencies = [ "dotenv", "jq-rs", "jsonformat", + "regex", "serde", "serde_json", "ureq", @@ -388,6 +398,23 @@ dependencies = [ ] [[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] name = "ring" version = "0.16.20" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -20,4 +20,5 @@ jsonformat = "1.2.0" jq-rs = "0.4.1" colored = "2.0.0" colored_json = "2.1.0" -dotenv = "0.15.0"
\ No newline at end of file +dotenv = "0.15.0" +regex = "1.5"
\ No newline at end of file diff --git a/src/barbfile.rs b/src/barbfile.rs index 66a8756..35b0224 100644 --- a/src/barbfile.rs +++ b/src/barbfile.rs @@ -1,3 +1,4 @@ +use regex::Regex; use std::str::FromStr; use std::string::ToString; use std::{error::Error, fmt}; @@ -7,6 +8,10 @@ pub struct BarbParseError {} impl Error for BarbParseError {} +trait PreambleLine { + fn is_match(s: String) -> bool; +} + impl fmt::Display for BarbParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Error parsing barb file") @@ -80,20 +85,65 @@ impl Header { } } +pub struct BarbFilter { + name: Option<String>, + filter: String, +} + +impl FromStr for BarbFilter { + type Err = BarbParseError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let re = Regex::new("^#(?P<name>[A-Za-z0-9_]*)\\|(?P<filter>.+)$").unwrap(); + let groups = re.captures(s).ok_or(BarbParseError {})?; + + Ok(BarbFilter::new( + match &groups["name"] { + "" => None, + any => Some(String::from(any)), + }, + String::from(&groups["filter"]), + )) + } +} + +impl PreambleLine for BarbFilter { + fn is_match(s: String) -> bool { + let re = Regex::new("^#(?P<name>[A-Za-z0-9_]*)\\|(?P<filter>.+)$").unwrap(); + re.is_match(s.as_str()) + } +} + +impl BarbFilter { + pub fn new(name: Option<String>, filter: String) -> BarbFilter { + BarbFilter { name, filter } + } + + pub fn name(&self) -> &Option<String> { + &self.name + } + + pub fn filter(&self) -> &String { + &self.filter + } +} + struct BarbPreamble { pub method: Method, pub url: String, pub headers: Vec<Header>, - pub filter: Option<String>, + pub filters: Vec<BarbFilter>, + //pub filter: Option<BarbFilter>, } impl BarbPreamble { - fn new(method: Method, url: String, headers: Vec<Header>, filter: Option<String>) -> Self { + fn new(method: Method, url: String, headers: Vec<Header>, filters: Vec<BarbFilter>) -> Self { BarbPreamble { - method: method, - url: url, - headers: headers, - filter: filter, + method, + url, + headers, + //filter: Vec<BarbFilter>, + filters, } } } @@ -120,8 +170,8 @@ impl BarbFile { &self.preamble.url } - pub fn filter(&self) -> &Option<String> { - &self.preamble.filter + pub fn filters(&self) -> &Vec<BarbFilter> { + &self.preamble.filters } pub fn body(&self) -> &Option<String> { @@ -153,7 +203,7 @@ impl FromStr for BarbFile { let mut lines = s.split('\n'); let (method, url) = decode_url_line(lines.next().ok_or(BarbParseError {})?)?; let mut headers: Vec<Header> = vec![]; - let mut filter = None; + let mut filters: Vec<BarbFilter> = vec![]; for line in &mut lines { if line == "" { @@ -165,18 +215,18 @@ impl FromStr for BarbFile { headers.push(decode_header(line).map_err(|_| BarbParseError {})?); } - if let None = filter { - filter = match &line[0..2] { - "#|" => Some(String::from(&line[2..])), - _ => None, - } + if BarbFilter::is_match(String::from(line)) { + match BarbFilter::from_str(line) { + Ok(filter) => filters.push(filter), + Err(_) => () + }; } } let body = lines.fold(String::from(""), |acc, x| acc + x); Ok(BarbFile { - preamble: BarbPreamble::new(method, url, headers, filter), + preamble: BarbPreamble::new(method, url, headers, filters), body: if body == "" { None } else { Some(body) }, }) } @@ -220,7 +270,7 @@ mod tests { .unwrap(); assert!(matches!(barbfile.preamble.method, Method::GET)); assert_eq!(barbfile.preamble.url, "https://blah.com/api/blah"); - assert_eq!(barbfile.preamble.filter, Some(String::from("filtr"))); + assert_eq!(barbfile.preamble.filters[0].filter(), &String::from("filtr")); assert_eq!(barbfile.preamble.headers.len(), 1); assert_eq!(barbfile.preamble.headers[0].name, "Authorization"); assert_eq!(barbfile.preamble.headers[0].value, "BLAH"); @@ -234,10 +284,24 @@ mod tests { .unwrap(); assert!(matches!(barbfile.preamble.method, Method::POST)); assert_eq!(barbfile.preamble.url, "https://blah.com/api/blah"); - assert_eq!(barbfile.preamble.filter, Some(String::from("filtr"))); + assert_eq!(barbfile.preamble.filters[0].filter(), &String::from("filtr")); assert_eq!(barbfile.preamble.headers.len(), 1); assert_eq!(barbfile.preamble.headers[0].name, "Authorization"); assert_eq!(barbfile.preamble.headers[0].value, "BLAH"); assert_eq!(barbfile.body, Some(String::from("{\"key\":\"value\"}"))) } + + #[test] + fn test_parse_named_filter() { + let filter = BarbFilter::from_str("#FOO|.bar.foo").unwrap(); + assert_eq!(filter.name, Some(String::from("FOO"))); + assert_eq!(filter.filter, String::from(".bar.foo")); + } + + #[test] + fn test_parse_named_filter_no_name() { + let filter = BarbFilter::from_str("#|.bar.foo").unwrap(); + assert_eq!(filter.name, None); + assert_eq!(filter.filter, String::from(".bar.foo")); + } } diff --git a/src/executor.rs b/src/executor.rs index f6ca4f5..03477e3 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -1,10 +1,15 @@ -use crate::barbfile::BarbFile; +use crate::barbfile::{BarbFile, BarbFilter}; use crate::output::BarbOutput; +use jq_rs; + use std::collections::HashMap; use ureq; use ureq::Error as UreqError; +use std::fs; +use std::str::FromStr; + pub struct Context { vars: HashMap<String, String>, } @@ -49,6 +54,26 @@ impl Context { } } + +fn apply_filter(filter: &BarbFilter, body: &String) -> Result<String, String> { + jq_rs::run(filter.filter().as_str(), body.as_str()).map_err(|x| x.to_string()) +} + +fn apply_filters(bfile: &BarbFile, body: String, arg_filter: &Option<String>) -> Result<String, String> { + if let Some(filter) = arg_filter { + return Ok(apply_filter(&BarbFilter::new(None, filter.to_string()), &body)?); + } else if bfile.filters().len() > 0 { + return Ok(bfile + .filters() + .iter() + .map(|filter| apply_filter(filter, &body)) + .last() + .unwrap().unwrap()); + } + Ok(String::from(body)) +} + + pub struct Executor { context: Context, } @@ -97,10 +122,35 @@ impl Executor { pub fn execute( &mut self, - bfile: &BarbFile, + file_name: &String, output: &BarbOutput, - ) -> Result<ureq::Response, String> { - self.run(bfile, self.make_req(&bfile, output)) + filter: &Option<String> + ) -> Result<(), String> { + let bfile = BarbFile::from_str( + fs::read_to_string(file_name.as_str()) + .map_err(|_| format!("Failed to read file '{}'", file_name))? + .as_str(), + ) + .map_err(|_| format!("Failed to parse file '{}'", file_name))?; + 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(); + + output.body(apply_filters( + &bfile, + response.into_string().unwrap(), + filter + )?); + + Ok(()) } } diff --git a/src/main.rs b/src/main.rs index bbe2bf0..2973b27 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,18 +2,14 @@ mod barbfile; mod executor; mod output; -use jq_rs; use std::slice::Iter; -use barbfile::BarbFile; use executor::{Context, Executor}; use output::BarbOutput; use clap::Parser; use std::env; -use std::fs; -use std::str::FromStr; use dotenv::dotenv; @@ -58,50 +54,15 @@ impl Args { } } -fn apply_filters(args: &Args, bfile: &BarbFile, body: String) -> Result<String, String> { - if let Some(filter) = args.jq_filter() { - return Ok(jq_rs::run(filter.as_str(), body.as_str()).map_err(|x| x.to_string())?); - } else if let Some(filter) = bfile.filter() { - return Ok(jq_rs::run(filter.as_str(), body.as_str()).map_err(|x| x.to_string())?); - } - Ok(String::from(body)) -} - -fn run_file(args: &Args, executor: &mut Executor, file_name: &String) -> Result<(), String> { - let bfile = BarbFile::from_str( - fs::read_to_string(file_name.as_str()) - .map_err(|_| format!("Failed to read file '{}'", file_name))? - .as_str(), - ) - .map_err(|_| format!("Failed to parse file '{}'", file_name))?; - let output = args.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(); - - output.body(apply_filters( - args, - &bfile, - response.into_string().unwrap(), - )?); - - Ok(()) -} - fn main() { let args = Args::parse(); dotenv().ok(); let mut executor = Executor::new(Context::new(env::vars())); + let output = args.output(); for file in args.files_iter() { - match run_file(&args, &mut executor, file) { + match executor.execute(file, &output, args.jq_filter()) { + //match run_file(&args, &mut executor, file) { Ok(()) => (), Err(err) => println!("{}", err), } diff --git a/test_api/test_api_get_filter.barb b/test_api/test_api_get_filter.barb new file mode 100644 index 0000000..674ff15 --- /dev/null +++ b/test_api/test_api_get_filter.barb @@ -0,0 +1,3 @@ +#GET^http://localhost:8080/ +#Foo: Bar +#BAR|.status |