diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 0bdeb720..0c275a05 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -8102,6 +8102,7 @@ name = "yaak-templates" version = "0.1.0" dependencies = [ "base64 0.22.1", + "log", "serde", "serde-wasm-bindgen", "serde_json", diff --git a/src-tauri/src/http_request.rs b/src-tauri/src/http_request.rs index 4cd1e5bb..959596e0 100644 --- a/src-tauri/src/http_request.rs +++ b/src-tauri/src/http_request.rs @@ -32,6 +32,7 @@ use yaak_plugins::events::{ }; use yaak_plugins::manager::PluginManager; use yaak_plugins::template_callback::PluginTemplateCallback; +use yaak_templates::{RenderErrorBehavior, RenderOptions}; pub async fn send_http_request( window: &WebviewWindow, @@ -76,7 +77,11 @@ pub async fn send_http_request( RenderPurpose::Send, ); - let request = match render_http_request(&resolved_request, environment_chain, &cb).await { + let opt = RenderOptions { + error_behavior: RenderErrorBehavior::Throw, + }; + + let request = match render_http_request(&resolved_request, environment_chain, &cb, &opt).await { Ok(r) => r, Err(e) => { return Ok(response_err( diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 471e1e8f..9327ed4e 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -49,7 +49,7 @@ use yaak_plugins::plugin_meta::PluginMetadata; use yaak_plugins::template_callback::PluginTemplateCallback; use yaak_sse::sse::ServerSentEvent; use yaak_templates::format::format_json; -use yaak_templates::{Tokens, transform_args}; +use yaak_templates::{Tokens, transform_args, RenderOptions, RenderErrorBehavior}; mod commands; mod encoding; @@ -126,6 +126,9 @@ async fn cmd_render_template( &PluginWindowContext::new(&window), RenderPurpose::Preview, ), + &RenderOptions { + error_behavior: RenderErrorBehavior::Throw, + }, ) .await?; Ok(result) @@ -167,6 +170,9 @@ async fn cmd_grpc_reflect( &PluginWindowContext::new(&window), RenderPurpose::Send, ), + &RenderOptions { + error_behavior: RenderErrorBehavior::Throw, + }, ) .await?; @@ -213,6 +219,9 @@ async fn cmd_grpc_go( &PluginWindowContext::new(&window), RenderPurpose::Send, ), + &RenderOptions { + error_behavior: RenderErrorBehavior::Throw, + }, ) .await?; @@ -335,6 +344,9 @@ async fn cmd_grpc_go( &PluginWindowContext::new(&window), RenderPurpose::Send, ), + &RenderOptions { + error_behavior: RenderErrorBehavior::Throw, + }, ) .await .expect("Failed to render template") @@ -404,6 +416,9 @@ async fn cmd_grpc_go( &PluginWindowContext::new(&window), RenderPurpose::Send, ), + &RenderOptions { + error_behavior: RenderErrorBehavior::Throw, + }, ) .await?; diff --git a/src-tauri/src/plugin_events.rs b/src-tauri/src/plugin_events.rs index 7cffe444..b3935b2e 100644 --- a/src-tauri/src/plugin_events.rs +++ b/src-tauri/src/plugin_events.rs @@ -23,6 +23,7 @@ use yaak_plugins::events::{ use yaak_plugins::manager::PluginManager; use yaak_plugins::plugin_handle::PluginHandle; use yaak_plugins::template_callback::PluginTemplateCallback; +use yaak_templates::{RenderErrorBehavior, RenderOptions}; pub(crate) async fn handle_plugin_event( app_handle: &AppHandle, @@ -80,7 +81,10 @@ pub(crate) async fn handle_plugin_event( .resolve_environments(&workspace.id, None, environment_id.as_deref()) .expect("Failed to resolve environments"); let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose); - let grpc_request = render_grpc_request(&req.grpc_request, environment_chain, &cb) + let opt = RenderOptions { + error_behavior: RenderErrorBehavior::Throw, + }; + let grpc_request = render_grpc_request(&req.grpc_request, environment_chain, &cb, &opt) .await .expect("Failed to render grpc request"); Some(InternalEventPayload::RenderGrpcRequestResponse(RenderGrpcRequestResponse { @@ -99,7 +103,10 @@ pub(crate) async fn handle_plugin_event( .resolve_environments(&workspace.id, None, environment_id.as_deref()) .expect("Failed to resolve environments"); let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose); - let http_request = render_http_request(&req.http_request, environment_chain, &cb) + let opt = &RenderOptions { + error_behavior: RenderErrorBehavior::Throw, + }; + let http_request = render_http_request(&req.http_request, environment_chain, &cb, &opt) .await .expect("Failed to render http request"); Some(InternalEventPayload::RenderHttpRequestResponse(RenderHttpRequestResponse { @@ -118,7 +125,10 @@ pub(crate) async fn handle_plugin_event( .resolve_environments(&workspace.id, None, environment_id.as_deref()) .expect("Failed to resolve environments"); let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose); - let data = render_json_value(req.data, environment_chain, &cb) + let opt = RenderOptions { + error_behavior: RenderErrorBehavior::Throw, + }; + let data = render_json_value(req.data, environment_chain, &cb, &opt) .await .expect("Failed to render template"); Some(InternalEventPayload::TemplateRenderResponse(TemplateRenderResponse { data })) diff --git a/src-tauri/src/render.rs b/src-tauri/src/render.rs index 05e1f8f9..3e437be2 100644 --- a/src-tauri/src/render.rs +++ b/src-tauri/src/render.rs @@ -1,34 +1,37 @@ use serde_json::Value; -use std::collections::{BTreeMap, HashMap}; +use std::collections::BTreeMap; use yaak_http::apply_path_placeholders; use yaak_models::models::{ Environment, GrpcRequest, HttpRequest, HttpRequestHeader, HttpUrlParameter, }; use yaak_models::render::make_vars_hashmap; -use yaak_templates::{TemplateCallback, parse_and_render, render_json_value_raw}; +use yaak_templates::{RenderOptions, TemplateCallback, parse_and_render, render_json_value_raw}; pub async fn render_template( template: &str, environment_chain: Vec, cb: &T, + opt: &RenderOptions, ) -> yaak_templates::error::Result { let vars = &make_vars_hashmap(environment_chain); - render(template, vars, cb).await + parse_and_render(template, vars, cb, &opt).await } pub async fn render_json_value( value: Value, environment_chain: Vec, cb: &T, + opt: &RenderOptions, ) -> yaak_templates::error::Result { let vars = &make_vars_hashmap(environment_chain); - render_json_value_raw(value, vars, cb).await + render_json_value_raw(value, vars, cb, opt).await } pub async fn render_grpc_request( r: &GrpcRequest, environment_chain: Vec, cb: &T, + opt: &RenderOptions, ) -> yaak_templates::error::Result { let vars = &make_vars_hashmap(environment_chain); @@ -36,18 +39,18 @@ pub async fn render_grpc_request( for p in r.metadata.clone() { metadata.push(HttpRequestHeader { enabled: p.enabled, - name: render(p.name.as_str(), vars, cb).await?, - value: render(p.value.as_str(), vars, cb).await?, + name: parse_and_render(p.name.as_str(), vars, cb, &opt).await?, + value: parse_and_render(p.value.as_str(), vars, cb, &opt).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, &opt).await?); } - let url = render(r.url.as_str(), vars, cb).await?; + let url = parse_and_render(r.url.as_str(), vars, cb, &opt).await?; Ok(GrpcRequest { url, @@ -61,6 +64,7 @@ pub async fn render_http_request( r: &HttpRequest, environment_chain: Vec, cb: &T, + opt: &RenderOptions, ) -> yaak_templates::error::Result { let vars = &make_vars_hashmap(environment_chain); @@ -68,8 +72,8 @@ pub async fn render_http_request( 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: parse_and_render(p.name.as_str(), vars, cb, &opt).await?, + value: parse_and_render(p.value.as_str(), vars, cb, &opt).await?, id: p.id, }) } @@ -78,23 +82,23 @@ 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: parse_and_render(p.name.as_str(), vars, cb, &opt).await?, + value: parse_and_render(p.value.as_str(), vars, cb, &opt).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, &opt).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, &opt).await?); } - let url = render(r.url.clone().as_str(), vars, cb).await?; + let url = parse_and_render(r.url.clone().as_str(), vars, cb, &opt).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); @@ -108,11 +112,3 @@ pub async fn render_http_request( ..r.to_owned() }) } - -pub async fn render( - template: &str, - vars: &HashMap, - cb: &T, -) -> yaak_templates::error::Result { - parse_and_render(template, vars, cb).await -} diff --git a/src-tauri/yaak-plugins/src/manager.rs b/src-tauri/yaak-plugins/src/manager.rs index 42907c79..305dcf62 100644 --- a/src-tauri/yaak-plugins/src/manager.rs +++ b/src-tauri/yaak-plugins/src/manager.rs @@ -39,7 +39,7 @@ use yaak_models::render::make_vars_hashmap; use yaak_models::util::generate_id; use yaak_templates::error::Error::RenderError; use yaak_templates::error::Result as TemplateResult; -use yaak_templates::render_json_value_raw; +use yaak_templates::{RenderErrorBehavior, RenderOptions, render_json_value_raw}; #[derive(Clone)] pub struct PluginManager { @@ -601,7 +601,11 @@ impl PluginManager { &PluginWindowContext::new(&window), RenderPurpose::Preview, ); - let rendered_values = render_json_value_raw(json!(values), vars, &cb).await?; + // We don't want to fail for this op because the UI will not be able to list any auth types then + let render_opt = RenderOptions { + error_behavior: RenderErrorBehavior::ReturnEmpty, + }; + let rendered_values = render_json_value_raw(json!(values), vars, &cb, &render_opt).await?; let context_id = format!("{:x}", md5::compute(request_id.to_string())); let event = self .send_to_plugin_and_wait( @@ -643,6 +647,9 @@ impl PluginManager { &PluginWindowContext::new(&window), RenderPurpose::Preview, ), + &RenderOptions { + error_behavior: RenderErrorBehavior::Throw, + }, ) .await?; let results = self.get_http_authentication_summaries(window).await?; diff --git a/src-tauri/yaak-sync/src/models.rs b/src-tauri/yaak-sync/src/models.rs index ee5d8c6d..6f037570 100644 --- a/src-tauri/yaak-sync/src/models.rs +++ b/src-tauri/yaak-sync/src/models.rs @@ -1,7 +1,7 @@ use crate::error::Error::UnknownModel; use crate::error::Result; use chrono::NaiveDateTime; -use log::warn; +use log::{debug, warn}; use serde::{Deserialize, Deserializer, Serialize}; use serde_yaml::{Mapping, Value}; use sha1::{Digest, Sha1}; @@ -86,7 +86,7 @@ impl<'de> Deserialize<'de> for SyncModel { fn migrate_environment(obj: &mut Mapping) { match (obj.get("base"), obj.get("parentModel")) { (Some(Value::Bool(base)), None) => { - println!("Migrating legacy environment {}", serde_yaml::to_string(obj).unwrap()); + debug!("Migrating legacy environment {}", serde_yaml::to_string(obj).unwrap()); if *base { obj.insert("parentModel".into(), "workspace".into()); } else { diff --git a/src-tauri/yaak-templates/Cargo.toml b/src-tauri/yaak-templates/Cargo.toml index be9061aa..90d92a78 100644 --- a/src-tauri/yaak-templates/Cargo.toml +++ b/src-tauri/yaak-templates/Cargo.toml @@ -19,3 +19,4 @@ tokio = { workspace = true, features = ["macros", "rt"] } ts-rs = { workspace = true } wasm-bindgen = { version = "0.2.100", features = ["serde-serialize"] } serde-wasm-bindgen = "0.6.5" +log = "0.4.27" diff --git a/src-tauri/yaak-templates/src/renderer.rs b/src-tauri/yaak-templates/src/renderer.rs index f7946363..9ed22aec 100644 --- a/src-tauri/yaak-templates/src/renderer.rs +++ b/src-tauri/yaak-templates/src/renderer.rs @@ -1,6 +1,7 @@ use crate::error::Error::{RenderStackExceededError, VariableNotFound}; use crate::error::Result; use crate::{Parser, Token, Tokens, Val}; +use log::warn; use serde_json::json; use std::collections::HashMap; use std::future::Future; @@ -21,21 +22,22 @@ pub async fn render_json_value_raw( v: serde_json::Value, vars: &HashMap, cb: &T, + opt: &RenderOptions, ) -> Result { let v = match v { - serde_json::Value::String(s) => json!(parse_and_render(&s, vars, cb).await?), + serde_json::Value::String(s) => json!(parse_and_render(&s, vars, cb, opt).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, opt)).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, opt)).await?; + let value = Box::pin(render_json_value_raw(v, vars, cb, opt)).await?; new_o.insert(key, value); } json!(new_o) @@ -49,30 +51,55 @@ async fn parse_and_render_at_depth( template: &str, vars: &HashMap, cb: &T, + opt: &RenderOptions, depth: usize, ) -> Result { let mut p = Parser::new(template); let tokens = p.parse()?; - render(tokens, vars, cb, depth + 1).await + render(tokens, vars, cb, opt, depth + 1).await } pub async fn parse_and_render( template: &str, vars: &HashMap, cb: &T, + opt: &RenderOptions, ) -> Result { - parse_and_render_at_depth(template, vars, cb, 1).await + parse_and_render_at_depth(template, vars, cb, opt, 1).await +} + +pub enum RenderErrorBehavior { + Throw, + ReturnEmpty, +} + +pub struct RenderOptions { + pub error_behavior: RenderErrorBehavior, +} + +impl RenderErrorBehavior { + pub fn handle(&self, r: Result) -> Result { + match (self, r) { + (_, Ok(v)) => Ok(v), + (RenderErrorBehavior::Throw, Err(e)) => Err(e), + (RenderErrorBehavior::ReturnEmpty, Err(e)) => { + warn!("Error rendering string: {}", e); + Ok("".to_string()) + } + } + } } pub async fn render( tokens: Tokens, vars: &HashMap, cb: &T, + opt: &RenderOptions, mut depth: usize, ) -> Result { depth += 1; if depth > MAX_DEPTH { - return Err(RenderStackExceededError); + return opt.error_behavior.handle(Err(RenderStackExceededError)); } let mut doc_str: Vec = Vec::new(); @@ -80,7 +107,10 @@ pub async fn render( for t in tokens.tokens { match t { Token::Raw { text } => doc_str.push(text), - Token::Tag { val } => doc_str.push(render_value(val, &vars, cb, depth).await?), + Token::Tag { val } => { + let val = render_value(val, &vars, cb, opt, depth).await; + doc_str.push(opt.error_behavior.handle(val)?) + } Token::Eof => {} } } @@ -92,16 +122,17 @@ async fn render_value( val: Val, vars: &HashMap, cb: &T, + opt: &RenderOptions, depth: usize, ) -> Result { let v = match val { Val::Str { text } => { - let r = Box::pin(parse_and_render_at_depth(&text, vars, cb, depth)).await?; + let r = Box::pin(parse_and_render_at_depth(&text, vars, cb, opt, depth)).await?; r.to_string() } Val::Var { name } => match vars.get(name.as_str()) { Some(v) => { - let r = Box::pin(parse_and_render_at_depth(v, vars, cb, depth)).await?; + let r = Box::pin(parse_and_render_at_depth(v, vars, cb, opt, depth)).await?; r.to_string() } None => return Err(VariableNotFound(name)), @@ -113,13 +144,13 @@ async fn render_value( Val::Bool { value } => serde_json::Value::Bool(value), Val::Null => serde_json::Value::Null, _ => serde_json::Value::String( - Box::pin(render_value(a.value, vars, cb, depth)).await?, + Box::pin(render_value(a.value, vars, cb, opt, depth)).await?, ), }; resolved_args.insert(a.name, v); } let result = cb.run(name.as_str(), resolved_args.clone()).await?; - Box::pin(parse_and_render_at_depth(&result, vars, cb, depth)).await? + Box::pin(parse_and_render_at_depth(&result, vars, cb, opt, depth)).await? } Val::Bool { value } => value.to_string(), Val::Null => "".into(), @@ -163,7 +194,10 @@ mod parse_and_render_tests { let template = ""; let vars = HashMap::new(); let result = ""; - assert_eq!(parse_and_render(template, &vars, &empty_cb).await?, result.to_string()); + let opt = RenderOptions { + error_behavior: RenderErrorBehavior::Throw, + }; + assert_eq!(parse_and_render(template, &vars, &empty_cb, &opt).await?, result.to_string()); Ok(()) } @@ -173,7 +207,10 @@ mod parse_and_render_tests { 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()); + let opt = RenderOptions { + error_behavior: RenderErrorBehavior::Throw, + }; + assert_eq!(parse_and_render(template, &vars, &empty_cb, &opt).await?, result.to_string()); Ok(()) } @@ -183,7 +220,10 @@ mod parse_and_render_tests { 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()); + let opt = RenderOptions { + error_behavior: RenderErrorBehavior::Throw, + }; + assert_eq!(parse_and_render(template, &vars, &empty_cb, &opt).await?, result.to_string()); Ok(()) } @@ -197,7 +237,10 @@ 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()); + let opt = RenderOptions { + error_behavior: RenderErrorBehavior::Throw, + }; + assert_eq!(parse_and_render(template, &vars, &empty_cb, &opt).await?, result.to_string()); Ok(()) } @@ -206,9 +249,11 @@ mod parse_and_render_tests { let empty_cb = EmptyCB {}; let template = "${[ foo ]}"; let vars = HashMap::new(); - + let opt = RenderOptions { + error_behavior: RenderErrorBehavior::Throw, + }; assert_eq!( - parse_and_render(template, &vars, &empty_cb).await, + parse_and_render(template, &vars, &empty_cb, &opt).await, Err(VariableNotFound("foo".to_string())) ); Ok(()) @@ -220,9 +265,11 @@ mod parse_and_render_tests { let template = "${[ foo ]}"; let mut vars = HashMap::new(); vars.insert("foo".to_string(), "${[ foo ]}".to_string()); - + let opt = RenderOptions { + error_behavior: RenderErrorBehavior::Throw, + }; assert_eq!( - parse_and_render(template, &vars, &empty_cb).await, + parse_and_render(template, &vars, &empty_cb, &opt).await, Err(RenderStackExceededError) ); Ok(()) @@ -234,7 +281,10 @@ mod parse_and_render_tests { 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()); + let opt = RenderOptions { + error_behavior: RenderErrorBehavior::Throw, + }; + assert_eq!(parse_and_render(template, &vars, &empty_cb, &opt).await?, result.to_string()); Ok(()) } @@ -243,6 +293,9 @@ mod parse_and_render_tests { let vars = HashMap::new(); let template = r#"${[ say_hello(a='John', b='Kate') ]}"#; let result = r#"say_hello: 2, Some(String("John")) Some(String("Kate"))"#; + let opt = RenderOptions { + error_behavior: RenderErrorBehavior::Throw, + }; struct CB {} impl TemplateCallback for CB { @@ -263,7 +316,7 @@ mod parse_and_render_tests { Ok(arg_value.to_string()) } } - assert_eq!(parse_and_render(template, &vars, &CB {}).await?, result); + assert_eq!(parse_and_render(template, &vars, &CB {}, &opt).await?, result); Ok(()) } @@ -272,6 +325,9 @@ mod parse_and_render_tests { let vars = HashMap::new(); let template = r#"${[ upper(foo='bar') ]}"#; let result = r#""BAR""#; + let opt = RenderOptions { + error_behavior: RenderErrorBehavior::Throw, + }; struct CB {} impl TemplateCallback for CB { async fn run( @@ -296,7 +352,7 @@ 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 {}, &opt).await?, result.to_string()); Ok(()) } @@ -306,9 +362,16 @@ mod parse_and_render_tests { vars.insert("foo".to_string(), "bar".to_string()); let template = r#"${[ upper(foo=b64'Zm9vICdiYXInIGJheg') ]}"#; let result = r#""FOO 'BAR' BAZ""#; + let opt = RenderOptions { + error_behavior: RenderErrorBehavior::Throw, + }; 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 { "upper" => args["foo"].to_string().to_uppercase(), _ => "".to_string(), @@ -325,7 +388,7 @@ 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 {}, &opt).await?, result.to_string()); Ok(()) } @@ -335,9 +398,17 @@ mod parse_and_render_tests { vars.insert("foo".to_string(), "bar".to_string()); let template = r#"${[ upper(foo='${[ foo ]}') ]}"#; let result = r#""BAR""#; + let opt = RenderOptions { + error_behavior: RenderErrorBehavior::Throw, + }; + 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(), @@ -355,7 +426,7 @@ 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 {}, &opt).await?, result.to_string()); Ok(()) } @@ -365,9 +436,17 @@ mod parse_and_render_tests { vars.insert("foo".to_string(), "bar".to_string()); let template = r#"${[ no_op(inner='${[ foo ]}') ]}"#; let result = r#""bar""#; + let opt = RenderOptions { + error_behavior: RenderErrorBehavior::Throw, + }; + 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 { "no_op" => args["inner"].to_string(), _ => "".to_string(), @@ -384,7 +463,7 @@ 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 {}, &opt).await?, result.to_string()); Ok(()) } @@ -393,9 +472,17 @@ mod parse_and_render_tests { let vars = HashMap::new(); let template = r#"${[ upper(foo=secret()) ]}"#; let result = r#""ABC""#; + + let opt = RenderOptions { + error_behavior: RenderErrorBehavior::Throw, + }; 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(), @@ -412,8 +499,7 @@ mod parse_and_render_tests { Ok(arg_value.to_string()) } } - - assert_eq!(parse_and_render(template, &vars, &CB {}).await?, result.to_string()); + assert_eq!(parse_and_render(template, &vars, &CB {}, &opt).await?, result.to_string()); Ok(()) } @@ -421,10 +507,17 @@ mod parse_and_render_tests { async fn render_fn_err() -> Result<()> { let vars = HashMap::new(); let template = r#"hello ${[ error() ]}"#; + let opt = RenderOptions { + error_behavior: RenderErrorBehavior::Throw, + }; 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 { Err(RenderError("Failed to do it!".to_string())) } @@ -439,7 +532,7 @@ mod parse_and_render_tests { } assert_eq!( - parse_and_render(template, &vars, &CB {}).await, + parse_and_render(template, &vars, &CB {}, &opt).await, Err(RenderError("Failed to do it!".to_string())) ); Ok(()) @@ -449,14 +542,21 @@ mod parse_and_render_tests { #[cfg(test)] mod render_json_value_raw_tests { use crate::error::Result; - use crate::{TemplateCallback, render_json_value_raw}; + use crate::{ + RenderErrorBehavior, RenderOptions, TemplateCallback, parse_and_render, + render_json_value_raw, + }; use serde_json::json; use std::collections::HashMap; 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!() } @@ -475,8 +575,11 @@ mod render_json_value_raw_tests { let v = json!("${[a]}"); let mut vars = HashMap::new(); vars.insert("a".to_string(), "aaa".to_string()); + let opt = RenderOptions { + error_behavior: RenderErrorBehavior::Throw, + }; - assert_eq!(render_json_value_raw(v, &vars, &EmptyCB {}).await?, json!("aaa")); + assert_eq!(render_json_value_raw(v, &vars, &EmptyCB {}, &opt).await?, json!("aaa")); Ok(()) } @@ -485,8 +588,11 @@ mod render_json_value_raw_tests { let v = json!(["${[a]}", "${[a]}"]); let mut vars = HashMap::new(); vars.insert("a".to_string(), "aaa".to_string()); + let opt = RenderOptions { + error_behavior: RenderErrorBehavior::Throw, + }; - let result = render_json_value_raw(v, &vars, &EmptyCB {}).await?; + let result = render_json_value_raw(v, &vars, &EmptyCB {}, &opt).await?; assert_eq!(result, json!(["aaa", "aaa"])); Ok(()) @@ -497,8 +603,11 @@ mod render_json_value_raw_tests { let v = json!({"${[a]}": "${[a]}"}); let mut vars = HashMap::new(); vars.insert("a".to_string(), "aaa".to_string()); + let opt = RenderOptions { + error_behavior: RenderErrorBehavior::Throw, + }; - let result = render_json_value_raw(v, &vars, &EmptyCB {}).await?; + let result = render_json_value_raw(v, &vars, &EmptyCB {}, &opt).await?; assert_eq!(result, json!({"aaa": "aaa"})); Ok(()) @@ -516,8 +625,11 @@ mod render_json_value_raw_tests { ]); let mut vars = HashMap::new(); vars.insert("a".to_string(), "aaa".to_string()); + let opt = RenderOptions { + error_behavior: RenderErrorBehavior::Throw, + }; - let result = render_json_value_raw(v, &vars, &EmptyCB {}).await?; + let result = render_json_value_raw(v, &vars, &EmptyCB {}, &opt).await?; assert_eq!( result, json!([ @@ -532,4 +644,17 @@ mod render_json_value_raw_tests { Ok(()) } + + #[tokio::test] + async fn render_opt_return_empty() -> Result<()> { + let vars = HashMap::new(); + let opt = RenderOptions { + error_behavior: RenderErrorBehavior::ReturnEmpty, + }; + + let result = parse_and_render("DNE: ${[hello]}", &vars, &EmptyCB {}, &opt).await?; + assert_eq!(result, "DNE: ".to_string()); + + Ok(()) + } } diff --git a/src-tauri/yaak-ws/src/commands.rs b/src-tauri/yaak-ws/src/commands.rs index 3851fbbf..f378d1ca 100644 --- a/src-tauri/yaak-ws/src/commands.rs +++ b/src-tauri/yaak-ws/src/commands.rs @@ -22,6 +22,7 @@ use yaak_plugins::events::{ }; use yaak_plugins::manager::PluginManager; use yaak_plugins::template_callback::PluginTemplateCallback; +use yaak_templates::{RenderErrorBehavior, RenderOptions}; #[tauri::command] pub(crate) async fn upsert_request( @@ -126,6 +127,9 @@ pub(crate) async fn send( &PluginWindowContext::new(&window), RenderPurpose::Send, ), + &RenderOptions { + error_behavior: RenderErrorBehavior::Throw, + }, ) .await?; @@ -202,6 +206,9 @@ pub(crate) async fn connect( &PluginWindowContext::new(&window), RenderPurpose::Send, ), + &RenderOptions { + error_behavior: RenderErrorBehavior::Throw, + }, ) .await?; diff --git a/src-tauri/yaak-ws/src/render.rs b/src-tauri/yaak-ws/src/render.rs index c7111277..7f667be6 100644 --- a/src-tauri/yaak-ws/src/render.rs +++ b/src-tauri/yaak-ws/src/render.rs @@ -2,12 +2,13 @@ use crate::error::Result; use std::collections::BTreeMap; use yaak_models::models::{Environment, HttpRequestHeader, WebsocketRequest}; use yaak_models::render::make_vars_hashmap; -use yaak_templates::{parse_and_render, render_json_value_raw, TemplateCallback}; +use yaak_templates::{parse_and_render, render_json_value_raw, RenderOptions, TemplateCallback}; pub async fn render_websocket_request( r: &WebsocketRequest, environment_chain: Vec, cb: &T, + opt: &RenderOptions, ) -> Result { let vars = &make_vars_hashmap(environment_chain); @@ -15,20 +16,20 @@ pub async fn render_websocket_request( 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, opt).await?, + value: parse_and_render(&p.value, vars, cb, opt).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, opt).await?); } - let url = parse_and_render(r.url.as_str(), vars, cb).await?; + let url = parse_and_render(r.url.as_str(), vars, cb, opt).await?; - let message = parse_and_render(&r.message.clone(), vars, cb).await?; + let message = parse_and_render(&r.message.clone(), vars, cb, opt).await?; Ok(WebsocketRequest { url,