Skip to content

Commit 48ca069

Browse files
authored
Support single operation parameter (#1535)
* support single operation parameter * changeset
1 parent 0b50c1e commit 48ca069

File tree

8 files changed

+77
-9
lines changed

8 files changed

+77
-9
lines changed

.changeset/breezy-trees-brake.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"openapi-typescript": patch
3+
---
4+
5+
Support single operation parameter

packages/openapi-typescript/src/transform/operation-object.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { GlobalContext, OperationObject, ParameterObject } from "../types.js";
2-
import { escObjKey, getEntries, getSchemaObjectComment, indent, tsOptionalProperty, tsReadonly } from "../utils.js";
2+
import { escObjKey, getEntries, getParametersArray, getSchemaObjectComment, indent, tsOptionalProperty, tsReadonly } from "../utils.js";
33
import transformParameterObject from "./parameter-object.js";
44
import transformRequestBodyObject from "./request-body-object.js";
55
import transformResponseObject from "./response-object.js";
@@ -19,13 +19,14 @@ export default function transformOperationObject(operationObject: OperationObjec
1919
// parameters
2020
{
2121
if (operationObject.parameters) {
22+
const parametersArray = getParametersArray(operationObject.parameters);
2223
const parameterOutput: string[] = [];
2324
indentLv++;
2425
for (const paramIn of ["query", "header", "path", "cookie"] as ParameterObject["in"][]) {
2526
const paramInternalOutput: string[] = [];
2627
indentLv++;
2728
let paramInOptional = true;
28-
for (const param of operationObject.parameters ?? []) {
29+
for (const param of parametersArray) {
2930
const node: ParameterObject | undefined = "$ref" in param ? ctx.parameters[param.$ref] : param;
3031
if (node?.in !== paramIn) continue;
3132
let key = escObjKey(node.name);

packages/openapi-typescript/src/transform/path-item-object.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { GlobalContext, ParameterObject, PathItemObject, ReferenceObject } from "../types.js";
2-
import { escStr, getSchemaObjectComment, indent } from "../utils.js";
2+
import { escStr, getParametersArray, getSchemaObjectComment, indent } from "../utils.js";
33
import transformOperationObject from "./operation-object.js";
44

55
export interface TransformPathItemObjectOptions {
@@ -26,7 +26,7 @@ export default function transformPathItemObject(pathItem: PathItemObject, { path
2626
const keyedParameters: Record<string, ParameterObject | ReferenceObject> = {};
2727
if (!("$ref" in operationObject)) {
2828
// important: OperationObject parameters come last, and will override any conflicts with PathItem parameters
29-
for (const parameter of [...(pathItem.parameters ?? []), ...(operationObject.parameters ?? [])]) {
29+
for (const parameter of [...(pathItem.parameters ?? []), ...getParametersArray(operationObject.parameters)]) {
3030
// note: the actual key doesn’t matter here, as long as it can match between PathItem and OperationObject
3131
keyedParameters["$ref" in parameter ? parameter.$ref : `${parameter.in}/${parameter.name}`] = parameter;
3232
}

packages/openapi-typescript/src/transform/paths-object.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { GlobalContext, PathsObject, PathItemObject, ParameterObject, ReferenceObject, OperationObject } from "../types.js";
2-
import { escStr, getEntries, getSchemaObjectComment, indent } from "../utils.js";
2+
import { escStr, getEntries, getParametersArray, getSchemaObjectComment, indent } from "../utils.js";
33
import transformParameterObject from "./parameter-object.js";
44
import transformPathItemObject from "./path-item-object.js";
55

@@ -8,7 +8,7 @@ const OPERATIONS = ["get", "post", "put", "delete", "options", "head", "patch",
88
function extractPathParams(obj?: ReferenceObject | PathItemObject | OperationObject | undefined): Map<string, ParameterObject> {
99
const params = new Map<string, ParameterObject>();
1010
if (obj && "parameters" in obj) {
11-
for (const p of obj.parameters ?? []) {
11+
for (const p of getParametersArray(obj.parameters)) {
1212
if ("in" in p && p.in === "path") params.set(p.name, p);
1313
}
1414
}

packages/openapi-typescript/src/types.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ export interface OperationObject extends Extensable {
196196
/** Unique string used to identify the operation. The id MUST be unique among all operations described in the API. The operationId value is case-sensitive. Tools and libraries MAY use the operationId to uniquely identify an operation, therefore, it is RECOMMENDED to follow common programming naming conventions. */
197197
operationId?: string;
198198
/** A list of parameters that are applicable for this operation. If a parameter is already defined at the Path Item, the new definition will override it but can never remove it. The list MUST NOT include duplicated parameters. A unique parameter is defined by a combination of a name and location. The list can use the Reference Object to link to parameters that are defined at the OpenAPI Object’s components/parameters. */
199-
parameters?: (ParameterObject | ReferenceObject)[];
199+
parameters?: ParameterObject | (ParameterObject | ReferenceObject)[];
200200
/** The request body applicable for this operation. The requestBody is fully supported in HTTP methods where the HTTP 1.1 specification [RFC7231] has explicitly defined semantics for request bodies. In other cases where the HTTP spec is vague (such as GET, HEAD and DELETE), requestBody is permitted but does not have well-defined semantics and SHOULD be avoided if possible. */
201201
requestBody?: RequestBodyObject | ReferenceObject;
202202
/** The list of possible responses as they are returned from executing this operation. */

packages/openapi-typescript/src/utils.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import c from "ansi-colors";
22
import { isAbsolute } from "node:path";
33
import supportsColor from "supports-color";
44
import { fetch as unidiciFetch } from "undici";
5-
import type { Fetch } from "./types.js";
5+
import type { Fetch, ParameterObject, ReferenceObject } from "./types.js";
66

77
// eslint-disable-next-line @typescript-eslint/no-unnecessary-boolean-literal-compare
88
if (!supportsColor.stdout || supportsColor.stdout.hasBasic === false) c.enabled = false;
@@ -322,3 +322,7 @@ export function getDefaultFetch(): Fetch {
322322
}
323323
return globalFetch;
324324
}
325+
326+
export function getParametersArray(parameters: ParameterObject | (ParameterObject | ReferenceObject)[] = []): (ParameterObject | ReferenceObject)[] {
327+
return Array.isArray(parameters) ? parameters : [parameters];
328+
}

packages/openapi-typescript/test/operation-object.test.ts

+36
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,42 @@ describe("Operation Object", () => {
162162
};
163163
};
164164
};
165+
}`);
166+
});
167+
168+
it("handles non-array parameters", () => {
169+
const schema: OperationObject = {
170+
parameters: {
171+
in: "query",
172+
name: "search",
173+
schema: { type: "string" },
174+
},
175+
responses: {
176+
"200": {
177+
description: "OK",
178+
content: {
179+
"application/json": {
180+
schema: { type: "string" },
181+
},
182+
},
183+
},
184+
},
185+
};
186+
const generated = transformOperationObject(schema, options);
187+
expect(generated).toBe(`{
188+
parameters: {
189+
query?: {
190+
search?: string;
191+
};
192+
};
193+
responses: {
194+
/** @description OK */
195+
200: {
196+
content: {
197+
"application/json": string;
198+
};
199+
};
200+
};
165201
}`);
166202
});
167203
});

packages/openapi-typescript/test/utils.test.ts

+23-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { comment, escObjKey, getSchemaObjectComment, parseRef, tsIntersectionOf, tsUnionOf } from "../src/utils.js";
1+
import { ParameterObject, ReferenceObject } from "../src/types.js";
2+
import { comment, escObjKey, getParametersArray, getSchemaObjectComment, parseRef, tsIntersectionOf, tsUnionOf } from "../src/utils.js";
23

34
describe("utils", () => {
45
describe("tsUnionOf", () => {
@@ -167,4 +168,25 @@ describe("utils", () => {
167168
);
168169
});
169170
});
171+
172+
describe('getParametersArray', () => {
173+
it('should return an empty array if no parameters are passed', () => {
174+
expect(getParametersArray()).toEqual([]);
175+
});
176+
177+
it('should return an array if a single parameter is passed', () => {
178+
const parameter: ParameterObject = { name: 'test', in: 'query' };
179+
expect(getParametersArray(parameter)).toEqual([parameter]);
180+
});
181+
182+
it('should return an array if an array of parameters is passed', () => {
183+
const parameters: ParameterObject[] = [{ name: 'test', in: 'query' }, { name: 'test2', in: 'query' }];
184+
expect(getParametersArray(parameters)).toEqual(parameters);
185+
});
186+
187+
it('should return an array if an array of references is passed', () => {
188+
const references: ReferenceObject[] = [{ $ref: 'test' }, { $ref: 'test2' }];
189+
expect(getParametersArray(references)).toEqual(references);
190+
});
191+
});
170192
});

0 commit comments

Comments
 (0)