mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-01-15 13:43:39 +01:00
Async template functions working
This commit is contained in:
4
plugin-runtime-types/src/gen/CallTemplateFunctionArgs.ts
Normal file
4
plugin-runtime-types/src/gen/CallTemplateFunctionArgs.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { CallTemplateFunctionPurpose } from "./CallTemplateFunctionPurpose";
|
||||
|
||||
export type CallTemplateFunctionArgs = { purpose: CallTemplateFunctionPurpose, values: { [key: string]: string }, };
|
||||
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type CallTemplateFunctionPurpose = { "type": "send" } | { "type": "preview" };
|
||||
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { CallTemplateFunctionArgs } from "./CallTemplateFunctionArgs";
|
||||
|
||||
export type CallTemplateFunctionRequest = { name: string, pluginRefId: string, args: CallTemplateFunctionArgs, };
|
||||
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { TemplateFunction } from "./TemplateFunction";
|
||||
|
||||
export type GetTemplateFunctionsResponse = { functions: Array<TemplateFunction>, pluginRefId: string, };
|
||||
@@ -2,6 +2,7 @@
|
||||
import type { BootRequest } from "./BootRequest";
|
||||
import type { BootResponse } from "./BootResponse";
|
||||
import type { CallHttpRequestActionRequest } from "./CallHttpRequestActionRequest";
|
||||
import type { CallTemplateFunctionRequest } from "./CallTemplateFunctionRequest";
|
||||
import type { CopyTextRequest } from "./CopyTextRequest";
|
||||
import type { EmptyResponse } from "./EmptyResponse";
|
||||
import type { ExportHttpRequestRequest } from "./ExportHttpRequestRequest";
|
||||
@@ -11,6 +12,7 @@ import type { FilterResponse } from "./FilterResponse";
|
||||
import type { GetHttpRequestActionsResponse } from "./GetHttpRequestActionsResponse";
|
||||
import type { GetHttpRequestByIdRequest } from "./GetHttpRequestByIdRequest";
|
||||
import type { GetHttpRequestByIdResponse } from "./GetHttpRequestByIdResponse";
|
||||
import type { GetTemplateFunctionsResponse } from "./GetTemplateFunctionsResponse";
|
||||
import type { ImportRequest } from "./ImportRequest";
|
||||
import type { ImportResponse } from "./ImportResponse";
|
||||
import type { RenderHttpRequestRequest } from "./RenderHttpRequestRequest";
|
||||
@@ -19,4 +21,4 @@ import type { SendHttpRequestRequest } from "./SendHttpRequestRequest";
|
||||
import type { SendHttpRequestResponse } from "./SendHttpRequestResponse";
|
||||
import type { ShowToastRequest } from "./ShowToastRequest";
|
||||
|
||||
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "get_http_request_actions_request" } | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "copy_text_request" } & CopyTextRequest | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "empty_response" } & EmptyResponse;
|
||||
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "get_http_request_actions_request" } | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "copy_text_request" } & CopyTextRequest | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "empty_response" } & EmptyResponse;
|
||||
|
||||
4
plugin-runtime-types/src/gen/TemplateFunction.ts
Normal file
4
plugin-runtime-types/src/gen/TemplateFunction.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { TemplateFunctionArg } from "./TemplateFunctionArg";
|
||||
|
||||
export type TemplateFunction = { name: string, args: Array<TemplateFunctionArg>, };
|
||||
6
plugin-runtime-types/src/gen/TemplateFunctionArg.ts
Normal file
6
plugin-runtime-types/src/gen/TemplateFunctionArg.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { TemplateFunctionHttpRequestArg } from "./TemplateFunctionHttpRequestArg";
|
||||
import type { TemplateFunctionSelectArg } from "./TemplateFunctionSelectArg";
|
||||
import type { TemplateFunctionTextArg } from "./TemplateFunctionTextArg";
|
||||
|
||||
export type TemplateFunctionArg = { "type": "text" } & TemplateFunctionTextArg | { "type": "select" } & TemplateFunctionSelectArg | { "type": "http_request" } & TemplateFunctionHttpRequestArg;
|
||||
3
plugin-runtime-types/src/gen/TemplateFunctionBaseArg.ts
Normal file
3
plugin-runtime-types/src/gen/TemplateFunctionBaseArg.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type TemplateFunctionBaseArg = { name: string, optional?: boolean | null, label?: string | null, defaultValue?: string | null, };
|
||||
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type TemplateFunctionHttpRequestArg = { name: string, optional?: boolean | null, label?: string | null, defaultValue?: string | null, };
|
||||
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { TemplateFunctionSelectOption } from "./TemplateFunctionSelectOption";
|
||||
|
||||
export type TemplateFunctionSelectArg = { options: Array<TemplateFunctionSelectOption>, name: string, optional?: boolean | null, label?: string | null, defaultValue?: string | null, };
|
||||
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type TemplateFunctionSelectOption = { name: string, value: string, };
|
||||
3
plugin-runtime-types/src/gen/TemplateFunctionTextArg.ts
Normal file
3
plugin-runtime-types/src/gen/TemplateFunctionTextArg.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type TemplateFunctionTextArg = { placeholder?: string | null, name: string, optional?: boolean | null, label?: string | null, defaultValue?: string | null, };
|
||||
@@ -4,12 +4,16 @@ export type * from './themes';
|
||||
// TODO: The next ts-rs release includes the ability to put everything in 1 file!
|
||||
export * from './gen/BootRequest';
|
||||
export * from './gen/BootResponse';
|
||||
export * from './gen/CallHttpRequestActionRequest';
|
||||
export * from './gen/CallHttpRequestActionArgs';
|
||||
export * from './gen/CallTemplateFunctionPurpose';
|
||||
export * from './gen/CallHttpRequestActionRequest';
|
||||
export * from './gen/CallTemplateFunctionRequest';
|
||||
export * from './gen/CallTemplateFunctionArgs';
|
||||
export * from './gen/Cookie';
|
||||
export * from './gen/CookieDomain';
|
||||
export * from './gen/CookieExpires';
|
||||
export * from './gen/CookieJar';
|
||||
export * from './gen/CopyTextRequest';
|
||||
export * from './gen/EmptyResponse';
|
||||
export * from './gen/Environment';
|
||||
export * from './gen/EnvironmentVariable';
|
||||
@@ -20,8 +24,8 @@ export * from './gen/FilterResponse';
|
||||
export * from './gen/Folder';
|
||||
export * from './gen/GetHttpRequestActionsResponse';
|
||||
export * from './gen/GetHttpRequestByIdRequest';
|
||||
export * from './gen/CopyTextRequest';
|
||||
export * from './gen/GetHttpRequestByIdResponse';
|
||||
export * from './gen/GetTemplateFunctionsResponse';
|
||||
export * from './gen/GrpcConnection';
|
||||
export * from './gen/GrpcEvent';
|
||||
export * from './gen/GrpcMetadataEntry';
|
||||
@@ -39,12 +43,19 @@ export * from './gen/InternalEvent';
|
||||
export * from './gen/InternalEventPayload';
|
||||
export * from './gen/KeyValue';
|
||||
export * from './gen/Model';
|
||||
export * from './gen/SendHttpRequestRequest';
|
||||
export * from './gen/ToastVariant';
|
||||
export * from './gen/ShowToastRequest';
|
||||
export * from './gen/RenderHttpRequestRequest';
|
||||
export * from './gen/RenderHttpRequestResponse';
|
||||
export * from './gen/SendHttpRequestRequest';
|
||||
export * from './gen/SendHttpRequestResponse';
|
||||
export * from './gen/SendHttpRequestResponse';
|
||||
export * from './gen/Settings';
|
||||
export * from './gen/ShowToastRequest';
|
||||
export * from './gen/TemplateFunction';
|
||||
export * from './gen/TemplateFunctionArg';
|
||||
export * from './gen/TemplateFunctionBaseArg';
|
||||
export * from './gen/TemplateFunctionHttpRequestArg';
|
||||
export * from './gen/TemplateFunctionSelectArg';
|
||||
export * from './gen/TemplateFunctionSelectOption';
|
||||
export * from './gen/TemplateFunctionTextArg';
|
||||
export * from './gen/ToastVariant';
|
||||
export * from './gen/Workspace';
|
||||
|
||||
@@ -6,7 +6,7 @@ import { SendHttpRequestRequest } from '../gen/SendHttpRequestRequest';
|
||||
import { SendHttpRequestResponse } from '../gen/SendHttpRequestResponse';
|
||||
import { ShowToastRequest } from '../gen/ShowToastRequest';
|
||||
|
||||
export type YaakContext = {
|
||||
export type Context = {
|
||||
clipboard: {
|
||||
copyText(text: string): void;
|
||||
};
|
||||
@@ -1,13 +1,13 @@
|
||||
import { YaakContext } from './context';
|
||||
import { Context } from './Context';
|
||||
|
||||
export type FilterPluginResponse = string[];
|
||||
|
||||
export type FilterPlugin = {
|
||||
name: string;
|
||||
description?: string;
|
||||
canFilter(ctx: YaakContext, args: { mimeType: string }): Promise<boolean>;
|
||||
canFilter(ctx: Context, args: { mimeType: string }): Promise<boolean>;
|
||||
onFilter(
|
||||
ctx: YaakContext,
|
||||
ctx: Context,
|
||||
args: { payload: string; mimeType: string },
|
||||
): Promise<FilterPluginResponse>;
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import { CallHttpRequestActionArgs } from '../gen/CallHttpRequestActionArgs';
|
||||
import { HttpRequestAction } from '../gen/HttpRequestAction';
|
||||
import { YaakContext } from './context';
|
||||
import { Context } from './Context';
|
||||
|
||||
export type HttpRequestActionPlugin = HttpRequestAction & {
|
||||
onSelect(ctx: YaakContext, args: CallHttpRequestActionArgs): Promise<void> | void;
|
||||
onSelect(ctx: Context, args: CallHttpRequestActionArgs): Promise<void> | void;
|
||||
};
|
||||
@@ -3,7 +3,7 @@ import { Folder } from '../gen/Folder';
|
||||
import { HttpRequest } from '../gen/HttpRequest';
|
||||
import { Workspace } from '../gen/Workspace';
|
||||
import { AtLeast } from '../helpers';
|
||||
import { YaakContext } from './context';
|
||||
import { Context } from './Context';
|
||||
|
||||
export type ImportPluginResponse = null | {
|
||||
workspaces: AtLeast<Workspace, 'name' | 'id' | 'model'>[];
|
||||
@@ -15,5 +15,5 @@ export type ImportPluginResponse = null | {
|
||||
export type ImporterPlugin = {
|
||||
name: string;
|
||||
description?: string;
|
||||
onImport(ctx: YaakContext, args: { text: string }): Promise<ImportPluginResponse>;
|
||||
onImport(ctx: Context, args: { text: string }): Promise<ImportPluginResponse>;
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
import { CallTemplateFunctionArgs } from '../gen/CallTemplateFunctionArgs';
|
||||
import { TemplateFunction } from '../gen/TemplateFunction';
|
||||
import { Context } from './Context';
|
||||
|
||||
export type TemplateFunctionPlugin = TemplateFunction & {
|
||||
onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string>;
|
||||
};
|
||||
8
plugin-runtime-types/src/plugins/ThemePlugin.ts
Normal file
8
plugin-runtime-types/src/plugins/ThemePlugin.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { Theme } from '../themes';
|
||||
import { Context } from './Context';
|
||||
|
||||
export type ThemePlugin = {
|
||||
name: string;
|
||||
description?: string;
|
||||
getTheme(ctx: Context, fileContents: string): Promise<Theme>;
|
||||
};
|
||||
@@ -1,16 +1,18 @@
|
||||
import { FilterPlugin } from './filter';
|
||||
import { HttpRequestActionPlugin } from './httpRequestAction';
|
||||
import { ImporterPlugin } from './import';
|
||||
import { ThemePlugin } from './theme';
|
||||
import { FilterPlugin } from './FilterPlugin';
|
||||
import { HttpRequestActionPlugin } from './HttpRequestActionPlugin';
|
||||
import { ImporterPlugin } from './ImporterPlugin';
|
||||
import { TemplateFunctionPlugin } from './TemplateFunctionPlugin';
|
||||
import { ThemePlugin } from './ThemePlugin';
|
||||
|
||||
export type { YaakContext } from './context';
|
||||
export type { Context } from './Context';
|
||||
|
||||
/**
|
||||
* The global structure of a Yaak plugin
|
||||
*/
|
||||
export type YaakPlugin = {
|
||||
export type Plugin = {
|
||||
importer?: ImporterPlugin;
|
||||
theme?: ThemePlugin;
|
||||
filter?: FilterPlugin;
|
||||
httpRequestActions?: HttpRequestActionPlugin[];
|
||||
templateFunctions?: TemplateFunctionPlugin[];
|
||||
};
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import { Theme } from '../themes';
|
||||
import { YaakContext } from './context';
|
||||
|
||||
export type ThemePlugin = {
|
||||
name: string;
|
||||
description?: string;
|
||||
getTheme(ctx: YaakContext, fileContents: string): Promise<Theme>;
|
||||
};
|
||||
@@ -6,9 +6,11 @@ import {
|
||||
InternalEventPayload,
|
||||
RenderHttpRequestResponse,
|
||||
SendHttpRequestResponse,
|
||||
TemplateFunction,
|
||||
} from '@yaakapp/api';
|
||||
import { YaakContext } from '@yaakapp/api/lib/plugins/context';
|
||||
import { Context } from '@yaakapp/api';
|
||||
import { HttpRequestActionPlugin } from '@yaakapp/api/lib/plugins/httpRequestAction';
|
||||
import { TemplateFunctionPlugin } from '@yaakapp/api/lib/plugins/TemplateFunctionPlugin';
|
||||
import interceptStdout from 'intercept-stdout';
|
||||
import * as console from 'node:console';
|
||||
import { readFileSync } from 'node:fs';
|
||||
@@ -88,7 +90,7 @@ new Promise<void>(async (resolve, reject) => {
|
||||
return promise as unknown as Promise<T>;
|
||||
}
|
||||
|
||||
const ctx: YaakContext = {
|
||||
const ctx: Context = {
|
||||
clipboard: {
|
||||
async copyText(text) {
|
||||
await sendAndWaitForReply({ type: 'copy_text_request', text });
|
||||
@@ -181,8 +183,8 @@ new Promise<void>(async (resolve, reject) => {
|
||||
const reply: HttpRequestAction[] = mod.plugin.httpRequestActions.map(
|
||||
(a: HttpRequestActionPlugin) => ({
|
||||
...a,
|
||||
onSelect: undefined,
|
||||
// Add everything except onSelect
|
||||
onSelect: undefined,
|
||||
}),
|
||||
);
|
||||
const replyPayload: InternalEventPayload = {
|
||||
@@ -194,6 +196,26 @@ new Promise<void>(async (resolve, reject) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
payload.type === 'get_template_functions_request' &&
|
||||
Array.isArray(mod.plugin?.templateFunctions)
|
||||
) {
|
||||
const reply: TemplateFunction[] = mod.plugin.templateFunctions.map(
|
||||
(a: TemplateFunctionPlugin) => ({
|
||||
...a,
|
||||
// Add everything except render
|
||||
onRender: undefined,
|
||||
}),
|
||||
);
|
||||
const replyPayload: InternalEventPayload = {
|
||||
type: 'get_template_functions_response',
|
||||
pluginRefId,
|
||||
functions: reply,
|
||||
};
|
||||
sendPayload(replyPayload, replyId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
payload.type === 'call_http_request_action_request' &&
|
||||
Array.isArray(mod.plugin?.httpRequestActions)
|
||||
@@ -205,6 +227,18 @@ new Promise<void>(async (resolve, reject) => {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
payload.type === 'call_template_function_request' &&
|
||||
Array.isArray(mod.plugin?.templateFunctions)
|
||||
) {
|
||||
const action = mod.plugin.templateFunctions.find((a) => a.name === payload.name);
|
||||
if (typeof action?.onRender() === 'function') {
|
||||
await action.onRender(ctx, payload.args);
|
||||
sendEmpty(replyId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Plugin call threw exception', payload.type, err);
|
||||
// TODO: Return errors to server
|
||||
|
||||
5
src-tauri/Cargo.lock
generated
5
src-tauri/Cargo.lock
generated
@@ -6218,9 +6218,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.39.2"
|
||||
version = "1.39.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1"
|
||||
checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
@@ -7651,6 +7651,7 @@ dependencies = [
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"ts-rs",
|
||||
]
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::fs::{create_dir_all, File};
|
||||
use std::io::Write;
|
||||
@@ -6,8 +7,8 @@ use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::render::variables_from_environment;
|
||||
use crate::{render, response_err};
|
||||
use crate::render::render_request;
|
||||
use crate::response_err;
|
||||
use base64::Engine;
|
||||
use http::header::{ACCEPT, USER_AGENT};
|
||||
use http::{HeaderMap, HeaderName, HeaderValue};
|
||||
@@ -16,6 +17,7 @@ use mime_guess::Mime;
|
||||
use reqwest::redirect::Policy;
|
||||
use reqwest::Method;
|
||||
use reqwest::{multipart, Url};
|
||||
use serde_json::Value;
|
||||
use tauri::{Manager, Runtime, WebviewWindow};
|
||||
use tokio::sync::oneshot;
|
||||
use tokio::sync::watch::Receiver;
|
||||
@@ -26,19 +28,18 @@ use yaak_models::queries::{get_workspace, update_response_if_id, upsert_cookie_j
|
||||
|
||||
pub async fn send_http_request<R: Runtime>(
|
||||
window: &WebviewWindow<R>,
|
||||
request: HttpRequest,
|
||||
request: &HttpRequest,
|
||||
response: &HttpResponse,
|
||||
environment: Option<Environment>,
|
||||
cookie_jar: Option<CookieJar>,
|
||||
cancel_rx: &mut Receiver<bool>,
|
||||
) -> Result<HttpResponse, String> {
|
||||
let environment_ref = environment.as_ref();
|
||||
let workspace = get_workspace(window, &request.workspace_id)
|
||||
.await
|
||||
.expect("Failed to get Workspace");
|
||||
let vars = variables_from_environment(&workspace, environment_ref);
|
||||
let rendered_request = render_request(&request, &workspace, environment.as_ref()).await;
|
||||
|
||||
let mut url_string = render::render(&request.url, &vars);
|
||||
let mut url_string = rendered_request.url;
|
||||
|
||||
url_string = ensure_proto(&url_string);
|
||||
if !url_string.starts_with("http://") && !url_string.starts_with("https://") {
|
||||
@@ -115,7 +116,7 @@ pub async fn send_http_request<R: Runtime>(
|
||||
}
|
||||
};
|
||||
|
||||
let m = Method::from_bytes(request.method.to_uppercase().as_bytes())
|
||||
let m = Method::from_bytes(rendered_request.method.to_uppercase().as_bytes())
|
||||
.expect("Failed to create method");
|
||||
let mut request_builder = client.request(m, url);
|
||||
|
||||
@@ -138,7 +139,7 @@ pub async fn send_http_request<R: Runtime>(
|
||||
// );
|
||||
// }
|
||||
|
||||
for h in request.headers {
|
||||
for h in rendered_request.headers {
|
||||
if h.name.is_empty() && h.value.is_empty() {
|
||||
continue;
|
||||
}
|
||||
@@ -147,17 +148,14 @@ pub async fn send_http_request<R: Runtime>(
|
||||
continue;
|
||||
}
|
||||
|
||||
let name = render::render(&h.name, &vars);
|
||||
let value = render::render(&h.value, &vars);
|
||||
|
||||
let header_name = match HeaderName::from_bytes(name.as_bytes()) {
|
||||
let header_name = match HeaderName::from_bytes(h.name.as_bytes()) {
|
||||
Ok(n) => n,
|
||||
Err(e) => {
|
||||
error!("Failed to create header name: {}", e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let header_value = match HeaderValue::from_str(value.as_str()) {
|
||||
let header_value = match HeaderValue::from_str(h.value.as_str()) {
|
||||
Ok(n) => n,
|
||||
Err(e) => {
|
||||
error!("Failed to create header value: {}", e);
|
||||
@@ -168,23 +166,21 @@ pub async fn send_http_request<R: Runtime>(
|
||||
headers.insert(header_name, header_value);
|
||||
}
|
||||
|
||||
if let Some(b) = &request.authentication_type {
|
||||
if let Some(b) = &rendered_request.authentication_type {
|
||||
let empty_value = &serde_json::to_value("").unwrap();
|
||||
let a = request.authentication;
|
||||
let a = rendered_request.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
|
||||
.unwrap_or_default();
|
||||
let password = a
|
||||
.get("password")
|
||||
.unwrap_or(empty_value)
|
||||
.as_str()
|
||||
.unwrap_or("");
|
||||
let username = render::render(raw_username, &vars);
|
||||
let password = render::render(raw_password, &vars);
|
||||
.unwrap_or_default();
|
||||
|
||||
let auth = format!("{username}:{password}");
|
||||
let encoded = base64::engine::general_purpose::STANDARD_NO_PAD.encode(auth);
|
||||
@@ -193,8 +189,11 @@ pub async fn send_http_request<R: Runtime>(
|
||||
HeaderValue::from_str(&format!("Basic {}", encoded)).unwrap(),
|
||||
);
|
||||
} 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);
|
||||
let token = a
|
||||
.get("token")
|
||||
.unwrap_or(empty_value)
|
||||
.as_str()
|
||||
.unwrap_or_default();
|
||||
headers.insert(
|
||||
"Authorization",
|
||||
HeaderValue::from_str(&format!("Bearer {token}")).unwrap(),
|
||||
@@ -203,56 +202,38 @@ pub async fn send_http_request<R: Runtime>(
|
||||
}
|
||||
|
||||
let mut query_params = Vec::new();
|
||||
for p in request.url_parameters {
|
||||
for p in rendered_request.url_parameters {
|
||||
if !p.enabled || p.name.is_empty() {
|
||||
continue;
|
||||
}
|
||||
query_params.push((
|
||||
render::render(&p.name, &vars),
|
||||
render::render(&p.value, &vars),
|
||||
));
|
||||
query_params.push((p.name, p.value));
|
||||
}
|
||||
request_builder = request_builder.query(&query_params);
|
||||
|
||||
if let Some(body_type) = &request.body_type {
|
||||
let empty_string = &serde_json::to_value("").unwrap();
|
||||
let empty_bool = &serde_json::to_value(false).unwrap();
|
||||
let request_body = request.body;
|
||||
|
||||
let request_body = rendered_request.body;
|
||||
if let Some(body_type) = &rendered_request.body_type {
|
||||
if request_body.contains_key("text") {
|
||||
let raw_text = request_body
|
||||
.get("text")
|
||||
.unwrap_or(empty_string)
|
||||
.as_str()
|
||||
.unwrap_or("");
|
||||
let body = render::render(raw_text, &vars);
|
||||
request_builder = request_builder.body(body);
|
||||
let body = get_str_h(&request_body, "text");
|
||||
request_builder = request_builder.body(body.to_owned());
|
||||
} else if body_type == "application/x-www-form-urlencoded"
|
||||
&& request_body.contains_key("form")
|
||||
{
|
||||
let mut form_params = Vec::new();
|
||||
let form = request_body.get("form");
|
||||
if let Some(f) = form {
|
||||
for p in f.as_array().unwrap_or(&Vec::new()) {
|
||||
let enabled = p
|
||||
.get("enabled")
|
||||
.unwrap_or(empty_bool)
|
||||
.as_bool()
|
||||
.unwrap_or(false);
|
||||
let name = p
|
||||
.get("name")
|
||||
.unwrap_or(empty_string)
|
||||
.as_str()
|
||||
.unwrap_or_default();
|
||||
if !enabled || name.is_empty() {
|
||||
continue;
|
||||
match f.as_array() {
|
||||
None => {}
|
||||
Some(a) => {
|
||||
for p in a {
|
||||
let enabled = get_bool(p, "enabled");
|
||||
let name = get_str(p, "name");
|
||||
if !enabled || name.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let value = get_str(p, "value");
|
||||
form_params.push((name, value));
|
||||
}
|
||||
}
|
||||
let value = p
|
||||
.get("value")
|
||||
.unwrap_or(empty_string)
|
||||
.as_str()
|
||||
.unwrap_or_default();
|
||||
form_params.push((render::render(name, &vars), render::render(value, &vars)));
|
||||
}
|
||||
}
|
||||
request_builder = request_builder.form(&form_params);
|
||||
@@ -274,77 +255,59 @@ pub async fn send_http_request<R: Runtime>(
|
||||
} else if body_type == "multipart/form-data" && request_body.contains_key("form") {
|
||||
let mut multipart_form = multipart::Form::new();
|
||||
if let Some(form_definition) = request_body.get("form") {
|
||||
for p in form_definition.as_array().unwrap_or(&Vec::new()) {
|
||||
let enabled = p
|
||||
.get("enabled")
|
||||
.unwrap_or(empty_bool)
|
||||
.as_bool()
|
||||
.unwrap_or(false);
|
||||
let name_raw = p
|
||||
.get("name")
|
||||
.unwrap_or(empty_string)
|
||||
.as_str()
|
||||
.unwrap_or_default();
|
||||
match form_definition.as_array() {
|
||||
None => {}
|
||||
Some(fd) => {
|
||||
for p in fd {
|
||||
let enabled = get_bool(p, "enabled");
|
||||
let name = get_str(p, "name").to_string();
|
||||
|
||||
if !enabled || name_raw.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let file_path = p
|
||||
.get("file")
|
||||
.unwrap_or(empty_string)
|
||||
.as_str()
|
||||
.unwrap_or_default();
|
||||
let value_raw = p
|
||||
.get("value")
|
||||
.unwrap_or(empty_string)
|
||||
.as_str()
|
||||
.unwrap_or_default();
|
||||
|
||||
let name = render::render(name_raw, &vars);
|
||||
let mut part = if file_path.is_empty() {
|
||||
multipart::Part::text(render::render(value_raw, &vars))
|
||||
} else {
|
||||
match fs::read(file_path) {
|
||||
Ok(f) => multipart::Part::bytes(f),
|
||||
Err(e) => {
|
||||
return response_err(response, e.to_string(), window).await;
|
||||
if !enabled || name.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let file_path = get_str(p, "file").to_owned();
|
||||
let value = get_str(p, "value").to_owned();
|
||||
|
||||
let mut part = if file_path.is_empty() {
|
||||
multipart::Part::text(value.clone())
|
||||
} else {
|
||||
match fs::read(file_path.clone()) {
|
||||
Ok(f) => multipart::Part::bytes(f),
|
||||
Err(e) => {
|
||||
return response_err(response, e.to_string(), window).await;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let content_type = get_str(p, "contentType");
|
||||
|
||||
// Set or guess mimetype
|
||||
if !content_type.is_empty() {
|
||||
part = part.mime_str(content_type).map_err(|e| e.to_string())?;
|
||||
} else if !file_path.is_empty() {
|
||||
let default_mime =
|
||||
Mime::from_str("application/octet-stream").unwrap();
|
||||
let mime =
|
||||
mime_guess::from_path(file_path.clone()).first_or(default_mime);
|
||||
part = part
|
||||
.mime_str(mime.essence_str())
|
||||
.map_err(|e| e.to_string())?;
|
||||
}
|
||||
|
||||
// Set file path if not empty
|
||||
if !file_path.is_empty() {
|
||||
let filename = PathBuf::from(file_path)
|
||||
.file_name()
|
||||
.unwrap_or_default()
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
part = part.file_name(filename);
|
||||
}
|
||||
|
||||
multipart_form = multipart_form.part(name, part);
|
||||
}
|
||||
};
|
||||
|
||||
let ct_raw = p
|
||||
.get("contentType")
|
||||
.unwrap_or(empty_string)
|
||||
.as_str()
|
||||
.unwrap_or_default();
|
||||
|
||||
// Set or guess mimetype
|
||||
if !ct_raw.is_empty() {
|
||||
let content_type = render::render(ct_raw, &vars);
|
||||
part = part
|
||||
.mime_str(content_type.as_str())
|
||||
.map_err(|e| e.to_string())?;
|
||||
} else if !file_path.is_empty() {
|
||||
let default_mime = Mime::from_str("application/octet-stream").unwrap();
|
||||
let mime = mime_guess::from_path(file_path).first_or(default_mime);
|
||||
part = part
|
||||
.mime_str(mime.essence_str())
|
||||
.map_err(|e| e.to_string())?;
|
||||
}
|
||||
|
||||
// Set fil path if not empty
|
||||
if !file_path.is_empty() {
|
||||
let filename = PathBuf::from(file_path)
|
||||
.file_name()
|
||||
.unwrap_or_default()
|
||||
.to_str()
|
||||
.unwrap_or_default()
|
||||
.to_string();
|
||||
part = part.file_name(filename);
|
||||
}
|
||||
|
||||
multipart_form = multipart_form.part(name, part);
|
||||
}
|
||||
}
|
||||
headers.remove("Content-Type"); // reqwest will add this automatically
|
||||
@@ -496,3 +459,24 @@ fn ensure_proto(url_str: &str) -> String {
|
||||
|
||||
format!("http://{url_str}")
|
||||
}
|
||||
|
||||
fn get_bool(v: &Value, key: &str) -> bool {
|
||||
match v.get(key) {
|
||||
None => false,
|
||||
Some(v) => v.as_bool().unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_str<'a>(v: &'a Value, key: &str) -> &'a str {
|
||||
match v.get(key) {
|
||||
None => "",
|
||||
Some(v) => v.as_str().unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_str_h<'a>(v: &'a HashMap<String, Value>, key: &str) -> &'a str {
|
||||
match v.get(key) {
|
||||
None => "",
|
||||
Some(v) => v.as_str().unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,10 +57,10 @@ use yaak_models::queries::{
|
||||
};
|
||||
use yaak_plugin_runtime::events::{
|
||||
CallHttpRequestActionRequest, FilterResponse, GetHttpRequestActionsResponse,
|
||||
GetHttpRequestByIdResponse, InternalEvent, InternalEventPayload, RenderHttpRequestResponse,
|
||||
SendHttpRequestResponse,
|
||||
GetHttpRequestByIdResponse, GetTemplateFunctionsResponse, InternalEvent, InternalEventPayload,
|
||||
RenderHttpRequestResponse, SendHttpRequestResponse,
|
||||
};
|
||||
use yaak_templates::{parse_and_render, Parser, Tokens};
|
||||
use yaak_templates::{Parser, Tokens};
|
||||
|
||||
mod analytics;
|
||||
mod export_resources;
|
||||
@@ -128,7 +128,7 @@ 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());
|
||||
let rendered = render_template(template, &workspace, environment.as_ref()).await;
|
||||
Ok(rendered)
|
||||
}
|
||||
|
||||
@@ -195,7 +195,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());
|
||||
let vars = variables_from_environment(&workspace, environment.as_ref()).await;
|
||||
|
||||
// Add rest of metadata
|
||||
for h in req.clone().metadata {
|
||||
@@ -207,8 +207,8 @@ async fn cmd_grpc_go(
|
||||
continue;
|
||||
}
|
||||
|
||||
let name = render::render(&h.name, &vars);
|
||||
let value = render::render(&h.value, &vars);
|
||||
let name = render::render(&h.name, &vars).await;
|
||||
let value = render::render(&h.value, &vars).await;
|
||||
|
||||
metadata.insert(name, value);
|
||||
}
|
||||
@@ -229,15 +229,15 @@ async fn cmd_grpc_go(
|
||||
.unwrap_or(empty_value)
|
||||
.as_str()
|
||||
.unwrap_or("");
|
||||
let username = render::render(raw_username, &vars);
|
||||
let password = render::render(raw_password, &vars);
|
||||
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);
|
||||
let token = render::render(raw_token, &vars).await;
|
||||
metadata.insert("Authorization".to_string(), format!("Bearer {token}"));
|
||||
}
|
||||
}
|
||||
@@ -355,7 +355,10 @@ async fn cmd_grpc_go(
|
||||
let w = w.clone();
|
||||
let base_msg = base_msg.clone();
|
||||
let method_desc = method_desc.clone();
|
||||
let msg = render::render(raw_msg.as_str(), &vars);
|
||||
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,
|
||||
@@ -413,7 +416,7 @@ async fn cmd_grpc_go(
|
||||
} else {
|
||||
req.message
|
||||
};
|
||||
let msg = render::render(&raw_msg, &vars);
|
||||
let msg = render::render(&raw_msg, &vars).await;
|
||||
|
||||
upsert_grpc_event(
|
||||
&w,
|
||||
@@ -733,7 +736,7 @@ async fn cmd_send_ephemeral_request(
|
||||
|
||||
send_http_request(
|
||||
&window,
|
||||
request,
|
||||
&request,
|
||||
&response,
|
||||
environment,
|
||||
cookie_jar,
|
||||
@@ -914,6 +917,16 @@ async fn cmd_http_request_actions(
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_template_functions(
|
||||
plugin_manager: State<'_, PluginManager>,
|
||||
) -> Result<Vec<GetTemplateFunctionsResponse>, String> {
|
||||
plugin_manager
|
||||
.run_template_functions()
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_call_http_request_action(
|
||||
req: CallHttpRequestActionRequest,
|
||||
@@ -1057,7 +1070,7 @@ async fn cmd_send_http_request(
|
||||
|
||||
send_http_request(
|
||||
&window,
|
||||
request.clone(),
|
||||
&request,
|
||||
&response,
|
||||
environment,
|
||||
cookie_jar,
|
||||
@@ -1692,6 +1705,7 @@ pub fn run() {
|
||||
cmd_grpc_go,
|
||||
cmd_grpc_reflect,
|
||||
cmd_http_request_actions,
|
||||
cmd_template_functions,
|
||||
cmd_import_data,
|
||||
cmd_list_cookie_jars,
|
||||
cmd_list_environments,
|
||||
@@ -1986,7 +2000,7 @@ async fn handle_plugin_event<R: Runtime>(
|
||||
Some(id) => get_environment(w, id.as_str()).await.ok(),
|
||||
};
|
||||
let rendered_http_request =
|
||||
render_request(&req.http_request, &workspace, environment.as_ref());
|
||||
render_request(&req.http_request, &workspace, environment.as_ref()).await;
|
||||
Some(InternalEventPayload::RenderHttpRequestResponse(
|
||||
RenderHttpRequestResponse {
|
||||
http_request: rendered_http_request,
|
||||
@@ -2025,7 +2039,7 @@ async fn handle_plugin_event<R: Runtime>(
|
||||
|
||||
let result = send_http_request(
|
||||
&w,
|
||||
req.http_request,
|
||||
&req.http_request,
|
||||
&resp,
|
||||
environment,
|
||||
cookie_jar,
|
||||
|
||||
@@ -4,73 +4,77 @@ use std::collections::HashMap;
|
||||
use yaak_models::models::{
|
||||
Environment, EnvironmentVariable, HttpRequest, HttpRequestHeader, HttpUrlParameter, Workspace,
|
||||
};
|
||||
use yaak_templates::parse_and_render;
|
||||
use yaak_templates::{parse_and_render, TemplateCallback};
|
||||
|
||||
pub fn render_template(template: &str, w: &Workspace, e: Option<&Environment>) -> String {
|
||||
let vars = &variables_from_environment(w, e);
|
||||
render(template, vars)
|
||||
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 fn render_request(r: &HttpRequest, w: &Workspace, e: Option<&Environment>) -> HttpRequest {
|
||||
pub async fn render_request(
|
||||
r: &HttpRequest,
|
||||
w: &Workspace,
|
||||
e: Option<&Environment>,
|
||||
) -> HttpRequest {
|
||||
let r = r.clone();
|
||||
let vars = &variables_from_environment(w, e);
|
||||
let vars = &variables_from_environment(w, e).await;
|
||||
|
||||
let mut url_parameters = Vec::new();
|
||||
for p in r.url_parameters {
|
||||
url_parameters.push(HttpUrlParameter {
|
||||
enabled: p.enabled,
|
||||
name: render(p.name.as_str(), vars).await,
|
||||
value: render(p.value.as_str(), vars).await,
|
||||
})
|
||||
}
|
||||
|
||||
let mut headers = Vec::new();
|
||||
for p in r.headers {
|
||||
headers.push(HttpRequestHeader {
|
||||
enabled: p.enabled,
|
||||
name: render(p.name.as_str(), vars).await,
|
||||
value: render(p.value.as_str(), vars).await,
|
||||
})
|
||||
}
|
||||
|
||||
let mut body = HashMap::new();
|
||||
for (k, v) in r.body {
|
||||
let v = if v.is_string() {
|
||||
render(v.as_str().unwrap(), vars).await
|
||||
} else {
|
||||
v.to_string()
|
||||
};
|
||||
body.insert(render(k.as_str(), vars).await, Value::from(v));
|
||||
}
|
||||
|
||||
let mut authentication = HashMap::new();
|
||||
for (k, v) in r.authentication {
|
||||
let v = if v.is_string() {
|
||||
render(v.as_str().unwrap(), vars).await
|
||||
} else {
|
||||
v.to_string()
|
||||
};
|
||||
authentication.insert(render(k.as_str(), vars).await, Value::from(v));
|
||||
}
|
||||
|
||||
HttpRequest {
|
||||
url: render(r.url.as_str(), vars),
|
||||
url_parameters: r
|
||||
.url_parameters
|
||||
.iter()
|
||||
.map(|p| HttpUrlParameter {
|
||||
enabled: p.enabled,
|
||||
name: render(p.name.as_str(), vars),
|
||||
value: render(p.value.as_str(), vars),
|
||||
})
|
||||
.collect::<Vec<HttpUrlParameter>>(),
|
||||
headers: r
|
||||
.headers
|
||||
.iter()
|
||||
.map(|p| HttpRequestHeader {
|
||||
enabled: p.enabled,
|
||||
name: render(p.name.as_str(), vars),
|
||||
value: render(p.value.as_str(), vars),
|
||||
})
|
||||
.collect::<Vec<HttpRequestHeader>>(),
|
||||
body: r
|
||||
.body
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
let v = if v.is_string() {
|
||||
render(v.as_str().unwrap(), vars)
|
||||
} else {
|
||||
v.to_string()
|
||||
};
|
||||
(render(k, vars), Value::from(v))
|
||||
})
|
||||
.collect::<HashMap<String, Value>>(),
|
||||
authentication: r
|
||||
.authentication
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
let v = if v.is_string() {
|
||||
render(v.as_str().unwrap(), vars)
|
||||
} else {
|
||||
v.to_string()
|
||||
};
|
||||
(render(k, vars), Value::from(v))
|
||||
})
|
||||
.collect::<HashMap<String, Value>>(),
|
||||
url: render(r.url.as_str(), vars).await,
|
||||
url_parameters,
|
||||
headers,
|
||||
body,
|
||||
authentication,
|
||||
..r
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recursively_render_variables<'s>(
|
||||
pub async 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);
|
||||
let rendered = Box::pin(render(v.as_str(), m)).await;
|
||||
if rendered != v {
|
||||
did_render = true
|
||||
}
|
||||
@@ -78,13 +82,13 @@ pub fn recursively_render_variables<'s>(
|
||||
}
|
||||
|
||||
if did_render && render_count <= 3 {
|
||||
new_map = recursively_render_variables(&new_map, render_count + 1);
|
||||
new_map = Box::pin(recursively_render_variables(&new_map, render_count + 1)).await;
|
||||
}
|
||||
|
||||
new_map
|
||||
}
|
||||
|
||||
pub fn variables_from_environment(
|
||||
pub async fn variables_from_environment(
|
||||
workspace: &Workspace,
|
||||
environment: Option<&Environment>,
|
||||
) -> HashMap<String, String> {
|
||||
@@ -95,17 +99,22 @@ pub fn variables_from_environment(
|
||||
variables = add_variable_to_map(variables, &e.variables);
|
||||
}
|
||||
|
||||
recursively_render_variables(&variables, 0)
|
||||
recursively_render_variables(&variables, 0).await
|
||||
}
|
||||
|
||||
pub fn render(template: &str, vars: &HashMap<String, String>) -> String {
|
||||
parse_and_render(template, vars, Some(template_callback))
|
||||
pub async fn render(template: &str, vars: &HashMap<String, String>) -> String {
|
||||
parse_and_render(template, vars, &Box::new(PluginTemplateCallback::default())).await
|
||||
}
|
||||
|
||||
fn template_callback(name: &str, args: HashMap<String, String>) -> Result<String, String> {
|
||||
match name {
|
||||
"timestamp" => timestamp(args),
|
||||
_ => Err(format!("Unknown template function {name}")),
|
||||
#[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}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::collections::HashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
@@ -17,8 +18,7 @@ pub struct InternalEvent {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(tag = "type")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[serde(rename_all = "snake_case", tag = "type")]
|
||||
#[ts(export)]
|
||||
pub enum InternalEventPayload {
|
||||
BootRequest(BootRequest),
|
||||
@@ -40,6 +40,10 @@ pub enum InternalEventPayload {
|
||||
GetHttpRequestActionsResponse(GetHttpRequestActionsResponse),
|
||||
CallHttpRequestActionRequest(CallHttpRequestActionRequest),
|
||||
|
||||
GetTemplateFunctionsRequest,
|
||||
GetTemplateFunctionsResponse(GetTemplateFunctionsResponse),
|
||||
CallTemplateFunctionRequest(CallTemplateFunctionRequest),
|
||||
|
||||
CopyTextRequest(CopyTextRequest),
|
||||
|
||||
RenderHttpRequestRequest(RenderHttpRequestRequest),
|
||||
@@ -180,6 +184,110 @@ impl Default for ToastVariant {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct GetTemplateFunctionsResponse {
|
||||
pub functions: Vec<TemplateFunction>,
|
||||
pub plugin_ref_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct TemplateFunction {
|
||||
pub name: String,
|
||||
pub args: Vec<TemplateFunctionArg>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "snake_case", tag = "type")]
|
||||
#[ts(export)]
|
||||
pub enum TemplateFunctionArg {
|
||||
Text(TemplateFunctionTextArg),
|
||||
Select(TemplateFunctionSelectArg),
|
||||
HttpRequest(TemplateFunctionHttpRequestArg),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct TemplateFunctionBaseArg {
|
||||
pub name: String,
|
||||
#[ts(optional = nullable)]
|
||||
pub optional: Option<bool>,
|
||||
#[ts(optional = nullable)]
|
||||
pub label: Option<String>,
|
||||
#[ts(optional = nullable)]
|
||||
pub default_value: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct TemplateFunctionTextArg {
|
||||
#[serde(flatten)]
|
||||
pub base: TemplateFunctionBaseArg,
|
||||
#[ts(optional = nullable)]
|
||||
pub placeholder: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct TemplateFunctionHttpRequestArg {
|
||||
#[serde(flatten)]
|
||||
pub base: TemplateFunctionBaseArg,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct TemplateFunctionSelectArg {
|
||||
#[serde(flatten)]
|
||||
pub base: TemplateFunctionBaseArg,
|
||||
pub options: Vec<TemplateFunctionSelectOption>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct TemplateFunctionSelectOption {
|
||||
pub name: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct CallTemplateFunctionRequest {
|
||||
pub name: String,
|
||||
pub plugin_ref_id: String,
|
||||
pub args: CallTemplateFunctionArgs,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct CallTemplateFunctionArgs {
|
||||
pub purpose: CallTemplateFunctionPurpose,
|
||||
pub values: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "snake_case", tag = "type")]
|
||||
#[ts(export)]
|
||||
pub enum CallTemplateFunctionPurpose {
|
||||
Send,
|
||||
Preview,
|
||||
}
|
||||
|
||||
impl Default for CallTemplateFunctionPurpose{
|
||||
fn default() -> Self {
|
||||
CallTemplateFunctionPurpose::Preview
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::error::Result;
|
||||
use crate::events::{CallHttpRequestActionRequest, FilterRequest, FilterResponse, GetHttpRequestActionsResponse, ImportRequest, ImportResponse, InternalEvent, InternalEventPayload};
|
||||
use crate::events::{CallHttpRequestActionRequest, CallTemplateFunctionRequest, FilterRequest, FilterResponse, GetHttpRequestActionsResponse, GetTemplateFunctionsResponse, ImportRequest, ImportResponse, InternalEvent, InternalEventPayload};
|
||||
|
||||
use crate::error::Error::PluginErr;
|
||||
use crate::nodejs::start_nodejs_plugin_runtime;
|
||||
@@ -74,6 +74,22 @@ impl PluginManager {
|
||||
Ok(all_actions)
|
||||
}
|
||||
|
||||
pub async fn run_template_functions(&self) -> Result<Vec<GetTemplateFunctionsResponse>> {
|
||||
let reply_events = self
|
||||
.server
|
||||
.send_and_wait(&InternalEventPayload::GetTemplateFunctionsRequest)
|
||||
.await?;
|
||||
|
||||
let mut all_actions = Vec::new();
|
||||
for event in reply_events {
|
||||
if let InternalEventPayload::GetTemplateFunctionsResponse(resp) = event.payload {
|
||||
all_actions.push(resp.clone());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(all_actions)
|
||||
}
|
||||
|
||||
pub async fn call_http_request_action(&self, req: CallHttpRequestActionRequest) -> Result<()> {
|
||||
let plugin = self.server.plugin_by_ref_id(req.plugin_ref_id.as_str()).await?;
|
||||
let event = plugin.build_event_to_send(&InternalEventPayload::CallHttpRequestActionRequest(req), None);
|
||||
@@ -81,6 +97,13 @@ impl PluginManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn call_template_function(&self, req: CallTemplateFunctionRequest) -> Result<()> {
|
||||
let plugin = self.server.plugin_by_ref_id(req.plugin_ref_id.as_str()).await?;
|
||||
let event = plugin.build_event_to_send(&InternalEventPayload::CallTemplateFunctionRequest(req), None);
|
||||
plugin.send(&event).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn run_import(&self, content: &str) -> Result<(ImportResponse, String)> {
|
||||
let reply_events = self
|
||||
.server
|
||||
|
||||
@@ -8,3 +8,4 @@ log = "0.4.22"
|
||||
serde = { version = "1.0.208", features = ["derive"] }
|
||||
serde_json = "1.0.125"
|
||||
ts-rs = { version = "9.0.1" }
|
||||
tokio = { version = "1.39.3", features = ["macros", "rt"] }
|
||||
|
||||
@@ -1,30 +1,35 @@
|
||||
use crate::{FnArg, Parser, Token, Tokens, Val};
|
||||
use log::warn;
|
||||
use std::collections::HashMap;
|
||||
use std::future::Future;
|
||||
|
||||
type TemplateCallback = fn(name: &str, args: HashMap<String, String>) -> Result<String, String>;
|
||||
|
||||
pub fn parse_and_render(
|
||||
template: &str,
|
||||
vars: &HashMap<String, String>,
|
||||
cb: Option<TemplateCallback>,
|
||||
) -> String {
|
||||
let mut p = Parser::new(template);
|
||||
let tokens = p.parse();
|
||||
render(tokens, vars, cb)
|
||||
pub trait TemplateCallback {
|
||||
fn run(&self, fn_name: &str, args: HashMap<String, String>) -> impl Future<Output = Result<String, String>> + Send;
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
tokens: Tokens,
|
||||
pub async fn parse_and_render<T>(
|
||||
template: &str,
|
||||
vars: &HashMap<String, String>,
|
||||
cb: Option<TemplateCallback>,
|
||||
) -> String {
|
||||
cb: &Box<T>,
|
||||
) -> String
|
||||
where
|
||||
T: TemplateCallback,
|
||||
{
|
||||
let mut p = Parser::new(template);
|
||||
let tokens = p.parse();
|
||||
render(tokens, vars, cb).await
|
||||
}
|
||||
|
||||
pub async fn render<T>(tokens: Tokens, vars: &HashMap<String, String>, cb: &Box<T>) -> String
|
||||
where
|
||||
T: TemplateCallback,
|
||||
{
|
||||
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)),
|
||||
Token::Tag { val } => doc_str.push(render_tag(val, &vars, cb).await),
|
||||
Token::Eof => {}
|
||||
}
|
||||
}
|
||||
@@ -32,7 +37,10 @@ pub fn render(
|
||||
doc_str.join("")
|
||||
}
|
||||
|
||||
fn render_tag(val: Val, vars: &HashMap<String, String>, cb: Option<TemplateCallback>) -> String {
|
||||
async fn render_tag<T>(val: Val, vars: &HashMap<String, String>, cb: &Box<T>) -> String
|
||||
where
|
||||
T: TemplateCallback,
|
||||
{
|
||||
match val {
|
||||
Val::Str { text } => text.into(),
|
||||
Val::Var { name } => match vars.get(name.as_str()) {
|
||||
@@ -41,9 +49,9 @@ fn render_tag(val: Val, vars: &HashMap<String, String>, cb: Option<TemplateCallb
|
||||
},
|
||||
Val::Fn { name, args } => {
|
||||
let empty = "".to_string();
|
||||
let resolved_args = args
|
||||
.iter()
|
||||
.map(|a| match a {
|
||||
let mut resolved_args: HashMap<String, String> = HashMap::new();
|
||||
for a in args {
|
||||
let (k, v) = match a {
|
||||
FnArg {
|
||||
name,
|
||||
value: Val::Str { text },
|
||||
@@ -56,113 +64,161 @@ fn render_tag(val: Val, vars: &HashMap<String, String>, cb: Option<TemplateCallb
|
||||
vars.get(var_name.as_str()).unwrap_or(&empty).to_string(),
|
||||
),
|
||||
FnArg { name, value: val } => {
|
||||
(name.to_string(), render_tag(val.clone(), vars, cb))
|
||||
let r = Box::pin(render_tag(val.clone(), vars, cb)).await;
|
||||
(name.to_string(), r)
|
||||
}
|
||||
})
|
||||
.collect::<HashMap<String, String>>();
|
||||
match cb {
|
||||
Some(cb) => match cb(name.as_str(), resolved_args.clone()) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"Failed to run template callback {}({:?}): {}",
|
||||
name, resolved_args, e
|
||||
);
|
||||
"".to_string()
|
||||
}
|
||||
},
|
||||
None => "".into(),
|
||||
};
|
||||
resolved_args.insert(k, v);
|
||||
}
|
||||
match cb.run(name.as_str(), resolved_args.clone()).await {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"Failed to run template callback {}({:?}): {}",
|
||||
name, resolved_args, e
|
||||
);
|
||||
"".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
Val::Null => "".into()
|
||||
Val::Null => "".into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::renderer::TemplateCallback;
|
||||
use crate::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::*;
|
||||
struct EmptyCB {}
|
||||
|
||||
#[test]
|
||||
fn render_empty() {
|
||||
impl TemplateCallback for EmptyCB {
|
||||
async fn run(&self, _fn_name: &str, _args: HashMap<String, String>) -> Result<String, String>{
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn render_empty() {
|
||||
let empty_cb = Box::new(EmptyCB {});
|
||||
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, &empty_cb).await,
|
||||
result.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_text_only() {
|
||||
#[tokio::test]
|
||||
async fn render_text_only() {
|
||||
let empty_cb = Box::new(EmptyCB {});
|
||||
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, &empty_cb).await,
|
||||
result.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_simple() {
|
||||
#[tokio::test]
|
||||
async fn render_simple() {
|
||||
let empty_cb = Box::new(EmptyCB {});
|
||||
let template = "${[ foo ]}";
|
||||
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, &empty_cb).await,
|
||||
result.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_surrounded() {
|
||||
#[tokio::test]
|
||||
async fn render_surrounded() {
|
||||
let empty_cb = Box::new(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, None), result.to_string());
|
||||
assert_eq!(
|
||||
parse_and_render(template, &vars, &empty_cb).await,
|
||||
result.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_valid_fn() {
|
||||
#[tokio::test]
|
||||
async fn render_valid_fn() {
|
||||
let vars = HashMap::new();
|
||||
let template = r#"${[ say_hello(a="John", b="Kate") ]}"#;
|
||||
let result = r#"say_hello: 2, Some("John") Some("Kate")"#;
|
||||
|
||||
fn cb(name: &str, args: HashMap<String, String>) -> Result<String, String> {
|
||||
Ok(format!(
|
||||
"{name}: {}, {:?} {:?}",
|
||||
args.len(),
|
||||
args.get("a"),
|
||||
args.get("b")
|
||||
))
|
||||
struct CB {}
|
||||
impl TemplateCallback for CB {
|
||||
async fn run(
|
||||
&self,
|
||||
fn_name: &str,
|
||||
args: HashMap<String, String>,
|
||||
) -> Result<String, String> {
|
||||
Ok(format!(
|
||||
"{fn_name}: {}, {:?} {:?}",
|
||||
args.len(),
|
||||
args.get("a"),
|
||||
args.get("b")
|
||||
))
|
||||
}
|
||||
}
|
||||
assert_eq!(parse_and_render(template, &vars, Some(cb)), result);
|
||||
assert_eq!(
|
||||
parse_and_render(template, &vars, &Box::new(CB {})).await,
|
||||
result
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_nested_fn() {
|
||||
#[tokio::test]
|
||||
async fn render_nested_fn() {
|
||||
let vars = HashMap::new();
|
||||
let template = r#"${[ upper(foo=secret()) ]}"#;
|
||||
let result = r#"ABC"#;
|
||||
fn cb(name: &str, args: HashMap<String, String>) -> Result<String, String> {
|
||||
Ok(match name {
|
||||
"secret" => "abc".to_string(),
|
||||
"upper" => args["foo"].to_string().to_uppercase(),
|
||||
_ => "".to_string(),
|
||||
})
|
||||
struct CB {}
|
||||
impl TemplateCallback for CB {
|
||||
async fn run(
|
||||
&self,
|
||||
fn_name: &str,
|
||||
args: HashMap<String, String>,
|
||||
) -> Result<String, String> {
|
||||
Ok(match fn_name {
|
||||
"secret" => "abc".to_string(),
|
||||
"upper" => args["foo"].to_string().to_uppercase(),
|
||||
_ => "".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
parse_and_render(template, &vars, Some(cb)),
|
||||
parse_and_render(template, &vars, &Box::new(CB {})).await,
|
||||
result.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_fn_err() {
|
||||
#[tokio::test]
|
||||
async fn render_fn_err() {
|
||||
let vars = HashMap::new();
|
||||
let template = r#"${[ error() ]}"#;
|
||||
let result = r#""#;
|
||||
fn cb(_name: &str, _args: HashMap<String, String>) -> Result<String, String> {
|
||||
Err("Failed to do it!".to_string())
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
parse_and_render(template, &vars, Some(cb)),
|
||||
parse_and_render(template, &vars, &Box::new(CB {})).await,
|
||||
result.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,88 +1,19 @@
|
||||
import type { HttpRequest } from '@yaakapp/api';
|
||||
|
||||
export interface TemplateFunctionArgBase {
|
||||
name: string;
|
||||
optional?: boolean;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export interface TemplateFunctionSelectArg extends TemplateFunctionArgBase {
|
||||
type: 'select';
|
||||
defaultValue?: string;
|
||||
options: readonly { name: string; value: string }[];
|
||||
}
|
||||
|
||||
export interface TemplateFunctionTextArg extends TemplateFunctionArgBase {
|
||||
type: 'text';
|
||||
defaultValue?: string;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export interface TemplateFunctionHttpRequestArg extends TemplateFunctionArgBase {
|
||||
type: HttpRequest['model'];
|
||||
}
|
||||
|
||||
export type TemplateFunctionArg =
|
||||
| TemplateFunctionSelectArg
|
||||
| TemplateFunctionTextArg
|
||||
| TemplateFunctionHttpRequestArg;
|
||||
|
||||
export interface TemplateFunction {
|
||||
name: string;
|
||||
args: TemplateFunctionArg[];
|
||||
}
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import type { GetTemplateFunctionsResponse } from '@yaakapp/api';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
|
||||
export function useTemplateFunctions() {
|
||||
const fns: TemplateFunction[] = [
|
||||
{
|
||||
name: 'timestamp',
|
||||
args: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'from',
|
||||
label: 'From',
|
||||
placeholder: '2023-23-12T04:03:03',
|
||||
optional: true,
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
label: 'Format',
|
||||
name: 'format',
|
||||
options: [
|
||||
{ name: 'RFC3339', value: 'rfc3339' },
|
||||
{ name: 'Unix', value: 'unix' },
|
||||
{ name: 'Unix (ms)', value: 'unix_millis' },
|
||||
],
|
||||
optional: true,
|
||||
defaultValue: 'rfc3339',
|
||||
},
|
||||
],
|
||||
const result = useQuery({
|
||||
queryKey: ['template_functions'],
|
||||
refetchOnWindowFocus: false,
|
||||
queryFn: async () => {
|
||||
const responses = (await invokeCmd(
|
||||
'cmd_template_functions',
|
||||
)) as GetTemplateFunctionsResponse[];
|
||||
return responses;
|
||||
},
|
||||
{
|
||||
name: 'response',
|
||||
args: [
|
||||
{
|
||||
type: 'http_request',
|
||||
name: 'request',
|
||||
label: 'Request',
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
name: 'attribute',
|
||||
label: 'Attribute',
|
||||
options: [
|
||||
{ name: 'Body', value: 'body' },
|
||||
{ name: 'Header', value: 'header' },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'filter',
|
||||
label: 'Filter',
|
||||
placeholder: 'JSONPath or XPath expression',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
const fns = result.data?.flatMap((r) => r.functions) ?? [];
|
||||
return fns;
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ type TauriCmd =
|
||||
| 'cmd_send_http_request'
|
||||
| 'cmd_set_key_value'
|
||||
| 'cmd_set_update_mode'
|
||||
| 'cmd_template_functions'
|
||||
| 'cmd_track_event'
|
||||
| 'cmd_update_cookie_jar'
|
||||
| 'cmd_update_environment'
|
||||
|
||||
Reference in New Issue
Block a user