Actually call template functions

This commit is contained in:
Gregory Schier
2024-08-19 10:34:22 -07:00
parent 1193e1d7aa
commit 3411575ecc
20 changed files with 286 additions and 217 deletions

View File

@@ -7,7 +7,7 @@ use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;
use crate::render::render_request;
use crate::render::render_http_request;
use crate::response_err;
use base64::Engine;
use http::header::{ACCEPT, USER_AGENT};
@@ -37,7 +37,13 @@ pub async fn send_http_request<R: Runtime>(
let workspace = get_workspace(window, &request.workspace_id)
.await
.expect("Failed to get Workspace");
let rendered_request = render_request(&request, &workspace, environment.as_ref()).await;
let rendered_request = render_http_request(
window.app_handle(),
&request,
&workspace,
environment.as_ref(),
)
.await;
let mut url_string = rendered_request.url;

View File

@@ -35,7 +35,8 @@ use crate::export_resources::{get_workspace_export_resources, WorkspaceExportRes
use crate::grpc::metadata_to_map;
use crate::http_request::send_http_request;
use crate::notifications::YaakNotifier;
use crate::render::{render_request, render_template, variables_from_environment};
use crate::render::{render_grpc_request, render_http_request, render_template};
use crate::template_callback::PluginTemplateCallback;
use crate::updates::{UpdateMode, YaakUpdater};
use crate::window_menu::app_menu;
use yaak_models::models::{
@@ -70,7 +71,7 @@ mod notifications;
mod render;
#[cfg(target_os = "macos")]
mod tauri_plugin_mac_window;
mod template_fns;
mod template_callback;
mod updates;
mod window_menu;
@@ -128,7 +129,13 @@ async fn cmd_render_template(
let workspace = get_workspace(&window, &workspace_id)
.await
.map_err(|e| e.to_string())?;
let rendered = render_template(template, &workspace, environment.as_ref()).await;
let rendered = render_template(
window.app_handle(),
template,
&workspace,
environment.as_ref(),
)
.await;
Ok(rendered)
}
@@ -180,9 +187,6 @@ async fn cmd_grpc_go(
window: WebviewWindow,
grpc_handle: State<'_, Mutex<GrpcHandle>>,
) -> Result<String, String> {
let req = get_grpc_request(&window, request_id)
.await
.map_err(|e| e.to_string())?;
let environment = match environment_id {
Some(id) => Some(
get_environment(&window, id)
@@ -191,13 +195,17 @@ async fn cmd_grpc_go(
),
None => None,
};
let req = get_grpc_request(&window, request_id)
.await
.map_err(|e| e.to_string())?;
let workspace = get_workspace(&window, &req.workspace_id)
.await
.map_err(|e| e.to_string())?;
let req =
render_grpc_request(window.app_handle(), &req, &workspace, environment.as_ref()).await;
let mut metadata = HashMap::new();
let vars = variables_from_environment(&workspace, environment.as_ref()).await;
// Add rest of metadata
// Add the rest of metadata
for h in req.clone().metadata {
if h.name.is_empty() && h.value.is_empty() {
continue;
@@ -207,10 +215,7 @@ async fn cmd_grpc_go(
continue;
}
let name = render::render(&h.name, &vars).await;
let value = render::render(&h.value, &vars).await;
metadata.insert(name, value);
metadata.insert(h.name, h.value);
}
if let Some(b) = &req.authentication_type {
@@ -219,25 +224,22 @@ async fn cmd_grpc_go(
let a = req.authentication;
if b == "basic" {
let raw_username = a
let username = a
.get("username")
.unwrap_or(empty_value)
.as_str()
.unwrap_or("");
let raw_password = a
let password = a
.get("password")
.unwrap_or(empty_value)
.as_str()
.unwrap_or("");
let username = render::render(raw_username, &vars).await;
let password = render::render(raw_password, &vars).await;
let auth = format!("{username}:{password}");
let encoded = base64::engine::general_purpose::STANDARD_NO_PAD.encode(auth);
metadata.insert("Authorization".to_string(), format!("Basic {}", encoded));
} else if b == "bearer" {
let raw_token = a.get("token").unwrap_or(empty_value).as_str().unwrap_or("");
let token = render::render(raw_token, &vars).await;
let token = a.get("token").unwrap_or(empty_value).as_str().unwrap_or("");
metadata.insert("Authorization".to_string(), format!("Bearer {token}"));
}
}
@@ -331,7 +333,6 @@ async fn cmd_grpc_go(
let w = window.clone();
let base_msg = base_msg.clone();
let method_desc = method_desc.clone();
let vars = vars.clone();
move |ev: tauri::Event| {
if *cancelled_rx.borrow() {
@@ -351,14 +352,10 @@ async fn cmd_grpc_go(
};
match serde_json::from_str::<IncomingMsg>(ev.payload()) {
Ok(IncomingMsg::Message(raw_msg)) => {
Ok(IncomingMsg::Message(msg)) => {
let w = w.clone();
let base_msg = base_msg.clone();
let method_desc = method_desc.clone();
let vars = vars.clone();
let msg = tauri::async_runtime::block_on(async move {
render::render(raw_msg.as_str(), &vars).await
});
let d_msg: DynamicMessage = match deserialize_message(msg.as_str(), method_desc)
{
Ok(d_msg) => d_msg,
@@ -410,13 +407,11 @@ async fn cmd_grpc_go(
let w = window.clone();
let base_event = base_msg.clone();
let req = req.clone();
let vars = vars.clone();
let raw_msg = if req.message.is_empty() {
let msg = if req.message.is_empty() {
"{}".to_string()
} else {
req.message
};
let msg = render::render(&raw_msg, &vars).await;
upsert_grpc_event(
&w,
@@ -772,7 +767,7 @@ async fn cmd_filter_response(
// TODO: Have plugins register their own content type (regex?)
plugin_manager
.run_filter(filter, &body, &content_type)
.filter_data(filter, &body, &content_type)
.await
.map_err(|e| e.to_string())
}
@@ -787,7 +782,7 @@ async fn cmd_import_data(
read_to_string(file_path).unwrap_or_else(|_| panic!("Unable to read file {}", file_path));
let file_contents = file.as_str();
let (import_result, plugin_name) = plugin_manager
.run_import(file_contents)
.import_data(file_contents)
.await
.map_err(|e| e.to_string())?;
@@ -912,7 +907,7 @@ async fn cmd_http_request_actions(
plugin_manager: State<'_, PluginManager>,
) -> Result<Vec<GetHttpRequestActionsResponse>, String> {
plugin_manager
.run_http_request_actions()
.get_http_request_actions()
.await
.map_err(|e| e.to_string())
}
@@ -922,7 +917,7 @@ async fn cmd_template_functions(
plugin_manager: State<'_, PluginManager>,
) -> Result<Vec<GetTemplateFunctionsResponse>, String> {
plugin_manager
.run_template_functions()
.get_template_functions()
.await
.map_err(|e| e.to_string())
}
@@ -947,7 +942,7 @@ async fn cmd_curl_to_request(
) -> Result<HttpRequest, String> {
let (import_result, plugin_name) = {
plugin_manager
.run_import(command)
.import_data(command)
.await
.map_err(|e| e.to_string())?
};
@@ -1661,6 +1656,10 @@ pub fn run() {
let grpc_handle = GrpcHandle::new(&app.app_handle());
app.manage(Mutex::new(grpc_handle));
// Plugin template callback
let plugin_cb = PluginTemplateCallback::new(app.app_handle().clone());
app.manage(plugin_cb);
let app_handle = app.app_handle().clone();
monitor_plugin_events(&app_handle);
@@ -1999,8 +1998,13 @@ async fn handle_plugin_event<R: Runtime>(
None => None,
Some(id) => get_environment(w, id.as_str()).await.ok(),
};
let rendered_http_request =
render_request(&req.http_request, &workspace, environment.as_ref()).await;
let rendered_http_request = render_http_request(
app_handle,
&req.http_request,
&workspace,
environment.as_ref(),
)
.await;
Some(InternalEventPayload::RenderHttpRequestResponse(
RenderHttpRequestResponse {
http_request: rendered_http_request,

View File

@@ -1,80 +1,126 @@
use crate::template_fns::timestamp;
use crate::template_callback::PluginTemplateCallback;
use serde_json::Value;
use std::collections::HashMap;
use yaak_models::models::{
Environment, EnvironmentVariable, HttpRequest, HttpRequestHeader, HttpUrlParameter, Workspace,
};
use tauri::{AppHandle, Manager, Runtime};
use yaak_models::models::{Environment, EnvironmentVariable, GrpcMetadataEntry, GrpcRequest, HttpRequest, HttpRequestHeader, HttpUrlParameter, Workspace};
use yaak_templates::{parse_and_render, TemplateCallback};
pub async fn render_template(template: &str, w: &Workspace, e: Option<&Environment>) -> String {
let vars = &variables_from_environment(w, e).await;
render(template, vars).await
pub async fn render_template<R: Runtime>(
app_handle: &AppHandle<R>,
template: &str,
w: &Workspace,
e: Option<&Environment>,
) -> String {
let cb = &*app_handle.state::<PluginTemplateCallback>();
let vars = &variables_from_environment(w, e, cb).await;
render(template, vars, cb).await
}
pub async fn render_request(
pub async fn render_grpc_request<R: Runtime>(
app_handle: &AppHandle<R>,
r: &GrpcRequest,
w: &Workspace,
e: Option<&Environment>,
) -> GrpcRequest {
let cb = &*app_handle.state::<PluginTemplateCallback>();
let vars = &variables_from_environment(w, e, cb).await;
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,
})
}
let mut authentication = HashMap::new();
for (k, v) in r.authentication.clone() {
let v = if v.is_string() {
render(v.as_str().unwrap(), vars, cb).await
} else {
v.to_string()
};
authentication.insert(render(k.as_str(), vars, cb).await, Value::from(v));
}
let url = render(r.url.as_str(), vars, cb).await;
GrpcRequest{
url,
metadata,
authentication,
..r.to_owned()
}
}
pub async fn render_http_request<R: Runtime>(
app_handle: &AppHandle<R>,
r: &HttpRequest,
w: &Workspace,
e: Option<&Environment>,
) -> HttpRequest {
let r = r.clone();
let vars = &variables_from_environment(w, e).await;
let cb = &*app_handle.state::<PluginTemplateCallback>();
let vars = &variables_from_environment(w, e, cb).await;
let mut url_parameters = Vec::new();
for p in r.url_parameters {
for p in r.url_parameters.clone() {
url_parameters.push(HttpUrlParameter {
enabled: p.enabled,
name: render(p.name.as_str(), vars).await,
value: render(p.value.as_str(), vars).await,
name: render(p.name.as_str(), vars, cb).await,
value: render(p.value.as_str(), vars, cb).await,
})
}
let mut headers = Vec::new();
for p in r.headers {
for p in r.headers.clone() {
headers.push(HttpRequestHeader {
enabled: p.enabled,
name: render(p.name.as_str(), vars).await,
value: render(p.value.as_str(), vars).await,
name: render(p.name.as_str(), vars, cb).await,
value: render(p.value.as_str(), vars, cb).await,
})
}
let mut body = HashMap::new();
for (k, v) in r.body {
for (k, v) in r.body.clone() {
let v = if v.is_string() {
render(v.as_str().unwrap(), vars).await
render(v.as_str().unwrap(), vars, cb).await
} else {
v.to_string()
};
body.insert(render(k.as_str(), vars).await, Value::from(v));
body.insert(render(k.as_str(), vars, cb).await, Value::from(v));
}
let mut authentication = HashMap::new();
for (k, v) in r.authentication {
for (k, v) in r.authentication.clone() {
let v = if v.is_string() {
render(v.as_str().unwrap(), vars).await
render(v.as_str().unwrap(), vars, cb).await
} else {
v.to_string()
};
authentication.insert(render(k.as_str(), vars).await, Value::from(v));
authentication.insert(render(k.as_str(), vars, cb).await, Value::from(v));
}
let url = render(r.url.clone().as_str(), vars, cb).await;
HttpRequest {
url: render(r.url.as_str(), vars).await,
url,
url_parameters,
headers,
body,
authentication,
..r
..r.to_owned()
}
}
pub async fn recursively_render_variables<'s>(
pub async fn recursively_render_variables<'s, T: TemplateCallback>(
m: &HashMap<String, String>,
render_count: usize,
cb: &T,
) -> HashMap<String, String> {
let mut did_render = false;
let mut new_map = m.clone();
for (k, v) in m.clone() {
let rendered = Box::pin(render(v.as_str(), m)).await;
let rendered = Box::pin(render(v.as_str(), m, cb)).await;
if rendered != v {
did_render = true
}
@@ -82,15 +128,16 @@ pub async fn recursively_render_variables<'s>(
}
if did_render && render_count <= 3 {
new_map = Box::pin(recursively_render_variables(&new_map, render_count + 1)).await;
new_map = Box::pin(recursively_render_variables(&new_map, render_count + 1, cb)).await;
}
new_map
}
pub async fn variables_from_environment(
pub async fn variables_from_environment<T: TemplateCallback>(
workspace: &Workspace,
environment: Option<&Environment>,
cb: &T,
) -> HashMap<String, String> {
let mut variables = HashMap::new();
variables = add_variable_to_map(variables, &workspace.variables);
@@ -99,23 +146,15 @@ pub async fn variables_from_environment(
variables = add_variable_to_map(variables, &e.variables);
}
recursively_render_variables(&variables, 0).await
recursively_render_variables(&variables, 0, cb).await
}
pub async fn render(template: &str, vars: &HashMap<String, String>) -> String {
parse_and_render(template, vars, &Box::new(PluginTemplateCallback::default())).await
}
#[derive(Default)]
struct PluginTemplateCallback {}
impl TemplateCallback for PluginTemplateCallback {
async fn run(&self, fn_name: &str, args: HashMap<String, String>) -> Result<String, String> {
match fn_name {
"timestamp" => timestamp(args),
_ => Err(format!("Unknown template function {fn_name}")),
}
}
pub async fn render<T: TemplateCallback>(
template: &str,
vars: &HashMap<String, String>,
cb: &T,
) -> String {
parse_and_render(template, vars, cb).await
}
fn add_variable_to_map(

View File

@@ -0,0 +1,25 @@
use std::collections::HashMap;
use tauri::{AppHandle, Manager};
use yaak_plugin_runtime::manager::PluginManager;
use yaak_templates::TemplateCallback;
pub struct PluginTemplateCallback {
app_handle: AppHandle,
}
impl PluginTemplateCallback {
pub fn new(app_handle: AppHandle) -> PluginTemplateCallback {
PluginTemplateCallback { app_handle }
}
}
impl TemplateCallback for PluginTemplateCallback {
async fn run(&self, fn_name: &str, args: HashMap<String, String>) -> Result<String, String> {
let plugin_manager = self.app_handle.state::<PluginManager>();
let resp = plugin_manager
.call_template_function(fn_name, args)
.await
.map_err(|e| e.to_string())?;
Ok(resp.unwrap_or_default())
}
}

View File

@@ -1,70 +0,0 @@
use chrono::{DateTime, Utc};
use std::collections::HashMap;
pub fn timestamp(args: HashMap<String, String>) -> Result<String, String> {
let from = args.get("from").map(|v| v.as_str()).unwrap_or("now");
let format = args.get("format").map(|v| v.as_str()).unwrap_or("rfc3339");
let dt = match from {
"now" => {
let now = Utc::now();
now
}
_ => {
let json_from = serde_json::to_string(from).unwrap_or_default();
let now: DateTime<Utc> = match serde_json::from_str(json_from.as_str()) {
Ok(r) => r,
Err(e) => return Err(e.to_string()),
};
now
}
};
let result = match format {
"rfc3339" => dt.to_rfc3339(),
"unix" => dt.timestamp().to_string(),
"unix_millis" => dt.timestamp_millis().to_string(),
_ => "".to_string(),
};
Ok(result)
}
// Test it
#[cfg(test)]
mod tests {
use crate::template_fns::timestamp;
use std::collections::HashMap;
#[test]
fn timestamp_empty() {
let args = HashMap::new();
assert_ne!(timestamp(args), Ok("".to_string()));
}
#[test]
fn timestamp_from() {
let mut args = HashMap::new();
args.insert("from".to_string(), "2024-07-31T14:16:41.983Z".to_string());
assert_eq!(
timestamp(args),
Ok("2024-07-31T14:16:41.983+00:00".to_string())
);
}
#[test]
fn timestamp_format_unix() {
let mut args = HashMap::new();
args.insert("from".to_string(), "2024-07-31T14:16:41.983Z".to_string());
args.insert("format".to_string(), "unix".to_string());
assert_eq!(timestamp(args), Ok("1722435401".to_string()));
}
#[test]
fn timestamp_format_unix_millis() {
let mut args = HashMap::new();
args.insert("from".to_string(), "2024-07-31T14:16:41.983Z".to_string());
args.insert("format".to_string(), "unix_millis".to_string());
assert_eq!(timestamp(args), Ok("1722435401983".to_string()));
}
}