Files
Web_BAI_Manage_ApiServer/.tmp-openapi-validate/add-openapi-examples.js

269 lines
7.7 KiB
JavaScript
Raw Normal View History

const fs = require('fs');
const path = require('path');
const YAML = require('yaml');
const repoRoot = process.cwd();
const rootFile = path.join(repoRoot, 'pocket-base', 'spec', 'openapi-wx.yaml');
const splitDir = path.join(repoRoot, 'pocket-base', 'spec', 'openapi-wx');
const filePaths = [
rootFile,
...fs
.readdirSync(splitDir)
.filter((name) => name.endsWith('.yaml'))
.map((name) => path.join(splitDir, name)),
];
const docs = new Map(
filePaths.map((filePath) => [filePath, YAML.parse(fs.readFileSync(filePath, 'utf8'))]),
);
function decodePointerSegment(segment) {
return segment.replace(/~1/g, '/').replace(/~0/g, '~');
}
function getByPointer(documentData, pointer) {
if (!pointer || pointer === '#' || pointer === '') {
return documentData;
}
const segments = pointer
.replace(/^#/, '')
.split('/')
.filter(Boolean)
.map(decodePointerSegment);
let current = documentData;
for (const segment of segments) {
if (current == null) {
return undefined;
}
current = current[segment];
}
return current;
}
function resolveRef(ref, currentFile) {
const [filePart, hashPart = ''] = ref.split('#');
const targetFile = filePart
? path.resolve(path.dirname(currentFile), filePart)
: currentFile;
const targetDoc = docs.get(targetFile);
if (!targetDoc) {
return { targetFile, targetKey: `${targetFile}#${hashPart}`, schema: undefined };
}
const pointer = hashPart ? `#${hashPart}` : '#';
return {
targetFile,
targetKey: `${targetFile}${pointer}`,
schema: getByPointer(targetDoc, pointer),
};
}
function pickType(typeValue) {
if (Array.isArray(typeValue)) {
return typeValue.find((item) => item !== 'null') || typeValue[0];
}
return typeValue;
}
function cleanLabelText(text, fallback) {
if (!text || typeof text !== 'string') {
return fallback;
}
const normalized = text
.split(/\r?\n/)
.map((line) => line.trim())
.filter(Boolean)
.map((line) => line.replace(/^[\-\d\.\)\s]+/, '').trim())
.find((line) => line && !/^(可选|必填|说明|注意)[。:]?$/u.test(line));
if (!normalized) {
return fallback;
}
return normalized.replace(/[`"'<>]/g, '').trim() || fallback;
}
function placeholderKey(key) {
return typeof key === 'string' && key.includes('|');
}
function deepMerge(baseValue, nextValue) {
if (
baseValue &&
nextValue &&
typeof baseValue === 'object' &&
typeof nextValue === 'object' &&
!Array.isArray(baseValue) &&
!Array.isArray(nextValue)
) {
const merged = { ...baseValue };
const nextKeys = Object.keys(nextValue);
const hasConcreteNextKeys = nextKeys.some((key) => !placeholderKey(key));
if (hasConcreteNextKeys) {
for (const key of Object.keys(merged)) {
if (placeholderKey(key)) {
delete merged[key];
}
}
}
for (const [key, value] of Object.entries(nextValue)) {
if (key in merged) {
merged[key] = deepMerge(merged[key], value);
} else {
merged[key] = value;
}
}
return merged;
}
return nextValue;
}
function buildLabel(schemaLike, fallback) {
return cleanLabelText(schemaLike?.description || schemaLike?.title, fallback);
}
function fallbackScalar(label, fieldType) {
return `${label}|${fieldType || 'string'}`;
}
function buildExample(schemaLike, currentFile, fieldName = 'field', seenRefs = new Set()) {
if (schemaLike == null) {
return fallbackScalar(fieldName, 'string');
}
if (typeof schemaLike !== 'object' || Array.isArray(schemaLike)) {
return fallbackScalar(fieldName, 'string');
}
if (schemaLike.$ref) {
const { targetFile, targetKey, schema } = resolveRef(schemaLike.$ref, currentFile);
if (!schema) {
return fallbackScalar(fieldName, 'string');
}
if (seenRefs.has(targetKey)) {
return fallbackScalar(fieldName, 'object');
}
const nextSeen = new Set(seenRefs);
nextSeen.add(targetKey);
return buildExample(schema, targetFile, fieldName, nextSeen);
}
if (schemaLike.allOf) {
return schemaLike.allOf.reduce((accumulator, item) => {
const nextValue = buildExample(item, currentFile, fieldName, seenRefs);
return deepMerge(accumulator, nextValue);
}, {});
}
if (schemaLike.oneOf && schemaLike.oneOf.length > 0) {
return buildExample(schemaLike.oneOf[0], currentFile, fieldName, seenRefs);
}
if (schemaLike.anyOf && schemaLike.anyOf.length > 0) {
return buildExample(schemaLike.anyOf[0], currentFile, fieldName, seenRefs);
}
const fieldType =
pickType(schemaLike.type) ||
(schemaLike.properties || schemaLike.additionalProperties ? 'object' : undefined) ||
(schemaLike.items ? 'array' : undefined) ||
'string';
const fieldLabel = buildLabel(schemaLike, fieldName);
if (fieldType === 'object') {
const exampleObject = {};
if (schemaLike.properties && typeof schemaLike.properties === 'object') {
for (const [propertyName, propertySchema] of Object.entries(schemaLike.properties)) {
exampleObject[propertyName] = buildExample(
propertySchema,
currentFile,
propertyName,
seenRefs,
);
}
}
if (Object.keys(exampleObject).length === 0 && schemaLike.additionalProperties) {
exampleObject[`${fieldLabel}字段|string`] = `${fieldLabel}值|string`;
}
if (Object.keys(exampleObject).length === 0) {
exampleObject[`${fieldLabel}|string`] = `${fieldLabel}|string`;
}
return exampleObject;
}
if (fieldType === 'array') {
const itemFieldName = fieldName.endsWith('s') ? fieldName.slice(0, -1) : `${fieldName}_item`;
return [buildExample(schemaLike.items || {}, currentFile, itemFieldName, seenRefs)];
}
return fallbackScalar(fieldLabel, fieldType);
}
function httpMethods(pathItem) {
return ['get', 'post', 'put', 'patch', 'delete', 'options', 'head', 'trace'].filter(
(method) => pathItem && typeof pathItem[method] === 'object',
);
}
for (const filePath of filePaths.filter((file) => file !== rootFile)) {
const documentData = docs.get(filePath);
if (!documentData || typeof documentData !== 'object') {
continue;
}
if (documentData.components && documentData.components.schemas) {
for (const [schemaName, schemaValue] of Object.entries(documentData.components.schemas)) {
schemaValue.example = buildExample(schemaValue, filePath, schemaName, new Set());
}
}
if (documentData.paths) {
for (const pathItem of Object.values(documentData.paths)) {
for (const method of httpMethods(pathItem)) {
const operation = pathItem[method];
const requestContent = operation?.requestBody?.content;
if (requestContent && typeof requestContent === 'object') {
for (const media of Object.values(requestContent)) {
if (media && media.schema) {
delete media.examples;
media.example = buildExample(media.schema, filePath, 'request', new Set());
}
}
}
const responses = operation?.responses;
if (responses && typeof responses === 'object') {
for (const response of Object.values(responses)) {
const responseContent = response?.content;
if (!responseContent || typeof responseContent !== 'object') {
continue;
}
for (const media of Object.values(responseContent)) {
if (media && media.schema) {
delete media.examples;
media.example = buildExample(media.schema, filePath, 'response', new Set());
}
}
}
}
}
}
}
fs.writeFileSync(filePath, YAML.stringify(documentData, { lineWidth: 0 }), 'utf8');
}
console.log(`Updated examples in ${filePaths.length - 1} split OpenAPI files.`);