Skip to content

Commit

Permalink
Add text box for filtering response body
Browse files Browse the repository at this point in the history
  • Loading branch information
LucasPickering committed Feb 21, 2024
1 parent 48bdd73 commit 3b4b3e2
Show file tree
Hide file tree
Showing 22 changed files with 470 additions and 168 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- New informational flags to `slumber request`
- `--exit-status` to set exit code based on response status ([#97](https://github.com/LucasPickering/slumber/issues/97))
- `--status`, `--headers`, and `--no-body` to control printed output
- Filter response via JSONPath ([#78](https://github.com/LucasPickering/slumber/issues/78))

## [0.12.1] - 2024-01-22

Expand Down
2 changes: 1 addition & 1 deletion RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

It's easy!

- Make sure `CHANGELOG.md` has the latest release notes under `Unreleased`
- Make sure `CHANGELOG.md` has the latest release notes under `[Unreleased] - ReleaseDate`
- `cargo release <major|minor|patch>`
- If it looks good, add `--execute`

Expand Down
2 changes: 1 addition & 1 deletion docs/src/user_guide/filter_query.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,6 @@ requests:
auth: "{{chains.auth_token}}"
```
While this example simple extracts inner fields, JSONPath can be used for much more powerful transformations. See the [JSONPath docs](https://www.ietf.org/archive/id/draft-goessner-dispatch-jsonpath-00.html) for more examples.
While this example simple extracts inner fields, JSONPath can be used for much more powerful transformations. See the [JSONPath docs](https://www.ietf.org/archive/id/draft-goessner-dispatch-jsonpath-00.html) or [this JSONPath editor](https://jsonpath.com/) for more examples.
<!-- TODO add screenshot of in-TUI querying -->
8 changes: 4 additions & 4 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ impl CliCommand {
/// Execute a non-TUI command
pub async fn execute(self, global: GlobalArgs) -> anyhow::Result<ExitCode> {
match self {
CliCommand::Request(command) => command.execute(global).await,
CliCommand::Import(command) => command.execute(global).await,
CliCommand::Collections(command) => command.execute(global).await,
CliCommand::Show(command) => command.execute(global).await,
Self::Request(command) => command.execute(global).await,
Self::Import(command) => command.execute(global).await,
Self::Collections(command) => command.execute(global).await,
Self::Show(command) => command.execute(global).await,
}
}
}
2 changes: 1 addition & 1 deletion src/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ use tokio::try_join;
use tracing::{debug, info, info_span};

const USER_AGENT: &str =
concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));

/// Utility for handling all HTTP operations. The main purpose of this is to
/// de-asyncify HTTP so it can be called in the main TUI thread. All heavy
Expand Down
10 changes: 6 additions & 4 deletions src/http/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,16 @@ impl ContentType {
}
}

/// Parse content from JSON into this format. Valid JSON should be valid
/// in any other format too, so this is infallible.
/// Convert content from JSON into this format. Valid JSON should be valid
/// in any other format too, so this is infallible. This takes a `Cow`
/// because some formats may need an owned JSON value while others may not.
/// You should pass an owned value if you have it, but it's not necessary.
pub fn parse_json(
self,
content: &serde_json::Value,
content: Cow<'_, serde_json::Value>,
) -> Box<dyn ResponseContent> {
match self {
Self::Json => Box::new(Json(content.clone())),
Self::Json => Box::new(Json(content.into_owned())),
}
}

Expand Down
25 changes: 21 additions & 4 deletions src/http/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ use crate::http::ResponseContent;
use derive_more::{Display, FromStr};
use serde::{Deserialize, Serialize};
use serde_json_path::{ExactlyOneError, JsonPath};
use std::borrow::Cow;
use thiserror::Error;

/// A wrapper around a JSONPath. This combines some common behavior, and will
/// make it easy to swap out the query language in the future if necessary.
#[derive(Clone, Debug, Display, FromStr, Serialize, Deserialize)]
#[derive(Clone, Debug, Display, FromStr, PartialEq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Query(JsonPath);

Expand All @@ -24,6 +25,22 @@ pub enum QueryError {
}

impl Query {
/// Apply a query to some content, returning the result in the original
/// format. This will convert to a common format, apply the query, then
/// convert back.
pub fn query(
&self,
value: &dyn ResponseContent,
) -> Box<dyn ResponseContent> {
let content_type = value.content_type();
let json_value = value.to_json();
// We have to clone all the elements to put them into a JSON array
let queried = serde_json::Value::Array(
self.0.query(&json_value).into_iter().cloned().collect(),
);
content_type.parse_json(Cow::Owned(queried))
}

/// Apply a query to some content, returning a string. The query should
/// return a single result. If it's a scalar, that will be stringified. If
/// it's an array/object, it'll be converted back into its input format,
Expand All @@ -37,17 +54,17 @@ impl Query {
// All content types get converted to JSON for querying, then converted
// back. This is fucky but we need *some* common format
let json_value = value.to_json();
let filtered = self.0.query(&json_value).exactly_one()?;
let queried = self.0.query(&json_value).exactly_one()?;

// If we got a scalar value, use that. Otherwise convert back to the
// input content type to re-stringify
let stringified = match filtered {
let stringified = match queried {
serde_json::Value::Null => "".into(),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::Bool(b) => b.to_string(),
serde_json::Value::String(s) => s.clone(),
serde_json::Value::Array(_) | serde_json::Value::Object(_) => {
content_type.parse_json(filtered).to_string()
content_type.parse_json(Cow::Borrowed(queried)).to_string()
}
};

Expand Down
4 changes: 4 additions & 0 deletions src/tui/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ impl InputEngine {
InputBinding::new(KeyCode::Char('f'), Action::Fullscreen),
InputBinding::new(KeyCode::Char('r'), Action::ReloadCollection),
InputBinding::new(KeyCode::F(2), Action::SendRequest),
InputBinding::new(KeyCode::Char('/'), Action::Search),
InputBinding::new(KeyCode::BackTab, Action::PreviousPane),
InputBinding::new(KeyCode::Tab, Action::NextPane),
InputBinding::new(KeyCode::Up, Action::Up).hide(),
Expand Down Expand Up @@ -168,6 +169,9 @@ pub enum Action {
/// Send the active request from *any* context
#[display("Send Request")]
SendRequest,
/// Start a search/filter operation
#[display("Search/Filter")]
Search,
/// Force a collection reload (typically it's automatic)
#[display("Reload")]
ReloadCollection,
Expand Down
2 changes: 1 addition & 1 deletion src/tui/view/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ impl<'a> Generate for Pane<'a> {
ratatui::widgets::Block::default()
.borders(Borders::ALL)
.border_style(
TuiContext::get().theme.pane_border_style(self.is_focused),
TuiContext::get().theme.pane.border_style(self.is_focused),
)
.title(self.title)
}
Expand Down
2 changes: 1 addition & 1 deletion src/tui/view/common/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,6 @@ where

ratatui::widgets::List::new(items)
.block(block)
.highlight_style(TuiContext::get().theme.list_highlight_style)
.highlight_style(TuiContext::get().theme.list.highlight)
}
}
17 changes: 7 additions & 10 deletions src/tui/view/common/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,29 +79,26 @@ impl<'a, const COLS: usize> Generate for Table<'a, COLS, Row<'a>> {
let rows = self.rows.into_iter().enumerate().map(|(i, row)| {
// Apply theme styles, but let the row's individual styles override
let base_style = if self.alternate_row_style && i % 2 == 1 {
theme.table_alt_style
theme.table.alt
} else {
theme.table_text_style
theme.table.text
};
let row_style = Styled::style(&row);
row.set_style(base_style.patch(row_style))
});
let mut table = ratatui::widgets::Table::new(rows, self.column_widths)
.highlight_style(theme.table_highlight_style);
.highlight_style(theme.table.highlight);

// Add title
if let Some(title) = self.title {
table = table.block(
Block::default()
.title(title)
.title_style(theme.table_title_style),
Block::default().title(title).title_style(theme.table.title),
);
}

// Add optional header if given
if let Some(header) = self.header {
table =
table.header(Row::new(header).style(theme.table_header_style));
table = table.header(Row::new(header).style(theme.table.header));
}

table
Expand Down Expand Up @@ -154,9 +151,9 @@ where
)),
)
.style(if self.enabled {
theme.table_text_style
theme.table.text
} else {
theme.table_disabled_style
theme.table.disabled
})
}
}
2 changes: 1 addition & 1 deletion src/tui/view/common/tabs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ impl<T: FixedSelect + Persistable> Draw for Tabs<T> {
frame.render_widget(
ratatui::widgets::Tabs::new(T::iter().map(|e| e.to_string()))
.select(self.tabs.selected_index())
.highlight_style(TuiContext::get().theme.tab_highlight_style),
.highlight_style(TuiContext::get().theme.tab.highlight),
area,
)
}
Expand Down
8 changes: 4 additions & 4 deletions src/tui/view/common/template_preview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ impl<'a> TextStitcher<'a> {
let chunk_text = Self::get_chunk_text(template, chunk);
let style = match &chunk {
TemplateChunk::Raw(_) => Style::default(),
TemplateChunk::Rendered { .. } => theme.template_preview_text,
TemplateChunk::Error(_) => theme.template_preview_error,
TemplateChunk::Rendered { .. } => theme.template_preview.text,
TemplateChunk::Error(_) => theme.template_preview.error,
};

stitcher.add_chunk(chunk_text, style);
Expand Down Expand Up @@ -254,8 +254,8 @@ mod tests {
let theme = &TuiContext::get().theme;

let text = TextStitcher::stitch_chunks(&template, &chunks);
let rendered_style = theme.template_preview_text;
let error_style = theme.template_preview_error;
let rendered_style = theme.template_preview.text;
let error_style = theme.template_preview.error;
let expected = Text::from(vec![
Line::from("intro"),
Line::from(Span::styled("🧡", rendered_style)),
Expand Down
Loading

0 comments on commit 3b4b3e2

Please sign in to comment.