Recursive environments

This commit is contained in:
Gregory Schier
2024-06-17 12:24:06 -07:00
parent 5b2162e48d
commit 7faa423aba
5 changed files with 89 additions and 67 deletions

View File

@@ -18,6 +18,7 @@ use tauri::{Manager, WebviewWindow};
use tokio::sync::oneshot; use tokio::sync::oneshot;
use tokio::sync::watch::Receiver; use tokio::sync::watch::Receiver;
use crate::render::variables_from_environment;
use crate::{models, render, response_err}; use crate::{models, render, response_err};
pub async fn send_http_request( pub async fn send_http_request(
@@ -33,8 +34,9 @@ pub async fn send_http_request(
let workspace = models::get_workspace(window, &request.workspace_id) let workspace = models::get_workspace(window, &request.workspace_id)
.await .await
.expect("Failed to get Workspace"); .expect("Failed to get Workspace");
let vars = variables_from_environment(&workspace, environment_ref);
let mut url_string = render::render(&request.url, &workspace, environment.as_ref()); let mut url_string = render::render(&request.url, &vars);
url_string = ensure_proto(&url_string); url_string = ensure_proto(&url_string);
if !url_string.starts_with("http://") && !url_string.starts_with("https://") { if !url_string.starts_with("http://") && !url_string.starts_with("https://") {
@@ -144,8 +146,8 @@ pub async fn send_http_request(
continue; continue;
} }
let name = render::render(&h.name, &workspace, environment_ref); let name = render::render(&h.name, &vars);
let value = render::render(&h.value, &workspace, environment_ref); let value = render::render(&h.value, &vars);
let header_name = match HeaderName::from_bytes(name.as_bytes()) { let header_name = match HeaderName::from_bytes(name.as_bytes()) {
Ok(n) => n, Ok(n) => n,
@@ -180,8 +182,8 @@ pub async fn send_http_request(
.unwrap_or(empty_value) .unwrap_or(empty_value)
.as_str() .as_str()
.unwrap_or(""); .unwrap_or("");
let username = render::render(raw_username, &workspace, environment_ref); let username = render::render(raw_username, &vars);
let password = render::render(raw_password, &workspace, environment_ref); let password = render::render(raw_password, &vars);
let auth = format!("{username}:{password}"); let auth = format!("{username}:{password}");
let encoded = base64::engine::general_purpose::STANDARD_NO_PAD.encode(auth); let encoded = base64::engine::general_purpose::STANDARD_NO_PAD.encode(auth);
@@ -191,7 +193,7 @@ pub async fn send_http_request(
); );
} else if b == "bearer" { } else if b == "bearer" {
let raw_token = a.get("token").unwrap_or(empty_value).as_str().unwrap_or(""); let raw_token = a.get("token").unwrap_or(empty_value).as_str().unwrap_or("");
let token = render::render(raw_token, &workspace, environment_ref); let token = render::render(raw_token, &vars);
headers.insert( headers.insert(
"Authorization", "Authorization",
HeaderValue::from_str(&format!("Bearer {token}")).unwrap(), HeaderValue::from_str(&format!("Bearer {token}")).unwrap(),
@@ -205,8 +207,8 @@ pub async fn send_http_request(
continue; continue;
} }
query_params.push(( query_params.push((
render::render(&p.name, &workspace, environment_ref), render::render(&p.name, &vars),
render::render(&p.value, &workspace, environment_ref), render::render(&p.value, &vars),
)); ));
} }
request_builder = request_builder.query(&query_params); request_builder = request_builder.query(&query_params);
@@ -222,7 +224,7 @@ pub async fn send_http_request(
.unwrap_or(empty_string) .unwrap_or(empty_string)
.as_str() .as_str()
.unwrap_or(""); .unwrap_or("");
let body = render::render(raw_text, &workspace, environment_ref); let body = render::render(raw_text, &vars);
request_builder = request_builder.body(body); request_builder = request_builder.body(body);
} else if body_type == "application/x-www-form-urlencoded" } else if body_type == "application/x-www-form-urlencoded"
&& request_body.contains_key("form") && request_body.contains_key("form")
@@ -249,10 +251,7 @@ pub async fn send_http_request(
.unwrap_or(empty_string) .unwrap_or(empty_string)
.as_str() .as_str()
.unwrap_or_default(); .unwrap_or_default();
form_params.push(( form_params.push((render::render(name, &vars), render::render(value, &vars)));
render::render(name, &workspace, environment_ref),
render::render(value, &workspace, environment_ref),
));
} }
} }
request_builder = request_builder.form(&form_params); request_builder = request_builder.form(&form_params);
@@ -301,13 +300,9 @@ pub async fn send_http_request(
.as_str() .as_str()
.unwrap_or_default(); .unwrap_or_default();
let name = render::render(name_raw, &workspace, environment_ref); let name = render::render(name_raw, &vars);
let mut part = if file_path.is_empty() { let mut part = if file_path.is_empty() {
multipart::Part::text(render::render( multipart::Part::text(render::render(value_raw, &vars))
value_raw,
&workspace,
environment_ref,
))
} else { } else {
match fs::read(file_path) { match fs::read(file_path) {
Ok(f) => multipart::Part::bytes(f), Ok(f) => multipart::Part::bytes(f),
@@ -324,7 +319,7 @@ pub async fn send_http_request(
.unwrap_or_default(); .unwrap_or_default();
if !ct_raw.is_empty() { if !ct_raw.is_empty() {
let content_type = render::render(ct_raw, &workspace, environment_ref); let content_type = render::render(ct_raw, &vars);
part = part part = part
.mime_str(content_type.as_str()) .mime_str(content_type.as_str())
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;

View File

@@ -57,7 +57,7 @@ use crate::plugin::{
find_plugins, get_plugin, ImportResult, PluginCapability, find_plugins, get_plugin, ImportResult, PluginCapability,
run_plugin_export_curl, run_plugin_filter, run_plugin_import, run_plugin_export_curl, run_plugin_filter, run_plugin_import,
}; };
use crate::render::render_request; use crate::render::{render_request, variables_from_environment};
use crate::updates::{UpdateMode, YaakUpdater}; use crate::updates::{UpdateMode, YaakUpdater};
use crate::window_menu::app_menu; use crate::window_menu::app_menu;
@@ -176,6 +176,7 @@ async fn cmd_grpc_go(
.await .await
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
let mut metadata = HashMap::new(); let mut metadata = HashMap::new();
let vars = variables_from_environment(&workspace, environment.as_ref());
// Add rest of metadata // Add rest of metadata
for h in req.clone().metadata.0 { for h in req.clone().metadata.0 {
@@ -187,15 +188,14 @@ async fn cmd_grpc_go(
continue; continue;
} }
let name = render::render(&h.name, &workspace, environment.as_ref()); let name = render::render(&h.name, &vars);
let value = render::render(&h.value, &workspace, environment.as_ref()); let value = render::render(&h.value, &vars);
metadata.insert(name, value); metadata.insert(name, value);
} }
if let Some(b) = &req.authentication_type { if let Some(b) = &req.authentication_type {
let req = req.clone(); let req = req.clone();
let environment_ref = environment.as_ref();
let empty_value = &serde_json::to_value("").unwrap(); let empty_value = &serde_json::to_value("").unwrap();
let a = req.authentication.0; let a = req.authentication.0;
@@ -210,15 +210,15 @@ async fn cmd_grpc_go(
.unwrap_or(empty_value) .unwrap_or(empty_value)
.as_str() .as_str()
.unwrap_or(""); .unwrap_or("");
let username = render::render(raw_username, &workspace, environment_ref); let username = render::render(raw_username, &vars);
let password = render::render(raw_password, &workspace, environment_ref); let password = render::render(raw_password, &vars);
let auth = format!("{username}:{password}"); let auth = format!("{username}:{password}");
let encoded = base64::engine::general_purpose::STANDARD_NO_PAD.encode(auth); let encoded = base64::engine::general_purpose::STANDARD_NO_PAD.encode(auth);
metadata.insert("Authorization".to_string(), format!("Basic {}", encoded)); metadata.insert("Authorization".to_string(), format!("Basic {}", encoded));
} else if b == "bearer" { } else if b == "bearer" {
let raw_token = a.get("token").unwrap_or(empty_value).as_str().unwrap_or(""); let raw_token = a.get("token").unwrap_or(empty_value).as_str().unwrap_or("");
let token = render::render(raw_token, &workspace, environment_ref); let token = render::render(raw_token, &vars);
metadata.insert("Authorization".to_string(), format!("Bearer {token}")); metadata.insert("Authorization".to_string(), format!("Bearer {token}"));
} }
} }
@@ -290,11 +290,10 @@ async fn cmd_grpc_go(
let cb = { let cb = {
let cancelled_rx = cancelled_rx.clone(); let cancelled_rx = cancelled_rx.clone();
let environment = environment.clone();
let workspace = workspace.clone();
let w = w.clone(); let w = w.clone();
let base_msg = base_msg.clone(); let base_msg = base_msg.clone();
let method_desc = method_desc.clone(); let method_desc = method_desc.clone();
let vars = vars.clone();
move |ev: tauri::Event| { move |ev: tauri::Event| {
if *cancelled_rx.borrow() { if *cancelled_rx.borrow() {
@@ -317,9 +316,8 @@ async fn cmd_grpc_go(
Ok(IncomingMsg::Message(raw_msg)) => { Ok(IncomingMsg::Message(raw_msg)) => {
let w = w.clone(); let w = w.clone();
let base_msg = base_msg.clone(); let base_msg = base_msg.clone();
let environment_ref = environment.as_ref();
let method_desc = method_desc.clone(); let method_desc = method_desc.clone();
let msg = render::render(raw_msg.as_str(), &workspace, environment_ref); let msg = render::render(raw_msg.as_str(), &vars);
let d_msg: DynamicMessage = match deserialize_message(msg.as_str(), method_desc) let d_msg: DynamicMessage = match deserialize_message(msg.as_str(), method_desc)
{ {
Ok(d_msg) => d_msg, Ok(d_msg) => d_msg,
@@ -371,14 +369,13 @@ async fn cmd_grpc_go(
let w = w.clone(); let w = w.clone();
let base_event = base_msg.clone(); let base_event = base_msg.clone();
let req = req.clone(); let req = req.clone();
let workspace = workspace.clone(); let vars = vars.clone();
let environment = environment.clone();
let raw_msg = if req.message.is_empty() { let raw_msg = if req.message.is_empty() {
"{}".to_string() "{}".to_string()
} else { } else {
req.message req.message
}; };
let msg = render::render(&raw_msg, &workspace, environment.as_ref()); let msg = render::render(&raw_msg, &vars);
upsert_grpc_event( upsert_grpc_event(
&w, &w,

View File

@@ -9,16 +9,18 @@ use templates::parse_and_render;
pub fn render_request(r: &HttpRequest, w: &Workspace, e: Option<&Environment>) -> HttpRequest { pub fn render_request(r: &HttpRequest, w: &Workspace, e: Option<&Environment>) -> HttpRequest {
let r = r.clone(); let r = r.clone();
let vars = &variables_from_environment(w, e);
HttpRequest { HttpRequest {
url: render(r.url.as_str(), w, e), url: render(r.url.as_str(), vars),
url_parameters: Json( url_parameters: Json(
r.url_parameters r.url_parameters
.0 .0
.iter() .iter()
.map(|p| HttpUrlParameter { .map(|p| HttpUrlParameter {
enabled: p.enabled, enabled: p.enabled,
name: render(p.name.as_str(), w, e), name: render(p.name.as_str(), vars),
value: render(p.value.as_str(), w, e), value: render(p.value.as_str(), vars),
}) })
.collect::<Vec<HttpUrlParameter>>(), .collect::<Vec<HttpUrlParameter>>(),
), ),
@@ -28,8 +30,8 @@ pub fn render_request(r: &HttpRequest, w: &Workspace, e: Option<&Environment>) -
.iter() .iter()
.map(|p| HttpRequestHeader { .map(|p| HttpRequestHeader {
enabled: p.enabled, enabled: p.enabled,
name: render(p.name.as_str(), w, e), name: render(p.name.as_str(), vars),
value: render(p.value.as_str(), w, e), value: render(p.value.as_str(), vars),
}) })
.collect::<Vec<HttpRequestHeader>>(), .collect::<Vec<HttpRequestHeader>>(),
), ),
@@ -39,11 +41,11 @@ pub fn render_request(r: &HttpRequest, w: &Workspace, e: Option<&Environment>) -
.iter() .iter()
.map(|(k, v)| { .map(|(k, v)| {
let v = if v.is_string() { let v = if v.is_string() {
render(v.as_str().unwrap(), w, e) render(v.as_str().unwrap(), vars)
} else { } else {
v.to_string() v.to_string()
}; };
(render(k, w, e), JsonValue::from(v)) (render(k, vars), JsonValue::from(v))
}) })
.collect::<HashMap<String, JsonValue>>(), .collect::<HashMap<String, JsonValue>>(),
), ),
@@ -53,11 +55,11 @@ pub fn render_request(r: &HttpRequest, w: &Workspace, e: Option<&Environment>) -
.iter() .iter()
.map(|(k, v)| { .map(|(k, v)| {
let v = if v.is_string() { let v = if v.is_string() {
render(v.as_str().unwrap(), w, e) render(v.as_str().unwrap(), vars)
} else { } else {
v.to_string() v.to_string()
}; };
(render(k, w, e), JsonValue::from(v)) (render(k, vars), JsonValue::from(v))
}) })
.collect::<HashMap<String, JsonValue>>(), .collect::<HashMap<String, JsonValue>>(),
), ),
@@ -65,7 +67,31 @@ pub fn render_request(r: &HttpRequest, w: &Workspace, e: Option<&Environment>) -
} }
} }
pub fn render(template: &str, workspace: &Workspace, environment: Option<&Environment>) -> String { pub fn recursively_render_variables<'s>(
m: &HashMap<String, String>,
render_count: usize,
) -> HashMap<String, String> {
let mut did_render = false;
let mut new_map = m.clone();
for (k, v) in m.clone() {
let rendered = render(v.as_str(), m);
if rendered != v {
did_render = true
}
new_map.insert(k, rendered);
}
if did_render && render_count <= 3 {
new_map = recursively_render_variables(&new_map, render_count + 1);
}
new_map
}
pub fn variables_from_environment(
workspace: &Workspace,
environment: Option<&Environment>,
) -> HashMap<String, String> {
let mut variables = HashMap::new(); let mut variables = HashMap::new();
variables = add_variable_to_map(variables, &workspace.variables.0); variables = add_variable_to_map(variables, &workspace.variables.0);
@@ -73,13 +99,17 @@ pub fn render(template: &str, workspace: &Workspace, environment: Option<&Enviro
variables = add_variable_to_map(variables, &e.variables.0); variables = add_variable_to_map(variables, &e.variables.0);
} }
parse_and_render(template, variables, None) recursively_render_variables(&variables, 0)
} }
fn add_variable_to_map<'a>( pub fn render(template: &str, vars: &HashMap<String, String>) -> String {
m: HashMap<&'a str, &'a str>, parse_and_render(template, vars, None)
variables: &'a Vec<EnvironmentVariable>, }
) -> HashMap<&'a str, &'a str> {
fn add_variable_to_map(
m: HashMap<String, String>,
variables: &Vec<EnvironmentVariable>,
) -> HashMap<String, String> {
let mut map = m.clone(); let mut map = m.clone();
for variable in variables { for variable in variables {
if !variable.enabled || variable.value.is_empty() { if !variable.enabled || variable.value.is_empty() {
@@ -87,7 +117,7 @@ fn add_variable_to_map<'a>(
} }
let name = variable.name.as_str(); let name = variable.name.as_str();
let value = variable.value.as_str(); let value = variable.value.as_str();
map.insert(name, value); map.insert(name.into(), value.into());
} }
map map

View File

@@ -6,7 +6,7 @@ type TemplateCallback = fn(name: &str, args: Vec<String>) -> String;
pub fn parse_and_render( pub fn parse_and_render(
template: &str, template: &str,
vars: HashMap<&str, &str>, vars: &HashMap<String, String>,
cb: Option<TemplateCallback>, cb: Option<TemplateCallback>,
) -> String { ) -> String {
let mut p = Parser::new(template); let mut p = Parser::new(template);
@@ -16,7 +16,7 @@ pub fn parse_and_render(
pub fn render( pub fn render(
tokens: Vec<Token>, tokens: Vec<Token>,
vars: HashMap<&str, &str>, vars: &HashMap<String, String>,
cb: Option<TemplateCallback>, cb: Option<TemplateCallback>,
) -> String { ) -> String {
let mut doc_str: Vec<String> = Vec::new(); let mut doc_str: Vec<String> = Vec::new();
@@ -24,7 +24,7 @@ pub fn render(
for t in tokens { for t in tokens {
match t { match t {
Token::Raw(s) => doc_str.push(s), Token::Raw(s) => doc_str.push(s),
Token::Tag(val) => doc_str.push(render_tag(val, vars.clone(), cb)), Token::Tag(val) => doc_str.push(render_tag(val, &vars, cb)),
Token::Eof => {} Token::Eof => {}
} }
} }
@@ -32,9 +32,9 @@ pub fn render(
return doc_str.join(""); return doc_str.join("");
} }
fn render_tag<'s>( fn render_tag(
val: Val, val: Val,
vars: HashMap<&'s str, &'s str>, vars: &HashMap<String, String>,
cb: Option<TemplateCallback>, cb: Option<TemplateCallback>,
) -> String { ) -> String {
match val { match val {
@@ -44,13 +44,13 @@ fn render_tag<'s>(
None => "".into(), None => "".into(),
}, },
Val::Fn { name, args } => { Val::Fn { name, args } => {
let empty = &""; let empty = "".to_string();
let resolved_args = args let resolved_args = args
.iter() .iter()
.map(|a| match a { .map(|a| match a {
Val::Str(s) => s.to_string(), Val::Str(s) => s.to_string(),
Val::Var(i) => vars.get(i.as_str()).unwrap_or(empty).to_string(), Val::Var(i) => vars.get(i.as_str()).unwrap_or(&empty).to_string(),
val => render_tag(val.clone(), vars.clone(), cb), val => render_tag(val.clone(), vars, cb),
}) })
.collect::<Vec<String>>(); .collect::<Vec<String>>();
match cb { match cb {
@@ -72,7 +72,7 @@ mod tests {
let template = ""; let template = "";
let vars = HashMap::new(); let vars = HashMap::new();
let result = ""; let result = "";
assert_eq!(parse_and_render(template, vars, None), result.to_string()); assert_eq!(parse_and_render(template, &vars, None), result.to_string());
} }
#[test] #[test]
@@ -80,23 +80,23 @@ mod tests {
let template = "Hello World!"; let template = "Hello World!";
let vars = HashMap::new(); let vars = HashMap::new();
let result = "Hello World!"; let result = "Hello World!";
assert_eq!(parse_and_render(template, vars, None), result.to_string()); assert_eq!(parse_and_render(template, &vars, None), result.to_string());
} }
#[test] #[test]
fn render_simple() { fn render_simple() {
let template = "${[ foo ]}"; let template = "${[ foo ]}";
let vars = HashMap::from([("foo", "bar")]); let vars = HashMap::from([("foo".to_string(), "bar".to_string())]);
let result = "bar"; let result = "bar";
assert_eq!(parse_and_render(template, vars, None), result.to_string()); assert_eq!(parse_and_render(template, &vars, None), result.to_string());
} }
#[test] #[test]
fn render_surrounded() { fn render_surrounded() {
let template = "hello ${[ word ]} world!"; let template = "hello ${[ word ]} world!";
let vars = HashMap::from([("word", "cruel")]); let vars = HashMap::from([("word".to_string(), "cruel".to_string())]);
let result = "hello cruel world!"; let result = "hello cruel world!";
assert_eq!(parse_and_render(template, vars, None), result.to_string()); assert_eq!(parse_and_render(template, &vars, None), result.to_string());
} }
#[test] #[test]
@@ -108,7 +108,7 @@ mod tests {
fn cb(name: &str, args: Vec<String>) -> String { fn cb(name: &str, args: Vec<String>) -> String {
format!("{name}: {:?}", args) format!("{name}: {:?}", args)
} }
assert_eq!(parse_and_render(template, vars, Some(cb)), result); assert_eq!(parse_and_render(template, &vars, Some(cb)), result);
} }
#[test] #[test]
@@ -125,7 +125,7 @@ mod tests {
} }
assert_eq!( assert_eq!(
parse_and_render(template, vars, Some(cb)), parse_and_render(template, &vars, Some(cb)),
result.to_string() result.to_string()
); );
} }

View File

@@ -193,7 +193,7 @@ const EnvironmentEditor = function ({
namePlaceholder="VAR_NAME" namePlaceholder="VAR_NAME"
nameValidate={validateName} nameValidate={validateName}
valueType={valueVisibility.value ? 'text' : 'password'} valueType={valueVisibility.value ? 'text' : 'password'}
valueAutocompleteVariables={false} valueAutocompleteVariables={true}
forceUpdateKey={environment?.id ?? workspace?.id ?? 'n/a'} forceUpdateKey={environment?.id ?? workspace?.id ?? 'n/a'}
pairs={variables} pairs={variables}
onChange={handleChange} onChange={handleChange}