添加文件下载方案
This commit is contained in:
parent
4dc4427f2a
commit
a2d1fb3875
14
README.md
14
README.md
|
|
@ -410,6 +410,20 @@ const createApi = (id, data) => request.post(buildUrl(`/api/v1/course/{id}`, { i
|
||||||
|
|
||||||
适合你明确想要“函数参数看起来更直接”的场景。
|
适合你明确想要“函数参数看起来更直接”的场景。
|
||||||
|
|
||||||
|
## 文件下载接口
|
||||||
|
|
||||||
|
如果响应声明为 `type: string, format: binary`、Swagger 2 的 `type: file`,或者使用常见文件下载 MIME 类型,例如 `application/octet-stream`、`application/pdf`、Excel、CSV、图片、音视频等,生成代码会自动带上 `responseType: 'blob'`。
|
||||||
|
|
||||||
|
有些后端 Swagger 不会给导出接口声明响应 `content` / `schema`。这种情况下,如果接口路径、`operationId`、摘要或描述里能识别到 `导出`、`下载`、`export`、`download`,或者 GET 接口名里有独立的 `File` 单词,也会按文件下载处理。若响应已经明确声明为 JSON 或普通 schema,则不会走这个兜底推断。
|
||||||
|
|
||||||
|
```js
|
||||||
|
const exportApi = (params = {}) =>
|
||||||
|
request.get(buildUrl(`/api/v1/report/export`, params), { responseType: 'blob' })
|
||||||
|
|
||||||
|
const createExportApi = (data) =>
|
||||||
|
request.post(`/api/v1/report/export`, data, { responseType: 'blob' })
|
||||||
|
```
|
||||||
|
|
||||||
## `sync` 会怎么改外部入口文件
|
## `sync` 会怎么改外部入口文件
|
||||||
|
|
||||||
`sync` 不会粗暴覆盖整个 `externalIndexFile`,它只维护一段带开始和结束标记的受管区块。
|
`sync` 不会粗暴覆盖整个 `externalIndexFile`,它只维护一段带开始和结束标记的受管区块。
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ const DEFAULT_SHARED_FILE_NAME = 'shared.js'
|
||||||
const DEFAULT_INDEX_FILE_NAME = 'index.js'
|
const DEFAULT_INDEX_FILE_NAME = 'index.js'
|
||||||
const AUTO_GENERATED_BANNER = '// Auto-generated. Do not edit manually.'
|
const AUTO_GENERATED_BANNER = '// Auto-generated. Do not edit manually.'
|
||||||
const HTTP_METHOD_ORDER = ['get', 'post', 'put', 'delete', 'patch']
|
const HTTP_METHOD_ORDER = ['get', 'post', 'put', 'delete', 'patch']
|
||||||
|
const BLOB_RESPONSE_TYPE = 'blob'
|
||||||
const JS_RESERVED_WORDS = new Set([
|
const JS_RESERVED_WORDS = new Set([
|
||||||
'await',
|
'await',
|
||||||
'break',
|
'break',
|
||||||
|
|
@ -952,7 +953,13 @@ const generateOperationBlock = (
|
||||||
}
|
}
|
||||||
|
|
||||||
const responseSchemaInfo = getResponseSchemaInfo(operation.responses, schemas)
|
const responseSchemaInfo = getResponseSchemaInfo(operation.responses, schemas)
|
||||||
const returnType = `Promise<${getExpandedJsDocType(responseSchemaInfo?.schema, schemas)}>`
|
const hasBlobResponse = isBlobDownloadOperation(operation, schemas, {
|
||||||
|
apiPath,
|
||||||
|
method,
|
||||||
|
})
|
||||||
|
const returnType = hasBlobResponse
|
||||||
|
? 'Promise<Blob>'
|
||||||
|
: `Promise<${getExpandedJsDocType(responseSchemaInfo?.schema, schemas)}>`
|
||||||
const responsePropertyLines = buildResponsePropertyDocLines(responseSchemaInfo?.schema, schemas)
|
const responsePropertyLines = buildResponsePropertyDocLines(responseSchemaInfo?.schema, schemas)
|
||||||
|
|
||||||
if (hasParams || hasBody) {
|
if (hasParams || hasBody) {
|
||||||
|
|
@ -982,6 +989,7 @@ const generateOperationBlock = (
|
||||||
urlExpression,
|
urlExpression,
|
||||||
hasBody,
|
hasBody,
|
||||||
signatureInfo.bodyArgName,
|
signatureInfo.bodyArgName,
|
||||||
|
hasBlobResponse ? BLOB_RESPONSE_TYPE : null,
|
||||||
)
|
)
|
||||||
|
|
||||||
return [...jsDocLines, `const ${functionName}Api = ${signature}${requestExpression}`, '']
|
return [...jsDocLines, `const ${functionName}Api = ${signature}${requestExpression}`, '']
|
||||||
|
|
@ -1032,33 +1040,80 @@ const buildSignatureInfo = ({ hasBody, parameters, paramStyle }) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildRequestExpression = (method, urlExpression, hasBody, bodyArgName = 'data') => {
|
const buildRequestExpression = (
|
||||||
|
method,
|
||||||
|
urlExpression,
|
||||||
|
hasBody,
|
||||||
|
bodyArgName = 'data',
|
||||||
|
responseType = null,
|
||||||
|
) => {
|
||||||
|
const requestConfigExpression = buildRequestConfigExpression({ responseType })
|
||||||
|
|
||||||
switch (method) {
|
switch (method) {
|
||||||
case 'get':
|
case 'get':
|
||||||
return `request.get(${urlExpression})`
|
return requestConfigExpression
|
||||||
|
? `request.get(${urlExpression}, ${requestConfigExpression})`
|
||||||
|
: `request.get(${urlExpression})`
|
||||||
case 'post':
|
case 'post':
|
||||||
return hasBody
|
return hasBody
|
||||||
? `request.post(${urlExpression}, ${bodyArgName})`
|
? requestConfigExpression
|
||||||
: `request.post(${urlExpression})`
|
? `request.post(${urlExpression}, ${bodyArgName}, ${requestConfigExpression})`
|
||||||
|
: `request.post(${urlExpression}, ${bodyArgName})`
|
||||||
|
: requestConfigExpression
|
||||||
|
? `request.post(${urlExpression}, undefined, ${requestConfigExpression})`
|
||||||
|
: `request.post(${urlExpression})`
|
||||||
case 'put':
|
case 'put':
|
||||||
return hasBody
|
return hasBody
|
||||||
? `request.put(${urlExpression}, ${bodyArgName})`
|
? requestConfigExpression
|
||||||
: `request.put(${urlExpression})`
|
? `request.put(${urlExpression}, ${bodyArgName}, ${requestConfigExpression})`
|
||||||
|
: `request.put(${urlExpression}, ${bodyArgName})`
|
||||||
|
: requestConfigExpression
|
||||||
|
? `request.put(${urlExpression}, undefined, ${requestConfigExpression})`
|
||||||
|
: `request.put(${urlExpression})`
|
||||||
case 'patch':
|
case 'patch':
|
||||||
return hasBody
|
return hasBody
|
||||||
? `request.patch(${urlExpression}, ${bodyArgName})`
|
? requestConfigExpression
|
||||||
: `request.patch(${urlExpression})`
|
? `request.patch(${urlExpression}, ${bodyArgName}, ${requestConfigExpression})`
|
||||||
|
: `request.patch(${urlExpression}, ${bodyArgName})`
|
||||||
|
: requestConfigExpression
|
||||||
|
? `request.patch(${urlExpression}, undefined, ${requestConfigExpression})`
|
||||||
|
: `request.patch(${urlExpression})`
|
||||||
case 'delete':
|
case 'delete':
|
||||||
return hasBody
|
if (hasBody) {
|
||||||
? `request.delete(${urlExpression}, { data: ${bodyArgName} })`
|
return `request.delete(${urlExpression}, ${buildRequestConfigExpression({
|
||||||
|
dataArgName: bodyArgName,
|
||||||
|
responseType,
|
||||||
|
})})`
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestConfigExpression
|
||||||
|
? `request.delete(${urlExpression}, ${requestConfigExpression})`
|
||||||
: `request.delete(${urlExpression})`
|
: `request.delete(${urlExpression})`
|
||||||
default:
|
default:
|
||||||
return hasBody
|
return hasBody
|
||||||
? `request.${method}(${urlExpression}, ${bodyArgName})`
|
? requestConfigExpression
|
||||||
: `request.${method}(${urlExpression})`
|
? `request.${method}(${urlExpression}, ${bodyArgName}, ${requestConfigExpression})`
|
||||||
|
: `request.${method}(${urlExpression}, ${bodyArgName})`
|
||||||
|
: requestConfigExpression
|
||||||
|
? `request.${method}(${urlExpression}, ${requestConfigExpression})`
|
||||||
|
: `request.${method}(${urlExpression})`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const buildRequestConfigExpression = ({ dataArgName = null, responseType = null } = {}) => {
|
||||||
|
const entries = []
|
||||||
|
|
||||||
|
if (dataArgName) {
|
||||||
|
entries.push(`data: ${dataArgName}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responseType) {
|
||||||
|
entries.push(`responseType: ${JSON.stringify(responseType)}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries.length ? `{ ${entries.join(', ')} }` : null
|
||||||
|
}
|
||||||
|
|
||||||
const buildUrlExpression = (apiPath, signatureInfo) => {
|
const buildUrlExpression = (apiPath, signatureInfo) => {
|
||||||
return signatureInfo.urlParamsExpression
|
return signatureInfo.urlParamsExpression
|
||||||
? `buildUrl(\`${apiPath}\`, ${signatureInfo.urlParamsExpression})`
|
? `buildUrl(\`${apiPath}\`, ${signatureInfo.urlParamsExpression})`
|
||||||
|
|
@ -1095,23 +1150,23 @@ const getRequestBodySchemaInfo = (requestBody, schemas) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const getResponseSchemaInfo = (responses, schemas) => {
|
const getResponseSchemaInfo = (responses, schemas) => {
|
||||||
if (!responses) {
|
const preferredResponse = getPreferredResponse(responses)
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const preferredResponse =
|
|
||||||
responses['200'] ||
|
|
||||||
responses['201'] ||
|
|
||||||
responses.default ||
|
|
||||||
Object.values(responses).find((response) => response?.content)
|
|
||||||
|
|
||||||
if (!preferredResponse?.content) {
|
if (!preferredResponse?.content) {
|
||||||
return null
|
if (!preferredResponse?.schema) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
displayName: getSchemaDisplayName(preferredResponse.schema, schemas),
|
||||||
|
resolvedSchema: resolveSchema(preferredResponse.schema, schemas),
|
||||||
|
schema: preferredResponse.schema,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentEntries = Object.entries(preferredResponse.content)
|
const contentEntries = Object.entries(preferredResponse.content)
|
||||||
const preferredContent =
|
const preferredContent =
|
||||||
contentEntries.find(([contentType]) => contentType === 'application/json') || contentEntries[0]
|
contentEntries.find(([contentType]) => isJsonContentType(contentType)) || contentEntries[0]
|
||||||
|
|
||||||
if (!preferredContent?.[1]?.schema) {
|
if (!preferredContent?.[1]?.schema) {
|
||||||
return null
|
return null
|
||||||
|
|
@ -1126,6 +1181,192 @@ const getResponseSchemaInfo = (responses, schemas) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getPreferredResponse = (responses) => {
|
||||||
|
if (!responses) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
responses['200'] ||
|
||||||
|
responses['201'] ||
|
||||||
|
responses.default ||
|
||||||
|
Object.values(responses).find((response) => response?.content || response?.schema)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isBlobDownloadOperation = (operation, schemas, { apiPath = '', method = '' } = {}) => {
|
||||||
|
if (isBlobDownloadResponse(operation.responses, schemas)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((operation.produces || []).some(isDownloadContentType)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return isImplicitBlobDownloadOperation({
|
||||||
|
apiPath,
|
||||||
|
method,
|
||||||
|
operation,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const isBlobDownloadResponse = (responses, schemas) => {
|
||||||
|
const preferredResponse = getPreferredResponse(responses)
|
||||||
|
|
||||||
|
if (!preferredResponse) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasContentDispositionHeader(preferredResponse.headers)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFileSchema(preferredResponse.schema, schemas)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.entries(preferredResponse.content || {}).some(([contentType, contentValue]) => {
|
||||||
|
return isFileSchema(contentValue?.schema, schemas) || isDownloadContentType(contentType)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasContentDispositionHeader = (headers = {}) => {
|
||||||
|
return Object.keys(headers).some((headerName) => {
|
||||||
|
return headerName.toLowerCase() === 'content-disposition'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const isImplicitBlobDownloadOperation = ({ apiPath, method, operation }) => {
|
||||||
|
const preferredResponse = getPreferredResponse(operation.responses)
|
||||||
|
|
||||||
|
if (hasExplicitNonFileResponse(preferredResponse)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const textParts = [
|
||||||
|
apiPath,
|
||||||
|
operation.operationId,
|
||||||
|
operation.summary,
|
||||||
|
operation.description,
|
||||||
|
...(operation.tags || []),
|
||||||
|
]
|
||||||
|
const rawText = textParts.filter(Boolean).join(' ')
|
||||||
|
const normalizedText = rawText.toLowerCase()
|
||||||
|
const wordTokens = new Set(splitWords(rawText).map((word) => word.toLowerCase()))
|
||||||
|
const hasDownloadKeyword =
|
||||||
|
wordTokens.has('download') ||
|
||||||
|
wordTokens.has('export') ||
|
||||||
|
normalizedText.includes('下载') ||
|
||||||
|
normalizedText.includes('导出')
|
||||||
|
const hasUploadOnlyKeyword =
|
||||||
|
(wordTokens.has('upload') ||
|
||||||
|
wordTokens.has('import') ||
|
||||||
|
normalizedText.includes('上传') ||
|
||||||
|
normalizedText.includes('导入')) &&
|
||||||
|
!hasDownloadKeyword
|
||||||
|
|
||||||
|
if (hasUploadOnlyKeyword) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasDownloadKeyword || (method === 'get' && wordTokens.has('file'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasExplicitNonFileResponse = (response) => {
|
||||||
|
if (!response) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.schema) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentEntries = Object.entries(response.content || {})
|
||||||
|
|
||||||
|
if (contentEntries.some(([contentType]) => isJsonContentType(contentType))) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return contentEntries.some(([, contentValue]) => Boolean(contentValue?.schema))
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFileSchema = (schema, schemas, seenRefs = new Set()) => {
|
||||||
|
if (!schema) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema.$ref) {
|
||||||
|
if (seenRefs.has(schema.$ref)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolvedSchema = resolveSchema(schema, schemas)
|
||||||
|
const nextSeenRefs = new Set(seenRefs)
|
||||||
|
nextSeenRefs.add(schema.$ref)
|
||||||
|
|
||||||
|
return isFileSchema(resolvedSchema, schemas, nextSeenRefs)
|
||||||
|
}
|
||||||
|
|
||||||
|
const schemaType = String(schema.type || '').toLowerCase()
|
||||||
|
const schemaFormat = String(schema.format || '').toLowerCase()
|
||||||
|
const contentEncoding = String(schema.contentEncoding || '').toLowerCase()
|
||||||
|
|
||||||
|
if (schemaType === 'file') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schemaType === 'string' && ['binary', 'file'].includes(schemaFormat)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schemaType === 'string' && contentEncoding === 'binary') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...(schema.allOf || []), ...(schema.anyOf || []), ...(schema.oneOf || [])].some(
|
||||||
|
(itemSchema) => isFileSchema(itemSchema, schemas, seenRefs),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDownloadContentType = (contentType) => {
|
||||||
|
const normalizedContentType = normalizeContentType(contentType)
|
||||||
|
|
||||||
|
if (!normalizedContentType || isJsonContentType(normalizedContentType)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
normalizedContentType === 'application/octet-stream' ||
|
||||||
|
normalizedContentType === 'application/pdf' ||
|
||||||
|
normalizedContentType === 'application/zip' ||
|
||||||
|
normalizedContentType === 'application/x-zip-compressed' ||
|
||||||
|
normalizedContentType === 'application/x-7z-compressed' ||
|
||||||
|
normalizedContentType === 'application/gzip' ||
|
||||||
|
normalizedContentType === 'application/x-tar' ||
|
||||||
|
normalizedContentType === 'application/msword' ||
|
||||||
|
normalizedContentType === 'application/csv' ||
|
||||||
|
normalizedContentType === 'text/csv' ||
|
||||||
|
normalizedContentType.startsWith('application/vnd.') ||
|
||||||
|
normalizedContentType.startsWith('image/') ||
|
||||||
|
normalizedContentType.startsWith('audio/') ||
|
||||||
|
normalizedContentType.startsWith('video/') ||
|
||||||
|
normalizedContentType.startsWith('font/')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isJsonContentType = (contentType) => {
|
||||||
|
const normalizedContentType = normalizeContentType(contentType)
|
||||||
|
|
||||||
|
return normalizedContentType === 'application/json' || normalizedContentType.endsWith('+json')
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeContentType = (contentType) => {
|
||||||
|
return String(contentType || '')
|
||||||
|
.split(';')[0]
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
}
|
||||||
|
|
||||||
const buildResponsePropertyDocLines = (schema, schemas) => {
|
const buildResponsePropertyDocLines = (schema, schemas) => {
|
||||||
return buildStructuredPropertyDocLines({
|
return buildStructuredPropertyDocLines({
|
||||||
title: 'Response fields:',
|
title: 'Response fields:',
|
||||||
|
|
|
||||||
|
|
@ -515,6 +515,177 @@ test('generation automatically avoids collisions with module namespace export na
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('file download responses generate blob request config', async () => {
|
||||||
|
const tempDir = await createTempDir()
|
||||||
|
|
||||||
|
try {
|
||||||
|
const swaggerPath = path.join(tempDir, 'swagger.json')
|
||||||
|
const outputDir = path.join(tempDir, 'generated')
|
||||||
|
|
||||||
|
await writeJson(swaggerPath, {
|
||||||
|
openapi: '3.0.0',
|
||||||
|
paths: {
|
||||||
|
'/api/v1/File/Export': {
|
||||||
|
get: {
|
||||||
|
tags: ['File'],
|
||||||
|
parameters: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
in: 'query',
|
||||||
|
schema: {
|
||||||
|
type: 'integer',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: 'OK',
|
||||||
|
content: {
|
||||||
|
'application/octet-stream': {
|
||||||
|
schema: {
|
||||||
|
type: 'string',
|
||||||
|
format: 'binary',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'/api/v1/File/CreateExport': {
|
||||||
|
post: {
|
||||||
|
tags: ['File'],
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: 'OK',
|
||||||
|
content: {
|
||||||
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await generateApiFiles({
|
||||||
|
projectRoot: tempDir,
|
||||||
|
swaggerUrl: swaggerPath,
|
||||||
|
swaggerTimeoutMs: 1000,
|
||||||
|
outputDir,
|
||||||
|
requestImport: '../request',
|
||||||
|
paramStyle: 'object',
|
||||||
|
modules: [],
|
||||||
|
cleanOutput: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const fileContent = await readFile(path.join(outputDir, 'file.js'))
|
||||||
|
|
||||||
|
assert.match(fileContent, /@returns\s+\{Promise<Blob>\}/)
|
||||||
|
assert.match(
|
||||||
|
fileContent,
|
||||||
|
/const exportApi = \(params = \{\}\) =>\s+request\.get\(buildUrl\(`\/api\/v1\/File\/Export`, params\), \{\s*responseType: ["']blob["'],?\s*\}\)/s,
|
||||||
|
)
|
||||||
|
assert.match(
|
||||||
|
fileContent,
|
||||||
|
/const createExportApi = \(data\) =>\s+request\.post\(`\/api\/v1\/File\/CreateExport`, data, \{\s*responseType: ["']blob["'],?\s*\}\)/s,
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
await fs.rm(tempDir, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('untyped export endpoints infer blob request config from operation metadata', async () => {
|
||||||
|
const tempDir = await createTempDir()
|
||||||
|
|
||||||
|
try {
|
||||||
|
const swaggerPath = path.join(tempDir, 'swagger.json')
|
||||||
|
const outputDir = path.join(tempDir, 'generated')
|
||||||
|
|
||||||
|
await writeJson(swaggerPath, {
|
||||||
|
openapi: '3.0.0',
|
||||||
|
paths: {
|
||||||
|
'/api/v1/Report/GetUserPointRecordFile': {
|
||||||
|
get: {
|
||||||
|
tags: ['Report'],
|
||||||
|
summary: '导出积分排行榜',
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: 'OK',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'/api/v1/Report/GetExportJob': {
|
||||||
|
get: {
|
||||||
|
tags: ['Report'],
|
||||||
|
summary: '获取导出任务状态',
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: 'OK',
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
status: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await generateApiFiles({
|
||||||
|
projectRoot: tempDir,
|
||||||
|
swaggerUrl: swaggerPath,
|
||||||
|
swaggerTimeoutMs: 1000,
|
||||||
|
outputDir,
|
||||||
|
requestImport: '../request',
|
||||||
|
paramStyle: 'object',
|
||||||
|
modules: [],
|
||||||
|
cleanOutput: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const reportContent = await readFile(path.join(outputDir, 'report.js'))
|
||||||
|
|
||||||
|
assert.match(
|
||||||
|
reportContent,
|
||||||
|
/const getUserPointRecordFileApi = \(\) =>\s+request\.get\(`\/api\/v1\/Report\/GetUserPointRecordFile`, \{\s*responseType: ["']blob["'],?\s*\}\)/s,
|
||||||
|
)
|
||||||
|
assert.match(
|
||||||
|
reportContent,
|
||||||
|
/const getExportJobApi = \(\) =>\s+request\.get\(`\/api\/v1\/Report\/GetExportJob`\)/s,
|
||||||
|
)
|
||||||
|
assert.doesNotMatch(
|
||||||
|
reportContent,
|
||||||
|
/request\.get\(`\/api\/v1\/Report\/GetExportJob`, \{\s*responseType: ["']blob["']/s,
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
await fs.rm(tempDir, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
test('response jsdoc expands nested array object fields', async () => {
|
test('response jsdoc expands nested array object fields', async () => {
|
||||||
const tempDir = await createTempDir()
|
const tempDir = await createTempDir()
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue