Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace JSONPath queries with commands #421

Merged
merged 1 commit into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ reqwest = {version = "0.12.5", default-features = false}
rstest = {version = "0.21.0", default-features = false}
serde = {version = "1.0.204", default-features = false}
serde_json = {version = "1.0.120", default-features = false, features = ["preserve_order"]}
serde_json_path = "0.7.1"
serde_test = "1.0.176"
serde_yaml = {version = "0.9.0", default-features = false}
slumber_cli = {path = "./crates/cli", version = "2.3.0"}
Expand Down
2 changes: 1 addition & 1 deletion crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ rusqlite = {version = "0.31.0", default-features = false, features = ["bundled",
rusqlite_migration = "1.2.0"
serde = {workspace = true, features = ["derive"]}
serde_json = {workspace = true}
serde_json_path = {workspace = true}
serde_json_path = "0.7.1"
serde_path_to_error = "0.1.16"
serde_yaml = {workspace = true}
strum = {workspace = true, features = ["derive"]}
Expand Down
4 changes: 2 additions & 2 deletions crates/core/src/db/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,13 +209,13 @@ impl<'a, 'b> TryFrom<&'a Row<'b>> for Exchange {
.get::<_, Option<SqlWrap<Bytes>>>("request_body")?
.map(|wrap| wrap.0),
}),
response: ResponseRecord {
response: Arc::new(ResponseRecord {
status: row.get::<_, SqlWrap<StatusCode>>("status_code")?.0,
headers: row
.get::<_, SqlWrap<HeaderMap>>("response_headers")?
.0,
body: row.get::<_, SqlWrap<Bytes>>("response_body")?.0.into(),
},
}),
})
}
}
Expand Down
4 changes: 2 additions & 2 deletions crates/core/src/db/migrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ fn migrate_requests_v2_up(transaction: &Transaction) -> HookResult {
end_time: row.get("end_time")?,
// Deserialize from bytes
request: Arc::new(row.get::<_, ByteEncoded<_>>("request")?.0),
response: row.get::<_, ByteEncoded<_>>("response")?.0,
response: Arc::new(row.get::<_, ByteEncoded<_>>("response")?.0),
};
Ok((collection_id, exchange))
}
Expand Down Expand Up @@ -430,7 +430,7 @@ mod tests {
":start_time": &exchange.start_time,
":end_time": &exchange.end_time,
":request": &ByteEncoded(&*exchange.request),
":response": &ByteEncoded(&exchange.response),
":response": &ByteEncoded(&*exchange.response),
":status_code": exchange.response.status.as_u16(),
},
)
Expand Down
4 changes: 2 additions & 2 deletions crates/core/src/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ impl RequestTicket {
let exchange = Exchange {
id,
request: self.record,
response,
response: response.into(),
start_time,
end_time,
};
Expand Down Expand Up @@ -1362,7 +1362,7 @@ mod tests {
.to_str()
.unwrap();
assert_eq!(
exchange.response,
*exchange.response,
ResponseRecord {
status: StatusCode::OK,
headers: header_map([
Expand Down
10 changes: 0 additions & 10 deletions crates/core/src/http/content_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,6 @@ pub trait ResponseContent: Debug + Display + Send + Sync {
where
Self: Sized;

/// Prettify a parsed body into something the user will really like. Once
/// a response is parsed, prettification is infallible. Could be slow
/// though!
fn prettify(&self) -> String;

/// Convert the content to JSON. JSON is the common language used for
/// querying internally, so everything needs to be convertible to/from JSON.
fn to_json(&self) -> Cow<'_, serde_json::Value>;
Expand All @@ -161,11 +156,6 @@ impl ResponseContent for Json {
Ok(Self(serde_json::from_slice(body)?))
}

fn prettify(&self) -> String {
// serde_json can't fail serializing its own Value type
serde_json::to_string_pretty(&self.0).unwrap()
}

fn to_json(&self) -> Cow<'_, serde_json::Value> {
Cow::Borrowed(&self.0)
}
Expand Down
76 changes: 20 additions & 56 deletions crates/core/src/http/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@

use crate::{
collection::{Authentication, ProfileId, RecipeBody, RecipeId},
http::{
cereal,
content_type::{ContentType, ResponseContent},
},
http::{cereal, content_type::ContentType},
template::Template,
};
use anyhow::Context;
Expand Down Expand Up @@ -183,7 +180,7 @@ pub struct Exchange {
/// What we said. Use an Arc so the view can hang onto it.
pub request: Arc<RequestRecord>,
/// What we heard
pub response: ResponseRecord,
pub response: Arc<ResponseRecord>,
/// When was the request sent to the server?
pub start_time: DateTime<Utc>,
/// When did we finish receiving the *entire* response?
Expand Down Expand Up @@ -423,7 +420,7 @@ impl crate::test_util::Factory<(RequestRecord, ResponseRecord)> for Exchange {
Self {
id: request.id,
request: request.into(),
response,
response: response.into(),
start_time: Utc::now(),
end_time: Utc::now(),
}
Expand All @@ -450,20 +447,10 @@ pub struct ResponseRecord {
}

impl ResponseRecord {
/// Stored the parsed form of this request's body
pub fn set_parsed_body(&mut self, body: Box<dyn ResponseContent>) {
self.body.parsed = Some(body);
}

/// Get the content type of the response body, according to the
/// `Content-Type` header
pub fn content_type(&self) -> Option<ContentType> {
// If we've parsed the body, we'll have the content type present. If
// not, check the header now
self.body
.parsed()
.map(|content| content.content_type())
.or_else(|| ContentType::from_headers(&self.headers).ok())
ContentType::from_headers(&self.headers).ok()
}

/// Get a suggested file name for the content of this response. First we'll
Expand Down Expand Up @@ -496,64 +483,41 @@ impl ResponseRecord {
}
}

pub enum ParseMode {
Immediate,
Background {
callback: Box<dyn 'static + FnOnce(Box<dyn ResponseContent>) + Send>,
},
}

/// HTTP response body. Content is stored as bytes because it may not
/// necessarily be valid UTF-8. Converted to text only as needed.
///
/// The generic type is to make this usable with references to bodies. In most
/// cases you can just use the default.
#[derive(Default, Deserialize)]
#[serde(from = "Bytes")] // Can't use into=Bytes because that requires cloning
pub struct ResponseBody {
#[serde(bound = "T: From<Bytes>", from = "Bytes")] // Can't use into=Bytes because that requires cloning
pub struct ResponseBody<T = Bytes> {
/// Raw body
data: Bytes,
/// For responses of a known content type, we can parse the body into a
/// real data structure. This is populated manually; Call
/// [ResponseRecord::parse_body] to set the parsed body. This uses a lock
/// so it can be parsed and populated in a background thread.
#[serde(skip)]
parsed: Option<Box<dyn ResponseContent>>,
data: T,
}

impl ResponseBody {
pub fn new(data: Bytes) -> Self {
Self {
data,
parsed: Default::default(),
}
impl<T: AsRef<[u8]>> ResponseBody<T> {
pub fn new(data: T) -> Self {
Self { data }
}

/// Raw content bytes
pub fn bytes(&self) -> &Bytes {
pub fn bytes(&self) -> &T {
&self.data
}

/// Owned raw content bytes
pub fn into_bytes(self) -> Bytes {
pub fn into_bytes(self) -> T {
self.data
}

/// Get bytes as text, if valid UTF-8
pub fn text(&self) -> Option<&str> {
std::str::from_utf8(&self.data).ok()
std::str::from_utf8(self.data.as_ref()).ok()
}

/// Get body size, in bytes
pub fn size(&self) -> usize {
self.bytes().len()
}

/// Get the parsed version of this body. Must haved call
/// [ResponseRecord::parse_body] first to actually do the parse. Parsing has
/// to be done on the parent because we don't have access to the
/// `Content-Type` header here, which tells us how to parse.
///
/// Return `None` if parsing either hasn't happened yet, or failed.
pub fn parsed(&self) -> Option<&dyn ResponseContent> {
self.parsed.as_deref()
self.data.as_ref().len()
}
}

Expand All @@ -566,9 +530,9 @@ impl Debug for ResponseBody {
}
}

impl From<Bytes> for ResponseBody {
fn from(bytes: Bytes) -> Self {
Self::new(bytes)
impl<T: From<Bytes>> From<Bytes> for ResponseBody<T> {
fn from(data: Bytes) -> Self {
Self { data: data.into() }
}
}

Expand Down
6 changes: 3 additions & 3 deletions crates/core/src/template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ mod tests {
response: ResponseRecord {
body: "not json!".into(),
..ResponseRecord::factory(())
},
}.into(),
..Exchange::factory(RecipeId::from("recipe1"))
}),
"content type not provided",
Expand All @@ -561,7 +561,7 @@ mod tests {
response: ResponseRecord {
body: "not json!".into(),
..ResponseRecord::factory(())
},
}.into(),
..Exchange::factory(RecipeId::from("recipe1"))
}),
"Parsing response: expected ident at line 1 column 2",
Expand All @@ -583,7 +583,7 @@ mod tests {
response: ResponseRecord {
body: "[1, 2]".into(),
..ResponseRecord::factory(())
},
}.into(),
..Exchange::factory(RecipeId::from("recipe1"))
}),
"No results from JSONPath query",
Expand Down
5 changes: 4 additions & 1 deletion crates/core/src/template/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,10 @@ impl<'a> ChainTemplateSource<'a> {
ChainRequestTrigger::Always => send_request().await?,
};

Ok(exchange.response)
// Safe because we just constructed the exchange and haven't shared it
let response =
Arc::into_inner(exchange.response).expect("Arc never shared");
Ok(response)
}

/// Extract the specified component bytes from the response. For headers,
Expand Down
2 changes: 1 addition & 1 deletion crates/tui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ persisted = {version = "0.3.1", features = ["serde"]}
ratatui = {workspace = true, features = ["crossterm", "underline-color", "unstable-widget-ref"]}
reqwest = {workspace = true}
serde = {workspace = true}
serde_json_path = {workspace = true}
serde_yaml = {workspace = true}
shellish_parse = "2.2.0"
slumber_config = {workspace = true}
slumber_core = {workspace = true}
strum = {workspace = true}
Expand Down
Loading
Loading