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

Add template preview #32

Merged
merged 7 commits into from
Nov 10, 2023
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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## [Unreleased] - ReleaseDate

### Added

- Add ability to preview template values. This will show the rendered value under current settings
- This includes a new modal to toggle the setting on/off, via the `X` key

### Changed

- HTTP method is now a plain string, not a template string. This simplifies some internal logic, and I don't think there was a compelling reason to make a template in the first place.

## [0.5.0] - 2023-11-07

### Added
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ indexmap = {version = "^2.0.1", features = ["serde"]}
itertools = "^0.11.0"
notify = {version = "^6.1.1", default-features = false, features = ["macos_fsevent"]}
ratatui = "^0.24.0"
regex = "^1.9.5"
regex = {version = "^1.9.5", features = ["pattern"]}
reqwest = {version = "^0.11.20", default-features = false, features = ["rustls-tls"]}
rmp-serde = "^1.1.2"
rusqlite = {version = "^0.29.0", default-features = false, features = ["bundled", "chrono", "uuid"]}
Expand Down
2 changes: 1 addition & 1 deletion run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# latest watchexec we can get rid of this.
# https://github.com/watchexec/cargo-watch/issues/269

RUST_LOG=slumber=trace USERNAME=user watchexec --restart \
RUST_LOG=slumber=trace watchexec --restart \
--watch Cargo.toml --watch Cargo.lock --watch src/ \
-- cargo run \
-- $@
2 changes: 1 addition & 1 deletion src/config/insomnia.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ impl From<Request> for RequestRecipe {
RequestRecipe {
id: request.id.into(),
name: Some(request.name),
method: request.method.into(),
method: request.method,
url: request.url,
body: match request.body {
Body::Empty {} => None,
Expand Down
4 changes: 3 additions & 1 deletion src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ pub struct ProfileId(String);
pub struct RequestRecipe {
pub id: RequestRecipeId,
pub name: Option<String>,
pub method: TemplateString,
/// *Not* a template string because the usefulness doesn't justify the
/// complexity
pub method: String,
pub url: TemplateString,
pub body: Option<TemplateString>,
#[serde(default)]
Expand Down
18 changes: 4 additions & 14 deletions src/http/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,15 +259,10 @@ impl RequestBuilder {
// Don't let any sub-futures try to move the context
let template_context = &self.template_context;

let method = recipe.method.parse()?;

// Build all the futures separately, then resolve them in parallel

let method_future = async {
Ok(recipe
.method
.render(template_context, "method")
.await?
.parse()?)
};
let url_future = recipe.url.render(template_context, "URL");

// Build header map
Expand Down Expand Up @@ -328,13 +323,8 @@ impl RequestBuilder {
};

// Zoooooooooooom!
let (method, url, headers, query, body) = try_join!(
method_future,
url_future,
headers_future,
query_future,
body_future,
)?;
let (url, headers, query, body) =
try_join!(url_future, headers_future, query_future, body_future,)?;

info!(
recipe_id = %recipe.id,
Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![deny(clippy::all)]
#![feature(associated_type_defaults)]
#![feature(iterator_try_collect)]
#![feature(lazy_cell)]
#![feature(trait_upcasting)]
#![feature(try_blocks)]

Expand Down
90 changes: 90 additions & 0 deletions src/template/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use serde_json_path::ExactlyOneError;
use std::{env::VarError, io, path::PathBuf};
use thiserror::Error;

pub type TemplateResult = Result<String, TemplateError>;

/// Any error that can occur during template rendering. The purpose of having a
/// structured error here (while the rest of the app just uses `anyhow`) is to
/// support localized error display in the UI, e.g. showing just one portion of
/// a string in red if that particular template key failed to render.
///
/// The error always holds owned data so it can be detached from the lifetime
/// of the template context. This requires a mild amount of cloning in error
/// cases, but those should be infrequent so it's fine.
#[derive(Debug, Error)]
#[cfg_attr(test, derive(PartialEq))]
pub enum TemplateError {
/// Template key could not be parsed
#[error("Failed to parse template key {key:?}")]
InvalidKey { key: String },

/// A basic field key contained an unknown field
#[error("Unknown field {field:?}")]
FieldUnknown { field: String },

#[error("Error resolving chain {chain_id:?}")]
Chain {
chain_id: String,
#[source]
error: ChainError,
},

/// Variable either didn't exist or had non-unicode content
#[error("Error accessing environment variable {variable:?}")]
EnvironmentVariable {
variable: String,
#[source]
error: VarError,
},
}

/// An error sub-type, for any error that occurs while resolving a chained
/// value. This is factored out because they all need to be paired with a chain
/// ID.
#[derive(Debug, Error)]
pub enum ChainError {
/// Reference to a chain that doesn't exist
#[error("Unknown chain")]
Unknown,
/// An error occurred accessing the request repository. This error is
/// generated by our code so we don't need any extra context.
#[error(transparent)]
Repository(anyhow::Error),
/// The chain ID is valid, but the corresponding recipe has no successful
/// response
#[error("No response available")]
NoResponse,
/// Failed to parse the response body before applying a selector
#[error("Error parsing response")]
ParseResponse {
#[source]
error: anyhow::Error,
},
/// Got either 0 or 2+ results for JSON path query
#[error("Expected exactly one result from selector")]
InvalidResult {
#[source]
error: ExactlyOneError,
},
#[error("Error reading from file {path:?}")]
File {
path: PathBuf,
#[source]
error: io::Error,
},
/// Never got a response from the prompt channel. Do *not* store the
/// `RecvError` here, because it provides useless extra output to the user.
#[error("No response from prompt")]
PromptNoResponse,
}

/// Placeholder implementation to allow equality checks for *other*
/// `TemplateError` variants. This one is hard to do because `anyhow::Error`
/// doesn't impl `PartialEq`
#[cfg(test)]
impl PartialEq for ChainError {
fn eq(&self, _: &Self) -> bool {
unimplemented!("PartialEq for ChainError is hard to implement")
}
}
Loading
Loading