aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGuillaume Pasquet <dev@etenil.net>2022-02-26 09:38:34 +0000
committerGuillaume Pasquet <dev@etenil.net>2022-02-27 23:01:01 +0000
commit8a5c6db3159cdc9f350f9083ae7d190c050b76be (patch)
tree9651a05d61db7e51f447d8da3f7accdd6a9f20e5
parentcc2839e870c45d284d45fb835ddc940bec8c4975 (diff)
Working implementation with list of filters.
-rw-r--r--Cargo.lock27
-rw-r--r--Cargo.toml3
-rw-r--r--src/barbfile.rs98
-rw-r--r--src/executor.rs58
-rw-r--r--src/main.rs45
-rw-r--r--test_api/test_api_get_filter.barb3
6 files changed, 170 insertions, 64 deletions
diff --git a/Cargo.lock b/Cargo.lock
index d170102..986d726 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 1bee62c..c02ca7e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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