From e59ecce8861ac88720f076e1ff567b760dc4dfbc Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Tue, 14 Jul 2026 09:13:47 -0700 Subject: [PATCH] Fix Insomnia text body imports (#507) --- plugins/importer-insomnia/src/common.ts | 87 +++++++++++++++++++ plugins/importer-insomnia/src/v4.ts | 44 +--------- plugins/importer-insomnia/src/v5.ts | 55 ++---------- plugins/importer-insomnia/tests/body.test.ts | 91 ++++++++++++++++++++ 4 files changed, 188 insertions(+), 89 deletions(-) create mode 100644 plugins/importer-insomnia/tests/body.test.ts diff --git a/plugins/importer-insomnia/src/common.ts b/plugins/importer-insomnia/src/common.ts index 56a93ca3..1fe0d082 100644 --- a/plugins/importer-insomnia/src/common.ts +++ b/plugins/importer-insomnia/src/common.ts @@ -1,3 +1,5 @@ +/* oxlint-disable no-explicit-any */ + export function isJSObject(obj: unknown) { return Object.prototype.toString.call(obj) === "[object Object]"; } @@ -13,6 +15,91 @@ export function convertId(id: string): string { return `GENERATE_ID::${id}`; } +export function importHttpBodyAndHeaders(obj: any) { + const { headers } = importHeaders(obj); + const { body, bodyType } = importHttpBody(obj.body); + const mimeType = typeof obj.body?.mimeType === "string" ? obj.body.mimeType.trim() : ""; + + if ( + bodyType != null && + mimeType !== "" && + !headers.some((header: { name: string }) => header.name.toLowerCase() === "content-type") + ) { + headers.push({ enabled: true, name: "Content-Type", value: mimeType }); + } + + return { body, bodyType, headers }; +} + +export function importHeaders(obj: any) { + const headers = (obj.headers ?? []) + .map((header: any) => ({ + enabled: !header.disabled, + name: header.name ?? "", + value: header.value ?? "", + })) + .filter(({ name, value }: any) => name !== "" || value !== ""); + return { headers } as const; +} + +function importHttpBody(rawBody: any) { + const mimeType = typeof rawBody?.mimeType === "string" ? rawBody.mimeType.trim() : ""; + const normalizedMimeType = mimeType.split(";", 1)[0]?.toLowerCase() ?? ""; + + if (normalizedMimeType === "application/octet-stream") { + return { bodyType: "binary", body: { filePath: rawBody.fileName ?? "" } }; + } + + if (normalizedMimeType === "application/x-www-form-urlencoded") { + return { + bodyType: "application/x-www-form-urlencoded", + body: { + form: (rawBody.params ?? []).map((parameter: any) => ({ + enabled: !parameter.disabled, + name: parameter.name ?? "", + value: parameter.value ?? "", + })), + }, + }; + } + + if (normalizedMimeType === "multipart/form-data") { + return { + bodyType: "multipart/form-data", + body: { + form: (rawBody.params ?? []).map((parameter: any) => ({ + enabled: !parameter.disabled, + name: parameter.name ?? "", + value: parameter.value ?? "", + file: parameter.fileName ?? null, + })), + }, + }; + } + + if (normalizedMimeType === "application/graphql") { + return { bodyType: "graphql", body: { text: rawBody.text ?? "" } }; + } + + if (normalizedMimeType === "application/json" || normalizedMimeType.endsWith("+json")) { + return { bodyType: "application/json", body: { text: rawBody.text ?? "" } }; + } + + if ( + normalizedMimeType === "text/xml" || + normalizedMimeType === "application/xml" || + normalizedMimeType.endsWith("+xml") + ) { + return { bodyType: "text/xml", body: { text: rawBody.text ?? "" } }; + } + + if (typeof rawBody?.text === "string") { + return { bodyType: "other", body: { text: rawBody.text } }; + } + + return { bodyType: null, body: {} }; +} + export function deleteUndefinedAttrs(obj: T): T { if (Array.isArray(obj) && obj != null) { return obj.map(deleteUndefinedAttrs) as T; diff --git a/plugins/importer-insomnia/src/v4.ts b/plugins/importer-insomnia/src/v4.ts index 8b8f4c1e..1e6e0397 100644 --- a/plugins/importer-insomnia/src/v4.ts +++ b/plugins/importer-insomnia/src/v4.ts @@ -1,6 +1,6 @@ /* oxlint-disable no-explicit-any */ import type { PartialImportResources } from "@yaakapp/api"; -import { convertId, convertTemplateSyntax, isJSObject } from "./common"; +import { convertId, convertTemplateSyntax, importHttpBodyAndHeaders, isJSObject } from "./common"; export function convertInsomniaV4(parsed: any) { if (!Array.isArray(parsed.resources)) return null; @@ -64,38 +64,6 @@ export function convertInsomniaV4(parsed: any) { } function importHttpRequest(r: any, workspaceId: string): PartialImportResources["httpRequests"][0] { - let bodyType: string | null = null; - let body = {}; - if (r.body.mimeType === "application/octet-stream") { - bodyType = "binary"; - body = { filePath: r.body.fileName ?? "" }; - } else if (r.body?.mimeType === "application/x-www-form-urlencoded") { - bodyType = "application/x-www-form-urlencoded"; - body = { - form: (r.body.params ?? []).map((p: any) => ({ - enabled: !p.disabled, - name: p.name ?? "", - value: p.value ?? "", - })), - }; - } else if (r.body?.mimeType === "multipart/form-data") { - bodyType = "multipart/form-data"; - body = { - form: (r.body.params ?? []).map((p: any) => ({ - enabled: !p.disabled, - name: p.name ?? "", - value: p.value ?? "", - file: p.fileName ?? null, - })), - }; - } else if (r.body?.mimeType === "application/graphql") { - bodyType = "graphql"; - body = { text: r.body.text ?? "" }; - } else if (r.body?.mimeType === "application/json") { - bodyType = "application/json"; - body = { text: r.body.text ?? "" }; - } - let authenticationType: string | null = null; let authentication = {}; if (r.authentication.type === "bearer") { @@ -127,18 +95,10 @@ function importHttpRequest(r: any, workspaceId: string): PartialImportResources[ name: p.name ?? "", value: p.value ?? "", })), - body, - bodyType, + ...importHttpBodyAndHeaders(r), authentication, authenticationType, method: r.method, - headers: (r.headers ?? []) - .map((h: any) => ({ - enabled: !h.disabled, - name: h.name ?? "", - value: h.value ?? "", - })) - .filter(({ name, value }: any) => name !== "" || value !== ""), }; } diff --git a/plugins/importer-insomnia/src/v5.ts b/plugins/importer-insomnia/src/v5.ts index c43e64e8..0e0647b9 100644 --- a/plugins/importer-insomnia/src/v5.ts +++ b/plugins/importer-insomnia/src/v5.ts @@ -1,6 +1,12 @@ /* oxlint-disable no-explicit-any */ import type { PartialImportResources } from "@yaakapp/api"; -import { convertId, convertTemplateSyntax, isJSObject } from "./common"; +import { + convertId, + convertTemplateSyntax, + importHeaders, + importHttpBodyAndHeaders, + isJSObject, +} from "./common"; export function convertInsomniaV5(parsed: any) { // Assert parsed is object @@ -82,38 +88,6 @@ function importHttpRequest( const updated = r.meta?.modified ?? r.updated; const sortKey = r.meta?.sortKey ?? r.sortKey; - let bodyType: string | null = null; - let body = {}; - if (r.body?.mimeType === "application/octet-stream") { - bodyType = "binary"; - body = { filePath: r.body.fileName ?? "" }; - } else if (r.body?.mimeType === "application/x-www-form-urlencoded") { - bodyType = "application/x-www-form-urlencoded"; - body = { - form: (r.body.params ?? []).map((p: any) => ({ - enabled: !p.disabled, - name: p.name ?? "", - value: p.value ?? "", - })), - }; - } else if (r.body?.mimeType === "multipart/form-data") { - bodyType = "multipart/form-data"; - body = { - form: (r.body.params ?? []).map((p: any) => ({ - enabled: !p.disabled, - name: p.name ?? "", - value: p.value ?? "", - file: p.fileName ?? null, - })), - }; - } else if (r.body?.mimeType === "application/graphql") { - bodyType = "graphql"; - body = { text: r.body.text ?? "" }; - } else if (r.body?.mimeType === "application/json") { - bodyType = "application/json"; - body = { text: r.body.text ?? "" }; - } - return { id: convertId(id), workspaceId: convertId(workspaceId), @@ -130,10 +104,8 @@ function importHttpRequest( name: p.name ?? "", value: p.value ?? "", })), - body, - bodyType, + ...importHttpBodyAndHeaders(r), method: r.method, - ...importHeaders(r), ...importAuthentication(r), }; } @@ -203,17 +175,6 @@ function importWebsocketRequest( }; } -function importHeaders(obj: any) { - const headers = (obj.headers ?? []) - .map((h: any) => ({ - enabled: !h.disabled, - name: h.name ?? "", - value: h.value ?? "", - })) - .filter(({ name, value }: any) => name !== "" || value !== ""); - return { headers } as const; -} - function importAuthentication(obj: any) { let authenticationType: string | null = null; let authentication = {}; diff --git a/plugins/importer-insomnia/tests/body.test.ts b/plugins/importer-insomnia/tests/body.test.ts new file mode 100644 index 00000000..4b51c9f7 --- /dev/null +++ b/plugins/importer-insomnia/tests/body.test.ts @@ -0,0 +1,91 @@ +import { describe, expect, test } from "vite-plus/test"; +import { importHttpBodyAndHeaders } from "../src/common"; + +describe("importHttpBodyAndHeaders", () => { + test("imports XML text using the native XML body type", () => { + const result = importHttpBodyAndHeaders({ + body: { + mimeType: "application/soap+xml; charset=utf-8", + text: "", + }, + }); + + expect(result).toEqual({ + bodyType: "text/xml", + body: { text: "" }, + headers: [ + { + enabled: true, + name: "Content-Type", + value: "application/soap+xml; charset=utf-8", + }, + ], + }); + }); + + test("imports vendor JSON using the native JSON body type", () => { + const result = importHttpBodyAndHeaders({ + body: { + mimeType: "application/problem+json", + text: '{"message":"Nope"}', + }, + }); + + expect(result.bodyType).toBe("application/json"); + expect(result.body).toEqual({ text: '{"message":"Nope"}' }); + }); + + test("imports unknown text using the other body type", () => { + const result = importHttpBodyAndHeaders({ + body: { + mimeType: "application/yaml", + text: "message: hello", + }, + }); + + expect(result).toEqual({ + bodyType: "other", + body: { text: "message: hello" }, + headers: [ + { + enabled: true, + name: "Content-Type", + value: "application/yaml", + }, + ], + }); + }); + + test("preserves an explicit content type instead of adding a duplicate", () => { + const result = importHttpBodyAndHeaders({ + body: { + mimeType: "application/yaml", + text: "message: hello", + }, + headers: [ + { + name: "content-type", + value: "application/x-yaml", + }, + ], + }); + + expect(result.headers).toEqual([ + { + enabled: true, + name: "content-type", + value: "application/x-yaml", + }, + ]); + }); + + test("imports text without inventing a content type", () => { + const result = importHttpBodyAndHeaders({ body: { text: "hello" } }); + + expect(result).toEqual({ + bodyType: "other", + body: { text: "hello" }, + headers: [], + }); + }); +});