Make rendering return Result, and handle infinite recursion

This commit is contained in:
Gregory Schier
2025-03-05 13:49:45 -08:00
parent 7a1a0689b0
commit fbf4d3c11e
25 changed files with 348 additions and 335 deletions

43
src-tauri/src/error.rs Normal file
View File

@@ -0,0 +1,43 @@
use serde::{Serialize, Serializer};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("Render error: {0}")]
TemplateError(#[from] yaak_templates::error::Error),
#[error("Model error: {0}")]
ModelError(#[from] yaak_models::error::Error),
#[error("Sync error: {0}")]
SyncError(#[from] yaak_sync::error::Error),
#[error("Git error: {0}")]
GitError(#[from] yaak_git::error::Error),
#[error("Websocket error: {0}")]
WebsocketError(#[from] yaak_ws::error::Error),
#[error("License error: {0}")]
LicenseError(#[from] yaak_license::error::Error),
#[error("Plugin error: {0}")]
PluginError(#[from] yaak_plugins::error::Error),
#[error("Request error: {0}")]
RequestError(#[from] reqwest::Error),
#[error("Generic error: {0}")]
GenericError(String),
}
impl Serialize for Error {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.to_string().as_ref())
}
}
pub type Result<T> = std::result::Result<T, Error>;

View File

@@ -1,3 +1,5 @@
use crate::error::Error::GenericError;
use crate::error::Result;
use crate::render::render_http_request;
use crate::response_err;
use http::header::{ACCEPT, USER_AGENT};
@@ -43,14 +45,10 @@ pub async fn send_http_request<R: Runtime>(
environment: Option<Environment>,
cookie_jar: Option<CookieJar>,
cancelled_rx: &mut Receiver<bool>,
) -> Result<HttpResponse, String> {
) -> Result<HttpResponse> {
let plugin_manager = window.state::<PluginManager>();
let workspace = get_workspace(window, &unrendered_request.workspace_id)
.await
.expect("Failed to get Workspace");
let base_environment = get_base_environment(window, &unrendered_request.workspace_id)
.await
.expect("Failed to get base environment");
let workspace = get_workspace(window, &unrendered_request.workspace_id).await?;
let base_environment = get_base_environment(window, &unrendered_request.workspace_id).await?;
let settings = get_or_create_settings(window).await;
let cb = PluginTemplateCallback::new(
window.app_handle(),
@@ -61,9 +59,17 @@ pub async fn send_http_request<R: Runtime>(
let response_id = og_response.id.clone();
let response = Arc::new(Mutex::new(og_response.clone()));
let request =
render_http_request(&unrendered_request, &base_environment, environment.as_ref(), &cb)
.await;
let request = match render_http_request(
&unrendered_request,
&base_environment,
environment.as_ref(),
&cb,
)
.await
{
Ok(r) => r,
Err(e) => return Ok(response_err(&*response.lock().await, e.to_string(), window).await),
};
let mut url_string = request.url;
@@ -139,10 +145,9 @@ pub async fn send_http_request<R: Runtime>(
serde_json::from_value(json_cookie).expect("Failed to deserialize cookie")
})
.map(|c| Ok(c))
.collect::<Vec<Result<_, ()>>>();
.collect::<Vec<Result<_>>>();
let store = reqwest_cookie_store::CookieStore::from_cookies(cookies, true)
.expect("Failed to create cookie store");
let store = reqwest_cookie_store::CookieStore::from_cookies(cookies, true)?;
let cookie_store = reqwest_cookie_store::CookieStoreMutex::new(store);
let cookie_store = Arc::new(cookie_store);
client_builder = client_builder.cookie_provider(Arc::clone(&cookie_store));
@@ -158,7 +163,7 @@ pub async fn send_http_request<R: Runtime>(
));
}
let client = client_builder.build().expect("Failed to build client");
let client = client_builder.build()?;
// Render query parameters
let mut query_params = Vec::new();
@@ -193,8 +198,8 @@ pub async fn send_http_request<R: Runtime>(
}
};
let m = Method::from_bytes(request.method.to_uppercase().as_bytes())
.expect("Failed to create method");
let m = Method::from_str(&request.method.to_uppercase())
.map_err(|e| GenericError(e.to_string()))?;
let mut request_builder = client.request(m, url).query(&query_params);
let mut headers = HeaderMap::new();
@@ -282,7 +287,7 @@ pub async fn send_http_request<R: Runtime>(
} else if body_type == "binary" && request_body.contains_key("filePath") {
let file_path = request_body
.get("filePath")
.ok_or("filePath not set")?
.ok_or(GenericError("filePath not set".to_string()))?
.as_str()
.unwrap_or_default();
@@ -431,7 +436,7 @@ pub async fn send_http_request<R: Runtime>(
}
}
let (resp_tx, resp_rx) = oneshot::channel::<Result<Response, reqwest::Error>>();
let (resp_tx, resp_rx) = oneshot::channel::<std::result::Result<Response, reqwest::Error>>();
let (done_tx, done_rx) = oneshot::channel::<HttpResponse>();
let start = std::time::Instant::now();

View File

@@ -2,11 +2,13 @@ extern crate core;
#[cfg(target_os = "macos")]
extern crate objc;
use crate::encoding::read_response_body;
use crate::error::Error::GenericError;
use crate::grpc::metadata_to_map;
use crate::http_request::send_http_request;
use crate::notifications::YaakNotifier;
use crate::render::{render_grpc_request, render_template};
use crate::updates::{UpdateMode, UpdateTrigger, YaakUpdater};
use error::Result as YaakResult;
use eventsource_client::{EventParser, SSE};
use log::{debug, error, warn};
use rand::random;
@@ -65,6 +67,7 @@ use yaak_templates::format::format_json;
use yaak_templates::{Parser, Tokens};
mod encoding;
mod error;
mod grpc;
mod history;
mod http_request;
@@ -126,14 +129,13 @@ async fn cmd_render_template<R: Runtime>(
template: &str,
workspace_id: &str,
environment_id: Option<&str>,
) -> Result<String, String> {
) -> YaakResult<String> {
let environment = match environment_id {
Some(id) => Some(get_environment(&window, id).await.map_err(|e| e.to_string())?),
Some(id) => get_environment(&window, id).await.ok(),
None => None,
};
let base_environment =
get_base_environment(&window, &workspace_id).await.map_err(|e| e.to_string())?;
let rendered = render_template(
let base_environment = get_base_environment(&window, &workspace_id).await?;
let result = render_template(
template,
&base_environment,
environment.as_ref(),
@@ -143,8 +145,8 @@ async fn cmd_render_template<R: Runtime>(
RenderPurpose::Preview,
),
)
.await;
Ok(rendered)
.await?;
Ok(result)
}
#[tauri::command]
@@ -189,18 +191,15 @@ async fn cmd_grpc_go<R: Runtime>(
window: WebviewWindow<R>,
plugin_manager: State<'_, PluginManager>,
grpc_handle: State<'_, Mutex<GrpcHandle>>,
) -> Result<String, String> {
) -> YaakResult<String> {
let environment = match environment_id {
Some(id) => Some(get_environment(&window, id).await.map_err(|e| e.to_string())?),
Some(id) => get_environment(&window, id).await.ok(),
None => None,
};
let unrendered_request = get_grpc_request(&window, request_id)
.await
.map_err(|e| e.to_string())?
.ok_or("Failed to find GRPC request")?;
let base_environment = get_base_environment(&window, &unrendered_request.workspace_id)
.await
.map_err(|e| e.to_string())?;
.await?
.ok_or(GenericError("Failed to get GRPC request".to_string()))?;
let base_environment = get_base_environment(&window, &unrendered_request.workspace_id).await?;
let request = render_grpc_request(
&unrendered_request,
&base_environment,
@@ -211,7 +210,7 @@ async fn cmd_grpc_go<R: Runtime>(
RenderPurpose::Send,
),
)
.await;
.await?;
let mut metadata = BTreeMap::new();
// Add the rest of metadata
@@ -242,33 +241,27 @@ async fn cmd_grpc_go<R: Runtime>(
})
.collect(),
};
let plugin_result = plugin_manager
.call_http_authentication(&window, &auth_name, plugin_req)
.await
.map_err(|e| e.to_string())?;
let plugin_result =
plugin_manager.call_http_authentication(&window, &auth_name, plugin_req).await?;
for header in plugin_result.set_headers {
metadata.insert(header.name, header.value);
}
}
let conn = {
let req = request.clone();
upsert_grpc_connection(
&window,
&GrpcConnection {
workspace_id: req.workspace_id,
request_id: req.id,
status: -1,
elapsed: 0,
state: GrpcConnectionState::Initialized,
url: req.url.clone(),
..Default::default()
},
&UpdateSource::Window,
)
.await
.map_err(|e| e.to_string())?
};
let conn = upsert_grpc_connection(
&window,
&GrpcConnection {
workspace_id: request.workspace_id.clone(),
request_id: request.id.clone(),
status: -1,
elapsed: 0,
state: GrpcConnectionState::Initialized,
url: request.url.clone(),
..Default::default()
},
&UpdateSource::Window,
)
.await?;
let conn_id = conn.id.clone();
@@ -291,7 +284,7 @@ async fn cmd_grpc_go<R: Runtime>(
let req = request.clone();
match (req.service, req.method) {
(Some(service), Some(method)) => (service, method),
_ => return Err("Service and method are required".to_string()),
_ => return Err(GenericError("Service and method are required".to_string())),
}
};
@@ -319,13 +312,13 @@ async fn cmd_grpc_go<R: Runtime>(
},
&UpdateSource::Window,
)
.await
.map_err(|e| e.to_string())?;
.await?;
return Ok(conn_id);
}
};
let method_desc = connection.method(&service, &method).map_err(|e| e.to_string())?;
let method_desc =
connection.method(&service, &method).map_err(|e| GenericError(e.to_string()))?;
#[derive(serde::Deserialize)]
enum IncomingMsg {
@@ -362,23 +355,21 @@ async fn cmd_grpc_go<R: Runtime>(
let window = window.clone();
let base_msg = base_msg.clone();
let method_desc = method_desc.clone();
let msg = {
block_in_place(|| {
tauri::async_runtime::block_on(async {
render_template(
msg.as_str(),
&workspace,
environment.as_ref(),
&PluginTemplateCallback::new(
window.app_handle(),
&WindowContext::from_window(&window),
RenderPurpose::Send,
),
)
.await
})
let msg = block_in_place(|| {
tauri::async_runtime::block_on(async {
render_template(
msg.as_str(),
&workspace,
environment.as_ref(),
&PluginTemplateCallback::new(
window.app_handle(),
&WindowContext::from_window(&window),
RenderPurpose::Send,
),
)
.await.expect("Failed to render template")
})
};
});
let d_msg: DynamicMessage = match deserialize_message(msg.as_str(), method_desc)
{
Ok(d_msg) => d_msg,
@@ -443,7 +434,7 @@ async fn cmd_grpc_go<R: Runtime>(
RenderPurpose::Send,
),
)
.await;
.await?;
upsert_grpc_event(
&window,
@@ -455,8 +446,7 @@ async fn cmd_grpc_go<R: Runtime>(
},
&UpdateSource::Window,
)
.await
.unwrap();
.await?;
async move {
let (maybe_stream, maybe_msg) =
@@ -738,7 +728,7 @@ async fn cmd_send_ephemeral_request(
environment_id: Option<&str>,
cookie_jar_id: Option<&str>,
window: WebviewWindow,
) -> Result<HttpResponse, String> {
) -> YaakResult<HttpResponse> {
let response = HttpResponse::new();
request.id = "".to_string();
let environment = match environment_id {
@@ -1087,10 +1077,9 @@ async fn cmd_send_http_request(
// condition where the user may have just edited a field before sending
// that has not yet been saved in the DB.
request: HttpRequest,
) -> Result<HttpResponse, String> {
let response = create_default_http_response(&window, &request.id, &UpdateSource::Window)
.await
.map_err(|e| e.to_string())?;
) -> YaakResult<HttpResponse> {
let response =
create_default_http_response(&window, &request.id, &UpdateSource::Window).await?;
let (cancel_tx, mut cancel_rx) = tokio::sync::watch::channel(false);
window.listen_any(format!("cancel_http_response_{}", response.id), move |_event| {

View File

@@ -90,7 +90,8 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
environment.as_ref(),
&cb,
)
.await;
.await
.expect("Failed to render http request");
Some(InternalEventPayload::RenderHttpRequestResponse(RenderHttpRequestResponse {
http_request,
}))
@@ -107,8 +108,9 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
.await
.expect("Failed to get base environment");
let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose);
let data =
render_json_value(req.data, &base_environment, environment.as_ref(), &cb).await;
let data = render_json_value(req.data, &base_environment, environment.as_ref(), &cb)
.await
.expect("Failed to render template");
Some(InternalEventPayload::TemplateRenderResponse(TemplateRenderResponse { data }))
}
InternalEventPayload::ErrorResponse(resp) => {

View File

@@ -12,7 +12,7 @@ pub async fn render_template<T: TemplateCallback>(
base_environment: &Environment,
environment: Option<&Environment>,
cb: &T,
) -> String {
) -> yaak_templates::error::Result<String> {
let vars = &make_vars_hashmap(base_environment, environment);
render(template, vars, cb).await
}
@@ -22,7 +22,7 @@ pub async fn render_json_value<T: TemplateCallback>(
base_environment: &Environment,
environment: Option<&Environment>,
cb: &T,
) -> Value {
) -> yaak_templates::error::Result<Value> {
let vars = &make_vars_hashmap(base_environment, environment);
render_json_value_raw(value, vars, cb).await
}
@@ -32,32 +32,32 @@ pub async fn render_grpc_request<T: TemplateCallback>(
base_environment: &Environment,
environment: Option<&Environment>,
cb: &T,
) -> GrpcRequest {
) -> yaak_templates::error::Result<GrpcRequest> {
let vars = &make_vars_hashmap(base_environment, environment);
let mut metadata = Vec::new();
for p in r.metadata.clone() {
metadata.push(GrpcMetadataEntry {
enabled: p.enabled,
name: render(p.name.as_str(), vars, cb).await,
value: render(p.value.as_str(), vars, cb).await,
name: render(p.name.as_str(), vars, cb).await?,
value: render(p.value.as_str(), vars, cb).await?,
id: p.id,
})
}
let mut authentication = BTreeMap::new();
for (k, v) in r.authentication.clone() {
authentication.insert(k, render_json_value_raw(v, vars, cb).await);
authentication.insert(k, render_json_value_raw(v, vars, cb).await?);
}
let url = render(r.url.as_str(), vars, cb).await;
let url = render(r.url.as_str(), vars, cb).await?;
GrpcRequest {
Ok(GrpcRequest {
url,
metadata,
authentication,
..r.to_owned()
}
})
}
pub async fn render_http_request<T: TemplateCallback>(
@@ -65,15 +65,15 @@ pub async fn render_http_request<T: TemplateCallback>(
base_environment: &Environment,
environment: Option<&Environment>,
cb: &T,
) -> HttpRequest {
) -> yaak_templates::error::Result<HttpRequest> {
let vars = &make_vars_hashmap(base_environment, environment);
let mut url_parameters = Vec::new();
for p in r.url_parameters.clone() {
url_parameters.push(HttpUrlParameter {
enabled: p.enabled,
name: render(p.name.as_str(), vars, cb).await,
value: render(p.value.as_str(), vars, cb).await,
name: render(p.name.as_str(), vars, cb).await?,
value: render(p.value.as_str(), vars, cb).await?,
id: p.id,
})
}
@@ -82,41 +82,41 @@ pub async fn render_http_request<T: TemplateCallback>(
for p in r.headers.clone() {
headers.push(HttpRequestHeader {
enabled: p.enabled,
name: render(p.name.as_str(), vars, cb).await,
value: render(p.value.as_str(), vars, cb).await,
name: render(p.name.as_str(), vars, cb).await?,
value: render(p.value.as_str(), vars, cb).await?,
id: p.id,
})
}
let mut body = BTreeMap::new();
for (k, v) in r.body.clone() {
body.insert(k, render_json_value_raw(v, vars, cb).await);
body.insert(k, render_json_value_raw(v, vars, cb).await?);
}
let mut authentication = BTreeMap::new();
for (k, v) in r.authentication.clone() {
authentication.insert(k, render_json_value_raw(v, vars, cb).await);
authentication.insert(k, render_json_value_raw(v, vars, cb).await?);
}
let url = render(r.url.clone().as_str(), vars, cb).await;
let url = render(r.url.clone().as_str(), vars, cb).await?;
// This doesn't fit perfectly with the concept of "rendering" but it kind of does
let (url, url_parameters) = apply_path_placeholders(&url, url_parameters);
HttpRequest {
Ok(HttpRequest {
url,
url_parameters,
headers,
body,
authentication,
..r.to_owned()
}
})
}
pub async fn render<T: TemplateCallback>(
template: &str,
vars: &HashMap<String, String>,
cb: &T,
) -> String {
) -> yaak_templates::error::Result<String> {
parse_and_render(template, vars, cb).await
}