Skip to content

Commit

Permalink
Remove need for optional persistence
Browse files Browse the repository at this point in the history
The request view doesn't need ExchangeBody because it will never be queryable. This removes the only need for optional persistence. Overall this is making code less generic in the name of simplicity.
  • Loading branch information
LucasPickering committed Jun 9, 2024
1 parent d010d13 commit 4aa419c
Show file tree
Hide file tree
Showing 10 changed files with 66 additions and 89 deletions.
5 changes: 3 additions & 2 deletions src/cli/history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::{
GlobalArgs,
};
use anyhow::anyhow;
use bytesize::ByteSize;
use clap::Parser;
use dialoguer::console::Style;
use std::process::ExitCode;
Expand Down Expand Up @@ -94,8 +95,8 @@ impl HistoryCommand {
print!(
"{} ({})\n{}",
subheader_style.apply_to("Body"),
body.size(),
MaybeStr(body.bytes())
ByteSize(body.len() as u64),
MaybeStr(body)
)
}
println!();
Expand Down
2 changes: 1 addition & 1 deletion src/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -945,7 +945,7 @@ mod tests {
template_context: TemplateContext,
#[case] body: RecipeBody,
#[case] content_type: Option<&str>,
#[case] expected_body: &[u8],
#[case] expected_body: &'static [u8],
#[case] expected_content_type: &str,
) {
let headers = if let Some(content_type) = content_type {
Expand Down
9 changes: 0 additions & 9 deletions src/http/content_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,6 @@ impl ContentType {
const EXTENSIONS: Mapping<'static, ContentType> =
Mapping::new(&[(Self::Json, &["json"])]);

/// Get MIME corresponding to this content type. Each content type maps to a
/// single MIME (although the reverse is not true)
pub fn mime(&self) -> Mime {
// Don't use a Mapping because this is a one-way relationship
match self {
ContentType::Json => mime::APPLICATION_JSON,
}
}

/// Parse the value of the content-type header and map it to a known content
/// type
fn from_mime(mime_type: &str) -> anyhow::Result<Self> {
Expand Down
15 changes: 6 additions & 9 deletions src/http/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ pub struct RequestRecord {
#[serde(with = "cereal::serde_header_map")]
pub headers: HeaderMap,
/// Body content as bytes. This should be decoded as needed
pub body: Option<ResponseBody>,
pub body: Option<Bytes>,
}

impl RequestRecord {
Expand All @@ -223,12 +223,10 @@ impl RequestRecord {
url: request.url().clone(),
headers: request.headers().clone(),
body: request.body().map(|body| {
ResponseBody::new(
body.as_bytes()
.expect("Streaming bodies not supported")
.to_owned()
.into(),
)
body.as_bytes()
.expect("Streaming bodies not supported")
.to_owned()
.into()
}),
}
}
Expand Down Expand Up @@ -264,8 +262,7 @@ impl RequestRecord {
pub fn body_str(&self) -> anyhow::Result<Option<&str>> {
if let Some(body) = &self.body {
Ok(Some(
std::str::from_utf8(&body.data)
.context("Error decoding body")?,
std::str::from_utf8(body).context("Error decoding body")?,
))
} else {
Ok(None)
Expand Down
7 changes: 0 additions & 7 deletions src/tui/view/common/template_preview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,6 @@ impl TemplatePreview {
Self::Disabled { template }
}
}

pub fn template(&self) -> &Template {
match self {
Self::Disabled { template } => template,
Self::Enabled { template, .. } => template,
}
}
}

impl Generate for &TemplatePreview {
Expand Down
2 changes: 1 addition & 1 deletion src/tui/view/component.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
mod exchange_body;
mod exchange_pane;
mod help;
mod history;
mod internal;
mod misc;
mod primary;
mod profile_select;
mod queryable_body;
mod recipe_list;
mod recipe_pane;
mod request_view;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ use serde_json_path::JsonPath;
use std::cell::Cell;
use Debug;

/// Display text body of a request OR response
/// Display response body as text, with a query box to filter it if the body has
/// been parsed
#[derive(Debug)]
pub struct ExchangeBody {
pub struct QueryableBody {
/// Body text content. State cell allows us to reset this whenever the
/// request changes
text_window: StateCell<Option<Query>, Component<TextWindow<String>>>,
Expand Down Expand Up @@ -59,11 +60,11 @@ enum QueryCallback {
Submit(String),
}

impl ExchangeBody {
impl QueryableBody {
/// Create a new body, optionally loading the query text from the
/// persistence DB. This is optional because not all callers use the query
/// box, or want to persist the value.
pub fn new(query_persistent_key: Option<PersistentKey>) -> Self {
pub fn new(query_persistent_key: PersistentKey) -> Self {
let text_box = TextBox::default()
.with_placeholder("'/' to filter body with JSONPath")
.with_validator(|text| JsonPath::parse(text).is_ok())
Expand All @@ -84,11 +85,8 @@ impl ExchangeBody {
query_available: Cell::new(false),
query_focused: false,
query: Default::default(),
query_text_box: Persistent::optional(
query_persistent_key,
text_box,
)
.into(),
query_text_box: Persistent::new(query_persistent_key, text_box)
.into(),
}
}

Expand All @@ -100,7 +98,7 @@ impl ExchangeBody {
}
}

impl EventHandler for ExchangeBody {
impl EventHandler for QueryableBody {
fn update(&mut self, event: Event) -> Update {
if let Some(Action::Search) = event.action() {
if self.query_available.get() {
Expand Down Expand Up @@ -148,7 +146,7 @@ impl EventHandler for ExchangeBody {
}
}

impl<'a> Draw<ExchangeBodyProps<'a>> for ExchangeBody {
impl<'a> Draw<ExchangeBodyProps<'a>> for QueryableBody {
fn draw(
&self,
frame: &mut Frame,
Expand Down Expand Up @@ -251,7 +249,9 @@ mod tests {
let body = ResponseBody::new(TEXT.into());
let component = TestComponent::new(
harness,
ExchangeBody::new(None),
QueryableBody::new(PersistentKey::ResponseBodyQuery(
RecipeId::factory(()),
)),
ExchangeBodyProps { body: &body },
);

Expand Down Expand Up @@ -279,7 +279,9 @@ mod tests {
) {
let mut component = TestComponent::new(
harness,
ExchangeBody::new(None),
QueryableBody::new(PersistentKey::ResponseBodyQuery(
RecipeId::factory(()),
)),
ExchangeBodyProps {
body: &json_response.body,
},
Expand Down Expand Up @@ -357,7 +359,7 @@ mod tests {
// correctly here
let component = TestComponent::new(
harness,
ExchangeBody::new(Some(persistent_key)),
QueryableBody::new(persistent_key),
ExchangeBodyProps {
body: &json_response.body,
},
Expand Down
38 changes: 21 additions & 17 deletions src/tui/view/component/request_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@ use crate::{
input::Action,
message::Message,
view::{
common::{actions::ActionsModal, header_table::HeaderTable},
component::exchange_body::{ExchangeBody, ExchangeBodyProps},
common::{
actions::ActionsModal,
header_table::HeaderTable,
text_window::{TextWindow, TextWindowProps},
},
draw::{Draw, DrawMetadata, Generate, ToStringGenerate},
event::{Event, EventHandler, Update},
state::StateCell,
Component, ViewContext,
},
},
util::MaybeStr,
};
use derive_more::Display;
use ratatui::{layout::Layout, prelude::Constraint, Frame};
Expand All @@ -34,9 +38,9 @@ pub struct RequestViewProps {
struct State {
/// Store pointer to the request, so we can access it in the update step
request: Arc<RequestRecord>,
/// Persist the request body to track view state. Update whenever the
/// loaded request changes
body: Component<ExchangeBody>,
/// Persist the request body to track view state. `None` only if request
/// doesn't have a body
body: Option<Component<TextWindow<String>>>,
}

/// Items in the actions popup menu
Expand Down Expand Up @@ -67,11 +71,9 @@ impl EventHandler for RequestView {
// Copy exactly what the user sees. Currently requests
// don't support formatting/querying but that could
// change
if let Some(body) = self
.state
.get()
.and_then(|state| state.body.data().text())
{
if let Some(body) = self.state.get().and_then(|state| {
Some(state.body.as_ref()?.data().text().clone())
}) {
ViewContext::send_message(Message::CopyText(body));
}
}
Expand All @@ -83,8 +85,10 @@ impl EventHandler for RequestView {
}

fn children(&mut self) -> Vec<Component<&mut dyn EventHandler>> {
if let Some(state) = self.state.get_mut() {
vec![state.body.as_child()]
if let Some(body) =
self.state.get_mut().and_then(|state| state.body.as_mut())
{
vec![body.as_child()]
} else {
vec![]
}
Expand All @@ -100,7 +104,9 @@ impl Draw<RequestViewProps> for RequestView {
) {
let state = self.state.get_or_update(props.request.id, || State {
request: Arc::clone(&props.request),
body: ExchangeBody::new(None).into(),
body: props.request.body.as_ref().map(|body| {
TextWindow::new(format!("{:#}", MaybeStr(body))).into()
}),
});

let [url_area, headers_area, body_area] = Layout::vertical([
Expand All @@ -120,10 +126,8 @@ impl Draw<RequestViewProps> for RequestView {
.generate(),
headers_area,
);
if let Some(body) = &state.request.body {
state
.body
.draw(frame, ExchangeBodyProps { body }, body_area, true);
if let Some(body) = &state.body {
body.draw(frame, TextWindowProps::default(), body_area, true);
}
}
}
8 changes: 4 additions & 4 deletions src/tui/view/component/response_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
message::Message,
view::{
common::{actions::ActionsModal, header_table::HeaderTable},
component::exchange_body::{ExchangeBody, ExchangeBodyProps},
component::queryable_body::{ExchangeBodyProps, QueryableBody},
draw::{Draw, DrawMetadata, Generate, ToStringGenerate},
event::{Event, EventHandler, Update},
state::{persistence::PersistentKey, StateCell},
Expand Down Expand Up @@ -55,7 +55,7 @@ struct State {
/// The presentable version of the response body, which may or may not
/// match the response body. We apply transformations such as filter,
/// prettification, or in the case of binary responses, a hex dump.
body: Component<ExchangeBody>,
body: Component<QueryableBody>,
}

impl EventHandler for ResponseBodyView {
Expand Down Expand Up @@ -128,9 +128,9 @@ impl<'a> Draw<ResponseBodyViewProps<'a>> for ResponseBodyView {
let response = &props.response;
let state = self.state.get_or_update(props.request_id, || State {
response: Arc::clone(&props.response),
body: ExchangeBody::new(Some(PersistentKey::ResponseBodyQuery(
body: QueryableBody::new(PersistentKey::ResponseBodyQuery(
props.recipe_id.clone(),
)))
))
.into(),
});

Expand Down
39 changes: 14 additions & 25 deletions src/tui/view/state/persistence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use std::fmt::Debug;
/// drop.
#[derive(Debug, Deref, DerefMut)]
pub struct Persistent<T: PersistentContainer> {
key: Option<PersistentKey>,
key: PersistentKey,
#[deref]
#[deref_mut]
container: T,
Expand All @@ -29,22 +29,13 @@ pub struct Persistent<T: PersistentContainer> {
impl<T: PersistentContainer> Persistent<T> {
/// Load the latest persisted value from the DB. If present, set the value
/// of the container.
pub fn new(key: PersistentKey, container: T) -> Self {
Self::optional(Some(key), container)
}

/// Create a new persistent cell, with an optional key. If the key is not
/// defined, this does not do any persistence loading/saving. This is
/// helpful for usages that should only be persistent sometimes.
pub fn optional(key: Option<PersistentKey>, mut container: T) -> Self {
if let Some(key) = &key {
// Load saved value from the database, and select it if available
let loaded = ViewContext::with_database(|database| {
database.get_ui::<_, <T::Value as Persistable>::Persisted>(key)
});
if let Ok(Some(value)) = loaded {
container.set(value);
}
pub fn new(key: PersistentKey, mut container: T) -> Self {
// Load saved value from the database, and select it if available
let loaded = ViewContext::with_database(|database| {
database.get_ui::<_, <T::Value as Persistable>::Persisted>(&key)
});
if let Ok(Some(value)) = loaded {
container.set(value);
}

Self { key, container }
Expand All @@ -67,14 +58,12 @@ where

impl<T: PersistentContainer> Drop for Persistent<T> {
fn drop(&mut self) {
if let Some(key) = &self.key {
let _ = ViewContext::with_database(|database| {
database.set_ui(
key,
self.container.get().map(Persistable::get_persistent),
)
});
}
let _ = ViewContext::with_database(|database| {
database.set_ui(
&self.key,
self.container.get().map(Persistable::get_persistent),
)
});
}
}

Expand Down

0 comments on commit 4aa419c

Please sign in to comment.