Skip to content

Commit d4b3a4b

Browse files
authored
Merge pull request #50 from zaghaghi/30-format-json-output-integrate-with-jq
feat: integrate jq filtering and search functionality in response viewer
2 parents d9b900b + 5462421 commit d4b3a4b

9 files changed

Lines changed: 485 additions & 21 deletions

File tree

Cargo.lock

Lines changed: 74 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ human-panic = "2.0.0"
3232
humansize = "2.1.3"
3333
json5 = "0.4.1"
3434
boon = { version = "0.6.1", default-features = false }
35+
jaq-core = "2"
36+
jaq-std = "2"
37+
jaq-json = { version = "1.0.0", features = ["serde_json"] }
3538
lazy_static = "1.4.0"
3639
libc = "0.2.153"
3740
log = "0.4.21"

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,11 @@ Then, add `openapi-tui` to your `configuration.nix`
170170
| `query`, `q` | Add or remove query strings. sub-commands are `add` or `rm`. e.g. `query add page` |
171171
| `header`, `h` | Add or remove headers. sub-commands are `add` or `rm`. e.g. `header add x-api-key` |
172172
| `request`, `r` | Load request payload. e.g. `request open /home/hamed/payload.json` |
173-
| `response`, `s` | Save response payload e.g/ `response save /home/hamed/result.json` |
173+
| `response`, `s` | Save response payload e.g. `response save /home/hamed/result.json` |
174+
| `jq <expr>` | Filter JSON response with a jq expression. e.g. `jq .items[0]` |
175+
| `jq` | Clear jq filter and return to normal response view |
176+
| `search <term>` | Search for a term in the response body (case-insensitive). e.g. `search error` |
177+
| `search` | Clear search and return to normal response view |
174178

175179
# Environment Variables
176180
| Variable | Description |
@@ -204,6 +208,12 @@ Then, add `openapi-tui` to your `configuration.nix`
204208
- [X] Support array query strings
205209
- [X] Suppert extra headers
206210
- [X] Support multiple servers
211+
- [X] Format response body by content-type (JSON pretty-print)
212+
- [X] Syntax highlighting in response viewer
213+
- [X] Line numbers in response viewer
214+
- [X] Scroll response body with j/k
215+
- [X] JQ filter for JSON responses
216+
- [X] Text search in response body
207217

208218
# Backlog
209219
- [ ] Schema Types (openapi-31)

src/action.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,6 @@ pub enum Action {
4545
RemoveHeader(String),
4646
OpenRequestPayload(String),
4747
SaveResponsePayload(String),
48+
ApplyJqQuery(String),
49+
ApplySearch(String),
4850
}

src/formatters/json.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
use super::Formatter;
2+
3+
pub struct JsonFormatter;
4+
5+
impl Formatter for JsonFormatter {
6+
fn can_format(&self, content_type: &str) -> bool {
7+
content_type.contains("json")
8+
}
9+
10+
fn syntax_name(&self) -> Option<&'static str> {
11+
Some("json")
12+
}
13+
14+
fn format(&self, input: &str) -> String {
15+
serde_json::from_str::<serde_json::Value>(input)
16+
.and_then(|v| serde_json::to_string_pretty(&v))
17+
.unwrap_or_else(|_| input.to_string())
18+
}
19+
}

src/formatters/mod.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
pub mod json;
2+
3+
pub trait Formatter: Send + Sync {
4+
fn can_format(&self, content_type: &str) -> bool;
5+
fn format(&self, input: &str) -> String;
6+
fn syntax_name(&self) -> Option<&'static str> {
7+
None
8+
}
9+
}
10+
11+
pub struct FormatterRegistry {
12+
formatters: Vec<Box<dyn Formatter>>,
13+
}
14+
15+
impl Default for FormatterRegistry {
16+
fn default() -> Self {
17+
Self::new()
18+
}
19+
}
20+
21+
impl FormatterRegistry {
22+
pub fn new() -> Self {
23+
Self { formatters: vec![] }
24+
}
25+
26+
pub fn register(&mut self, formatter: Box<dyn Formatter>) {
27+
self.formatters.push(formatter);
28+
}
29+
30+
pub fn format(&self, content_type: &str, input: &str) -> String {
31+
self
32+
.formatters
33+
.iter()
34+
.find(|f| f.can_format(content_type))
35+
.map(|f| f.format(input))
36+
.unwrap_or_else(|| input.to_string())
37+
}
38+
39+
pub fn syntax_name(&self, content_type: &str) -> Option<&'static str> {
40+
self.formatters.iter().find(|f| f.can_format(content_type)).and_then(|f| f.syntax_name())
41+
}
42+
}

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pub mod app;
33
pub mod cli;
44
pub mod components;
55
pub mod config;
6+
pub mod formatters;
67
pub mod pages;
78
pub mod panes;
89
pub mod request;

src/pages/phone.rs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,20 @@ impl Phone {
118118
}
119119
return Some(Action::TimedStatusLine("invalid response args. response save <payload-file-name>".into(), 3));
120120
}
121+
if let Some(expr) = command_args.strip_prefix("jq ") {
122+
return Some(Action::ApplyJqQuery(expr.trim().to_string()));
123+
}
124+
if command_args.eq("jq") {
125+
return Some(Action::ApplyJqQuery(String::new()));
126+
}
127+
if let Some(term) = command_args.strip_prefix("search ") {
128+
return Some(Action::ApplySearch(term.trim().to_string()));
129+
}
130+
if command_args.eq("search") {
131+
return Some(Action::ApplySearch(String::new()));
132+
}
121133
Some(Action::TimedStatusLine(
122-
"unknown command. available commands are: send, query, header, request, response".into(),
134+
"unknown command. available commands are: send, query, header, request, response, jq, search".into(),
123135
3,
124136
))
125137
}
@@ -238,11 +250,12 @@ impl Page for Phone {
238250
pane.update(Action::Focus, state)?;
239251
}
240252
if let Some(action) = self.handle_commands(args) {
241-
for pane in self.panes.iter_mut() {
242-
actions.push(pane.update(action.clone(), state)?);
243-
}
244253
if let Action::TimedStatusLine(_, _) = action {
245-
actions.push(Some(action))
254+
actions.push(Some(action));
255+
} else {
256+
for pane in self.panes.iter_mut() {
257+
actions.push(pane.update(action.clone(), state)?);
258+
}
246259
}
247260
}
248261
},

0 commit comments

Comments
 (0)