Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tricky-pants-remain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openapi-ts-request': patch
---

fix: fix parse parameters ref error #486
63 changes: 46 additions & 17 deletions src/generator/serviceGenarator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -501,28 +501,41 @@ export default class ServiceGenerator {
markAllowedSchema(JSON.stringify(pathItem), this.openAPIData);

operationObject.parameters = operationObject.parameters?.filter(
(item: ParameterObject) => item?.in !== `${parametersInsEnum.header}`
(item: ParameterObject | ReferenceObject) => {
const parameter = this.resolveParameterRef(item);
return parameter?.in !== `${parametersInsEnum.header}`;
}
);
const props = [] as IPropObject[];

operationObject.parameters?.forEach((parameter: ParameterObject) => {
props.push({
name: parameter.name,
desc: (parameter.description ?? '').replace(lineBreakReg, ''),
required: parameter.required || false,
type: this.getType(parameter.schema),
});
});
operationObject.parameters?.forEach(
(param: ParameterObject | ReferenceObject) => {
const parameter = this.resolveParameterRef(param);
if (parameter) {
props.push({
name: parameter.name,
desc: (parameter.description ?? '').replace(lineBreakReg, ''),
required: parameter.required || false,
type: this.getType(parameter.schema),
});
}
}
);

// parameters may be in path
pathItem.parameters?.forEach((parameter: ParameterObject) => {
props.push({
name: parameter.name,
desc: (parameter.description ?? '').replace(lineBreakReg, ''),
required: parameter.required,
type: this.getType(parameter.schema),
});
});
pathItem.parameters?.forEach(
(param: ParameterObject | ReferenceObject) => {
const parameter = this.resolveParameterRef(param);
if (parameter) {
props.push({
name: parameter.name,
desc: (parameter.description ?? '').replace(lineBreakReg, ''),
required: parameter.required,
type: this.getType(parameter.schema),
});
}
}
);

const typeName = this.getFunctionParamsTypeName({
...operationObject,
Expand Down Expand Up @@ -1468,6 +1481,22 @@ export default class ServiceGenerator {
});
}

private resolveParameterRef(
param: ParameterObject | ReferenceObject
): ParameterObject | null {
if (!isReferenceObject(param)) {
return param;
}

// 解析 $ref 引用,从 components.parameters 中获取实际定义
const refName = getLastRefName(param.$ref);
const parameter = this.openAPIData.components?.parameters?.[
refName
] as ParameterObject;

return parameter || null;
}

private resolveRefObject<T>(refObject: ReferenceObject | T): T {
if (!isReferenceObject(refObject)) {
return refObject;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/* eslint-disable */
// @ts-ignore
export * from './types';

export * from './products';
/* eslint-disable */
// @ts-ignore
import { request } from 'axios';

import * as API from './types';

/** Get product list Get paginated product list with search, filter, and sort support GET /products */
export function productsUsingGet({
params,
options,
}: {
// 叠加生成的Param类型 (非body参数openapi默认没有生成对象)
params: API.ProductsUsingGetParams;
options?: { [key: string]: unknown };
}) {
return request<
API.BaseResponse & {
data?: API.ProductListResponse;
}
>('/products', {
method: 'GET',
params: {
// page has a default value: 1
page: '1',
// pageSize has a default value: 10
pageSize: '10',

...params,
},
...(options || {}),
});
}
/* eslint-disable */
// @ts-ignore

export type BaseResponse = {
/** Response status code */
code?: number;
/** Response message */
message?: string;
/** Response data */
data?: Record<string, unknown>;
};

export type PaginatedResponse = {
/** Data list */
list?: Record<string, unknown>[];
/** Total records */
total?: number;
/** Current page */
page?: number;
};

export type Product = {
/** Product ID */
productId?: string;
/** Product name */
name?: string;
/** Product description */
description?: string;
};

export type ProductListResponse =
// #/components/schemas/PaginatedResponse
PaginatedResponse & {
list?: Product[];
};

export type ProductsUsingGetParams = {
/** Page number */
page?: number;
/** Page size */
pageSize?: number;
/** Search keyword */
keyword?: string;
/** Product category ID */
categoryId?: string;
};

export type ProductsUsingGetResponses = {
/**
* Get successful
*/
200: BaseResponse & {
data?: ProductListResponse;
};
};
13 changes: 13 additions & 0 deletions test/common.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -484,4 +484,17 @@ export async function ${api.functionName}(${api.body ? `data: ${api.body.type}`
readGeneratedFiles('./apis/openapi-anonymous-response')
).resolves.toMatchFileSnapshot(getSnapshotDir(ctx));
});

it('测试解析 components.parameters 中的 $ref 引用', async (ctx) => {
await openAPI.generateService({
schemaPath: join(
import.meta.dirname,
'./example-files/openapi-components-parameters.yaml'
),
serversPath: './apis/components-parameters',
});
await expect(
readGeneratedFiles('./apis/components-parameters')
).resolves.toMatchFileSnapshot(getSnapshotDir(ctx));
});
});
128 changes: 128 additions & 0 deletions test/example-files/openapi-components-parameters.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
openapi: 3.0.3
servers:
- url: http://localhost:3000/v1
description: 开发环境

security:
- BearerAuth: []

paths:
/products:
get:
tags:
- Products
summary: Get product list
description: Get paginated product list with search, filter, and sort support
parameters:
- $ref: '#/components/parameters/PageParam'
- $ref: '#/components/parameters/PageSizeParam'
- $ref: '#/components/parameters/KeywordParam'
- name: categoryId
in: query
description: Product category ID
schema:
type: string
example: cat_001
responses:
'200':
description: Get successful
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/BaseResponse'
- type: object
properties:
data:
$ref: '#/components/schemas/ProductListResponse'

components:
schemas:
# Base response format
BaseResponse:
type: object
properties:
code:
type: integer
description: Response status code
example: 200
message:
type: string
description: Response message
example: Operation successful
data:
type: object
description: Response data

ProductListResponse:
allOf:
- $ref: '#/components/schemas/PaginatedResponse'
- type: object
properties:
list:
type: array
items:
$ref: '#/components/schemas/Product'

PaginatedResponse:
type: object
properties:
list:
type: array
description: Data list
items:
type: object
total:
type: integer
description: Total records
example: 100
page:
type: integer
description: Current page
example: 1

Product:
type: object
properties:
productId:
type: string
description: Product ID
example: prod_001
name:
type: string
description: Product name
example: Classic Pearl Milk Tea
description:
type: string
description: Product description
example: 'Rich milk tea with chewy pearls, a classic that never goes out of style'

parameters:
# Pagination parameters
PageParam:
name: page
in: query
description: Page number
schema:
type: integer
minimum: 1
default: 1
example: 1

PageSizeParam:
name: pageSize
in: query
description: Page size
schema:
type: integer
minimum: 1
maximum: 100
default: 10
example: 10
KeywordParam:
name: keyword
in: query
description: Search keyword
schema:
type: string
example: Pearl Milk Tea