From fbf4d3c11e6f56571dd9a129d0bfe446cf51b32e Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Wed, 5 Mar 2025 13:49:45 -0800 Subject: [PATCH] Make rendering return Result, and handle infinite recursion --- src-tauri/Cargo.lock | 93 ++------- src-tauri/Cargo.toml | 3 +- src-tauri/src/error.rs | 43 ++++ src-tauri/src/http_request.rs | 41 ++-- src-tauri/src/lib.rs | 121 ++++++------ src-tauri/src/plugin_events.rs | 8 +- src-tauri/src/render.rs | 40 ++-- src-tauri/yaak-git/src/lib.rs | 2 +- src-tauri/yaak-license/Cargo.toml | 12 +- src-tauri/yaak-license/bindings/gen_models.ts | 2 +- src-tauri/yaak-license/bindings/license.ts | 2 +- src-tauri/yaak-license/src/commands.rs | 19 +- .../yaak-license/src/{errors.rs => error.rs} | 0 src-tauri/yaak-license/src/lib.rs | 2 +- src-tauri/yaak-license/src/license.rs | 4 +- src-tauri/yaak-plugins/src/manager.rs | 11 +- .../yaak-plugins/src/template_callback.rs | 42 +--- src-tauri/yaak-templates/Cargo.toml | 5 +- src-tauri/yaak-templates/src/error.rs | 22 +++ src-tauri/yaak-templates/src/lib.rs | 1 + src-tauri/yaak-templates/src/renderer.rs | 184 +++++++++++------- src-tauri/yaak-ws/src/commands.rs | 4 +- src-tauri/yaak-ws/src/error.rs | 3 + src-tauri/yaak-ws/src/lib.rs | 2 +- src-tauri/yaak-ws/src/render.rs | 17 +- 25 files changed, 348 insertions(+), 335 deletions(-) create mode 100644 src-tauri/src/error.rs rename src-tauri/yaak-license/src/{errors.rs => error.rs} (100%) create mode 100644 src-tauri/yaak-templates/src/error.rs diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 003ca0f5..841c8748 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -31,7 +31,7 @@ checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ "getrandom 0.2.15", "once_cell", - "version_check 0.9.5", + "version_check", ] [[package]] @@ -42,7 +42,7 @@ checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "once_cell", - "version_check 0.9.5", + "version_check", "zerocopy 0.7.35", ] @@ -932,7 +932,7 @@ checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ "percent-encoding", "time", - "version_check 0.9.5", + "version_check", ] [[package]] @@ -1171,20 +1171,6 @@ version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e60eed09d8c01d3cee5b7d30acb059b76614c918fa0f992e0dd6eeb10daad6f" -[[package]] -name = "datetime" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c3f7a77f3e57fedf80e09136f2d8777ebf621207306f6d96d610af048354bc" -dependencies = [ - "iso8601", - "libc", - "locale", - "pad", - "redox_syscall 0.1.57", - "winapi", -] - [[package]] name = "der" version = "0.7.9" @@ -1914,7 +1900,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", - "version_check 0.9.5", + "version_check", ] [[package]] @@ -2584,15 +2570,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "iso8601" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e86914a73535f3f541a765adea0a9fafcf53fa6adb73662c4988fd9233766f" -dependencies = [ - "nom 4.2.3", -] - [[package]] name = "itertools" version = "0.13.0" @@ -2882,15 +2859,6 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" -[[package]] -name = "locale" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fdbe492a9c0238da900a1165c42fc5067161ce292678a6fe80921f30fe307fd" -dependencies = [ - "libc", -] - [[package]] name = "lock_api" version = "0.4.12" @@ -2903,9 +2871,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" dependencies = [ "value-bag", ] @@ -3155,16 +3123,6 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" -[[package]] -name = "nom" -version = "4.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" -dependencies = [ - "memchr", - "version_check 0.1.5", -] - [[package]] name = "nom" version = "7.1.3" @@ -3666,15 +3624,6 @@ dependencies = [ "thiserror 1.0.63", ] -[[package]] -name = "pad" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ad9b889f1b12e0b9ee24db044b5129150d5eada288edc800f789928dc8c0e3" -dependencies = [ - "unicode-width", -] - [[package]] name = "pango" version = "0.18.3" @@ -4066,7 +4015,7 @@ dependencies = [ "proc-macro2", "quote", "syn 1.0.109", - "version_check 0.9.5", + "version_check", ] [[package]] @@ -4077,7 +4026,7 @@ checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ "proc-macro2", "quote", - "version_check 0.9.5", + "version_check", ] [[package]] @@ -4410,12 +4359,6 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" -[[package]] -name = "redox_syscall" -version = "0.1.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" - [[package]] name = "redox_syscall" version = "0.4.1" @@ -5320,7 +5263,7 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f895e3734318cc55f1fe66258926c9b910c124d47520339efecbb6c59cec7c1f" dependencies = [ - "nom 7.1.3", + "nom", "unicode_categories", ] @@ -6733,7 +6676,7 @@ version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" dependencies = [ - "version_check 0.9.5", + "version_check", ] [[package]] @@ -6769,12 +6712,6 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" -[[package]] -name = "unicode-width" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" - [[package]] name = "unicode_categories" version = "0.1.1" @@ -6864,12 +6801,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" -[[package]] -name = "version_check" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" - [[package]] name = "version_check" version = "0.9.5" @@ -7710,7 +7641,6 @@ version = "0.0.0" dependencies = [ "chrono", "cocoa 0.26.0", - "datetime", "encoding_rs", "eventsource-client", "hex_color", @@ -7740,9 +7670,9 @@ dependencies = [ "tauri-plugin-single-instance", "tauri-plugin-updater", "tauri-plugin-window-state", + "thiserror 2.0.11", "tokio", "tokio-stream", - "ts-rs", "uuid", "yaak-git", "yaak-grpc", @@ -7905,6 +7835,7 @@ dependencies = [ "log", "serde", "serde_json", + "thiserror 2.0.11", "tokio", "ts-rs", ] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 5b9c4bc6..01e99026 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -42,7 +42,6 @@ openssl-sys = { version = "0.9.105", features = ["vendored"] } # For Ubuntu inst [dependencies] chrono = { version = "0.4.31", features = ["serde"] } -datetime = "0.5.2" encoding_rs = "0.8.35" eventsource-client = { git = "https://github.com/yaakapp/rust-eventsource-client", version = "0.14.0" } hex_color = "3.0.0" @@ -71,7 +70,7 @@ tauri-plugin-updater = "2.5.0" tauri-plugin-window-state = "2.2.1" tokio = { version = "1.43.0", features = ["sync"] } tokio-stream = "0.1.17" -ts-rs = { workspace = true } +thiserror = { workspace = true } uuid = "1.12.1" yaak-git = { path = "yaak-git" } yaak-grpc = { path = "yaak-grpc" } diff --git a/src-tauri/src/error.rs b/src-tauri/src/error.rs new file mode 100644 index 00000000..4c775897 --- /dev/null +++ b/src-tauri/src/error.rs @@ -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(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} + +pub type Result = std::result::Result; diff --git a/src-tauri/src/http_request.rs b/src-tauri/src/http_request.rs index 4ff1944d..d19c9a29 100644 --- a/src-tauri/src/http_request.rs +++ b/src-tauri/src/http_request.rs @@ -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( environment: Option, cookie_jar: Option, cancelled_rx: &mut Receiver, -) -> Result { +) -> Result { let plugin_manager = window.state::(); - 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( 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( serde_json::from_value(json_cookie).expect("Failed to deserialize cookie") }) .map(|c| Ok(c)) - .collect::>>(); + .collect::>>(); - 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( )); } - 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( } }; - 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( } 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( } } - let (resp_tx, resp_rx) = oneshot::channel::>(); + let (resp_tx, resp_rx) = oneshot::channel::>(); let (done_tx, done_rx) = oneshot::channel::(); let start = std::time::Instant::now(); diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 77265f3e..cebb6eed 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -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( template: &str, workspace_id: &str, environment_id: Option<&str>, -) -> Result { +) -> YaakResult { 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( RenderPurpose::Preview, ), ) - .await; - Ok(rendered) + .await?; + Ok(result) } #[tauri::command] @@ -189,18 +191,15 @@ async fn cmd_grpc_go( window: WebviewWindow, plugin_manager: State<'_, PluginManager>, grpc_handle: State<'_, Mutex>, -) -> Result { +) -> YaakResult { 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( RenderPurpose::Send, ), ) - .await; + .await?; let mut metadata = BTreeMap::new(); // Add the rest of metadata @@ -242,33 +241,27 @@ async fn cmd_grpc_go( }) .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( 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( }, &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( 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( RenderPurpose::Send, ), ) - .await; + .await?; upsert_grpc_event( &window, @@ -455,8 +446,7 @@ async fn cmd_grpc_go( }, &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 { +) -> YaakResult { 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 { - let response = create_default_http_response(&window, &request.id, &UpdateSource::Window) - .await - .map_err(|e| e.to_string())?; +) -> YaakResult { + 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| { diff --git a/src-tauri/src/plugin_events.rs b/src-tauri/src/plugin_events.rs index 4c991f6b..c4f203ab 100644 --- a/src-tauri/src/plugin_events.rs +++ b/src-tauri/src/plugin_events.rs @@ -90,7 +90,8 @@ pub(crate) async fn handle_plugin_event( 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( .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) => { diff --git a/src-tauri/src/render.rs b/src-tauri/src/render.rs index 716e6da1..08f3efb0 100644 --- a/src-tauri/src/render.rs +++ b/src-tauri/src/render.rs @@ -12,7 +12,7 @@ pub async fn render_template( base_environment: &Environment, environment: Option<&Environment>, cb: &T, -) -> String { +) -> yaak_templates::error::Result { let vars = &make_vars_hashmap(base_environment, environment); render(template, vars, cb).await } @@ -22,7 +22,7 @@ pub async fn render_json_value( base_environment: &Environment, environment: Option<&Environment>, cb: &T, -) -> Value { +) -> yaak_templates::error::Result { 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( base_environment: &Environment, environment: Option<&Environment>, cb: &T, -) -> GrpcRequest { +) -> yaak_templates::error::Result { 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( @@ -65,15 +65,15 @@ pub async fn render_http_request( base_environment: &Environment, environment: Option<&Environment>, cb: &T, -) -> HttpRequest { +) -> yaak_templates::error::Result { 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( 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( template: &str, vars: &HashMap, cb: &T, -) -> String { +) -> yaak_templates::error::Result { parse_and_render(template, vars, cb).await } diff --git a/src-tauri/yaak-git/src/lib.rs b/src-tauri/yaak-git/src/lib.rs index 9af352a9..a56e18ed 100644 --- a/src-tauri/yaak-git/src/lib.rs +++ b/src-tauri/yaak-git/src/lib.rs @@ -8,7 +8,7 @@ use tauri::{ mod branch; mod callbacks; mod commands; -mod error; +pub mod error; mod fetch; mod git; mod merge; diff --git a/src-tauri/yaak-license/Cargo.toml b/src-tauri/yaak-license/Cargo.toml index b26dd296..3602e6ba 100644 --- a/src-tauri/yaak-license/Cargo.toml +++ b/src-tauri/yaak-license/Cargo.toml @@ -6,15 +6,15 @@ edition = "2021" publish = false [dependencies] +chrono = "0.4.38" +log = "0.4.26" reqwest = { workspace = true, features = ["json"] } serde = { version = "1.0.208", features = ["derive"] } -ts-rs = { workspace = true } -thiserror = { workspace = true } -tauri = { workspace = true } -yaak-models = { workspace = true } -chrono = "0.4.38" -log = "0.4.22" serde_json = "1.0.132" +tauri = { workspace = true } +thiserror = { workspace = true } +ts-rs = { workspace = true } +yaak-models = { workspace = true } [build-dependencies] tauri-plugin = { workspace = true, features = ["build"] } diff --git a/src-tauri/yaak-license/bindings/gen_models.ts b/src-tauri/yaak-license/bindings/gen_models.ts index 51c17d66..bc2f9988 100644 --- a/src-tauri/yaak-license/bindings/gen_models.ts +++ b/src-tauri/yaak-license/bindings/gen_models.ts @@ -1,3 +1,3 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type CheckActivationRequestPayload = { activationId: string, }; +export type CheckActivationRequestPayload = { appVersion: string, appPlatform: string, }; diff --git a/src-tauri/yaak-license/bindings/license.ts b/src-tauri/yaak-license/bindings/license.ts index 9e64bec3..3d4fd90d 100644 --- a/src-tauri/yaak-license/bindings/license.ts +++ b/src-tauri/yaak-license/bindings/license.ts @@ -8,6 +8,6 @@ export type ActivateLicenseResponsePayload = { activationId: string, }; export type CheckActivationResponsePayload = { active: boolean, }; -export type DeactivateLicenseRequestPayload = { licenseKey: string, appVersion: string, appPlatform: string, }; +export type DeactivateLicenseRequestPayload = { appVersion: string, appPlatform: string, }; export type LicenseCheckStatus = { "type": "personal_use", trial_ended: string, } | { "type": "commercial_use" } | { "type": "invalid_license" } | { "type": "trialing", end: string, }; diff --git a/src-tauri/yaak-license/src/commands.rs b/src-tauri/yaak-license/src/commands.rs index 15246dc7..aadf268d 100644 --- a/src-tauri/yaak-license/src/commands.rs +++ b/src-tauri/yaak-license/src/commands.rs @@ -1,5 +1,8 @@ -use crate::errors::Result; -use crate::{activate_license, check_license, deactivate_license, get_activation_id, ActivateLicenseRequestPayload, CheckActivationRequestPayload, DeactivateLicenseRequestPayload, LicenseCheckStatus}; +use crate::error::Result; +use crate::{ + activate_license, check_license, deactivate_license, ActivateLicenseRequestPayload, + CheckActivationRequestPayload, DeactivateLicenseRequestPayload, LicenseCheckStatus, +}; use log::{debug, info}; use std::string::ToString; use tauri::{command, AppHandle, Manager, Runtime, WebviewWindow}; @@ -7,10 +10,14 @@ use tauri::{command, AppHandle, Manager, Runtime, WebviewWindow}; #[command] pub async fn check(app_handle: AppHandle) -> Result { debug!("Checking license"); - check_license(&app_handle, CheckActivationRequestPayload{ - app_platform: get_os().to_string(), - app_version: app_handle.package_info().version.to_string(), - }).await + check_license( + &app_handle, + CheckActivationRequestPayload { + app_platform: get_os().to_string(), + app_version: app_handle.package_info().version.to_string(), + }, + ) + .await } #[command] diff --git a/src-tauri/yaak-license/src/errors.rs b/src-tauri/yaak-license/src/error.rs similarity index 100% rename from src-tauri/yaak-license/src/errors.rs rename to src-tauri/yaak-license/src/error.rs diff --git a/src-tauri/yaak-license/src/lib.rs b/src-tauri/yaak-license/src/lib.rs index 254086c7..03b76bfc 100644 --- a/src-tauri/yaak-license/src/lib.rs +++ b/src-tauri/yaak-license/src/lib.rs @@ -5,7 +5,7 @@ use tauri::{ }; mod commands; -mod errors; +pub mod error; mod license; use crate::commands::{activate, check, deactivate}; diff --git a/src-tauri/yaak-license/src/license.rs b/src-tauri/yaak-license/src/license.rs index db58b03c..f24003b3 100644 --- a/src-tauri/yaak-license/src/license.rs +++ b/src-tauri/yaak-license/src/license.rs @@ -1,5 +1,5 @@ -use crate::errors::Error::{ClientError, ServerError}; -use crate::errors::Result; +use crate::error::Error::{ClientError, ServerError}; +use crate::error::Result; use chrono::{NaiveDateTime, Utc}; use log::{debug, info, warn}; use serde::{Deserialize, Serialize}; diff --git a/src-tauri/yaak-plugins/src/manager.rs b/src-tauri/yaak-plugins/src/manager.rs index 82255adf..fd417c8f 100644 --- a/src-tauri/yaak-plugins/src/manager.rs +++ b/src-tauri/yaak-plugins/src/manager.rs @@ -27,6 +27,7 @@ use tokio::net::TcpListener; use tokio::sync::{mpsc, Mutex}; use tokio::time::timeout; use yaak_models::queries::{generate_id, list_plugins}; +use yaak_templates::error::Error::RenderError; #[derive(Clone)] pub struct PluginManager { @@ -596,7 +597,7 @@ impl PluginManager { fn_name: &str, args: HashMap, purpose: RenderPurpose, - ) -> Result> { + ) -> yaak_templates::error::Result { let req = CallTemplateFunctionRequest { name: fn_name.to_string(), args: CallTemplateFunctionArgs { @@ -607,7 +608,8 @@ impl PluginManager { let events = self .send_and_wait(window_context, &InternalEventPayload::CallTemplateFunctionRequest(req)) - .await?; + .await + .map_err(|e| RenderError(format!("Failed to call template function {e:}")))?; let value = events.into_iter().find_map(|e| match e.payload { InternalEventPayload::CallTemplateFunctionResponse(CallTemplateFunctionResponse { @@ -616,7 +618,10 @@ impl PluginManager { _ => None, }); - Ok(value) + match value { + None => Err(RenderError(format!("Template function not found {fn_name}"))), + Some(v) => Ok(v), + } } pub async fn import_data( diff --git a/src-tauri/yaak-plugins/src/template_callback.rs b/src-tauri/yaak-plugins/src/template_callback.rs index 2c408f7a..62b252f1 100644 --- a/src-tauri/yaak-plugins/src/template_callback.rs +++ b/src-tauri/yaak-plugins/src/template_callback.rs @@ -1,7 +1,8 @@ -use crate::events::{FormInput, RenderPurpose, WindowContext}; +use crate::events::{RenderPurpose, WindowContext}; use crate::manager::PluginManager; use std::collections::HashMap; use tauri::{AppHandle, Manager, Runtime}; +use yaak_templates::error::Result; use yaak_templates::TemplateCallback; #[derive(Clone)] @@ -27,51 +28,20 @@ impl PluginTemplateCallback { } impl TemplateCallback for PluginTemplateCallback { - async fn run(&self, fn_name: &str, args: HashMap) -> Result { + async fn run(&self, fn_name: &str, args: HashMap) -> Result { // The beta named the function `Response` but was changed in stable. // Keep this here for a while because there's no easy way to migrate let fn_name = if fn_name == "Response" { "response" } else { fn_name }; - let function = self - .plugin_manager - .get_template_functions_with_context(&self.window_context) - .await - .map_err(|e| e.to_string())? - .iter() - .flat_map(|f| f.functions.clone()) - .find(|f| f.name == fn_name) - .ok_or("")?; - - let mut args_with_defaults = args.clone(); - - // Fill in default values for all args - for arg in function.args { - let base = match arg { - FormInput::Text(a) => a.base, - FormInput::Editor(a) => a.base, - FormInput::Select(a) => a.base, - FormInput::Checkbox(a) => a.base, - FormInput::File(a) => a.base, - FormInput::HttpRequest(a) => a.base, - FormInput::Accordion(_) => continue, - FormInput::Banner(_) => continue, - FormInput::Markdown(_) => continue, - }; - if let None = args_with_defaults.get(base.name.as_str()) { - args_with_defaults.insert(base.name, base.default_value.unwrap_or_default()); - } - } - let resp = self .plugin_manager .call_template_function( &self.window_context, fn_name, - args_with_defaults, + args, self.render_purpose.to_owned(), ) - .await - .map_err(|e| e.to_string())?; - Ok(resp.unwrap_or_default()) + .await?; + Ok(resp) } } diff --git a/src-tauri/yaak-templates/Cargo.toml b/src-tauri/yaak-templates/Cargo.toml index 5bb0d3bb..e6dae6ec 100644 --- a/src-tauri/yaak-templates/Cargo.toml +++ b/src-tauri/yaak-templates/Cargo.toml @@ -7,6 +7,7 @@ publish = false [dependencies] log = "0.4.22" serde = { version = "1.0.208", features = ["derive"] } -ts-rs = { version = "10.0.0" } -tokio = { version = "1.39.3", features = ["macros", "rt"] } serde_json = "1.0.132" +thiserror = { workspace = true } +tokio = { version = "1.39.3", features = ["macros", "rt"] } +ts-rs = { version = "10.0.0" } diff --git a/src-tauri/yaak-templates/src/error.rs b/src-tauri/yaak-templates/src/error.rs new file mode 100644 index 00000000..c1df0377 --- /dev/null +++ b/src-tauri/yaak-templates/src/error.rs @@ -0,0 +1,22 @@ +use serde::{Serialize, Serializer}; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum Error { + #[error("Render Error: {0}")] + RenderError(String), + + #[error("Render Error: Max recursion depth exceeded")] + RenderStackExceededError, +} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} + +pub type Result = std::result::Result; diff --git a/src-tauri/yaak-templates/src/lib.rs b/src-tauri/yaak-templates/src/lib.rs index 80a3b023..605d5812 100644 --- a/src-tauri/yaak-templates/src/lib.rs +++ b/src-tauri/yaak-templates/src/lib.rs @@ -1,6 +1,7 @@ pub mod format; pub mod parser; pub mod renderer; +pub mod error; pub use parser::*; pub use renderer::*; diff --git a/src-tauri/yaak-templates/src/renderer.rs b/src-tauri/yaak-templates/src/renderer.rs index bf745da1..b87b76d4 100644 --- a/src-tauri/yaak-templates/src/renderer.rs +++ b/src-tauri/yaak-templates/src/renderer.rs @@ -1,82 +1,103 @@ +use crate::error::Error::RenderStackExceededError; +use crate::error::Result; use crate::{FnArg, Parser, Token, Tokens, Val}; use log::warn; use serde_json::json; use std::collections::HashMap; use std::future::Future; +const MAX_DEPTH: usize = 50; + pub trait TemplateCallback { fn run( &self, fn_name: &str, args: HashMap, - ) -> impl Future> + Send; + ) -> impl Future> + Send; } pub async fn render_json_value_raw( v: serde_json::Value, vars: &HashMap, cb: &T, -) -> serde_json::Value { - match v { - serde_json::Value::String(s) => json!(parse_and_render(&s, vars, cb).await), +) -> Result { + let v = match v { + serde_json::Value::String(s) => json!(parse_and_render(&s, vars, cb).await?), serde_json::Value::Array(a) => { let mut new_a = Vec::new(); for v in a { - new_a.push(Box::pin(render_json_value_raw(v, vars, cb)).await) + new_a.push(Box::pin(render_json_value_raw(v, vars, cb)).await?) } json!(new_a) } serde_json::Value::Object(o) => { let mut new_o = serde_json::Map::new(); for (k, v) in o { - let key = Box::pin(parse_and_render(&k, vars, cb)).await; - let value = Box::pin(render_json_value_raw(v, vars, cb)).await; + let key = Box::pin(parse_and_render(&k, vars, cb)).await?; + let value = Box::pin(render_json_value_raw(v, vars, cb)).await?; new_o.insert(key, value); } json!(new_o) } v => v, - } + }; + Ok(v) +} + +async fn parse_and_render_with_depth( + template: &str, + vars: &HashMap, + cb: &T, + depth: usize, +) -> Result { + let mut p = Parser::new(template); + let tokens = p.parse(); + render(tokens, vars, cb, depth + 1).await } pub async fn parse_and_render( template: &str, vars: &HashMap, cb: &T, -) -> String { - let mut p = Parser::new(template); - let tokens = p.parse(); - render(tokens, vars, cb).await +) -> Result { + parse_and_render_with_depth(template, vars, cb, 1).await } pub async fn render( tokens: Tokens, vars: &HashMap, cb: &T, -) -> String { + mut depth: usize, +) -> Result { + depth += 1; + if depth > MAX_DEPTH { + return Err(RenderStackExceededError); + } + let mut doc_str: Vec = Vec::new(); for t in tokens.tokens { match t { Token::Raw { text } => doc_str.push(text), - Token::Tag { val } => doc_str.push(render_tag(val, &vars, cb).await), + Token::Tag { val } => doc_str.push(render_tag(val, &vars, cb, depth).await?), Token::Eof => {} } } - doc_str.join("") + Ok(doc_str.join("")) } async fn render_tag( val: Val, vars: &HashMap, cb: &T, -) -> String { - match val { + depth: usize, +) -> Result { + let v = match val { Val::Str { text } => text.into(), Val::Var { name } => match vars.get(name.as_str()) { Some(v) => { - let r = Box::pin(parse_and_render(v, vars, cb)).await; + let r = Box::pin(parse_and_render_with_depth(v, vars, cb, depth)).await?; r.to_string() } None => "".into(), @@ -99,7 +120,7 @@ async fn render_tag( vars.get(var_name.as_str()).unwrap_or(&empty).to_string(), ), FnArg { name, value: val } => { - let r = Box::pin(render_tag(val.clone(), vars, cb)).await; + let r = Box::pin(render_tag(val.clone(), vars, cb, depth)).await?; (name.to_string(), r) } }; @@ -114,11 +135,15 @@ async fn render_tag( } } Val::Null => "".into(), - } + }; + + Ok(v) } #[cfg(test)] mod parse_and_render_tests { + use crate::error::Error::{RenderError, RenderStackExceededError}; + use crate::error::Result; use crate::renderer::TemplateCallback; use crate::*; use std::collections::HashMap; @@ -126,44 +151,43 @@ mod parse_and_render_tests { struct EmptyCB {} impl TemplateCallback for EmptyCB { - async fn run( - &self, - _fn_name: &str, - _args: HashMap, - ) -> Result { + async fn run(&self, _fn_name: &str, _args: HashMap) -> Result { todo!() } } #[tokio::test] - async fn render_empty() { + async fn render_empty() -> Result<()> { let empty_cb = EmptyCB {}; let template = ""; let vars = HashMap::new(); let result = ""; - assert_eq!(parse_and_render(template, &vars, &empty_cb).await, result.to_string()); + assert_eq!(parse_and_render(template, &vars, &empty_cb).await?, result.to_string()); + Ok(()) } #[tokio::test] - async fn render_text_only() { + async fn render_text_only() -> Result<()> { let empty_cb = EmptyCB {}; let template = "Hello World!"; let vars = HashMap::new(); let result = "Hello World!"; - assert_eq!(parse_and_render(template, &vars, &empty_cb).await, result.to_string()); + assert_eq!(parse_and_render(template, &vars, &empty_cb).await?, result.to_string()); + Ok(()) } #[tokio::test] - async fn render_simple() { + async fn render_simple() -> Result<()> { let empty_cb = EmptyCB {}; let template = "${[ foo ]}"; let vars = HashMap::from([("foo".to_string(), "bar".to_string())]); let result = "bar"; - assert_eq!(parse_and_render(template, &vars, &empty_cb).await, result.to_string()); + assert_eq!(parse_and_render(template, &vars, &empty_cb).await?, result.to_string()); + Ok(()) } #[tokio::test] - async fn render_recursive_var() { + async fn render_recursive_var() -> Result<()> { let empty_cb = EmptyCB {}; let template = "${[ foo ]}"; let mut vars = HashMap::new(); @@ -172,49 +196,58 @@ mod parse_and_render_tests { vars.insert("baz".to_string(), "baz".to_string()); let result = "foo: bar: baz"; - assert_eq!(parse_and_render(template, &vars, &empty_cb).await, result.to_string()); + assert_eq!(parse_and_render(template, &vars, &empty_cb).await?, result.to_string()); + Ok(()) } #[tokio::test] - async fn render_surrounded() { + async fn render_self_referencing_var() -> Result<()> { + let empty_cb = EmptyCB {}; + let template = "${[ foo ]}"; + let mut vars = HashMap::new(); + vars.insert("foo".to_string(), "${[ foo ]}".to_string()); + + assert_eq!( + parse_and_render(template, &vars, &empty_cb).await, + Err(RenderStackExceededError) + ); + Ok(()) + } + + #[tokio::test] + async fn render_surrounded() -> Result<()> { let empty_cb = EmptyCB {}; let template = "hello ${[ word ]} world!"; let vars = HashMap::from([("word".to_string(), "cruel".to_string())]); let result = "hello cruel world!"; - assert_eq!(parse_and_render(template, &vars, &empty_cb).await, result.to_string()); + assert_eq!(parse_and_render(template, &vars, &empty_cb).await?, result.to_string()); + Ok(()) } #[tokio::test] - async fn render_valid_fn() { + async fn render_valid_fn() -> Result<()> { let vars = HashMap::new(); let template = r#"${[ say_hello(a='John', b='Kate') ]}"#; let result = r#"say_hello: 2, Some("John") Some("Kate")"#; struct CB {} impl TemplateCallback for CB { - async fn run( - &self, - fn_name: &str, - args: HashMap, - ) -> Result { + async fn run(&self, fn_name: &str, args: HashMap) -> Result { Ok(format!("{fn_name}: {}, {:?} {:?}", args.len(), args.get("a"), args.get("b"))) } } - assert_eq!(parse_and_render(template, &vars, &CB {}).await, result); + assert_eq!(parse_and_render(template, &vars, &CB {}).await?, result); + Ok(()) } #[tokio::test] - async fn render_nested_fn() { + async fn render_nested_fn() -> Result<()> { let vars = HashMap::new(); let template = r#"${[ upper(foo=secret()) ]}"#; let result = r#"ABC"#; struct CB {} impl TemplateCallback for CB { - async fn run( - &self, - fn_name: &str, - args: HashMap, - ) -> Result { + async fn run(&self, fn_name: &str, args: HashMap) -> Result { Ok(match fn_name { "secret" => "abc".to_string(), "upper" => args["foo"].to_string().to_uppercase(), @@ -223,80 +256,79 @@ mod parse_and_render_tests { } } - assert_eq!(parse_and_render(template, &vars, &CB {}).await, result.to_string()); + assert_eq!(parse_and_render(template, &vars, &CB {}).await?, result.to_string()); + Ok(()) } #[tokio::test] - async fn render_fn_err() { + async fn render_fn_err() -> Result<()> { let vars = HashMap::new(); let template = r#"${[ error() ]}"#; let result = r#""#; struct CB {} impl TemplateCallback for CB { - async fn run( - &self, - _fn_name: &str, - _args: HashMap, - ) -> Result { - Err("Failed to do it!".to_string()) + async fn run(&self, _fn_name: &str, _args: HashMap) -> Result { + Err(RenderError("Failed to do it!".to_string())) } } - assert_eq!(parse_and_render(template, &vars, &CB {}).await, result.to_string()); + assert_eq!(parse_and_render(template, &vars, &CB {}).await?, result.to_string()); + Ok(()) } } #[cfg(test)] mod render_json_value_raw_tests { + use crate::error::Result; + use crate::{render_json_value_raw, TemplateCallback}; use serde_json::json; use std::collections::HashMap; - use crate::{render_json_value_raw, TemplateCallback}; struct EmptyCB {} impl TemplateCallback for EmptyCB { - async fn run( - &self, - _fn_name: &str, - _args: HashMap, - ) -> Result { + async fn run(&self, _fn_name: &str, _args: HashMap) -> Result { todo!() } } #[tokio::test] - async fn render_json_value_string() { + async fn render_json_value_string() -> Result<()> { let v = json!("${[a]}"); let mut vars = HashMap::new(); vars.insert("a".to_string(), "aaa".to_string()); - let result = render_json_value_raw(v, &vars, &EmptyCB {}).await; - assert_eq!(result, json!("aaa")) + assert_eq!(render_json_value_raw(v, &vars, &EmptyCB {}).await?, json!("aaa")); + Ok(()) } #[tokio::test] - async fn render_json_value_array() { + async fn render_json_value_array() -> Result<()> { let v = json!(["${[a]}", "${[a]}"]); let mut vars = HashMap::new(); vars.insert("a".to_string(), "aaa".to_string()); - let result = render_json_value_raw(v, &vars, &EmptyCB {}).await; - assert_eq!(result, json!(["aaa", "aaa"])) + let result = render_json_value_raw(v, &vars, &EmptyCB {}).await?; + assert_eq!(result, json!(["aaa", "aaa"])); + + Ok(()) } #[tokio::test] - async fn render_json_value_object() { + async fn render_json_value_object() -> Result<()> { let v = json!({"${[a]}": "${[a]}"}); let mut vars = HashMap::new(); vars.insert("a".to_string(), "aaa".to_string()); - let result = render_json_value_raw(v, &vars, &EmptyCB {}).await; - assert_eq!(result, json!({"aaa": "aaa"})) + let result = render_json_value_raw(v, &vars, &EmptyCB {}).await?; + assert_eq!(result, json!({"aaa": "aaa"})); + + Ok(()) } #[tokio::test] - async fn render_json_value_nested() { + async fn render_json_value_nested() -> Result<()> { let v = json!([ 123, {"${[a]}": "${[a]}"}, @@ -308,7 +340,7 @@ mod render_json_value_raw_tests { let mut vars = HashMap::new(); vars.insert("a".to_string(), "aaa".to_string()); - let result = render_json_value_raw(v, &vars, &EmptyCB {}).await; + let result = render_json_value_raw(v, &vars, &EmptyCB {}).await?; assert_eq!( result, json!([ @@ -319,6 +351,8 @@ mod render_json_value_raw_tests { false, {"x": ["aaa"]} ]) - ) + ); + + Ok(()) } } diff --git a/src-tauri/yaak-ws/src/commands.rs b/src-tauri/yaak-ws/src/commands.rs index 9b075567..12179756 100644 --- a/src-tauri/yaak-ws/src/commands.rs +++ b/src-tauri/yaak-ws/src/commands.rs @@ -116,7 +116,7 @@ pub(crate) async fn send( RenderPurpose::Send, ), ) - .await; + .await?; let mut ws_manager = ws_manager.lock().await; ws_manager.send(&connection.id, Message::Text(request.message.clone().into())).await?; @@ -214,7 +214,7 @@ pub(crate) async fn connect( RenderPurpose::Send, ), ) - .await; + .await?; let mut headers = HeaderMap::new(); if let Some(auth_name) = request.authentication_type.clone() { diff --git a/src-tauri/yaak-ws/src/error.rs b/src-tauri/yaak-ws/src/error.rs index 026106af..98e7556a 100644 --- a/src-tauri/yaak-ws/src/error.rs +++ b/src-tauri/yaak-ws/src/error.rs @@ -13,6 +13,9 @@ pub enum Error { #[error("Plugin error: {0}")] PluginError(#[from] yaak_plugins::error::Error), + #[error("Render error: {0}")] + TemplateError(#[from] yaak_templates::error::Error), + #[error("WebSocket error: {0}")] GenericError(String), } diff --git a/src-tauri/yaak-ws/src/lib.rs b/src-tauri/yaak-ws/src/lib.rs index 48026a8d..6b934008 100644 --- a/src-tauri/yaak-ws/src/lib.rs +++ b/src-tauri/yaak-ws/src/lib.rs @@ -1,6 +1,6 @@ mod commands; mod connect; -mod error; +pub mod error; mod manager; mod render; diff --git a/src-tauri/yaak-ws/src/render.rs b/src-tauri/yaak-ws/src/render.rs index a04a6d28..717e8ee4 100644 --- a/src-tauri/yaak-ws/src/render.rs +++ b/src-tauri/yaak-ws/src/render.rs @@ -1,3 +1,4 @@ +use crate::error::Result; use std::collections::BTreeMap; use yaak_models::models::{Environment, HttpRequestHeader, WebsocketRequest}; use yaak_models::render::make_vars_hashmap; @@ -8,33 +9,33 @@ pub async fn render_request( base_environment: &Environment, environment: Option<&Environment>, cb: &T, -) -> WebsocketRequest { +) -> Result { let vars = &make_vars_hashmap(base_environment, environment); let mut headers = Vec::new(); for p in r.headers.clone() { headers.push(HttpRequestHeader { enabled: p.enabled, - name: parse_and_render(&p.name, vars, cb).await, - value: parse_and_render(&p.value, vars, cb).await, + name: parse_and_render(&p.name, vars, cb).await?, + value: parse_and_render(&p.value, 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 = parse_and_render(r.url.as_str(), vars, cb).await; + let url = parse_and_render(r.url.as_str(), vars, cb).await?; - let message = parse_and_render(&r.message.clone(), vars, cb).await; + let message = parse_and_render(&r.message.clone(), vars, cb).await?; - WebsocketRequest { + Ok(WebsocketRequest { url, headers, authentication, message, ..r.to_owned() - } + }) }