Skip to content
Open
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
52 changes: 50 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ npm run openapi2ts
| requestLibPath | No | Custom request method path | string | - |
| requestOptionsType | No | Custom request options type | string | {[key: string]: any} |
| requestImportStatement | No | Custom request import statement | string | - |
| apiPrefix | No | API prefix | string | - |
| apiPrefix | No | API prefix | string \| function | - |
| dedupeApiPrefix | No | Whether to deduplicate API prefix when it already exists in the path | boolean | true |
| serversPath | No | Output directory path | string | - |
| schemaPath | No | Swagger 2.0 or OpenAPI 3.0 URL | string | - |
| projectName | No | Project name | string | - |
Expand All @@ -83,6 +84,29 @@ npm run openapi2ts
| declareType | No | Interface declaration type | type/interface | type |
| splitDeclare | No | Generate a separate .d.ts file for each tag group. | boolean | - |

#### About `apiPrefix` and `dedupeApiPrefix`

The `apiPrefix` option allows you to add a prefix to all generated API paths. It can be:
- A string literal: `apiPrefix: "'/api'"` - The prefix will be processed as a string value
- A function that returns a string: `apiPrefix: (data) => "/api"` - Dynamically determine the prefix

The `dedupeApiPrefix` option controls how to handle prefixes:
- `true` (default): When the API path already contains the specified prefix, it will be deduplicated (not added again)
- Example: path `/api/user/list` with `apiPrefix: "'/api'"` → remains `/api/user/list` (not `/api/api/user/list`)
- `false`: No deduplication, the prefix will be treated as a variable reference
- Example: path `/user/list` with `apiPrefix: "'/api'"` and `dedupeApiPrefix: false` → becomes `${'/api'}/user/list`

Example configuration:

```typescript
export default {
schemaPath: 'http://petstore.swagger.io/v2/swagger.json',
serversPath: './servers',
apiPrefix: "'/api'", // or apiPrefix: (data) => "/api"
dedupeApiPrefix: true, // Deduplicate prefix if it already exists
}
```

### Custom Hooks

| Property | Type | Description |
Expand Down Expand Up @@ -159,7 +183,8 @@ npm run openapi2ts
| requestLibPath | 否 | 自定义请求方法路径 | string | - |
| requestOptionsType | 否 | 自定义请求方法 options 参数类型 | string | {[key: string]: any} |
| requestImportStatement | 否 | 自定义请求方法表达式 | string | - |
| apiPrefix | 否 | API 前缀 | string | - |
| apiPrefix | 否 | API 前缀 | string \| function | - |
| dedupeApiPrefix | 否 | 当路径中已存在前缀时是否进行去重 | boolean | true |
| serversPath | 否 | 生成文件夹的路径 | string | - |
| schemaPath | 否 | Swagger 2.0 或 OpenAPI 3.0 的地址 | string | - |
| projectName | 否 | 项目名称 | string | - |
Expand All @@ -173,6 +198,29 @@ npm run openapi2ts
| declareType | 否 | interface 声明类型 | type/interface | type |
| splitDeclare | 否 | 每个tag组一个独立的.d.ts. | boolean | - |

#### 关于 `apiPrefix` 和 `dedupeApiPrefix`

`apiPrefix` 选项用于为生成的所有 API 路径添加前缀,支持两种形式:
- 字符串字面量:`apiPrefix: "'/api'"` - 前缀将被作为字符串值处理
- 函数:`apiPrefix: (data) => "/api"` - 动态决定前缀

`dedupeApiPrefix` 选项控制如何处理前缀:
- `true`(默认):当 API 路径已包含指定的前缀时,会进行去重处理(不再添加)
- 示例:路径 `/api/user/list`,配置 `apiPrefix: "'/api'"` → 保持 `/api/user/list`(不会变成 `/api/api/user/list`)
- `false`:不进行去重,前缀将被作为变量引用处理
- 示例:路径 `/user/list`,配置 `apiPrefix: "'/api'"` 和 `dedupeApiPrefix: false` → 生成 `${'/api'}/user/list`

配置示例:

```typescript
export default {
schemaPath: 'http://petstore.swagger.io/v2/swagger.json',
serversPath: './servers',
apiPrefix: "'/api'", // 或者 apiPrefix: (data) => "/api"
dedupeApiPrefix: true, // 当前缀已存在时进行去重
}
```

### 自定义钩子

| 属性 | 类型 | 说明 |
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export type GenerateServiceProps = {
functionName: string;
autoExclude?: boolean;
}) => string);
dedupeApiPrefix?: boolean
/**
* 生成的文件夹的路径
*/
Expand Down
3 changes: 2 additions & 1 deletion src/serviceGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ class ServiceGenerator {
this.config = {
projectName: 'api',
templatesFolder: join(__dirname, '../', 'templates'),
dedupeApiPrefix: true,
...config,
};
if (this.config.hook?.afterOpenApiDataInited) {
Expand Down Expand Up @@ -554,7 +555,7 @@ class ServiceGenerator {
return formattedPath;
}

if (prefix.startsWith("'") || prefix.startsWith('"') || prefix.startsWith('`')) {
if (this.config.dedupeApiPrefix && (prefix.startsWith("'") || prefix.startsWith('"') || prefix.startsWith('`'))) {
const finalPrefix = prefix.slice(1, prefix.length - 1);
if (
formattedPath.startsWith(finalPrefix) ||
Expand Down
2 changes: 1 addition & 1 deletion test/apispe/api/api0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export async function postLicenceActive(body: {}, file?: File, options?: { [key:
if (item instanceof Array) {
item.forEach((f) => formData.append(ele, f || ''));
} else {
formData.append(ele, JSON.stringify(item));
formData.append(ele, new Blob([JSON.stringify(item)], { type: 'application/json' }));
}
} else {
formData.append(ele, item);
Expand Down
111 changes: 111 additions & 0 deletions test/example-files/swagger-dedupe-api-prefix.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
{
"openapi": "3.0.0",
"info": {
"title": "Test Dedupe API Prefix True",
"version": "1.0.0"
},
"paths": {
"/api/apiInfo/get": {
"get": {
"operationId": "getApiInfo",
"tags": ["api"],
"summary": "Get API Info",
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"code": {
"type": "string"
}
}
}
}
}
}
}
}
},
"/api/apiInfo/update": {
"post": {
"operationId": "updateApiInfo",
"tags": ["api"],
"summary": "Update API Info",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": {
"type": "string"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Success"
}
}
}
},
"/api/user/profile": {
"get": {
"operationId": "getUserProfile",
"tags": ["user"],
"summary": "Get User Profile",
"responses": {
"200": {
"description": "Success"
}
}
}
},
"/api/user/info": {
"post": {
"operationId": "getUserInfo",
"tags": ["user"],
"summary": "Get User Info",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": {
"type": "string"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Success"
}
}
}
},
"/api/product/list": {
"get": {
"operationId": "getProductList",
"tags": ["product"],
"summary": "Get Product List",
"responses": {
"200": {
"description": "Success"
}
}
}
}
}
}
72 changes: 69 additions & 3 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,76 @@ const gen = async () => {
},
});

// await openAPI.generateService({
// schemaPath: `${__dirname}/example-files/swagger-splitdeclare.json`,
// serversPath: './splitDeclare',
// splitDeclare:true
// });

// Test dedupeApiPrefix 配置的两种场景
// 使用同一个 swagger 文件,通过不同的 dedupeApiPrefix 配置演示不同的行为

// 场景1:dedupeApiPrefix: true - 去重模式
// 当 apiPrefix 为 '/api',路径已经包含 '/api' 前缀时(如 /api/apiInfo/xxx),
// 设置 dedupeApiPrefix: true 会去重,生成的路径仍为 /api/apiInfo/xxx(不重复添加)
await openAPI.generateService({
schemaPath: `${__dirname}/example-files/swagger-dedupe-api-prefix.json`,
serversPath: './servers/dedupe-api-prefix/true',
apiPrefix: `'/api'`,
dedupeApiPrefix: true,
});

const dedupeApiPrefixTrueApiControllerStr = fs.readFileSync(
path.join(__dirname, 'servers/dedupe-api-prefix/true/api/api.ts'),
'utf8',
);
const dedupeApiPrefixTrueUserControllerStr = fs.readFileSync(
path.join(__dirname, 'servers/dedupe-api-prefix/true/api/user.ts'),
'utf8',
);

// /api/apiInfo/get 已经有 /api 前缀,dedupeApiPrefix: true 会去重,保持 /api/apiInfo/get
assert(dedupeApiPrefixTrueApiControllerStr.indexOf(`'/api/apiInfo/get'`) > 0 ||
dedupeApiPrefixTrueApiControllerStr.indexOf('`/api/apiInfo/get`') > 0,
'dedupeApiPrefix=true: /api/apiInfo/get should remain as /api/apiInfo/get (not /api/api/apiInfo/get)');

assert(dedupeApiPrefixTrueApiControllerStr.indexOf(`'/api/apiInfo/update'`) > 0 ||
dedupeApiPrefixTrueApiControllerStr.indexOf('`/api/apiInfo/update`') > 0,
'dedupeApiPrefix=true: /api/apiInfo/update should remain as /api/apiInfo/update');

assert(dedupeApiPrefixTrueUserControllerStr.indexOf(`'/api/user/profile'`) > 0 ||
dedupeApiPrefixTrueUserControllerStr.indexOf('`/api/user/profile`') > 0,
'dedupeApiPrefix=true: /api/user/profile should remain as /api/user/profile');


// 场景2:dedupeApiPrefix: false - 非去重模式
// 同一个 swagger 文件,设置 dedupeApiPrefix: false 时,不检查前缀,直接作为变量引用
// 导致前缀被重复添加
await openAPI.generateService({
schemaPath: `${__dirname}/example-files/swagger-splitdeclare.json`,
serversPath: './splitDeclare',
splitDeclare:true
schemaPath: `${__dirname}/example-files/swagger-dedupe-api-prefix.json`,
serversPath: './servers/dedupe-api-prefix/false',
apiPrefix: `'/api'`,
dedupeApiPrefix: false,
});

const dedupeApiPrefixFalseApiControllerStr = fs.readFileSync(
path.join(__dirname, 'servers/dedupe-api-prefix/false/api/api.ts'),
'utf8',
);
const dedupeApiPrefixFalseUserControllerStr = fs.readFileSync(
path.join(__dirname, 'servers/dedupe-api-prefix/false/api/user.ts'),
'utf8',
);

// 当 dedupeApiPrefix: false 时,同样的路径会被作为变量引用,导致前缀被拼接
// /api/apiInfo/get 会变成 ${'/api'}/api/apiInfo/get
assert(dedupeApiPrefixFalseApiControllerStr.indexOf("${'/api'}/api/apiInfo/get") > 0,
'dedupeApiPrefix=false: /api/apiInfo/get should become ${' + "'" + '/api' + "'" + '}/api/apiInfo/get (duplication)');

assert(dedupeApiPrefixFalseApiControllerStr.indexOf("${'/api'}/api/apiInfo/update") > 0,
'dedupeApiPrefix=false: /api/apiInfo/update should become ${' + "'" + '/api' + "'" + '}/api/apiInfo/update (duplication)');

assert(dedupeApiPrefixFalseUserControllerStr.indexOf("${'/api'}/api/user/profile") > 0,
'dedupeApiPrefix=false: /api/user/profile should become ${' + "'" + '/api' + "'" + '}/api/user/profile (duplication)');
};
gen();