mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-21 08:59:07 +01:00
Make rendering return Result, and handle infinite recursion
This commit is contained in:
93
src-tauri/Cargo.lock
generated
93
src-tauri/Cargo.lock
generated
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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" }
|
||||
|
||||
43
src-tauri/src/error.rs
Normal file
43
src-tauri/src/error.rs
Normal file
@@ -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<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.to_string().as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@@ -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<R: Runtime>(
|
||||
environment: Option<Environment>,
|
||||
cookie_jar: Option<CookieJar>,
|
||||
cancelled_rx: &mut Receiver<bool>,
|
||||
) -> Result<HttpResponse, String> {
|
||||
) -> Result<HttpResponse> {
|
||||
let plugin_manager = window.state::<PluginManager>();
|
||||
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<R: Runtime>(
|
||||
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<R: Runtime>(
|
||||
serde_json::from_value(json_cookie).expect("Failed to deserialize cookie")
|
||||
})
|
||||
.map(|c| Ok(c))
|
||||
.collect::<Vec<Result<_, ()>>>();
|
||||
.collect::<Vec<Result<_>>>();
|
||||
|
||||
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<R: Runtime>(
|
||||
));
|
||||
}
|
||||
|
||||
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<R: Runtime>(
|
||||
}
|
||||
};
|
||||
|
||||
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<R: Runtime>(
|
||||
} 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<R: Runtime>(
|
||||
}
|
||||
}
|
||||
|
||||
let (resp_tx, resp_rx) = oneshot::channel::<Result<Response, reqwest::Error>>();
|
||||
let (resp_tx, resp_rx) = oneshot::channel::<std::result::Result<Response, reqwest::Error>>();
|
||||
let (done_tx, done_rx) = oneshot::channel::<HttpResponse>();
|
||||
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
@@ -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<R: Runtime>(
|
||||
template: &str,
|
||||
workspace_id: &str,
|
||||
environment_id: Option<&str>,
|
||||
) -> Result<String, String> {
|
||||
) -> YaakResult<String> {
|
||||
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<R: Runtime>(
|
||||
RenderPurpose::Preview,
|
||||
),
|
||||
)
|
||||
.await;
|
||||
Ok(rendered)
|
||||
.await?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -189,18 +191,15 @@ async fn cmd_grpc_go<R: Runtime>(
|
||||
window: WebviewWindow<R>,
|
||||
plugin_manager: State<'_, PluginManager>,
|
||||
grpc_handle: State<'_, Mutex<GrpcHandle>>,
|
||||
) -> Result<String, String> {
|
||||
) -> YaakResult<String> {
|
||||
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<R: Runtime>(
|
||||
RenderPurpose::Send,
|
||||
),
|
||||
)
|
||||
.await;
|
||||
.await?;
|
||||
let mut metadata = BTreeMap::new();
|
||||
|
||||
// Add the rest of metadata
|
||||
@@ -242,33 +241,27 @@ async fn cmd_grpc_go<R: Runtime>(
|
||||
})
|
||||
.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<R: Runtime>(
|
||||
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<R: Runtime>(
|
||||
},
|
||||
&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<R: Runtime>(
|
||||
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<R: Runtime>(
|
||||
RenderPurpose::Send,
|
||||
),
|
||||
)
|
||||
.await;
|
||||
.await?;
|
||||
|
||||
upsert_grpc_event(
|
||||
&window,
|
||||
@@ -455,8 +446,7 @@ async fn cmd_grpc_go<R: Runtime>(
|
||||
},
|
||||
&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<HttpResponse, String> {
|
||||
) -> YaakResult<HttpResponse> {
|
||||
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<HttpResponse, String> {
|
||||
let response = create_default_http_response(&window, &request.id, &UpdateSource::Window)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
) -> YaakResult<HttpResponse> {
|
||||
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| {
|
||||
|
||||
@@ -90,7 +90,8 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
|
||||
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<R: Runtime>(
|
||||
.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) => {
|
||||
|
||||
@@ -12,7 +12,7 @@ pub async fn render_template<T: TemplateCallback>(
|
||||
base_environment: &Environment,
|
||||
environment: Option<&Environment>,
|
||||
cb: &T,
|
||||
) -> String {
|
||||
) -> yaak_templates::error::Result<String> {
|
||||
let vars = &make_vars_hashmap(base_environment, environment);
|
||||
render(template, vars, cb).await
|
||||
}
|
||||
@@ -22,7 +22,7 @@ pub async fn render_json_value<T: TemplateCallback>(
|
||||
base_environment: &Environment,
|
||||
environment: Option<&Environment>,
|
||||
cb: &T,
|
||||
) -> Value {
|
||||
) -> yaak_templates::error::Result<Value> {
|
||||
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<T: TemplateCallback>(
|
||||
base_environment: &Environment,
|
||||
environment: Option<&Environment>,
|
||||
cb: &T,
|
||||
) -> GrpcRequest {
|
||||
) -> yaak_templates::error::Result<GrpcRequest> {
|
||||
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<T: TemplateCallback>(
|
||||
@@ -65,15 +65,15 @@ pub async fn render_http_request<T: TemplateCallback>(
|
||||
base_environment: &Environment,
|
||||
environment: Option<&Environment>,
|
||||
cb: &T,
|
||||
) -> HttpRequest {
|
||||
) -> yaak_templates::error::Result<HttpRequest> {
|
||||
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<T: TemplateCallback>(
|
||||
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<T: TemplateCallback>(
|
||||
template: &str,
|
||||
vars: &HashMap<String, String>,
|
||||
cb: &T,
|
||||
) -> String {
|
||||
) -> yaak_templates::error::Result<String> {
|
||||
parse_and_render(template, vars, cb).await
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use tauri::{
|
||||
mod branch;
|
||||
mod callbacks;
|
||||
mod commands;
|
||||
mod error;
|
||||
pub mod error;
|
||||
mod fetch;
|
||||
mod git;
|
||||
mod merge;
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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, };
|
||||
|
||||
@@ -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, };
|
||||
|
||||
@@ -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<R: Runtime>(app_handle: AppHandle<R>) -> Result<LicenseCheckStatus> {
|
||||
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]
|
||||
|
||||
@@ -5,7 +5,7 @@ use tauri::{
|
||||
};
|
||||
|
||||
mod commands;
|
||||
mod errors;
|
||||
pub mod error;
|
||||
mod license;
|
||||
|
||||
use crate::commands::{activate, check, deactivate};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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<String, String>,
|
||||
purpose: RenderPurpose,
|
||||
) -> Result<Option<String>> {
|
||||
) -> yaak_templates::error::Result<String> {
|
||||
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<R: Runtime>(
|
||||
|
||||
@@ -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<String, String>) -> Result<String, String> {
|
||||
async fn run(&self, fn_name: &str, args: HashMap<String, String>) -> Result<String> {
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
|
||||
22
src-tauri/yaak-templates/src/error.rs
Normal file
22
src-tauri/yaak-templates/src/error.rs
Normal file
@@ -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<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.to_string().as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@@ -1,6 +1,7 @@
|
||||
pub mod format;
|
||||
pub mod parser;
|
||||
pub mod renderer;
|
||||
pub mod error;
|
||||
|
||||
pub use parser::*;
|
||||
pub use renderer::*;
|
||||
|
||||
@@ -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<String, String>,
|
||||
) -> impl Future<Output = Result<String, String>> + Send;
|
||||
) -> impl Future<Output = Result<String>> + Send;
|
||||
}
|
||||
|
||||
pub async fn render_json_value_raw<T: TemplateCallback>(
|
||||
v: serde_json::Value,
|
||||
vars: &HashMap<String, String>,
|
||||
cb: &T,
|
||||
) -> serde_json::Value {
|
||||
match v {
|
||||
serde_json::Value::String(s) => json!(parse_and_render(&s, vars, cb).await),
|
||||
) -> Result<serde_json::Value> {
|
||||
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<T: TemplateCallback>(
|
||||
template: &str,
|
||||
vars: &HashMap<String, String>,
|
||||
cb: &T,
|
||||
depth: usize,
|
||||
) -> Result<String> {
|
||||
let mut p = Parser::new(template);
|
||||
let tokens = p.parse();
|
||||
render(tokens, vars, cb, depth + 1).await
|
||||
}
|
||||
|
||||
pub async fn parse_and_render<T: TemplateCallback>(
|
||||
template: &str,
|
||||
vars: &HashMap<String, String>,
|
||||
cb: &T,
|
||||
) -> String {
|
||||
let mut p = Parser::new(template);
|
||||
let tokens = p.parse();
|
||||
render(tokens, vars, cb).await
|
||||
) -> Result<String> {
|
||||
parse_and_render_with_depth(template, vars, cb, 1).await
|
||||
}
|
||||
|
||||
pub async fn render<T: TemplateCallback>(
|
||||
tokens: Tokens,
|
||||
vars: &HashMap<String, String>,
|
||||
cb: &T,
|
||||
) -> String {
|
||||
mut depth: usize,
|
||||
) -> Result<String> {
|
||||
depth += 1;
|
||||
if depth > MAX_DEPTH {
|
||||
return Err(RenderStackExceededError);
|
||||
}
|
||||
|
||||
let mut doc_str: Vec<String> = 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<T: TemplateCallback>(
|
||||
val: Val,
|
||||
vars: &HashMap<String, String>,
|
||||
cb: &T,
|
||||
) -> String {
|
||||
match val {
|
||||
depth: usize,
|
||||
) -> Result<String> {
|
||||
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<T: TemplateCallback>(
|
||||
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<T: TemplateCallback>(
|
||||
}
|
||||
}
|
||||
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<String, String>,
|
||||
) -> Result<String, String> {
|
||||
async fn run(&self, _fn_name: &str, _args: HashMap<String, String>) -> Result<String> {
|
||||
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<String, String>,
|
||||
) -> Result<String, String> {
|
||||
async fn run(&self, fn_name: &str, args: HashMap<String, String>) -> Result<String> {
|
||||
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<String, String>,
|
||||
) -> Result<String, String> {
|
||||
async fn run(&self, fn_name: &str, args: HashMap<String, String>) -> Result<String> {
|
||||
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<String, String>,
|
||||
) -> Result<String, String> {
|
||||
Err("Failed to do it!".to_string())
|
||||
async fn run(&self, _fn_name: &str, _args: HashMap<String, String>) -> Result<String> {
|
||||
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<String, String>,
|
||||
) -> Result<String, String> {
|
||||
async fn run(&self, _fn_name: &str, _args: HashMap<String, String>) -> Result<String> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ pub(crate) async fn send<R: Runtime>(
|
||||
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<R: Runtime>(
|
||||
RenderPurpose::Send,
|
||||
),
|
||||
)
|
||||
.await;
|
||||
.await?;
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
if let Some(auth_name) = request.authentication_type.clone() {
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
mod commands;
|
||||
mod connect;
|
||||
mod error;
|
||||
pub mod error;
|
||||
mod manager;
|
||||
mod render;
|
||||
|
||||
|
||||
@@ -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<T: TemplateCallback>(
|
||||
base_environment: &Environment,
|
||||
environment: Option<&Environment>,
|
||||
cb: &T,
|
||||
) -> WebsocketRequest {
|
||||
) -> Result<WebsocketRequest> {
|
||||
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()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user