Skip to content

Commit

Permalink
Replace JSONPath queries with commands
Browse files Browse the repository at this point in the history
  • Loading branch information
LucasPickering committed Dec 6, 2024
1 parent 8cd971c commit 28927e0
Show file tree
Hide file tree
Showing 20 changed files with 321 additions and 574 deletions.
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

0 comments on commit 28927e0

Please sign in to comment.