Skip to content

Commit

Permalink
Merge branch 'main' into Alioth1017-patch-1
Browse files Browse the repository at this point in the history
* main: (29 commits)
  fix: 修复类型错误并更新文档 (chenshuai2144#155)
  chore: 支持docker开发 (chenshuai2144#158)
  feat: 将afterOpenApiDataInited提升到最顶层,this.apiData可以使用自定义处理后的数据 (chenshuai2144#159)
  1.12.1
  fix tempTypeName is null error
  1.12.0
  fix tempTypeName is null error
  mock生成支持allOf,结果读取支持更多值,修复生成时堆栈溢出 (chenshuai2144#150)
  fix: 解决JSON中components.schemas为空导致的生成报错 (chenshuai2144#151)
  1.11.1
  fix: 消除require的缓存 (chenshuai2144#144)
  1.11.0
  fix: required在schemaObject和properties中的情况 (chenshuai2144#142)
  处理anyOf和null类型 (chenshuai2144#143)
  fix: 兼容调用resolveObject函数,schemaObject为null导致报错的场景 (chenshuai2144#141)
  fix:Content-Type 类型为 application/x-www-form-urlencoded 代码生成错误 @see chenshuai2144#139 (chenshuai2144#140)
  1.10.1
  1.10.0
  回退默认把int64转换为string的修改 (chenshuai2144#133)
  fix: application/x-www-form-urlencoded不适用formData传参 (chenshuai2144#135)
  ...
  • Loading branch information
Alioth1017 committed Jun 19, 2024
2 parents e1862d2 + e875f3d commit a824f5d
Show file tree
Hide file tree
Showing 17 changed files with 777 additions and 54 deletions.
5 changes: 5 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
FROM node:18-alpine
RUN npm install pnpm@^8.15.7 -g
RUN pnpm config set store-dir /path/to/.pnpm-store
WORKDIR /code
EXPOSE 8080
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ npm run openapi
| 属性 | 必填 | 备注 | 类型 | 默认值 |
| ---- | ---- | ---- | ---- | - |
| requestLibPath || 自定义请求方法路径 | string | - |
| requestOptionsType || 自定义请求方法 options 参数类型 | string | {[key: string]: any} |
| requestImportStatement || 自定义请求方法表达式 | string | - |
| apiPrefix || api 的前缀 | string | - |
| serversPath || 生成的文件夹的路径 | string | - |
Expand All @@ -40,3 +41,16 @@ npm run openapi
| enumStyle || 枚举样式 | string-literal \| enum | string-literal |
| nullable || 使用null代替可选 | boolean | false |
| dataFields || response中数据字段 | string[] | - |
| isCamelCase || 小驼峰命名文件和请求函数 | boolean | true |
| hook || 自定义 hook | [Custom Hook](#Custom-Hook) | - |

## Custom Hook

| 属性 | 类型 | 说明 |
| -------------- | ---- | ------------------ |
| afterOpenApiDataInited | (openAPIData: OpenAPIObject) => OpenAPIObject | - |
| customFunctionName | (data: APIDataType) => string | 自定义请求方法函数名称 |
| customTypeName | (data: APIDataType) => string | 自定义类型名称 |
| customClassName | (tagName: string) => string | 自定义类名 |
| customType | (<br>schemaObject: SchemaObject \| undefined,<br>namespace: string,<br>originGetType:(schemaObject: SchemaObject \| undefined, namespace: string) => string,<br>) => string | 自定义获取类型 <br> *返回非字符串将使用默认方法获取type* |
| customFileNames | (<br>operationObject: OperationObject,<br>apiPath: string,<br>_apiMethod: string,<br>) => string[] | 自定义生成文件名,可返回多个,表示生成多个文件. <br> *返回为空,则使用默认的获取方法获取* |
10 changes: 10 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: '3'

services:
openapi:
build: .
volumes:
- .:/code
stdin_open: true
tty: true

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@umijs/openapi",
"version": "1.9.1",
"version": "1.12.1",
"description": "",
"repository": {
"type": "git",
Expand All @@ -18,7 +18,7 @@
"localConvert4Project": "rm -rf ./test/servers/ ./test/file-servers/ && npm run build && cd ./test && node ./test.js && cd .. && rm -rf /Users/fd/wj/psp-web-pro/src/services/wj && mv ./test/servers/api/ /Users/fd/wj/psp-web-pro/src/services/wj",
"prepublishOnly": "npm run build && np --no-cleanup --yolo --no-publish --any-branch",
"start": "tsc -w",
"test": "rm -rf ./test/servers/ ./test/file-servers/ && npm run build && cd ./test && node ./test.js && cd ..",
"test": "rm -rf ./test/servers/ ./test/servers-allof/ ./test/file-servers/ && npm run build && cd ./test && node ./test.js && cd ..",
"test:windows": "rimraf ./test/servers/ ./test/servers-allof/ ./test/file-servers/ && npm run build && cd ./test && ts-node ./test.js --project tsconfig.json && cd .."
},
"dependencies": {
Expand Down
19 changes: 17 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import converter from 'swagger2openapi';
import Log from './log';
import { mockGenerator } from './mockGenerator';
import { ServiceGenerator } from './serviceGenerator';
import type { APIDataType } from './serviceGenerator';

const getImportStatement = (requestLibPath: string) => {
if (requestLibPath && requestLibPath.startsWith('import')) {
Expand All @@ -21,6 +22,7 @@ const getImportStatement = (requestLibPath: string) => {

export type GenerateServiceProps = {
requestLibPath?: string;
requestOptionsType?: string;
requestImportStatement?: string;
/**
* api 的前缀
Expand Down Expand Up @@ -52,9 +54,11 @@ export type GenerateServiceProps = {
afterOpenApiDataInited?: (openAPIData: OpenAPIObject) => OpenAPIObject;

/** 自定义函数名称 */
customFunctionName?: (data: OperationObject) => string;
customFunctionName?: (data: APIDataType) => string;
/** 自定义类型名称 */
customTypeName?: (data: OperationObject) => string;
customTypeName?: (data: APIDataType) => string;
/** 自定义 options 默认值 */
customOptionsDefaultValue?: (data: OperationObject) => Record<string, any> | undefined;
/** 自定义类名 */
customClassName?: (tagName: string) => string;

Expand Down Expand Up @@ -129,6 +133,11 @@ export type GenerateServiceProps = {
* example: ['result', 'res']
*/
dataFields?: string[];

/**
* 模板文件、请求函数采用小驼峰命名
*/
isCamelCase?: boolean;
};

const converterSwaggerToOpenApi = (swagger: any) => {
Expand Down Expand Up @@ -162,6 +171,9 @@ export const getSchema = async (schemaPath: string) => {
}
return null;
}
if (require.cache[schemaPath]) {
delete require.cache[schemaPath];
}
const schema = require(schemaPath);
return schema;
};
Expand All @@ -181,16 +193,19 @@ export const generateService = async ({
schemaPath,
mockFolder,
nullable = false,
requestOptionsType = '{[key: string]: any}',
...rest
}: GenerateServiceProps) => {
const openAPI = await getOpenAPIConfig(schemaPath);
const requestImportStatement = getImportStatement(requestLibPath);
const serviceGenerator = new ServiceGenerator(
{
namespace: 'API',
requestOptionsType,
requestImportStatement,
enumStyle: 'string-literal',
nullable,
isCamelCase: true,
...rest,
},
openAPI,
Expand Down
84 changes: 71 additions & 13 deletions src/openAPIParserMock/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,29 +120,62 @@ class OpenAPIGeneratorMockJs {
this.sampleFromSchema = memoizee(this.sampleFromSchema);
}

sampleFromSchema = (schema: any, propsName?: string[]) => {
const localSchema = schema.$ref
? utils.get(this.openAPI, schema.$ref.replace('#/', '').split('/'))
sampleFromSchema = (schema: any, propsName?: string[], schemaSet: Set<string> = new Set()) => {
let schemaRef = schema.$ref;

if (schemaRef) {
// 如果之前已经使用过该引用结构,直接返回null,不然会陷入无限递归的情况
if (schemaSet.has(schemaRef)) {
return null;
} else {
schemaSet.add(schemaRef);
}
}

const localSchema = schemaRef
? utils.get(this.openAPI, schemaRef.replace('#/', '').split('/'))
: utils.objectify(schema);

let { type } = localSchema;
const { properties, additionalProperties, items } = localSchema;
const { properties, additionalProperties, items, anyOf, oneOf, allOf } = localSchema;

if (allOf) {
let obj = {};
allOf.forEach((item) => {
const newObj = this.sampleFromSchema(item, propsName, new Set(schemaSet));
obj = {
...obj,
...newObj,
};
});
return obj;
}

if (!type) {
if (properties) {
type = 'object';
} else if (items) {
type = 'array';
} else if (anyOf || oneOf) {
type = 'union';
} else {
return null;
}
}

if (type === 'null') {
return null;
}

if (type === 'object') {
const props = utils.objectify(properties);
const obj: Record<string, any> = {};
for (const name in props) {
obj[name] = this.sampleFromSchema(props[name], [...(propsName || []), name]);
obj[name] = this.sampleFromSchema(
props[name],
[...(propsName || []), name],
new Set(schemaSet),
);
}

if (additionalProperties === true) {
Expand All @@ -151,7 +184,11 @@ class OpenAPIGeneratorMockJs {
}
if (additionalProperties) {
const additionalProps = utils.objectify(additionalProperties);
const additionalPropVal = this.sampleFromSchema(additionalProps, propsName);
const additionalPropVal = this.sampleFromSchema(
additionalProps,
propsName,
new Set(schemaSet),
);

for (let i = 1; i < 4; i += 1) {
obj[`additionalProp${i}`] = additionalPropVal;
Expand All @@ -161,10 +198,20 @@ class OpenAPIGeneratorMockJs {
}

if (type === 'array') {
const item = this.sampleFromSchema(items, propsName);
const item = this.sampleFromSchema(items, propsName, new Set(schemaSet));
return new Array(parseInt((Math.random() * 20).toFixed(0), 10)).fill(item);
}

if (type === 'union') {
const subschemas = anyOf || oneOf;
const subschemas_length = (subschemas && subschemas.length) || 0;
if (subschemas_length) {
const index = utils.getRandomInt(0, subschemas_length);
const obj = this.sampleFromSchema(subschemas[index], propsName, new Set(schemaSet));
return obj;
}
}

if (localSchema.enum) {
if (localSchema.default) return localSchema.default;
return utils.normalizeArray(localSchema.enum)[0];
Expand All @@ -185,13 +232,24 @@ class OpenAPIGeneratorMockJs {
const api = openAPI.paths[path][method];
for (const code in api.responses) {
const response = api.responses[code];
const schema =
response.content &&
response.content['application/json'] &&
utils.inferSchema(response.content['application/json']);

if (schema) {
response.example = schema ? this.sampleFromSchema(schema) : null;
const keys = Object.keys(response.content || {});
if (keys.length) {
let key: string;

if (keys.includes('application/json')) {
key = 'application/json';
} else if (keys.includes('*/*')) {
key = '*/*';
} else {
key = keys[0];
}

const schema = utils.inferSchema(response.content[key]);

if (schema) {
response.example = schema ? this.sampleFromSchema(schema) : null;
}
}
}
if (!api.parameters) continue;
Expand Down
2 changes: 1 addition & 1 deletion src/openAPIParserMock/primitives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default {
string_city: '@city',
string_avatar: '@avatar',
string_href: '@href',
'string_string(16)': 'string(16)',
'string_string(16)': '@string(16)',
'string_date-time': '@datetime',
string_date: '@date',
number: '@integer(60, 100)',
Expand Down
8 changes: 7 additions & 1 deletion src/openAPIParserMock/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,10 @@ function inferSchema(thing) {
return thing;
}

export { isObject, get, objectify, isFunc, inferSchema, normalizeArray };
function getRandomInt(min, max) {
const minCeiled = Math.ceil(min);
const maxFloored = Math.floor(max);
return Math.floor(Math.random() * (maxFloored - minCeiled) + minCeiled);
}

export { isObject, get, objectify, isFunc, inferSchema, normalizeArray, getRandomInt };
Loading

0 comments on commit a824f5d

Please sign in to comment.