mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-09-16 14:51:37 +02:00
Convert Postman's simple dynamic variables to template functions (#680)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
c9990d8492
commit
ae518dfb4c
@@ -30,6 +30,19 @@ const OAUTH1_SIGNATURE_METHODS: Record<string, string> = {
|
||||
};
|
||||
const DEFAULT_OAUTH1_SIGNATURE_METHOD = "HMAC-SHA1";
|
||||
|
||||
// Postman's dynamic variables that a Yaak template function reproduces exactly.
|
||||
// The faker-backed ones ({{$randomFirstName}} and friends) have no equivalent and
|
||||
// are left alone. Arguments have to be single-quoted; the template parser reads a
|
||||
// double-quoted call as raw text.
|
||||
const POSTMAN_DYNAMIC_VARIABLES: Record<string, string> = {
|
||||
$guid: "uuid.v4()",
|
||||
$randomUUID: "uuid.v4()",
|
||||
$timestamp: "timestamp.unix()",
|
||||
$isoTimestamp: "timestamp.iso8601()",
|
||||
// Postman's $randomInt is an integer from 0 to 1000
|
||||
$randomInt: "random.range(min='0',max='1000',decimals='0')",
|
||||
};
|
||||
|
||||
type AtLeast<T, K extends keyof T> = Partial<T> & Pick<T, K>;
|
||||
|
||||
interface ExportResources {
|
||||
@@ -604,10 +617,14 @@ function importDescription(rawDescription: unknown): string | undefined {
|
||||
/** Recursively render all nested object properties */
|
||||
function convertTemplateSyntax<T>(obj: T): T {
|
||||
if (typeof obj === "string") {
|
||||
return obj.replace(
|
||||
/{{\s*(_\.)?([^}]*)\s*}}/g,
|
||||
(_m, _dot, expr) => `\${[${expr.trim().replace(/^vault:/, "")}]}`,
|
||||
) as T;
|
||||
return obj.replace(/{{\s*(_\.)?([^}]*)\s*}}/g, (_m, _dot, expr) => {
|
||||
const name = String(expr).trim();
|
||||
// hasOwn, so a collection using {{constructor}} doesn't reach Object.prototype
|
||||
if (Object.hasOwn(POSTMAN_DYNAMIC_VARIABLES, name)) {
|
||||
return `\${[${POSTMAN_DYNAMIC_VARIABLES[name]}]}`;
|
||||
}
|
||||
return `\${[${name.replace(/^vault:/, "")}]}`;
|
||||
}) as T;
|
||||
}
|
||||
if (Array.isArray(obj) && obj != null) {
|
||||
return obj.map(convertTemplateSyntax) as T;
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"info": {
|
||||
"_postman_id": "5c0ffee0-dyn0-4var-9abc-000000000001",
|
||||
"name": "Dynamic Variables",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||
},
|
||||
"item": [
|
||||
{
|
||||
"name": "Dynamic Variables",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "X-Request-Id",
|
||||
"value": "{{$guid}}"
|
||||
},
|
||||
{
|
||||
"key": "X-Correlation-Id",
|
||||
"value": "{{$randomUUID}}"
|
||||
},
|
||||
{
|
||||
"key": "X-Sent-At",
|
||||
"value": "{{$timestamp}}"
|
||||
},
|
||||
{
|
||||
"key": "X-Sent-At-Iso",
|
||||
"value": "{{$isoTimestamp}}"
|
||||
},
|
||||
{
|
||||
"key": "X-Spaced",
|
||||
"value": "{{ $guid }}"
|
||||
},
|
||||
{
|
||||
"key": "X-Faker",
|
||||
"value": "{{$randomFirstName}}"
|
||||
},
|
||||
{
|
||||
"key": "X-Plain",
|
||||
"value": "{{base_url}}"
|
||||
}
|
||||
],
|
||||
"url": {
|
||||
"raw": "https://yaak.app/x/echo?n={{$randomInt}}",
|
||||
"protocol": "https",
|
||||
"host": ["yaak", "app"],
|
||||
"path": ["x", "echo"],
|
||||
"query": [
|
||||
{
|
||||
"key": "n",
|
||||
"value": "{{$randomInt}}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"id\": \"{{$guid}}\",\n \"at\": {{$timestamp}},\n \"mixed\": \"{{$guid}}/{{base_url}}\"\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
{
|
||||
"resources": {
|
||||
"workspaces": [
|
||||
{
|
||||
"model": "workspace",
|
||||
"id": "GENERATE_ID::WORKSPACE_1",
|
||||
"name": "Dynamic Variables",
|
||||
"authenticationType": null,
|
||||
"authentication": {}
|
||||
}
|
||||
],
|
||||
"environments": [
|
||||
{
|
||||
"model": "environment",
|
||||
"id": "GENERATE_ID::ENVIRONMENT_1",
|
||||
"name": "Global Variables",
|
||||
"workspaceId": "GENERATE_ID::WORKSPACE_1",
|
||||
"parentModel": "workspace",
|
||||
"parentId": null,
|
||||
"variables": []
|
||||
}
|
||||
],
|
||||
"httpRequests": [
|
||||
{
|
||||
"model": "http_request",
|
||||
"id": "GENERATE_ID::HTTP_REQUEST_15",
|
||||
"workspaceId": "GENERATE_ID::WORKSPACE_1",
|
||||
"folderId": null,
|
||||
"name": "Dynamic Variables",
|
||||
"method": "POST",
|
||||
"url": "https://yaak.app/x/echo",
|
||||
"urlParameters": [
|
||||
{
|
||||
"name": "n",
|
||||
"value": "${[random.range(min='0',max='1000',decimals='0')]}",
|
||||
"enabled": true
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"text": "{\n \"id\": \"${[uuid.v4()]}\",\n \"at\": ${[timestamp.unix()]},\n \"mixed\": \"${[uuid.v4()]}/${[base_url]}\"\n}"
|
||||
},
|
||||
"bodyType": "application/json",
|
||||
"sortPriority": 0,
|
||||
"headers": [
|
||||
{
|
||||
"name": "X-Request-Id",
|
||||
"value": "${[uuid.v4()]}",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "X-Correlation-Id",
|
||||
"value": "${[uuid.v4()]}",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "X-Sent-At",
|
||||
"value": "${[timestamp.unix()]}",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "X-Sent-At-Iso",
|
||||
"value": "${[timestamp.iso8601()]}",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "X-Spaced",
|
||||
"value": "${[uuid.v4()]}",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "X-Faker",
|
||||
"value": "${[$randomFirstName]}",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "X-Plain",
|
||||
"value": "${[base_url]}",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json",
|
||||
"enabled": true
|
||||
}
|
||||
],
|
||||
"authenticationType": null,
|
||||
"authentication": {}
|
||||
}
|
||||
],
|
||||
"folders": []
|
||||
},
|
||||
"sourceKeys": {
|
||||
"GENERATE_ID::WORKSPACE_1": "collection:5c0ffee0-dyn0-4var-9abc-000000000001"
|
||||
}
|
||||
}
|
||||
+12
-12
@@ -3,7 +3,7 @@
|
||||
"workspaces": [
|
||||
{
|
||||
"model": "workspace",
|
||||
"id": "GENERATE_ID::WORKSPACE_1",
|
||||
"id": "GENERATE_ID::WORKSPACE_2",
|
||||
"name": "New Collection",
|
||||
"authenticationType": null,
|
||||
"authentication": {}
|
||||
@@ -12,9 +12,9 @@
|
||||
"environments": [
|
||||
{
|
||||
"model": "environment",
|
||||
"id": "GENERATE_ID::ENVIRONMENT_1",
|
||||
"id": "GENERATE_ID::ENVIRONMENT_2",
|
||||
"name": "Global Variables",
|
||||
"workspaceId": "GENERATE_ID::WORKSPACE_1",
|
||||
"workspaceId": "GENERATE_ID::WORKSPACE_2",
|
||||
"parentModel": "workspace",
|
||||
"parentId": null,
|
||||
"variables": []
|
||||
@@ -23,8 +23,8 @@
|
||||
"httpRequests": [
|
||||
{
|
||||
"model": "http_request",
|
||||
"id": "GENERATE_ID::HTTP_REQUEST_15",
|
||||
"workspaceId": "GENERATE_ID::WORKSPACE_1",
|
||||
"id": "GENERATE_ID::HTTP_REQUEST_16",
|
||||
"workspaceId": "GENERATE_ID::WORKSPACE_2",
|
||||
"folderId": "GENERATE_ID::FOLDER_2",
|
||||
"name": "Request 1",
|
||||
"method": "GET",
|
||||
@@ -39,8 +39,8 @@
|
||||
},
|
||||
{
|
||||
"model": "http_request",
|
||||
"id": "GENERATE_ID::HTTP_REQUEST_16",
|
||||
"workspaceId": "GENERATE_ID::WORKSPACE_1",
|
||||
"id": "GENERATE_ID::HTTP_REQUEST_17",
|
||||
"workspaceId": "GENERATE_ID::WORKSPACE_2",
|
||||
"folderId": "GENERATE_ID::FOLDER_1",
|
||||
"name": "Request 2",
|
||||
"method": "GET",
|
||||
@@ -55,8 +55,8 @@
|
||||
},
|
||||
{
|
||||
"model": "http_request",
|
||||
"id": "GENERATE_ID::HTTP_REQUEST_17",
|
||||
"workspaceId": "GENERATE_ID::WORKSPACE_1",
|
||||
"id": "GENERATE_ID::HTTP_REQUEST_18",
|
||||
"workspaceId": "GENERATE_ID::WORKSPACE_2",
|
||||
"folderId": null,
|
||||
"name": "Request 3",
|
||||
"method": "GET",
|
||||
@@ -74,7 +74,7 @@
|
||||
{
|
||||
"model": "folder",
|
||||
"sortPriority": 0,
|
||||
"workspaceId": "GENERATE_ID::WORKSPACE_1",
|
||||
"workspaceId": "GENERATE_ID::WORKSPACE_2",
|
||||
"id": "GENERATE_ID::FOLDER_1",
|
||||
"name": "Top Folder",
|
||||
"folderId": null,
|
||||
@@ -84,7 +84,7 @@
|
||||
{
|
||||
"model": "folder",
|
||||
"sortPriority": 1,
|
||||
"workspaceId": "GENERATE_ID::WORKSPACE_1",
|
||||
"workspaceId": "GENERATE_ID::WORKSPACE_2",
|
||||
"id": "GENERATE_ID::FOLDER_2",
|
||||
"name": "Nested Folder",
|
||||
"folderId": "GENERATE_ID::FOLDER_1",
|
||||
@@ -94,6 +94,6 @@
|
||||
]
|
||||
},
|
||||
"sourceKeys": {
|
||||
"GENERATE_ID::WORKSPACE_1": "collection:9e6dfada-256c-49ea-a38f-7d1b05b7ca2d"
|
||||
"GENERATE_ID::WORKSPACE_2": "collection:9e6dfada-256c-49ea-a38f-7d1b05b7ca2d"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"workspaces": [
|
||||
{
|
||||
"model": "workspace",
|
||||
"id": "GENERATE_ID::WORKSPACE_2",
|
||||
"id": "GENERATE_ID::WORKSPACE_3",
|
||||
"name": "New Collection",
|
||||
"authenticationType": "basic",
|
||||
"authentication": {
|
||||
@@ -15,9 +15,9 @@
|
||||
"environments": [
|
||||
{
|
||||
"model": "environment",
|
||||
"id": "GENERATE_ID::ENVIRONMENT_2",
|
||||
"id": "GENERATE_ID::ENVIRONMENT_3",
|
||||
"name": "Global Variables",
|
||||
"workspaceId": "GENERATE_ID::WORKSPACE_2",
|
||||
"workspaceId": "GENERATE_ID::WORKSPACE_3",
|
||||
"parentModel": "workspace",
|
||||
"parentId": null,
|
||||
"variables": [
|
||||
@@ -31,8 +31,8 @@
|
||||
"httpRequests": [
|
||||
{
|
||||
"model": "http_request",
|
||||
"id": "GENERATE_ID::HTTP_REQUEST_18",
|
||||
"workspaceId": "GENERATE_ID::WORKSPACE_2",
|
||||
"id": "GENERATE_ID::HTTP_REQUEST_19",
|
||||
"workspaceId": "GENERATE_ID::WORKSPACE_3",
|
||||
"folderId": null,
|
||||
"name": "Form URL",
|
||||
"method": "POST",
|
||||
@@ -102,6 +102,6 @@
|
||||
"folders": []
|
||||
},
|
||||
"sourceKeys": {
|
||||
"GENERATE_ID::WORKSPACE_2": "collection:9e6dfada-256c-49ea-a38f-7d1b05b7ca2d"
|
||||
"GENERATE_ID::WORKSPACE_3": "collection:9e6dfada-256c-49ea-a38f-7d1b05b7ca2d"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,6 +154,35 @@ describe("importer-postman", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test("Leaves {{constructor}} alone instead of reaching Object.prototype", () => {
|
||||
const result = convertPostman(
|
||||
JSON.stringify({
|
||||
info: {
|
||||
name: "Prototype Key",
|
||||
schema: "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
|
||||
},
|
||||
item: [
|
||||
{
|
||||
name: "Request",
|
||||
request: {
|
||||
method: "GET",
|
||||
url: "https://yaak.app",
|
||||
header: [
|
||||
{ key: "X-A", value: "{{constructor}}" },
|
||||
{ key: "X-B", value: "{{toString}}" },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result?.resources.httpRequests[0]?.headers).toEqual([
|
||||
{ name: "X-A", value: "${[constructor]}", enabled: true },
|
||||
{ name: "X-B", value: "${[toString]}", enabled: true },
|
||||
]);
|
||||
});
|
||||
|
||||
test("Omits keys for items the collection never identified", () => {
|
||||
const result = convertPostman(
|
||||
JSON.stringify({
|
||||
|
||||
Reference in New Issue
Block a user