mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-08-24 12:24:01 +02:00
fix(openapi): serialize request parameters
This commit is contained in:
@@ -741,17 +741,70 @@ function importUrlParameters({
|
|||||||
.map((p) => importState.resolve(p))
|
.map((p) => importState.resolve(p))
|
||||||
.filter(isRecord)
|
.filter(isRecord)
|
||||||
.filter((p) => stringAt(p, "in") === "query" || stringAt(p, "in") === "path")
|
.filter((p) => stringAt(p, "in") === "query" || stringAt(p, "in") === "path")
|
||||||
.map((p) => ({
|
.flatMap((p) => serializeUrlParameter(p, importState))
|
||||||
enabled: p.required === true,
|
|
||||||
name:
|
|
||||||
stringAt(p, "in") === "path"
|
|
||||||
? `:${stringAt(p, "name") ?? ""}`
|
|
||||||
: (stringAt(p, "name") ?? ""),
|
|
||||||
value: parameterExample(p, importState),
|
|
||||||
}))
|
|
||||||
.filter(({ name }) => name.length > 0);
|
.filter(({ name }) => name.length > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function serializeUrlParameter(
|
||||||
|
parameter: UnknownRecord,
|
||||||
|
importState: ImportState,
|
||||||
|
): HttpUrlParameter[] {
|
||||||
|
const name = stringAt(parameter, "name") ?? "";
|
||||||
|
const location = stringAt(parameter, "in");
|
||||||
|
const enabled = parameter.required === true;
|
||||||
|
const value = parameterExampleValue(parameter, importState);
|
||||||
|
if (isRecord(parameter.content)) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
enabled,
|
||||||
|
name: location === "path" ? `:${name}` : name,
|
||||||
|
value: serializeContentParameter(parameter, importState),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (location === "path") {
|
||||||
|
return [{ enabled, name: `:${name}`, value: serializePathParameter(name, value, parameter) }];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRecord(value)) {
|
||||||
|
const entries = Object.entries(value);
|
||||||
|
const style = stringAt(parameter, "style") ?? "form";
|
||||||
|
const explode = parameter.explode !== false;
|
||||||
|
if (style === "deepObject") {
|
||||||
|
return entries.map(([key, entryValue]) => ({
|
||||||
|
enabled,
|
||||||
|
name: `${name}[${key}]`,
|
||||||
|
value: stringifyExampleValue(entryValue),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (style === "form" && explode) {
|
||||||
|
return entries.map(([key, entryValue]) => ({
|
||||||
|
enabled,
|
||||||
|
name: key,
|
||||||
|
value: stringifyExampleValue(entryValue),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
const separator = style === "spaceDelimited" ? " " : style === "pipeDelimited" ? "|" : ",";
|
||||||
|
return [{ enabled, name, value: entries.flat().map(stringifyExampleValue).join(separator) }];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
const style = stringAt(parameter, "style") ?? "form";
|
||||||
|
const explode = parameter.explode !== false;
|
||||||
|
if (style === "form" && explode) {
|
||||||
|
return value.map((entryValue) => ({
|
||||||
|
enabled,
|
||||||
|
name,
|
||||||
|
value: stringifyExampleValue(entryValue),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
const separator = style === "spaceDelimited" ? " " : style === "pipeDelimited" ? "|" : ",";
|
||||||
|
return [{ enabled, name, value: value.map(stringifyExampleValue).join(separator) }];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [{ enabled, name, value: stringifyExampleValue(value) }];
|
||||||
|
}
|
||||||
|
|
||||||
function importHeaderParameters({
|
function importHeaderParameters({
|
||||||
importState,
|
importState,
|
||||||
parameters,
|
parameters,
|
||||||
@@ -763,18 +816,112 @@ function importHeaderParameters({
|
|||||||
.map((p) => importState.resolve(p))
|
.map((p) => importState.resolve(p))
|
||||||
.filter(isRecord)
|
.filter(isRecord)
|
||||||
.filter((p) => stringAt(p, "in") === "header")
|
.filter((p) => stringAt(p, "in") === "header")
|
||||||
|
.filter(
|
||||||
|
(p) =>
|
||||||
|
!["accept", "authorization", "content-type"].includes(
|
||||||
|
(stringAt(p, "name") ?? "").toLowerCase(),
|
||||||
|
),
|
||||||
|
)
|
||||||
.map((p) => ({
|
.map((p) => ({
|
||||||
enabled: p.required === true,
|
enabled: p.required === true,
|
||||||
name: stringAt(p, "name") ?? "",
|
name: stringAt(p, "name") ?? "",
|
||||||
value: parameterExample(p, importState),
|
value: serializeParameterValue(p, importState),
|
||||||
|
}))
|
||||||
|
.filter(({ name }) => name.length > 0)
|
||||||
|
.concat(importCookieHeader(parameters, importState));
|
||||||
|
}
|
||||||
|
|
||||||
|
function importCookieHeader(parameters: unknown[], importState: ImportState): HttpRequestHeader[] {
|
||||||
|
const cookies = parameters
|
||||||
|
.map((p) => importState.resolve(p))
|
||||||
|
.filter(isRecord)
|
||||||
|
.filter((p) => stringAt(p, "in") === "cookie")
|
||||||
|
.map((p) => ({
|
||||||
|
enabled: p.required === true,
|
||||||
|
name: stringAt(p, "name") ?? "",
|
||||||
|
value: serializeParameterValue(p, importState),
|
||||||
}))
|
}))
|
||||||
.filter(({ name }) => name.length > 0);
|
.filter(({ name }) => name.length > 0);
|
||||||
|
if (cookies.length === 0) return [];
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
enabled: cookies.some(({ enabled }) => enabled),
|
||||||
|
name: "Cookie",
|
||||||
|
value: cookies.map(({ name, value }) => `${name}=${value}`).join("; "),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function serializeParameterValue(parameter: UnknownRecord, importState: ImportState): string {
|
||||||
|
if (isRecord(parameter.content)) return serializeContentParameter(parameter, importState);
|
||||||
|
return serializeSimpleParameter(parameterExampleValue(parameter, importState), parameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
function serializeContentParameter(parameter: UnknownRecord, importState: ImportState): string {
|
||||||
|
const [contentType, rawMediaType] = Object.entries(toRecord(parameter.content))[0] ?? [];
|
||||||
|
const value = mediaTypeExample(toRecord(rawMediaType), importState);
|
||||||
|
return contentType?.toLowerCase().includes("json")
|
||||||
|
? (JSON.stringify(value) ?? "")
|
||||||
|
: stringifyExampleValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function serializePathParameter(name: string, value: unknown, parameter: UnknownRecord): string {
|
||||||
|
const style = stringAt(parameter, "style") ?? "simple";
|
||||||
|
const explode = parameter.explode === true;
|
||||||
|
const values = Array.isArray(value)
|
||||||
|
? value.map(stringifyExampleValue)
|
||||||
|
: isRecord(value)
|
||||||
|
? Object.entries(value).flatMap(([key, entryValue]) => [
|
||||||
|
key,
|
||||||
|
stringifyExampleValue(entryValue),
|
||||||
|
])
|
||||||
|
: [stringifyExampleValue(value)];
|
||||||
|
|
||||||
|
if (style === "label") {
|
||||||
|
if (explode && isRecord(value)) {
|
||||||
|
return `.${Object.entries(value)
|
||||||
|
.map(([key, entryValue]) => `${key}=${stringifyExampleValue(entryValue)}`)
|
||||||
|
.join(".")}`;
|
||||||
|
}
|
||||||
|
return `.${values.join(explode ? "." : ",")}`;
|
||||||
|
}
|
||||||
|
if (style === "matrix") {
|
||||||
|
if (explode && Array.isArray(value)) {
|
||||||
|
return value.map((entryValue) => `;${name}=${stringifyExampleValue(entryValue)}`).join("");
|
||||||
|
}
|
||||||
|
if (explode && isRecord(value)) {
|
||||||
|
return Object.entries(value)
|
||||||
|
.map(([key, entryValue]) => `;${key}=${stringifyExampleValue(entryValue)}`)
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
|
return `;${name}=${values.join(",")}`;
|
||||||
|
}
|
||||||
|
return serializeSimpleParameter(value, parameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
function serializeSimpleParameter(value: unknown, parameter: UnknownRecord): string {
|
||||||
|
if (Array.isArray(value)) return value.map(stringifyExampleValue).join(",");
|
||||||
|
if (isRecord(value)) {
|
||||||
|
const entries = Object.entries(value);
|
||||||
|
return parameter.explode === true
|
||||||
|
? entries.map(([key, entryValue]) => `${key}=${stringifyExampleValue(entryValue)}`).join(",")
|
||||||
|
: entries.flat().map(stringifyExampleValue).join(",");
|
||||||
|
}
|
||||||
|
return stringifyExampleValue(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parameterExample(parameter: UnknownRecord, importState: ImportState): string {
|
function parameterExample(parameter: UnknownRecord, importState: ImportState): string {
|
||||||
|
return stringifyExampleValue(parameterExampleValue(parameter, importState));
|
||||||
|
}
|
||||||
|
|
||||||
|
function parameterExampleValue(parameter: UnknownRecord, importState: ImportState): unknown {
|
||||||
const directExample = firstPresent(parameter.example, firstExampleValue(parameter.examples));
|
const directExample = firstPresent(parameter.example, firstExampleValue(parameter.examples));
|
||||||
if (directExample != null) return stringifyExampleValue(directExample);
|
if (directExample != null) return directExample;
|
||||||
return stringifyExampleValue(schemaToExample(importState.resolve(parameter.schema), importState));
|
if (isRecord(parameter.content)) {
|
||||||
|
const mediaType = toRecord(Object.values(parameter.content)[0]);
|
||||||
|
return mediaTypeExample(mediaType, importState);
|
||||||
|
}
|
||||||
|
return schemaToExample(importState.resolve(parameter.schema), importState);
|
||||||
}
|
}
|
||||||
|
|
||||||
function importBody({
|
function importBody({
|
||||||
|
|||||||
@@ -860,13 +860,7 @@ Responses:
|
|||||||
- 200: Sucessful authentication.
|
- 200: Sucessful authentication.
|
||||||
- 401: Unsuccessful authentication.",
|
- 401: Unsuccessful authentication.",
|
||||||
"folderId": "GENERATE_ID::FOLDER_1",
|
"folderId": "GENERATE_ID::FOLDER_1",
|
||||||
"headers": [
|
"headers": [],
|
||||||
{
|
|
||||||
"enabled": false,
|
|
||||||
"name": "Authorization",
|
|
||||||
"value": "",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"id": "GENERATE_ID::HTTP_REQUEST_15",
|
"id": "GENERATE_ID::HTTP_REQUEST_15",
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"model": "http_request",
|
"model": "http_request",
|
||||||
|
|||||||
@@ -766,6 +766,126 @@ describe("importer-openapi", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Imports cookie and content-based parameters", async () => {
|
||||||
|
const imported = await convertOpenApi(
|
||||||
|
JSON.stringify({
|
||||||
|
openapi: "3.0.4",
|
||||||
|
info: { title: "Parameter Test", version: "1.0.0" },
|
||||||
|
paths: {
|
||||||
|
"/items": {
|
||||||
|
get: {
|
||||||
|
parameters: [
|
||||||
|
{
|
||||||
|
name: "session",
|
||||||
|
in: "cookie",
|
||||||
|
required: true,
|
||||||
|
schema: { type: "string", example: "abc" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "X-Filter",
|
||||||
|
in: "header",
|
||||||
|
required: true,
|
||||||
|
content: { "text/plain": { example: "active" } },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
responses: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(imported?.resources.httpRequests[0]?.headers).toEqual([
|
||||||
|
{ enabled: true, name: "X-Filter", value: "active" },
|
||||||
|
{ enabled: true, name: "Cookie", value: "session=abc" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Serializes structured query parameters according to style and explode", async () => {
|
||||||
|
const imported = await convertOpenApi(
|
||||||
|
JSON.stringify({
|
||||||
|
openapi: "3.0.4",
|
||||||
|
info: { title: "Serialization Test", version: "1.0.0" },
|
||||||
|
paths: {
|
||||||
|
"/items": {
|
||||||
|
get: {
|
||||||
|
parameters: [
|
||||||
|
{
|
||||||
|
name: "filter",
|
||||||
|
in: "query",
|
||||||
|
required: true,
|
||||||
|
style: "deepObject",
|
||||||
|
explode: true,
|
||||||
|
schema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
role: { example: "admin" },
|
||||||
|
active: { example: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tags",
|
||||||
|
in: "query",
|
||||||
|
style: "form",
|
||||||
|
explode: true,
|
||||||
|
schema: { type: "array", example: ["one", "two"] },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
responses: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(imported?.resources.httpRequests[0]?.urlParameters).toEqual([
|
||||||
|
{ enabled: true, name: "filter[role]", value: "admin" },
|
||||||
|
{ enabled: true, name: "filter[active]", value: "true" },
|
||||||
|
{ enabled: false, name: "tags", value: "one" },
|
||||||
|
{ enabled: false, name: "tags", value: "two" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Serializes label and matrix path parameters", async () => {
|
||||||
|
const imported = await convertOpenApi(
|
||||||
|
JSON.stringify({
|
||||||
|
openapi: "3.0.4",
|
||||||
|
info: { title: "Path Serialization Test", version: "1.0.0" },
|
||||||
|
paths: {
|
||||||
|
"/labels/{labels}/matrix/{coordinates}": {
|
||||||
|
get: {
|
||||||
|
parameters: [
|
||||||
|
{
|
||||||
|
name: "labels",
|
||||||
|
in: "path",
|
||||||
|
required: true,
|
||||||
|
style: "label",
|
||||||
|
explode: true,
|
||||||
|
schema: { type: "array", example: ["one", "two"] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "coordinates",
|
||||||
|
in: "path",
|
||||||
|
required: true,
|
||||||
|
style: "matrix",
|
||||||
|
explode: true,
|
||||||
|
schema: { type: "object", example: { x: 1, y: 2 } },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
responses: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(imported?.resources.httpRequests[0]?.urlParameters).toEqual([
|
||||||
|
{ enabled: true, name: ":labels", value: ".one.two" },
|
||||||
|
{ enabled: true, name: ":coordinates", value: ";x=1;y=2" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
test("Prefers operation-level consumes for Swagger bodies", async () => {
|
test("Prefers operation-level consumes for Swagger bodies", async () => {
|
||||||
const imported = await convertOpenApi(
|
const imported = await convertOpenApi(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
|||||||
Reference in New Issue
Block a user