mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-21 17:09:09 +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 { BootRequest } from "./BootRequest";
|
||||||
import type { BootResponse } from "./BootResponse";
|
import type { BootResponse } from "./BootResponse";
|
||||||
import type { CallHttpRequestActionRequest } from "./CallHttpRequestActionRequest";
|
import type { CallHttpRequestActionRequest } from "./CallHttpRequestActionRequest";
|
||||||
|
import type { CallTemplateFunctionRequest } from "./CallTemplateFunctionRequest";
|
||||||
import type { CopyTextRequest } from "./CopyTextRequest";
|
import type { CopyTextRequest } from "./CopyTextRequest";
|
||||||
import type { EmptyResponse } from "./EmptyResponse";
|
import type { EmptyResponse } from "./EmptyResponse";
|
||||||
import type { ExportHttpRequestRequest } from "./ExportHttpRequestRequest";
|
import type { ExportHttpRequestRequest } from "./ExportHttpRequestRequest";
|
||||||
@@ -11,6 +12,7 @@ import type { FilterResponse } from "./FilterResponse";
|
|||||||
import type { GetHttpRequestActionsResponse } from "./GetHttpRequestActionsResponse";
|
import type { GetHttpRequestActionsResponse } from "./GetHttpRequestActionsResponse";
|
||||||
import type { GetHttpRequestByIdRequest } from "./GetHttpRequestByIdRequest";
|
import type { GetHttpRequestByIdRequest } from "./GetHttpRequestByIdRequest";
|
||||||
import type { GetHttpRequestByIdResponse } from "./GetHttpRequestByIdResponse";
|
import type { GetHttpRequestByIdResponse } from "./GetHttpRequestByIdResponse";
|
||||||
|
import type { GetTemplateFunctionsResponse } from "./GetTemplateFunctionsResponse";
|
||||||
import type { ImportRequest } from "./ImportRequest";
|
import type { ImportRequest } from "./ImportRequest";
|
||||||
import type { ImportResponse } from "./ImportResponse";
|
import type { ImportResponse } from "./ImportResponse";
|
||||||
import type { RenderHttpRequestRequest } from "./RenderHttpRequestRequest";
|
import type { RenderHttpRequestRequest } from "./RenderHttpRequestRequest";
|
||||||
@@ -19,4 +21,4 @@ import type { SendHttpRequestRequest } from "./SendHttpRequestRequest";
|
|||||||
import type { SendHttpRequestResponse } from "./SendHttpRequestResponse";
|
import type { SendHttpRequestResponse } from "./SendHttpRequestResponse";
|
||||||
import type { ShowToastRequest } from "./ShowToastRequest";
|
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!
|
// TODO: The next ts-rs release includes the ability to put everything in 1 file!
|
||||||
export * from './gen/BootRequest';
|
export * from './gen/BootRequest';
|
||||||
export * from './gen/BootResponse';
|
export * from './gen/BootResponse';
|
||||||
export * from './gen/CallHttpRequestActionRequest';
|
|
||||||
export * from './gen/CallHttpRequestActionArgs';
|
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/Cookie';
|
||||||
export * from './gen/CookieDomain';
|
export * from './gen/CookieDomain';
|
||||||
export * from './gen/CookieExpires';
|
export * from './gen/CookieExpires';
|
||||||
export * from './gen/CookieJar';
|
export * from './gen/CookieJar';
|
||||||
|
export * from './gen/CopyTextRequest';
|
||||||
export * from './gen/EmptyResponse';
|
export * from './gen/EmptyResponse';
|
||||||
export * from './gen/Environment';
|
export * from './gen/Environment';
|
||||||
export * from './gen/EnvironmentVariable';
|
export * from './gen/EnvironmentVariable';
|
||||||
@@ -20,8 +24,8 @@ export * from './gen/FilterResponse';
|
|||||||
export * from './gen/Folder';
|
export * from './gen/Folder';
|
||||||
export * from './gen/GetHttpRequestActionsResponse';
|
export * from './gen/GetHttpRequestActionsResponse';
|
||||||
export * from './gen/GetHttpRequestByIdRequest';
|
export * from './gen/GetHttpRequestByIdRequest';
|
||||||
export * from './gen/CopyTextRequest';
|
|
||||||
export * from './gen/GetHttpRequestByIdResponse';
|
export * from './gen/GetHttpRequestByIdResponse';
|
||||||
|
export * from './gen/GetTemplateFunctionsResponse';
|
||||||
export * from './gen/GrpcConnection';
|
export * from './gen/GrpcConnection';
|
||||||
export * from './gen/GrpcEvent';
|
export * from './gen/GrpcEvent';
|
||||||
export * from './gen/GrpcMetadataEntry';
|
export * from './gen/GrpcMetadataEntry';
|
||||||
@@ -39,12 +43,19 @@ export * from './gen/InternalEvent';
|
|||||||
export * from './gen/InternalEventPayload';
|
export * from './gen/InternalEventPayload';
|
||||||
export * from './gen/KeyValue';
|
export * from './gen/KeyValue';
|
||||||
export * from './gen/Model';
|
export * from './gen/Model';
|
||||||
export * from './gen/SendHttpRequestRequest';
|
|
||||||
export * from './gen/ToastVariant';
|
|
||||||
export * from './gen/ShowToastRequest';
|
|
||||||
export * from './gen/RenderHttpRequestRequest';
|
export * from './gen/RenderHttpRequestRequest';
|
||||||
export * from './gen/RenderHttpRequestResponse';
|
export * from './gen/RenderHttpRequestResponse';
|
||||||
|
export * from './gen/SendHttpRequestRequest';
|
||||||
export * from './gen/SendHttpRequestResponse';
|
export * from './gen/SendHttpRequestResponse';
|
||||||
export * from './gen/SendHttpRequestResponse';
|
export * from './gen/SendHttpRequestResponse';
|
||||||
export * from './gen/Settings';
|
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';
|
export * from './gen/Workspace';
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { SendHttpRequestRequest } from '../gen/SendHttpRequestRequest';
|
|||||||
import { SendHttpRequestResponse } from '../gen/SendHttpRequestResponse';
|
import { SendHttpRequestResponse } from '../gen/SendHttpRequestResponse';
|
||||||
import { ShowToastRequest } from '../gen/ShowToastRequest';
|
import { ShowToastRequest } from '../gen/ShowToastRequest';
|
||||||
|
|
||||||
export type YaakContext = {
|
export type Context = {
|
||||||
clipboard: {
|
clipboard: {
|
||||||
copyText(text: string): void;
|
copyText(text: string): void;
|
||||||
};
|
};
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import { YaakContext } from './context';
|
import { Context } from './Context';
|
||||||
|
|
||||||
export type FilterPluginResponse = string[];
|
export type FilterPluginResponse = string[];
|
||||||
|
|
||||||
export type FilterPlugin = {
|
export type FilterPlugin = {
|
||||||
name: string;
|
name: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
canFilter(ctx: YaakContext, args: { mimeType: string }): Promise<boolean>;
|
canFilter(ctx: Context, args: { mimeType: string }): Promise<boolean>;
|
||||||
onFilter(
|
onFilter(
|
||||||
ctx: YaakContext,
|
ctx: Context,
|
||||||
args: { payload: string; mimeType: string },
|
args: { payload: string; mimeType: string },
|
||||||
): Promise<FilterPluginResponse>;
|
): Promise<FilterPluginResponse>;
|
||||||
};
|
};
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { CallHttpRequestActionArgs } from '../gen/CallHttpRequestActionArgs';
|
import { CallHttpRequestActionArgs } from '../gen/CallHttpRequestActionArgs';
|
||||||
import { HttpRequestAction } from '../gen/HttpRequestAction';
|
import { HttpRequestAction } from '../gen/HttpRequestAction';
|
||||||
import { YaakContext } from './context';
|
import { Context } from './Context';
|
||||||
|
|
||||||
export type HttpRequestActionPlugin = HttpRequestAction & {
|
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 { HttpRequest } from '../gen/HttpRequest';
|
||||||
import { Workspace } from '../gen/Workspace';
|
import { Workspace } from '../gen/Workspace';
|
||||||
import { AtLeast } from '../helpers';
|
import { AtLeast } from '../helpers';
|
||||||
import { YaakContext } from './context';
|
import { Context } from './Context';
|
||||||
|
|
||||||
export type ImportPluginResponse = null | {
|
export type ImportPluginResponse = null | {
|
||||||
workspaces: AtLeast<Workspace, 'name' | 'id' | 'model'>[];
|
workspaces: AtLeast<Workspace, 'name' | 'id' | 'model'>[];
|
||||||
@@ -15,5 +15,5 @@ export type ImportPluginResponse = null | {
|
|||||||
export type ImporterPlugin = {
|
export type ImporterPlugin = {
|
||||||
name: string;
|
name: string;
|
||||||
description?: 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 { FilterPlugin } from './FilterPlugin';
|
||||||
import { HttpRequestActionPlugin } from './httpRequestAction';
|
import { HttpRequestActionPlugin } from './HttpRequestActionPlugin';
|
||||||
import { ImporterPlugin } from './import';
|
import { ImporterPlugin } from './ImporterPlugin';
|
||||||
import { ThemePlugin } from './theme';
|
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
|
* The global structure of a Yaak plugin
|
||||||
*/
|
*/
|
||||||
export type YaakPlugin = {
|
export type Plugin = {
|
||||||
importer?: ImporterPlugin;
|
importer?: ImporterPlugin;
|
||||||
theme?: ThemePlugin;
|
theme?: ThemePlugin;
|
||||||
filter?: FilterPlugin;
|
filter?: FilterPlugin;
|
||||||
httpRequestActions?: HttpRequestActionPlugin[];
|
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,
|
InternalEventPayload,
|
||||||
RenderHttpRequestResponse,
|
RenderHttpRequestResponse,
|
||||||
SendHttpRequestResponse,
|
SendHttpRequestResponse,
|
||||||
|
TemplateFunction,
|
||||||
} from '@yaakapp/api';
|
} 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 { HttpRequestActionPlugin } from '@yaakapp/api/lib/plugins/httpRequestAction';
|
||||||
|
import { TemplateFunctionPlugin } from '@yaakapp/api/lib/plugins/TemplateFunctionPlugin';
|
||||||
import interceptStdout from 'intercept-stdout';
|
import interceptStdout from 'intercept-stdout';
|
||||||
import * as console from 'node:console';
|
import * as console from 'node:console';
|
||||||
import { readFileSync } from 'node:fs';
|
import { readFileSync } from 'node:fs';
|
||||||
@@ -88,7 +90,7 @@ new Promise<void>(async (resolve, reject) => {
|
|||||||
return promise as unknown as Promise<T>;
|
return promise as unknown as Promise<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ctx: YaakContext = {
|
const ctx: Context = {
|
||||||
clipboard: {
|
clipboard: {
|
||||||
async copyText(text) {
|
async copyText(text) {
|
||||||
await sendAndWaitForReply({ type: 'copy_text_request', 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(
|
const reply: HttpRequestAction[] = mod.plugin.httpRequestActions.map(
|
||||||
(a: HttpRequestActionPlugin) => ({
|
(a: HttpRequestActionPlugin) => ({
|
||||||
...a,
|
...a,
|
||||||
onSelect: undefined,
|
|
||||||
// Add everything except onSelect
|
// Add everything except onSelect
|
||||||
|
onSelect: undefined,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const replyPayload: InternalEventPayload = {
|
const replyPayload: InternalEventPayload = {
|
||||||
@@ -194,6 +196,26 @@ new Promise<void>(async (resolve, reject) => {
|
|||||||
return;
|
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 (
|
if (
|
||||||
payload.type === 'call_http_request_action_request' &&
|
payload.type === 'call_http_request_action_request' &&
|
||||||
Array.isArray(mod.plugin?.httpRequestActions)
|
Array.isArray(mod.plugin?.httpRequestActions)
|
||||||
@@ -205,6 +227,18 @@ new Promise<void>(async (resolve, reject) => {
|
|||||||
return;
|
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) {
|
} catch (err) {
|
||||||
console.log('Plugin call threw exception', payload.type, err);
|
console.log('Plugin call threw exception', payload.type, err);
|
||||||
// TODO: Return errors to server
|
// TODO: Return errors to server
|
||||||
|
|||||||
5
src-tauri/Cargo.lock
generated
5
src-tauri/Cargo.lock
generated
@@ -6218,9 +6218,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.39.2"
|
version = "1.39.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1"
|
checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -7651,6 +7651,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"tokio",
|
||||||
"ts-rs",
|
"ts-rs",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::fs::{create_dir_all, File};
|
use std::fs::{create_dir_all, File};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
@@ -6,8 +7,8 @@ use std::str::FromStr;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::render::variables_from_environment;
|
use crate::render::render_request;
|
||||||
use crate::{render, response_err};
|
use crate::response_err;
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use http::header::{ACCEPT, USER_AGENT};
|
use http::header::{ACCEPT, USER_AGENT};
|
||||||
use http::{HeaderMap, HeaderName, HeaderValue};
|
use http::{HeaderMap, HeaderName, HeaderValue};
|
||||||
@@ -16,6 +17,7 @@ use mime_guess::Mime;
|
|||||||
use reqwest::redirect::Policy;
|
use reqwest::redirect::Policy;
|
||||||
use reqwest::Method;
|
use reqwest::Method;
|
||||||
use reqwest::{multipart, Url};
|
use reqwest::{multipart, Url};
|
||||||
|
use serde_json::Value;
|
||||||
use tauri::{Manager, Runtime, WebviewWindow};
|
use tauri::{Manager, Runtime, WebviewWindow};
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
use tokio::sync::watch::Receiver;
|
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>(
|
pub async fn send_http_request<R: Runtime>(
|
||||||
window: &WebviewWindow<R>,
|
window: &WebviewWindow<R>,
|
||||||
request: HttpRequest,
|
request: &HttpRequest,
|
||||||
response: &HttpResponse,
|
response: &HttpResponse,
|
||||||
environment: Option<Environment>,
|
environment: Option<Environment>,
|
||||||
cookie_jar: Option<CookieJar>,
|
cookie_jar: Option<CookieJar>,
|
||||||
cancel_rx: &mut Receiver<bool>,
|
cancel_rx: &mut Receiver<bool>,
|
||||||
) -> Result<HttpResponse, String> {
|
) -> Result<HttpResponse, String> {
|
||||||
let environment_ref = environment.as_ref();
|
|
||||||
let workspace = get_workspace(window, &request.workspace_id)
|
let workspace = get_workspace(window, &request.workspace_id)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to get Workspace");
|
.expect("Failed to get Workspace");
|
||||||
let vars = variables_from_environment(&workspace, environment_ref);
|
let 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);
|
url_string = ensure_proto(&url_string);
|
||||||
if !url_string.starts_with("http://") && !url_string.starts_with("https://") {
|
if !url_string.starts_with("http://") && !url_string.starts_with("https://") {
|
||||||
@@ -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");
|
.expect("Failed to create method");
|
||||||
let mut request_builder = client.request(m, url);
|
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() {
|
if h.name.is_empty() && h.value.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -147,17 +148,14 @@ pub async fn send_http_request<R: Runtime>(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let name = render::render(&h.name, &vars);
|
let header_name = match HeaderName::from_bytes(h.name.as_bytes()) {
|
||||||
let value = render::render(&h.value, &vars);
|
|
||||||
|
|
||||||
let header_name = match HeaderName::from_bytes(name.as_bytes()) {
|
|
||||||
Ok(n) => n,
|
Ok(n) => n,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to create header name: {}", e);
|
error!("Failed to create header name: {}", e);
|
||||||
continue;
|
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,
|
Ok(n) => n,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to create header value: {}", 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);
|
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 empty_value = &serde_json::to_value("").unwrap();
|
||||||
let a = request.authentication;
|
let a = rendered_request.authentication;
|
||||||
|
|
||||||
if b == "basic" {
|
if b == "basic" {
|
||||||
let raw_username = a
|
let username = a
|
||||||
.get("username")
|
.get("username")
|
||||||
.unwrap_or(empty_value)
|
.unwrap_or(empty_value)
|
||||||
.as_str()
|
.as_str()
|
||||||
.unwrap_or("");
|
.unwrap_or_default();
|
||||||
let raw_password = a
|
let password = a
|
||||||
.get("password")
|
.get("password")
|
||||||
.unwrap_or(empty_value)
|
.unwrap_or(empty_value)
|
||||||
.as_str()
|
.as_str()
|
||||||
.unwrap_or("");
|
.unwrap_or_default();
|
||||||
let username = render::render(raw_username, &vars);
|
|
||||||
let password = render::render(raw_password, &vars);
|
|
||||||
|
|
||||||
let auth = format!("{username}:{password}");
|
let auth = format!("{username}:{password}");
|
||||||
let encoded = base64::engine::general_purpose::STANDARD_NO_PAD.encode(auth);
|
let encoded = base64::engine::general_purpose::STANDARD_NO_PAD.encode(auth);
|
||||||
@@ -193,8 +189,11 @@ pub async fn send_http_request<R: Runtime>(
|
|||||||
HeaderValue::from_str(&format!("Basic {}", encoded)).unwrap(),
|
HeaderValue::from_str(&format!("Basic {}", encoded)).unwrap(),
|
||||||
);
|
);
|
||||||
} else if b == "bearer" {
|
} else if b == "bearer" {
|
||||||
let raw_token = a.get("token").unwrap_or(empty_value).as_str().unwrap_or("");
|
let token = a
|
||||||
let token = render::render(raw_token, &vars);
|
.get("token")
|
||||||
|
.unwrap_or(empty_value)
|
||||||
|
.as_str()
|
||||||
|
.unwrap_or_default();
|
||||||
headers.insert(
|
headers.insert(
|
||||||
"Authorization",
|
"Authorization",
|
||||||
HeaderValue::from_str(&format!("Bearer {token}")).unwrap(),
|
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();
|
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() {
|
if !p.enabled || p.name.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
query_params.push((
|
query_params.push((p.name, p.value));
|
||||||
render::render(&p.name, &vars),
|
|
||||||
render::render(&p.value, &vars),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
request_builder = request_builder.query(&query_params);
|
request_builder = request_builder.query(&query_params);
|
||||||
|
|
||||||
if let Some(body_type) = &request.body_type {
|
let request_body = rendered_request.body;
|
||||||
let empty_string = &serde_json::to_value("").unwrap();
|
if let Some(body_type) = &rendered_request.body_type {
|
||||||
let empty_bool = &serde_json::to_value(false).unwrap();
|
|
||||||
let request_body = request.body;
|
|
||||||
|
|
||||||
if request_body.contains_key("text") {
|
if request_body.contains_key("text") {
|
||||||
let raw_text = request_body
|
let body = get_str_h(&request_body, "text");
|
||||||
.get("text")
|
request_builder = request_builder.body(body.to_owned());
|
||||||
.unwrap_or(empty_string)
|
|
||||||
.as_str()
|
|
||||||
.unwrap_or("");
|
|
||||||
let body = render::render(raw_text, &vars);
|
|
||||||
request_builder = request_builder.body(body);
|
|
||||||
} else if body_type == "application/x-www-form-urlencoded"
|
} else if body_type == "application/x-www-form-urlencoded"
|
||||||
&& request_body.contains_key("form")
|
&& request_body.contains_key("form")
|
||||||
{
|
{
|
||||||
let mut form_params = Vec::new();
|
let mut form_params = Vec::new();
|
||||||
let form = request_body.get("form");
|
let form = request_body.get("form");
|
||||||
if let Some(f) = form {
|
if let Some(f) = form {
|
||||||
for p in f.as_array().unwrap_or(&Vec::new()) {
|
match f.as_array() {
|
||||||
let enabled = p
|
None => {}
|
||||||
.get("enabled")
|
Some(a) => {
|
||||||
.unwrap_or(empty_bool)
|
for p in a {
|
||||||
.as_bool()
|
let enabled = get_bool(p, "enabled");
|
||||||
.unwrap_or(false);
|
let name = get_str(p, "name");
|
||||||
let name = p
|
if !enabled || name.is_empty() {
|
||||||
.get("name")
|
continue;
|
||||||
.unwrap_or(empty_string)
|
}
|
||||||
.as_str()
|
let value = get_str(p, "value");
|
||||||
.unwrap_or_default();
|
form_params.push((name, value));
|
||||||
if !enabled || name.is_empty() {
|
}
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
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);
|
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") {
|
} else if body_type == "multipart/form-data" && request_body.contains_key("form") {
|
||||||
let mut multipart_form = multipart::Form::new();
|
let mut multipart_form = multipart::Form::new();
|
||||||
if let Some(form_definition) = request_body.get("form") {
|
if let Some(form_definition) = request_body.get("form") {
|
||||||
for p in form_definition.as_array().unwrap_or(&Vec::new()) {
|
match form_definition.as_array() {
|
||||||
let enabled = p
|
None => {}
|
||||||
.get("enabled")
|
Some(fd) => {
|
||||||
.unwrap_or(empty_bool)
|
for p in fd {
|
||||||
.as_bool()
|
let enabled = get_bool(p, "enabled");
|
||||||
.unwrap_or(false);
|
let name = get_str(p, "name").to_string();
|
||||||
let name_raw = p
|
|
||||||
.get("name")
|
|
||||||
.unwrap_or(empty_string)
|
|
||||||
.as_str()
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
if !enabled || name_raw.is_empty() {
|
if !enabled || name.is_empty() {
|
||||||
continue;
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
headers.remove("Content-Type"); // reqwest will add this automatically
|
||||||
@@ -496,3 +459,24 @@ fn ensure_proto(url_str: &str) -> String {
|
|||||||
|
|
||||||
format!("http://{url_str}")
|
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::{
|
use yaak_plugin_runtime::events::{
|
||||||
CallHttpRequestActionRequest, FilterResponse, GetHttpRequestActionsResponse,
|
CallHttpRequestActionRequest, FilterResponse, GetHttpRequestActionsResponse,
|
||||||
GetHttpRequestByIdResponse, InternalEvent, InternalEventPayload, RenderHttpRequestResponse,
|
GetHttpRequestByIdResponse, GetTemplateFunctionsResponse, InternalEvent, InternalEventPayload,
|
||||||
SendHttpRequestResponse,
|
RenderHttpRequestResponse, SendHttpRequestResponse,
|
||||||
};
|
};
|
||||||
use yaak_templates::{parse_and_render, Parser, Tokens};
|
use yaak_templates::{Parser, Tokens};
|
||||||
|
|
||||||
mod analytics;
|
mod analytics;
|
||||||
mod export_resources;
|
mod export_resources;
|
||||||
@@ -128,7 +128,7 @@ async fn cmd_render_template(
|
|||||||
let workspace = get_workspace(&window, &workspace_id)
|
let workspace = get_workspace(&window, &workspace_id)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.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)
|
Ok(rendered)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,7 +195,7 @@ async fn cmd_grpc_go(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
let mut metadata = HashMap::new();
|
let mut metadata = HashMap::new();
|
||||||
let vars = variables_from_environment(&workspace, environment.as_ref());
|
let vars = variables_from_environment(&workspace, environment.as_ref()).await;
|
||||||
|
|
||||||
// Add rest of metadata
|
// Add rest of metadata
|
||||||
for h in req.clone().metadata {
|
for h in req.clone().metadata {
|
||||||
@@ -207,8 +207,8 @@ async fn cmd_grpc_go(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let name = render::render(&h.name, &vars);
|
let name = render::render(&h.name, &vars).await;
|
||||||
let value = render::render(&h.value, &vars);
|
let value = render::render(&h.value, &vars).await;
|
||||||
|
|
||||||
metadata.insert(name, value);
|
metadata.insert(name, value);
|
||||||
}
|
}
|
||||||
@@ -229,15 +229,15 @@ async fn cmd_grpc_go(
|
|||||||
.unwrap_or(empty_value)
|
.unwrap_or(empty_value)
|
||||||
.as_str()
|
.as_str()
|
||||||
.unwrap_or("");
|
.unwrap_or("");
|
||||||
let username = render::render(raw_username, &vars);
|
let username = render::render(raw_username, &vars).await;
|
||||||
let password = render::render(raw_password, &vars);
|
let password = render::render(raw_password, &vars).await;
|
||||||
|
|
||||||
let auth = format!("{username}:{password}");
|
let auth = format!("{username}:{password}");
|
||||||
let encoded = base64::engine::general_purpose::STANDARD_NO_PAD.encode(auth);
|
let encoded = base64::engine::general_purpose::STANDARD_NO_PAD.encode(auth);
|
||||||
metadata.insert("Authorization".to_string(), format!("Basic {}", encoded));
|
metadata.insert("Authorization".to_string(), format!("Basic {}", encoded));
|
||||||
} else if b == "bearer" {
|
} else if b == "bearer" {
|
||||||
let raw_token = a.get("token").unwrap_or(empty_value).as_str().unwrap_or("");
|
let raw_token = a.get("token").unwrap_or(empty_value).as_str().unwrap_or("");
|
||||||
let token = render::render(raw_token, &vars);
|
let token = render::render(raw_token, &vars).await;
|
||||||
metadata.insert("Authorization".to_string(), format!("Bearer {token}"));
|
metadata.insert("Authorization".to_string(), format!("Bearer {token}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -355,7 +355,10 @@ async fn cmd_grpc_go(
|
|||||||
let w = w.clone();
|
let w = w.clone();
|
||||||
let base_msg = base_msg.clone();
|
let base_msg = base_msg.clone();
|
||||||
let method_desc = method_desc.clone();
|
let method_desc = method_desc.clone();
|
||||||
let 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)
|
let d_msg: DynamicMessage = match deserialize_message(msg.as_str(), method_desc)
|
||||||
{
|
{
|
||||||
Ok(d_msg) => d_msg,
|
Ok(d_msg) => d_msg,
|
||||||
@@ -413,7 +416,7 @@ async fn cmd_grpc_go(
|
|||||||
} else {
|
} else {
|
||||||
req.message
|
req.message
|
||||||
};
|
};
|
||||||
let msg = render::render(&raw_msg, &vars);
|
let msg = render::render(&raw_msg, &vars).await;
|
||||||
|
|
||||||
upsert_grpc_event(
|
upsert_grpc_event(
|
||||||
&w,
|
&w,
|
||||||
@@ -733,7 +736,7 @@ async fn cmd_send_ephemeral_request(
|
|||||||
|
|
||||||
send_http_request(
|
send_http_request(
|
||||||
&window,
|
&window,
|
||||||
request,
|
&request,
|
||||||
&response,
|
&response,
|
||||||
environment,
|
environment,
|
||||||
cookie_jar,
|
cookie_jar,
|
||||||
@@ -914,6 +917,16 @@ async fn cmd_http_request_actions(
|
|||||||
.map_err(|e| e.to_string())
|
.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]
|
#[tauri::command]
|
||||||
async fn cmd_call_http_request_action(
|
async fn cmd_call_http_request_action(
|
||||||
req: CallHttpRequestActionRequest,
|
req: CallHttpRequestActionRequest,
|
||||||
@@ -1057,7 +1070,7 @@ async fn cmd_send_http_request(
|
|||||||
|
|
||||||
send_http_request(
|
send_http_request(
|
||||||
&window,
|
&window,
|
||||||
request.clone(),
|
&request,
|
||||||
&response,
|
&response,
|
||||||
environment,
|
environment,
|
||||||
cookie_jar,
|
cookie_jar,
|
||||||
@@ -1692,6 +1705,7 @@ pub fn run() {
|
|||||||
cmd_grpc_go,
|
cmd_grpc_go,
|
||||||
cmd_grpc_reflect,
|
cmd_grpc_reflect,
|
||||||
cmd_http_request_actions,
|
cmd_http_request_actions,
|
||||||
|
cmd_template_functions,
|
||||||
cmd_import_data,
|
cmd_import_data,
|
||||||
cmd_list_cookie_jars,
|
cmd_list_cookie_jars,
|
||||||
cmd_list_environments,
|
cmd_list_environments,
|
||||||
@@ -1986,7 +2000,7 @@ async fn handle_plugin_event<R: Runtime>(
|
|||||||
Some(id) => get_environment(w, id.as_str()).await.ok(),
|
Some(id) => get_environment(w, id.as_str()).await.ok(),
|
||||||
};
|
};
|
||||||
let rendered_http_request =
|
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(
|
Some(InternalEventPayload::RenderHttpRequestResponse(
|
||||||
RenderHttpRequestResponse {
|
RenderHttpRequestResponse {
|
||||||
http_request: rendered_http_request,
|
http_request: rendered_http_request,
|
||||||
@@ -2025,7 +2039,7 @@ async fn handle_plugin_event<R: Runtime>(
|
|||||||
|
|
||||||
let result = send_http_request(
|
let result = send_http_request(
|
||||||
&w,
|
&w,
|
||||||
req.http_request,
|
&req.http_request,
|
||||||
&resp,
|
&resp,
|
||||||
environment,
|
environment,
|
||||||
cookie_jar,
|
cookie_jar,
|
||||||
|
|||||||
@@ -4,73 +4,77 @@ use std::collections::HashMap;
|
|||||||
use yaak_models::models::{
|
use yaak_models::models::{
|
||||||
Environment, EnvironmentVariable, HttpRequest, HttpRequestHeader, HttpUrlParameter, Workspace,
|
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 {
|
pub async fn render_template(template: &str, w: &Workspace, e: Option<&Environment>) -> String {
|
||||||
let vars = &variables_from_environment(w, e);
|
let vars = &variables_from_environment(w, e).await;
|
||||||
render(template, vars)
|
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 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 {
|
HttpRequest {
|
||||||
url: render(r.url.as_str(), vars),
|
url: render(r.url.as_str(), vars).await,
|
||||||
url_parameters: r
|
url_parameters,
|
||||||
.url_parameters
|
headers,
|
||||||
.iter()
|
body,
|
||||||
.map(|p| HttpUrlParameter {
|
authentication,
|
||||||
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>>(),
|
|
||||||
..r
|
..r
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn recursively_render_variables<'s>(
|
pub async fn recursively_render_variables<'s>(
|
||||||
m: &HashMap<String, String>,
|
m: &HashMap<String, String>,
|
||||||
render_count: usize,
|
render_count: usize,
|
||||||
) -> HashMap<String, String> {
|
) -> HashMap<String, String> {
|
||||||
let mut did_render = false;
|
let mut did_render = false;
|
||||||
let mut new_map = m.clone();
|
let mut new_map = m.clone();
|
||||||
for (k, v) in 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 {
|
if rendered != v {
|
||||||
did_render = true
|
did_render = true
|
||||||
}
|
}
|
||||||
@@ -78,13 +82,13 @@ pub fn recursively_render_variables<'s>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if did_render && render_count <= 3 {
|
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
|
new_map
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn variables_from_environment(
|
pub async fn variables_from_environment(
|
||||||
workspace: &Workspace,
|
workspace: &Workspace,
|
||||||
environment: Option<&Environment>,
|
environment: Option<&Environment>,
|
||||||
) -> HashMap<String, String> {
|
) -> HashMap<String, String> {
|
||||||
@@ -95,17 +99,22 @@ pub fn variables_from_environment(
|
|||||||
variables = add_variable_to_map(variables, &e.variables);
|
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 {
|
pub async fn render(template: &str, vars: &HashMap<String, String>) -> String {
|
||||||
parse_and_render(template, vars, Some(template_callback))
|
parse_and_render(template, vars, &Box::new(PluginTemplateCallback::default())).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn template_callback(name: &str, args: HashMap<String, String>) -> Result<String, String> {
|
#[derive(Default)]
|
||||||
match name {
|
struct PluginTemplateCallback {}
|
||||||
"timestamp" => timestamp(args),
|
|
||||||
_ => Err(format!("Unknown template function {name}")),
|
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 serde::{Deserialize, Serialize};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
@@ -17,8 +18,7 @@ pub struct InternalEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||||
#[serde(tag = "type")]
|
#[serde(rename_all = "snake_case", tag = "type")]
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub enum InternalEventPayload {
|
pub enum InternalEventPayload {
|
||||||
BootRequest(BootRequest),
|
BootRequest(BootRequest),
|
||||||
@@ -40,6 +40,10 @@ pub enum InternalEventPayload {
|
|||||||
GetHttpRequestActionsResponse(GetHttpRequestActionsResponse),
|
GetHttpRequestActionsResponse(GetHttpRequestActionsResponse),
|
||||||
CallHttpRequestActionRequest(CallHttpRequestActionRequest),
|
CallHttpRequestActionRequest(CallHttpRequestActionRequest),
|
||||||
|
|
||||||
|
GetTemplateFunctionsRequest,
|
||||||
|
GetTemplateFunctionsResponse(GetTemplateFunctionsResponse),
|
||||||
|
CallTemplateFunctionRequest(CallTemplateFunctionRequest),
|
||||||
|
|
||||||
CopyTextRequest(CopyTextRequest),
|
CopyTextRequest(CopyTextRequest),
|
||||||
|
|
||||||
RenderHttpRequestRequest(RenderHttpRequestRequest),
|
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)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||||
#[serde(default, rename_all = "camelCase")]
|
#[serde(default, rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::error::Result;
|
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::error::Error::PluginErr;
|
||||||
use crate::nodejs::start_nodejs_plugin_runtime;
|
use crate::nodejs::start_nodejs_plugin_runtime;
|
||||||
@@ -74,6 +74,22 @@ impl PluginManager {
|
|||||||
Ok(all_actions)
|
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<()> {
|
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 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);
|
let event = plugin.build_event_to_send(&InternalEventPayload::CallHttpRequestActionRequest(req), None);
|
||||||
@@ -81,6 +97,13 @@ impl PluginManager {
|
|||||||
Ok(())
|
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)> {
|
pub async fn run_import(&self, content: &str) -> Result<(ImportResponse, String)> {
|
||||||
let reply_events = self
|
let reply_events = self
|
||||||
.server
|
.server
|
||||||
|
|||||||
@@ -8,3 +8,4 @@ log = "0.4.22"
|
|||||||
serde = { version = "1.0.208", features = ["derive"] }
|
serde = { version = "1.0.208", features = ["derive"] }
|
||||||
serde_json = "1.0.125"
|
serde_json = "1.0.125"
|
||||||
ts-rs = { version = "9.0.1" }
|
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 crate::{FnArg, Parser, Token, Tokens, Val};
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::future::Future;
|
||||||
|
|
||||||
type TemplateCallback = fn(name: &str, args: HashMap<String, String>) -> Result<String, String>;
|
pub trait TemplateCallback {
|
||||||
|
fn run(&self, fn_name: &str, args: HashMap<String, String>) -> impl Future<Output = Result<String, String>> + Send;
|
||||||
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 fn render(
|
pub async fn parse_and_render<T>(
|
||||||
tokens: Tokens,
|
template: &str,
|
||||||
vars: &HashMap<String, String>,
|
vars: &HashMap<String, String>,
|
||||||
cb: Option<TemplateCallback>,
|
cb: &Box<T>,
|
||||||
) -> String {
|
) -> 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();
|
let mut doc_str: Vec<String> = Vec::new();
|
||||||
|
|
||||||
for t in tokens.tokens {
|
for t in tokens.tokens {
|
||||||
match t {
|
match t {
|
||||||
Token::Raw { text } => doc_str.push(text),
|
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 => {}
|
Token::Eof => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,7 +37,10 @@ pub fn render(
|
|||||||
doc_str.join("")
|
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 {
|
match val {
|
||||||
Val::Str { text } => text.into(),
|
Val::Str { text } => text.into(),
|
||||||
Val::Var { name } => match vars.get(name.as_str()) {
|
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 } => {
|
Val::Fn { name, args } => {
|
||||||
let empty = "".to_string();
|
let empty = "".to_string();
|
||||||
let resolved_args = args
|
let mut resolved_args: HashMap<String, String> = HashMap::new();
|
||||||
.iter()
|
for a in args {
|
||||||
.map(|a| match a {
|
let (k, v) = match a {
|
||||||
FnArg {
|
FnArg {
|
||||||
name,
|
name,
|
||||||
value: Val::Str { text },
|
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(),
|
vars.get(var_name.as_str()).unwrap_or(&empty).to_string(),
|
||||||
),
|
),
|
||||||
FnArg { name, value: val } => {
|
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>>();
|
resolved_args.insert(k, v);
|
||||||
match cb {
|
}
|
||||||
Some(cb) => match cb(name.as_str(), resolved_args.clone()) {
|
match cb.run(name.as_str(), resolved_args.clone()).await {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!(
|
warn!(
|
||||||
"Failed to run template callback {}({:?}): {}",
|
"Failed to run template callback {}({:?}): {}",
|
||||||
name, resolved_args, e
|
name, resolved_args, e
|
||||||
);
|
);
|
||||||
"".to_string()
|
"".to_string()
|
||||||
}
|
}
|
||||||
},
|
|
||||||
None => "".into(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Val::Null => "".into()
|
Val::Null => "".into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::renderer::TemplateCallback;
|
||||||
|
use crate::*;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::*;
|
struct EmptyCB {}
|
||||||
|
|
||||||
#[test]
|
impl TemplateCallback for EmptyCB {
|
||||||
fn render_empty() {
|
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 template = "";
|
||||||
let vars = HashMap::new();
|
let vars = HashMap::new();
|
||||||
let result = "";
|
let result = "";
|
||||||
assert_eq!(parse_and_render(template, &vars, None), result.to_string());
|
assert_eq!(
|
||||||
|
parse_and_render(template, &vars, &empty_cb).await,
|
||||||
|
result.to_string()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn render_text_only() {
|
async fn render_text_only() {
|
||||||
|
let empty_cb = Box::new(EmptyCB {});
|
||||||
let template = "Hello World!";
|
let template = "Hello World!";
|
||||||
let vars = HashMap::new();
|
let vars = HashMap::new();
|
||||||
let result = "Hello World!";
|
let result = "Hello World!";
|
||||||
assert_eq!(parse_and_render(template, &vars, None), result.to_string());
|
assert_eq!(
|
||||||
|
parse_and_render(template, &vars, &empty_cb).await,
|
||||||
|
result.to_string()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn render_simple() {
|
async fn render_simple() {
|
||||||
|
let empty_cb = Box::new(EmptyCB {});
|
||||||
let template = "${[ foo ]}";
|
let template = "${[ foo ]}";
|
||||||
let vars = HashMap::from([("foo".to_string(), "bar".to_string())]);
|
let vars = HashMap::from([("foo".to_string(), "bar".to_string())]);
|
||||||
let result = "bar";
|
let result = "bar";
|
||||||
assert_eq!(parse_and_render(template, &vars, None), result.to_string());
|
assert_eq!(
|
||||||
|
parse_and_render(template, &vars, &empty_cb).await,
|
||||||
|
result.to_string()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn render_surrounded() {
|
async fn render_surrounded() {
|
||||||
|
let empty_cb = Box::new(EmptyCB {});
|
||||||
let template = "hello ${[ word ]} world!";
|
let template = "hello ${[ word ]} world!";
|
||||||
let vars = HashMap::from([("word".to_string(), "cruel".to_string())]);
|
let vars = HashMap::from([("word".to_string(), "cruel".to_string())]);
|
||||||
let result = "hello cruel world!";
|
let result = "hello cruel world!";
|
||||||
assert_eq!(parse_and_render(template, &vars, None), result.to_string());
|
assert_eq!(
|
||||||
|
parse_and_render(template, &vars, &empty_cb).await,
|
||||||
|
result.to_string()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn render_valid_fn() {
|
async fn render_valid_fn() {
|
||||||
let vars = HashMap::new();
|
let vars = HashMap::new();
|
||||||
let template = r#"${[ say_hello(a="John", b="Kate") ]}"#;
|
let template = r#"${[ say_hello(a="John", b="Kate") ]}"#;
|
||||||
let result = r#"say_hello: 2, Some("John") Some("Kate")"#;
|
let result = r#"say_hello: 2, Some("John") Some("Kate")"#;
|
||||||
|
|
||||||
fn cb(name: &str, args: HashMap<String, String>) -> Result<String, String> {
|
struct CB {}
|
||||||
Ok(format!(
|
impl TemplateCallback for CB {
|
||||||
"{name}: {}, {:?} {:?}",
|
async fn run(
|
||||||
args.len(),
|
&self,
|
||||||
args.get("a"),
|
fn_name: &str,
|
||||||
args.get("b")
|
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]
|
#[tokio::test]
|
||||||
fn render_nested_fn() {
|
async fn render_nested_fn() {
|
||||||
let vars = HashMap::new();
|
let vars = HashMap::new();
|
||||||
let template = r#"${[ upper(foo=secret()) ]}"#;
|
let template = r#"${[ upper(foo=secret()) ]}"#;
|
||||||
let result = r#"ABC"#;
|
let result = r#"ABC"#;
|
||||||
fn cb(name: &str, args: HashMap<String, String>) -> Result<String, String> {
|
struct CB {}
|
||||||
Ok(match name {
|
impl TemplateCallback for CB {
|
||||||
"secret" => "abc".to_string(),
|
async fn run(
|
||||||
"upper" => args["foo"].to_string().to_uppercase(),
|
&self,
|
||||||
_ => "".to_string(),
|
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!(
|
assert_eq!(
|
||||||
parse_and_render(template, &vars, Some(cb)),
|
parse_and_render(template, &vars, &Box::new(CB {})).await,
|
||||||
result.to_string()
|
result.to_string()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn render_fn_err() {
|
async fn render_fn_err() {
|
||||||
let vars = HashMap::new();
|
let vars = HashMap::new();
|
||||||
let template = r#"${[ error() ]}"#;
|
let template = r#"${[ error() ]}"#;
|
||||||
let result = r#""#;
|
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!(
|
assert_eq!(
|
||||||
parse_and_render(template, &vars, Some(cb)),
|
parse_and_render(template, &vars, &Box::new(CB {})).await,
|
||||||
result.to_string()
|
result.to_string()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,88 +1,19 @@
|
|||||||
import type { HttpRequest } from '@yaakapp/api';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import type { GetTemplateFunctionsResponse } from '@yaakapp/api';
|
||||||
export interface TemplateFunctionArgBase {
|
import { invokeCmd } from '../lib/tauri';
|
||||||
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[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useTemplateFunctions() {
|
export function useTemplateFunctions() {
|
||||||
const fns: TemplateFunction[] = [
|
const result = useQuery({
|
||||||
{
|
queryKey: ['template_functions'],
|
||||||
name: 'timestamp',
|
refetchOnWindowFocus: false,
|
||||||
args: [
|
queryFn: async () => {
|
||||||
{
|
const responses = (await invokeCmd(
|
||||||
type: 'text',
|
'cmd_template_functions',
|
||||||
name: 'from',
|
)) as GetTemplateFunctionsResponse[];
|
||||||
label: 'From',
|
return responses;
|
||||||
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',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
});
|
||||||
name: 'response',
|
|
||||||
args: [
|
const fns = result.data?.flatMap((r) => r.functions) ?? [];
|
||||||
{
|
|
||||||
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',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
return fns;
|
return fns;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ type TauriCmd =
|
|||||||
| 'cmd_send_http_request'
|
| 'cmd_send_http_request'
|
||||||
| 'cmd_set_key_value'
|
| 'cmd_set_key_value'
|
||||||
| 'cmd_set_update_mode'
|
| 'cmd_set_update_mode'
|
||||||
|
| 'cmd_template_functions'
|
||||||
| 'cmd_track_event'
|
| 'cmd_track_event'
|
||||||
| 'cmd_update_cookie_jar'
|
| 'cmd_update_cookie_jar'
|
||||||
| 'cmd_update_environment'
|
| 'cmd_update_environment'
|
||||||
|
|||||||
Reference in New Issue
Block a user