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::watch::Receiver;
use crate::render::variables_from_environment;
use crate::{models, render, response_err};
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)
.await
.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);
if !url_string.starts_with("http://") && !url_string.starts_with("https://") {
@@ -144,8 +146,8 @@ pub async fn send_http_request(
continue;
}
let name = render::render(&h.name, &workspace, environment_ref);
let value = render::render(&h.value, &workspace, environment_ref);
let name = render::render(&h.name, &vars);
let value = render::render(&h.value, &vars);
let header_name = match HeaderName::from_bytes(name.as_bytes()) {
Ok(n) => n,
@@ -180,8 +182,8 @@ pub async fn send_http_request(
.unwrap_or(empty_value)
.as_str()
.unwrap_or("");
let username = render::render(raw_username, &workspace, environment_ref);
let password = render::render(raw_password, &workspace, environment_ref);
let username = render::render(raw_username, &vars);
let password = render::render(raw_password, &vars);
let auth = format!("{username}:{password}");
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" {
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(
"Authorization",
HeaderValue::from_str(&format!("Bearer {token}")).unwrap(),
@@ -205,8 +207,8 @@ pub async fn send_http_request(
continue;
}
query_params.push((
render::render(&p.name, &workspace, environment_ref),
render::render(&p.value, &workspace, environment_ref),
render::render(&p.name, &vars),
render::render(&p.value, &vars),
));
}
request_builder = request_builder.query(&query_params);
@@ -222,7 +224,7 @@ pub async fn send_http_request(
.unwrap_or(empty_string)
.as_str()
.unwrap_or("");
let body = render::render(raw_text, &workspace, environment_ref);
let body = render::render(raw_text, &vars);
request_builder = request_builder.body(body);
} else if body_type == "application/x-www-form-urlencoded"
&& request_body.contains_key("form")
@@ -249,10 +251,7 @@ pub async fn send_http_request(
.unwrap_or(empty_string)
.as_str()
.unwrap_or_default();
form_params.push((
render::render(name, &workspace, environment_ref),
render::render(value, &workspace, environment_ref),
));
form_params.push((render::render(name, &vars), render::render(value, &vars)));
}
}
request_builder = request_builder.form(&form_params);
@@ -301,13 +300,9 @@ pub async fn send_http_request(
.as_str()
.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() {
multipart::Part::text(render::render(
value_raw,
&workspace,
environment_ref,
))
multipart::Part::text(render::render(value_raw, &vars))
} else {
match fs::read(file_path) {
Ok(f) => multipart::Part::bytes(f),
@@ -324,7 +319,7 @@ pub async fn send_http_request(
.unwrap_or_default();
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
.mime_str(content_type.as_str())
.map_err(|e| e.to_string())?;

View File

@@ -57,7 +57,7 @@ use crate::plugin::{
find_plugins, get_plugin, ImportResult, PluginCapability,
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::window_menu::app_menu;
@@ -176,6 +176,7 @@ async fn cmd_grpc_go(
.await
.map_err(|e| e.to_string())?;
let mut metadata = HashMap::new();
let vars = variables_from_environment(&workspace, environment.as_ref());
// Add rest of metadata
for h in req.clone().metadata.0 {
@@ -187,15 +188,14 @@ async fn cmd_grpc_go(
continue;
}
let name = render::render(&h.name, &workspace, environment.as_ref());
let value = render::render(&h.value, &workspace, environment.as_ref());
let name = render::render(&h.name, &vars);
let value = render::render(&h.value, &vars);
metadata.insert(name, value);
}
if let Some(b) = &req.authentication_type {
let req = req.clone();
let environment_ref = environment.as_ref();
let empty_value = &serde_json::to_value("").unwrap();
let a = req.authentication.0;
@@ -210,15 +210,15 @@ async fn cmd_grpc_go(
.unwrap_or(empty_value)
.as_str()
.unwrap_or("");
let username = render::render(raw_username, &workspace, environment_ref);
let password = render::render(raw_password, &workspace, environment_ref);
let username = render::render(raw_username, &vars);
let password = render::render(raw_password, &vars);
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, &workspace, environment_ref);
let token = render::render(raw_token, &vars);
metadata.insert("Authorization".to_string(), format!("Bearer {token}"));
}
}
@@ -290,11 +290,10 @@ async fn cmd_grpc_go(
let cb = {
let cancelled_rx = cancelled_rx.clone();
let environment = environment.clone();
let workspace = workspace.clone();
let w = w.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() {
@@ -317,9 +316,8 @@ async fn cmd_grpc_go(
Ok(IncomingMsg::Message(raw_msg)) => {
let w = w.clone();
let base_msg = base_msg.clone();
let environment_ref = environment.as_ref();
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)
{
Ok(d_msg) => d_msg,
@@ -371,14 +369,13 @@ async fn cmd_grpc_go(
let w = w.clone();
let base_event = base_msg.clone();
let req = req.clone();
let workspace = workspace.clone();
let environment = environment.clone();
let vars = vars.clone();
let raw_msg = if req.message.is_empty() {
"{}".to_string()
} else {
req.message
};
let msg = render::render(&raw_msg, &workspace, environment.as_ref());
let msg = render::render(&raw_msg, &vars);
upsert_grpc_event(
&w,

View File

@@ -9,16 +9,18 @@ use templates::parse_and_render;
pub fn render_request(r: &HttpRequest, w: &Workspace, e: Option<&Environment>) -> HttpRequest {
let r = r.clone();
let vars = &variables_from_environment(w, e);
HttpRequest {
url: render(r.url.as_str(), w, e),
url: render(r.url.as_str(), vars),
url_parameters: Json(
r.url_parameters
.0
.iter()
.map(|p| HttpUrlParameter {
enabled: p.enabled,
name: render(p.name.as_str(), w, e),
value: render(p.value.as_str(), w, e),
name: render(p.name.as_str(), vars),
value: render(p.value.as_str(), vars),
})
.collect::<Vec<HttpUrlParameter>>(),
),
@@ -28,8 +30,8 @@ pub fn render_request(r: &HttpRequest, w: &Workspace, e: Option<&Environment>) -
.iter()
.map(|p| HttpRequestHeader {
enabled: p.enabled,
name: render(p.name.as_str(), w, e),
value: render(p.value.as_str(), w, e),
name: render(p.name.as_str(), vars),
value: render(p.value.as_str(), vars),
})
.collect::<Vec<HttpRequestHeader>>(),
),
@@ -39,11 +41,11 @@ pub fn render_request(r: &HttpRequest, w: &Workspace, e: Option<&Environment>) -
.iter()
.map(|(k, v)| {
let v = if v.is_string() {
render(v.as_str().unwrap(), w, e)
render(v.as_str().unwrap(), vars)
} else {
v.to_string()
};
(render(k, w, e), JsonValue::from(v))
(render(k, vars), JsonValue::from(v))
})
.collect::<HashMap<String, JsonValue>>(),
),
@@ -53,11 +55,11 @@ pub fn render_request(r: &HttpRequest, w: &Workspace, e: Option<&Environment>) -
.iter()
.map(|(k, v)| {
let v = if v.is_string() {
render(v.as_str().unwrap(), w, e)
render(v.as_str().unwrap(), vars)
} else {
v.to_string()
};
(render(k, w, e), JsonValue::from(v))
(render(k, vars), JsonValue::from(v))
})
.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();
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);
}
parse_and_render(template, variables, None)
recursively_render_variables(&variables, 0)
}
fn add_variable_to_map<'a>(
m: HashMap<&'a str, &'a str>,
variables: &'a Vec<EnvironmentVariable>,
) -> HashMap<&'a str, &'a str> {
pub fn render(template: &str, vars: &HashMap<String, String>) -> String {
parse_and_render(template, vars, None)
}
fn add_variable_to_map(
m: HashMap<String, String>,
variables: &Vec<EnvironmentVariable>,
) -> HashMap<String, String> {
let mut map = m.clone();
for variable in variables {
if !variable.enabled || variable.value.is_empty() {
@@ -87,7 +117,7 @@ fn add_variable_to_map<'a>(
}
let name = variable.name.as_str();
let value = variable.value.as_str();
map.insert(name, value);
map.insert(name.into(), value.into());
}
map

View File

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

View File

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