mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-01-11 22:40:26 +01:00
Add previewArgs support for template functions and enhance validation logic for form inputs
This commit is contained in:
@@ -28,6 +28,7 @@ export const plugin: PluginDefinition = {
|
||||
{
|
||||
name: '1password.item',
|
||||
description: 'Get a secret',
|
||||
previewArgs: ['field'],
|
||||
args: [
|
||||
{
|
||||
name: 'token',
|
||||
|
||||
@@ -5,15 +5,18 @@ export const plugin: PluginDefinition = {
|
||||
{
|
||||
name: 'cookie.value',
|
||||
description: 'Read the value of a cookie in the jar, by name',
|
||||
previewArgs: ['name'],
|
||||
args: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'cookie_name',
|
||||
name: 'name',
|
||||
label: 'Cookie Name',
|
||||
},
|
||||
],
|
||||
async onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
||||
return ctx.cookies.getValue({ name: String(args.values.cookie_name) });
|
||||
// The legacy name was cookie_name, but we changed it
|
||||
const name = args.values.cookie_name ?? args.values.name;
|
||||
return ctx.cookies.getValue({ name: String(name) });
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -16,6 +16,7 @@ export const plugin: PluginDefinition = {
|
||||
{
|
||||
name: 'fs.readFile',
|
||||
description: 'Read the contents of a file as utf-8',
|
||||
previewArgs: ['encoding'],
|
||||
args: [
|
||||
{ title: 'Select File', type: 'file', name: 'path', label: 'File' },
|
||||
{
|
||||
@@ -26,14 +27,21 @@ export const plugin: PluginDefinition = {
|
||||
description: "Specifies how the file's bytes are decoded into text when read",
|
||||
options,
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'trim',
|
||||
label: 'Trim Whitespace',
|
||||
description: 'Remove leading and trailing whitespace from the file contents',
|
||||
},
|
||||
],
|
||||
async onRender(_ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
||||
if (!args.values.path || !args.values.encoding) return null;
|
||||
|
||||
try {
|
||||
return fs.promises.readFile(String(args.values.path ?? ''), {
|
||||
const v = await fs.promises.readFile(String(args.values.path ?? ''), {
|
||||
encoding: String(args.values.encoding ?? 'utf-8') as BufferEncoding,
|
||||
});
|
||||
return args.values.trim ? v.trim() : v;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ export const plugin: PluginDefinition = {
|
||||
{
|
||||
name: 'json.jsonpath',
|
||||
description: 'Filter JSON-formatted text using JSONPath syntax',
|
||||
previewArgs: ['query'],
|
||||
args: [
|
||||
{
|
||||
type: 'editor',
|
||||
|
||||
@@ -16,8 +16,22 @@ export const plugin: PluginDefinition = {
|
||||
name: 'prompt.text',
|
||||
description: 'Prompt the user for input when sending a request',
|
||||
previewType: 'click',
|
||||
previewArgs: ['label'],
|
||||
args: [
|
||||
{ type: 'text', name: 'label', label: 'Label', optional: true },
|
||||
{
|
||||
type: 'text',
|
||||
name: 'label',
|
||||
label: 'Label',
|
||||
optional: true,
|
||||
dynamic(_ctx, args) {
|
||||
if (
|
||||
args.values.store === STORE_EXPIRE ||
|
||||
(args.values.store === STORE_FOREVER && !args.values.key)
|
||||
) {
|
||||
return { optional: false };
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
name: 'store',
|
||||
@@ -68,21 +82,24 @@ export const plugin: PluginDefinition = {
|
||||
{
|
||||
type: 'banner',
|
||||
color: 'info',
|
||||
inputs: [],
|
||||
dynamic(_ctx, args) {
|
||||
return { hidden: args.values.store === STORE_NONE };
|
||||
},
|
||||
inputs: [
|
||||
{
|
||||
type: 'markdown',
|
||||
content: '',
|
||||
async dynamic(_ctx, args) {
|
||||
const key = buildKey(args);
|
||||
return {
|
||||
let key: string;
|
||||
try {
|
||||
key = buildKey(args);
|
||||
} catch (err) {
|
||||
return { color: 'danger', inputs: [{ type: 'markdown', content: String(err) }] };
|
||||
}
|
||||
return {
|
||||
hidden: args.values.store === STORE_NONE,
|
||||
inputs: [
|
||||
{
|
||||
type: 'markdown',
|
||||
content: [`Value will be saved under: \`${key}\``].join('\n\n'),
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'accordion',
|
||||
@@ -139,7 +156,7 @@ export const plugin: PluginDefinition = {
|
||||
|
||||
function buildKey(args: CallTemplateFunctionArgs) {
|
||||
if (!args.values.key && !args.values.label) {
|
||||
throw new Error('Key or Label is required when storing values');
|
||||
throw new Error('A label or key is required when storing values');
|
||||
}
|
||||
return [args.values.namespace, args.values.key || args.values.label]
|
||||
.filter((v) => !!v)
|
||||
|
||||
@@ -5,6 +5,7 @@ export const plugin: PluginDefinition = {
|
||||
{
|
||||
name: 'random.range',
|
||||
description: 'Generate a random number between two values',
|
||||
previewArgs: ['min', 'max'],
|
||||
args: [
|
||||
{
|
||||
type: 'text',
|
||||
|
||||
@@ -24,6 +24,7 @@ export const plugin: PluginDefinition = {
|
||||
name: 'regex.match',
|
||||
description: 'Extract text using a regular expression',
|
||||
args: [inputArg, regexArg],
|
||||
previewArgs: [regexArg.name],
|
||||
async onRender(_ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
||||
const input = String(args.values.input ?? '');
|
||||
const regex = new RegExp(String(args.values.regex ?? ''));
|
||||
@@ -37,6 +38,7 @@ export const plugin: PluginDefinition = {
|
||||
{
|
||||
name: 'regex.replace',
|
||||
description: 'Replace text using a regular expression',
|
||||
previewArgs: [regexArg.name],
|
||||
args: [
|
||||
inputArg,
|
||||
regexArg,
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api';
|
||||
import type { AnyModel, HttpUrlParameter } from '@yaakapp-internal/models';
|
||||
import type { GenericCompletionOption } from '@yaakapp-internal/plugins';
|
||||
import type { JSONPathResult } from '../../template-function-json';
|
||||
import { filterJSONPath } from '../../template-function-json';
|
||||
import type { XPathResult } from '../../template-function-xml';
|
||||
import { filterXPath } from '../../template-function-xml';
|
||||
|
||||
const RETURN_FIRST = 'first';
|
||||
const RETURN_ALL = 'all';
|
||||
const RETURN_JOIN = 'join';
|
||||
|
||||
export const plugin: PluginDefinition = {
|
||||
templateFunctions: [
|
||||
{
|
||||
name: 'request.body',
|
||||
name: 'request.body.raw',
|
||||
aliases: ['request.body'],
|
||||
args: [
|
||||
{
|
||||
name: 'requestId',
|
||||
@@ -25,8 +34,115 @@ export const plugin: PluginDefinition = {
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'request.body.path',
|
||||
previewArgs: ['path'],
|
||||
args: [
|
||||
{ name: 'requestId', label: 'Http Request', type: 'http_request' },
|
||||
{
|
||||
type: 'h_stack',
|
||||
inputs: [
|
||||
{
|
||||
type: 'select',
|
||||
name: 'result',
|
||||
label: 'Return Format',
|
||||
defaultValue: RETURN_FIRST,
|
||||
options: [
|
||||
{ label: 'First result', value: RETURN_FIRST },
|
||||
{ label: 'All results', value: RETURN_ALL },
|
||||
{ label: 'Join with separator', value: RETURN_JOIN },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'join',
|
||||
type: 'text',
|
||||
label: 'Separator',
|
||||
optional: true,
|
||||
defaultValue: ', ',
|
||||
dynamic(_ctx, args) {
|
||||
return { hidden: args.values.result !== RETURN_JOIN };
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'path',
|
||||
label: 'JSONPath or XPath',
|
||||
placeholder: '$.books[0].id or /books[0]/id',
|
||||
dynamic: async (ctx, args) => {
|
||||
const requestId = String(args.values.requestId ?? 'n/a');
|
||||
const httpRequest = await ctx.httpRequest.getById({ id: requestId });
|
||||
if (httpRequest == null) return null;
|
||||
|
||||
const contentType =
|
||||
httpRequest.headers
|
||||
.find((h) => h.name.toLowerCase() === 'content-type')
|
||||
?.value.toLowerCase() ?? '';
|
||||
if (contentType.includes('xml') || contentType?.includes('html')) {
|
||||
return {
|
||||
label: 'XPath',
|
||||
placeholder: '/books[0]/id',
|
||||
description: 'Enter an XPath expression used to filter the results',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
label: 'JSONPath',
|
||||
placeholder: '$.books[0].id',
|
||||
description: 'Enter a JSONPath expression used to filter the results',
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
async onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
||||
const requestId = String(args.values.requestId ?? 'n/a');
|
||||
const httpRequest = await ctx.httpRequest.getById({ id: requestId });
|
||||
if (httpRequest == null) return null;
|
||||
|
||||
const body = httpRequest.body?.text ?? '';
|
||||
|
||||
try {
|
||||
const result: JSONPathResult =
|
||||
args.values.result === RETURN_ALL
|
||||
? 'all'
|
||||
: args.values.result === RETURN_JOIN
|
||||
? 'join'
|
||||
: 'first';
|
||||
return filterJSONPath(
|
||||
body,
|
||||
String(args.values.path || ''),
|
||||
result,
|
||||
args.values.join == null ? null : String(args.values.join),
|
||||
);
|
||||
} catch {
|
||||
// Probably not JSON, try XPath
|
||||
}
|
||||
|
||||
try {
|
||||
const result: XPathResult =
|
||||
args.values.result === RETURN_ALL
|
||||
? 'all'
|
||||
: args.values.result === RETURN_JOIN
|
||||
? 'join'
|
||||
: 'first';
|
||||
return filterXPath(
|
||||
body,
|
||||
String(args.values.path || ''),
|
||||
result,
|
||||
args.values.join == null ? null : String(args.values.join),
|
||||
);
|
||||
} catch {
|
||||
// Probably not XML
|
||||
}
|
||||
|
||||
return null; // Bail out
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'request.header',
|
||||
description: 'Read the value of a request header, by name',
|
||||
previewArgs: ['header'],
|
||||
args: [
|
||||
{
|
||||
name: 'requestId',
|
||||
|
||||
@@ -62,6 +62,7 @@ export const plugin: PluginDefinition = {
|
||||
{
|
||||
name: 'response.header',
|
||||
description: 'Read the value of a response header, by name',
|
||||
previewArgs: ['header'],
|
||||
args: [
|
||||
requestArg,
|
||||
behaviorArgs,
|
||||
@@ -108,6 +109,7 @@ export const plugin: PluginDefinition = {
|
||||
name: 'response.body.path',
|
||||
description: 'Access a field of the response body using JsonPath or XPath',
|
||||
aliases: ['response'],
|
||||
previewArgs: ['path'],
|
||||
args: [
|
||||
requestArg,
|
||||
behaviorArgs,
|
||||
@@ -155,7 +157,9 @@ export const plugin: PluginDefinition = {
|
||||
}
|
||||
|
||||
const contentType =
|
||||
resp?.headers.find((h) => h.name.toLowerCase() === 'content-type')?.value ?? '';
|
||||
resp?.headers
|
||||
.find((h) => h.name.toLowerCase() === 'content-type')
|
||||
?.value.toLowerCase() ?? '';
|
||||
if (contentType.includes('xml') || contentType?.includes('html')) {
|
||||
return {
|
||||
label: 'XPath',
|
||||
@@ -187,9 +191,10 @@ export const plugin: PluginDefinition = {
|
||||
return null;
|
||||
}
|
||||
|
||||
const BOM = '\ufeff';
|
||||
let body: string;
|
||||
try {
|
||||
body = readFileSync(response.bodyPath, 'utf-8');
|
||||
body = readFileSync(response.bodyPath, 'utf-8').replace(BOM, '');
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -81,12 +81,14 @@ export const plugin: PluginDefinition = {
|
||||
name: 'timestamp.format',
|
||||
description: 'Format a date using a dayjs-compatible format string',
|
||||
args: [dateArg, formatArg],
|
||||
previewArgs: [formatArg.name],
|
||||
onRender: async (_ctx, args) => formatDatetime(args.values),
|
||||
},
|
||||
{
|
||||
name: 'timestamp.offset',
|
||||
description: 'Get the offset of a date based on an expression',
|
||||
args: [dateArg, expressionArg],
|
||||
previewArgs: [expressionArg.name],
|
||||
onRender: async (_ctx, args) => calculateDatetime(args.values),
|
||||
},
|
||||
],
|
||||
|
||||
@@ -11,6 +11,7 @@ export const plugin: PluginDefinition = {
|
||||
{
|
||||
name: 'xml.xpath',
|
||||
description: 'Filter XML-formatted text using XPath syntax',
|
||||
previewArgs: ['query'],
|
||||
args: [
|
||||
{
|
||||
type: 'text',
|
||||
|
||||
Reference in New Issue
Block a user