aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGuillaume Pasquet <dev@etenil.net>2022-03-03 23:18:08 +0000
committerGuillaume Pasquet <dev@etenil.net>2022-03-03 23:18:08 +0000
commit57af17cfa6e08a6fe9bf6d73d401af17dccfa3bb (patch)
treecd550ccf0eb78f360e6813ffdfd3c89d817eb21f
parentf797f10c2eda28a221759fc26ddf483f41232604 (diff)
Introduce JSONPath and potentially break old stuff.
-rw-r--r--Cargo.lock151
-rw-r--r--Cargo.toml20
-rw-r--r--README.md44
-rw-r--r--src/barbfile.rs76
-rw-r--r--src/executor.rs8
-rw-r--r--src/main.rs3
-rw-r--r--test_api/test_api_get_filter.barb2
-rw-r--r--test_api/test_api_get_jsonpath.barb3
8 files changed, 280 insertions, 27 deletions
diff --git a/Cargo.lock b/Cargo.lock
index cba7f73..e97d03e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -45,7 +45,7 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "barb"
-version = "0.2.2"
+version = "0.3.0"
dependencies = [
"clap 3.0.14",
"colored",
@@ -53,6 +53,7 @@ dependencies = [
"dotenv",
"jq-rs",
"jsonformat",
+ "jsonpath-rust",
"regex",
"serde",
"serde_json",
@@ -72,12 +73,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
+name = "block-buffer"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
+dependencies = [
+ "block-padding",
+ "byte-tools",
+ "byteorder",
+ "generic-array",
+]
+
+[[package]]
+name = "block-padding"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
+dependencies = [
+ "byte-tools",
+]
+
+[[package]]
name = "bumpalo"
version = "3.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899"
[[package]]
+name = "byte-tools"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
+
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
name = "cc"
version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -174,12 +208,27 @@ dependencies = [
]
[[package]]
+name = "digest"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
name = "dotenv"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
[[package]]
+name = "fake-simd"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
+
+[[package]]
name = "flate2"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -202,6 +251,15 @@ dependencies = [
]
[[package]]
+name = "generic-array"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
+dependencies = [
+ "typenum",
+]
+
+[[package]]
name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -286,6 +344,18 @@ dependencies = [
]
[[package]]
+name = "jsonpath-rust"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da86e332e6de16fa0e12388aeac5a295c522f9930c103cef43b2d17ccf0ba147"
+dependencies = [
+ "pest",
+ "pest_derive",
+ "regex",
+ "serde_json",
+]
+
+[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -307,6 +377,12 @@ dependencies = [
]
[[package]]
+name = "maplit"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
+
+[[package]]
name = "matches"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -335,6 +411,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
[[package]]
+name = "opaque-debug"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
+
+[[package]]
name = "os_str_bytes"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -350,6 +432,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
+name = "pest"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
+dependencies = [
+ "ucd-trie",
+]
+
+[[package]]
+name = "pest_derive"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0"
+dependencies = [
+ "pest",
+ "pest_generator",
+]
+
+[[package]]
+name = "pest_generator"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
+dependencies = [
+ "pest",
+ "pest_meta",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pest_meta"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d"
+dependencies = [
+ "maplit",
+ "pest",
+ "sha-1",
+]
+
+[[package]]
name = "pkg-config"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -475,6 +600,18 @@ dependencies = [
]
[[package]]
+name = "sha-1"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
+dependencies = [
+ "block-buffer",
+ "digest",
+ "fake-simd",
+ "opaque-debug",
+]
+
+[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -543,6 +680,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
+name = "typenum"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
+
+[[package]]
+name = "ucd-trie"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
+
+[[package]]
name = "unicode-bidi"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 78e2347..9fd7ba1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "barb"
-version = "0.2.2"
+version = "0.3.0"
edition = "2021"
license = "GPL-3.0-or-later"
description = "Command-line tool to perform file-based HTTP(s) requests."
@@ -14,11 +14,17 @@ categories = ["command-line-utilities","network-programming","web-programming"]
[dependencies]
ureq = { version = "2.3.1", features = ["json"] }
clap = { version = "3.0.14", features = ["derive"] }
+jq-rs = { version = "0.4", optional = true }
serde = "1.0.130"
-serde_json = "1.0.70"
-jsonformat = "1.2.0"
-jq-rs = "0.4.1"
-colored = "2.0.0"
-colored_json = "2.1.0"
+serde_json = "1.0"
+jsonformat = "1.2"
+colored = "2.0"
+colored_json = "2.1"
dotenv = "0.15.0"
-regex = "1.5" \ No newline at end of file
+regex = "1.5"
+jsonpath-rust = "0.1"
+
+
+[features]
+default = []
+jq = ["jq-rs"] \ No newline at end of file
diff --git a/README.md b/README.md
index c9d9e41..5af2e03 100644
--- a/README.md
+++ b/README.md
@@ -5,6 +5,20 @@ BARB
Barb is a file-based API query tool that works nicely with version control and fits into UNIX terminal usage.
+## Installation
+
+Barb is only available through `Cargo` at this time. To install the default version with JSONPath only, [install rust with rustup](https://rustup.rs).do like so:
+
+```
+cargo install barb
+```
+
+If you'd like to have JQ filtering; ensure the `libjq` is installed on your machine, then run:
+
+```
+cargo install barb --features jq
+```
+
## Example usage
```
@@ -19,6 +33,8 @@ barb [options] <file 1> <file 2> ... <file n>
- `-r, --raw`: Don't format the response body
- `-V, --version`: Print the software version
- `-n, --no-color`: Don't use color output
+- `-f, --filter`: A JSON path to override any filters defined in the barb file
+- `--help`: Displays the help page
## Barb format
@@ -27,7 +43,7 @@ Barb uses a custom file format to perform requests. Each file contains _one_ req
```
#POST^http://my-blog.com/posts
#Authorization: TOKEN {API_TOKEN}
-#|jq.filter
+#$$.filter
{
"title": "A post",
@@ -70,6 +86,26 @@ There can be none or many headers.
### Filter
+Barb supports [JSONPath](https://goessner.net/articles/JsonPath/) filtering by default, and optionally JQ.
+
+#### JSONPath
+
+Barb supports filtering of the response body with JSONPath. This has the following format:
+
+```
+#$<JSON path>
+```
+
+The `PATH` supports variable substitution. Refer to the [JSONPath](https://goessner.net/articles/JsonPath/) for more information on the filters and their syntax.
+
+Filters can be named to populate execution variables by extracting values. Consider the following that will set the value of variable FOOBAR:
+
+```
+#FOOBAR$<JSON path>
+```
+
+#### JQ
+
Barb supports JQ filtering of the response body. This has the following format:
```
@@ -78,6 +114,12 @@ Barb supports JQ filtering of the response body. This has the following format:
The `JQ FILTER` supports variable substitution. Refer to the [JQ manual](https://stedolan.github.io/jq/manual/#Basicfilters) for more information on the filters and their syntax.
+Filters can be named to populate execution variables by extracting values. Consider the following that will set the value of variable FOOBAR:
+
+```
+#FOOBAR|<JQ FILTER>
+```
+
### Body
Anything after the preamble is considered as a body and will be send in the request for the following methods:
diff --git a/src/barbfile.rs b/src/barbfile.rs
index 91ffd0a..f3cb58f 100644
--- a/src/barbfile.rs
+++ b/src/barbfile.rs
@@ -2,6 +2,11 @@ use regex::Regex;
use std::str::FromStr;
use std::string::ToString;
use std::{error::Error, fmt};
+
+use jsonpath_rust::JsonPathQuery;
+use serde_json::{json, Value};
+
+#[cfg(feature = "jq")]
use jq_rs;
#[derive(Debug)]
@@ -86,16 +91,22 @@ impl Header {
}
}
+enum FilterType {
+ JQ,
+ PATH,
+}
+
pub struct BarbFilter {
name: Option<String>,
filter: String,
+ filter_type: FilterType,
}
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 re = Regex::new("^#(?P<name>[A-Za-z0-9_]*)(?P<type>[|$])(?P<filter>.+)$").unwrap();
let groups = re.captures(s).ok_or(BarbParseError {})?;
Ok(BarbFilter::new(
@@ -104,20 +115,36 @@ impl FromStr for BarbFilter {
any => Some(String::from(any)),
},
String::from(&groups["filter"]),
+ match &groups["type"] {
+ "|" => FilterType::JQ,
+ _ => FilterType::PATH,
+ },
))
}
}
impl PreambleLine for BarbFilter {
fn is_match(s: String) -> bool {
- let re = Regex::new("^#(?P<name>[A-Za-z0-9_]*)\\|(?P<filter>.+)$").unwrap();
+ 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 }
+ fn new(name: Option<String>, filter: String, filter_type: FilterType) -> BarbFilter {
+ BarbFilter {
+ name,
+ filter,
+ filter_type,
+ }
+ }
+
+ pub fn from_path(filter: String) -> BarbFilter {
+ BarbFilter {
+ name: None,
+ filter_type: FilterType::PATH,
+ filter,
+ }
}
pub fn name(&self) -> &Option<String> {
@@ -128,11 +155,38 @@ impl BarbFilter {
&self.filter
}
- pub fn apply(&self, body: &String) -> Result<String, String> {
+ #[cfg(feature = "jq")]
+ fn apply_jq(&self, body: &String) -> Result<String, String> {
jq_rs::run(self.filter.as_str(), body.as_str())
.map_err(|x| x.to_string())
.map(|x| String::from(x.trim().trim_matches('"')))
}
+
+ fn apply_path(&self, body: &String) -> Result<String, String> {
+ let json: Value = serde_json::from_str(body.as_str())
+ .map_err(|_| String::from("Failed to decode body"))?;
+ let path = &json.path(self.filter.as_str())?;
+ Ok(match path {
+ Value::Array(val) => match val.len() {
+ 1 => val.first().unwrap(),
+ _ => path,
+ },
+ _ => path,
+ }
+ .to_string()
+ .trim()
+ .trim_matches('"')
+ .to_string())
+ }
+
+ pub fn apply(&self, body: &String) -> Result<String, String> {
+ match self.filter_type {
+ #[cfg(feature = "jq")]
+ FilterType::JQ => self.apply_jq(body),
+ FilterType::PATH => self.apply_path(body),
+ _ => Ok(body.to_string()),
+ }
+ }
}
struct BarbPreamble {
@@ -225,7 +279,7 @@ impl FromStr for BarbFile {
if BarbFilter::is_match(String::from(line)) {
match BarbFilter::from_str(line) {
Ok(filter) => filters.push(filter),
- Err(_) => ()
+ Err(_) => (),
};
}
}
@@ -277,7 +331,10 @@ mod tests {
.unwrap();
assert!(matches!(barbfile.preamble.method, Method::GET));
assert_eq!(barbfile.preamble.url, "https://blah.com/api/blah");
- assert_eq!(barbfile.preamble.filters[0].filter(), &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");
@@ -291,7 +348,10 @@ mod tests {
.unwrap();
assert!(matches!(barbfile.preamble.method, Method::POST));
assert_eq!(barbfile.preamble.url, "https://blah.com/api/blah");
- assert_eq!(barbfile.preamble.filters[0].filter(), &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");
diff --git a/src/executor.rs b/src/executor.rs
index 15704c2..eccab39 100644
--- a/src/executor.rs
+++ b/src/executor.rs
@@ -89,7 +89,7 @@ impl Executor {
arg_filter: &Option<String>,
) -> Result<String, String> {
if let Some(filter) = arg_filter {
- return Ok(BarbFilter::new(None, filter.to_string()).apply(&body)?);
+ 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() {
@@ -144,11 +144,7 @@ impl Executor {
}
output.end_resp_hdr();
- output.body(self.apply_filters(
- &bfile,
- response.into_string().unwrap(),
- filter,
- )?);
+ output.body(self.apply_filters(&bfile, response.into_string().unwrap(), filter)?);
Ok(())
}
diff --git a/src/main.rs b/src/main.rs
index 2973b27..1738391 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -27,8 +27,6 @@ struct Args {
#[clap(short, long)]
filter: Option<String>,
#[clap(short, long)]
- path: Option<String>,
- #[clap(short, long)]
no_color: bool,
files: Vec<String>,
}
@@ -62,7 +60,6 @@ fn main() {
for file in args.files_iter() {
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
index 674ff15..83ba52f 100644
--- a/test_api/test_api_get_filter.barb
+++ b/test_api/test_api_get_filter.barb
@@ -1,3 +1,3 @@
#GET^http://localhost:8080/
#Foo: Bar
-#BAR|.status
+#|.status
diff --git a/test_api/test_api_get_jsonpath.barb b/test_api/test_api_get_jsonpath.barb
new file mode 100644
index 0000000..d4f323e
--- /dev/null
+++ b/test_api/test_api_get_jsonpath.barb
@@ -0,0 +1,3 @@
+#GET^http://localhost:8080/
+#Foo: Bar
+#$$.status