erest
Version:
Easy to build api server depend on @leizm/web and express.
326 lines (325 loc) • 11.4 kB
JavaScript
"use strict";
/**
* @file API Docs
* 参考 hojs
* @author Yourtion Guo <yourtion@gmail.com>
*/
Object.defineProperty(exports, "__esModule", { value: true });
const node_assert_1 = require("node:assert");
const fs = require("node:fs");
const path = require("node:path");
const debug_1 = require("../debug");
const generate_axios_1 = require("../plugin/generate_axios");
const generate_markdown_1 = require("../plugin/generate_markdown");
const generate_postman_1 = require("../plugin/generate_postman");
const generate_swagger_1 = require("../plugin/generate_swagger");
// Generate all.json function
function generateAll(data, dir, options, writer) {
const filename = (0, utils_1.getPath)("all.json", options.all);
// 创建一个没有循环引用的数据副本
const cleanData = {
...data,
erest: undefined, // 移除循环引用
};
writer(path.resolve(dir, filename), (0, utils_1.jsonStringify)(cleanData, 2));
}
const utils_1 = require("../utils");
/** 从文档获取的字段 */
const DOC_FIELD = [
"method",
"path",
"realPath",
"examples",
"middlewares",
"query",
"body",
"params",
"group",
"title",
"description",
"response",
"required",
"requiredOneOf",
"tested",
"responseSchema",
"headers",
];
/** 默认文档输出格式化 */
const docOutputFormat = (out) => out;
/** 默认文档输出函数(直接写文件) */
const docWriteSync = (path, data) => fs.writeFileSync(path, data);
function generateJosn(data, dir, options, writer) {
const filename = (0, utils_1.getPath)("doc.json", options.json);
// 创建一个没有循环引用的数据副本
const cleanData = {
...data,
erest: undefined, // 移除循环引用
};
writer(path.resolve(dir, filename), (0, utils_1.jsonStringify)(cleanData, 2));
}
class IAPIDoc {
erest;
info;
groups;
docsOptions;
plugins = [];
writer = docWriteSync;
docDataCache = null;
constructor(erestIns) {
this.erest = erestIns;
const { info, groups, docsOptions } = this.erest.privateInfo;
this.info = info;
this.groups = groups;
this.docsOptions = docsOptions;
}
/** 生成类型文档 - 支持新的 Zod 实现 */
generateTypeDocumentation(data) {
// 从类型注册表中获取所有注册的类型
const typeRegistry = this.erest.typeRegistry;
if (typeRegistry && typeRegistry.size > 0) {
for (const [typeName, zodSchema] of typeRegistry.entries()) {
const typeDoc = {
name: typeName,
description: this.extractZodSchemaDescription(zodSchema),
isBuiltin: false,
tsType: this.extractTypeScriptType(zodSchema),
isDefaultFormat: true,
isParamsRequired: false,
};
data.types[typeName] = typeDoc;
}
}
// 从schema注册表中获取所有注册的schema
const schemaRegistry = this.erest.schemaRegistry;
if (schemaRegistry && schemaRegistry.size > 0) {
for (const [schemaName, zodSchema] of schemaRegistry.entries()) {
const schemaDoc = {
name: schemaName,
description: this.extractZodSchemaDescription(zodSchema),
isBuiltin: false,
tsType: this.extractTypeScriptType(zodSchema),
isDefaultFormat: true,
isParamsRequired: false,
};
data.types[schemaName] = schemaDoc;
}
}
}
/** 从Zod Schema中提取描述信息 */
extractZodSchemaDescription(zodSchema) {
if (!zodSchema || !zodSchema._def) {
return "未知类型";
}
const typeName = zodSchema._def.typeName ||
zodSchema._def.type;
switch (typeName) {
case "ZodString":
case "string":
return "字符串类型";
case "ZodNumber":
case "number":
return "数字类型";
case "ZodBoolean":
case "boolean":
return "布尔类型";
case "ZodDate":
case "date":
return "日期类型";
case "ZodArray":
case "array":
return "数组类型";
case "ZodObject":
case "object":
return "对象类型";
case "ZodEnum":
case "enum":
return "枚举类型";
case "ZodUnion":
case "union":
return "联合类型";
case "ZodOptional":
case "optional":
return "可选类型";
case "ZodNullable":
case "nullable":
return "可空类型";
default:
return `Zod ${typeName} 类型`;
}
}
/** 从Zod Schema中提取TypeScript类型 */
extractTypeScriptType(zodSchema) {
if (!zodSchema || !zodSchema._def) {
return "unknown";
}
const typeName = zodSchema._def.typeName ||
zodSchema._def.type;
switch (typeName) {
case "ZodString":
case "string":
return "string";
case "ZodNumber":
case "number":
return "number";
case "ZodBoolean":
case "boolean":
return "boolean";
case "ZodDate":
case "date":
return "Date";
case "ZodArray":
case "array": {
const defObj = zodSchema._def;
const elementType = defObj.element || defObj.type || defObj.innerType;
const innerType = elementType ? this.extractTypeScriptType(elementType) : "unknown";
return `${innerType}[]`;
}
case "ZodObject":
case "object":
return "object";
case "ZodEnum":
case "enum": {
const enumValues = zodSchema._def.values ||
zodSchema._def.entries;
if (Array.isArray(enumValues)) {
return enumValues.map((v) => (typeof v === "string" ? `"${v}"` : String(v))).join(" | ");
}
else if (enumValues && typeof enumValues === "object") {
// 处理 { red: 'red', green: 'green', blue: 'blue' } 格式
const values = Object.values(enumValues);
return values.map((v) => (typeof v === "string" ? `"${v}"` : String(v))).join(" | ");
}
return "string";
}
case "ZodUnion":
case "union": {
const unionTypes = zodSchema._def.options;
if (Array.isArray(unionTypes)) {
return unionTypes.map((t) => this.extractTypeScriptType(t)).join(" | ");
}
return "unknown";
}
case "ZodOptional":
case "optional":
return `${this.extractTypeScriptType(zodSchema._def.innerType)} | undefined`;
case "ZodNullable":
case "nullable":
return `${this.extractTypeScriptType(zodSchema._def.innerType)} | null`;
default:
return "unknown";
}
}
/** 获取文档数据 */
buildDocData() {
if (this.docDataCache)
return this.docDataCache;
(0, debug_1.docs)("data");
const now = new Date();
const data = {
info: this.info,
// FIXME: 对日期格式需要优化
genTime: `${now.toLocaleDateString()} ${now.toLocaleTimeString()}`,
errorManager: this.erest.errors,
schema: this.erest.schema,
typeManager: this.erest.type,
group: this.groups,
types: {},
apis: {},
apiInfo: {
count: 0,
tested: 0,
untest: [],
},
erest: this.erest, // 添加erest实例引用
};
const formatOutput = this.erest.api.docOutputForamt || docOutputFormat;
// 生成类型文档 - 支持新的 Zod 实现
this.generateTypeDocumentation(data);
for (const [k, schema] of this.erest.api.$apis.entries()) {
const o = schema.options;
data.apis[k] = {};
for (const key of DOC_FIELD) {
data.apis[k][key] = o[key];
}
const examples = data.apis[k].examples;
if (examples) {
examples.forEach((item) => {
if (item && typeof item === "object") {
item.output = formatOutput(item.output);
}
});
}
}
this.docDataCache = data;
return data;
}
/** 设置文档输出函数 */
setWritter(writer) {
this.writer = writer;
}
/** 生成文档 */
genDocs() {
(0, debug_1.docs)("genDocs");
if (this.docsOptions.markdown) {
this.registerPlugin("markdown", generate_markdown_1.default);
}
if (this.docsOptions.swagger) {
this.registerPlugin("swagger", generate_swagger_1.default);
}
if (this.docsOptions.postman) {
this.registerPlugin("postman", generate_postman_1.default);
}
if (this.docsOptions.json) {
this.registerPlugin("json", generateJosn);
}
if (this.docsOptions.axios) {
this.registerPlugin("axios", generate_axios_1.default);
}
if (this.docsOptions.all) {
this.registerPlugin("all", generateAll);
}
return this;
}
getSwaggerInfo() {
return (0, generate_swagger_1.buildSwagger)(this.buildDocData());
}
registerPlugin(name, plugin) {
(0, debug_1.docs)(name);
this.plugins.push(plugin);
}
/** 保存文档 */
save(dir) {
(0, node_assert_1.strict)(typeof dir === "string" && dir.length > 0, `文档存储目录"${dir}"格式不正确:必须是字符串类型`);
// 保存 all.json
const data = this.buildDocData();
for (const [key, api] of Object.entries(data.apis)) {
data.apiInfo.count += 1;
if (api.examples && api.examples.length > 0) {
data.apiInfo.tested += 1;
}
else {
data.apiInfo.untest.push(key);
}
}
(0, debug_1.docs)("save: %s", dir);
// 根据插件生成文档
for (const fn of this.plugins) {
(0, debug_1.docs)("build doc: %s", fn);
// 防止文档生成插件报错
try {
fn(data, dir, this.docsOptions, this.writer);
}
catch (error) {
console.error(error);
}
}
return this;
}
/** 当进程退出时存储文档 */
saveOnExit(dir) {
(0, debug_1.docs)("saveOnExit: %s", dir);
process.on("exit", () => this.save(dir));
return this;
}
}
exports.default = IAPIDoc;