UNPKG

wrekenfile-converter

Version:

Convert OpenAPI and Postman specs to Wrekenfile format with mini-chunking for vector DB storage

524 lines (523 loc) 23.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.generateWrekenfile = generateWrekenfile; // openapi-to-wreken.ts const fs = __importStar(require("fs")); const path = __importStar(require("path")); const js_yaml_1 = require("js-yaml"); const js_yaml_2 = require("js-yaml"); const externalRefCache = {}; function mapType(type, format) { if (format === 'uuid') return 'UUID'; if (format === 'date-time') return 'TIMESTAMP'; if (format === 'binary') return 'STRING'; // File uploads if (typeof type === 'string') { const t = type.toLowerCase(); if (t === 'string') return 'STRING'; if (t === 'integer' || t === 'int') return 'INT'; if (t === 'number') return 'FLOAT'; if (t === 'boolean') return 'BOOL'; return 'ANY'; } // Handle array of types (OpenAPI allows type: ['string', 'null']) if (Array.isArray(type) && type.length > 0 && typeof type[0] === 'string') { const t = type[0].toLowerCase(); if (t === 'string') return 'STRING'; if (t === 'integer' || t === 'int') return 'INT'; if (t === 'number') return 'FLOAT'; if (t === 'boolean') return 'BOOL'; return 'ANY'; } // Fallback for missing or unexpected type return 'ANY'; } function generateDesc(op, method, path) { if (op.summary) return op.summary; if (op.description) return op.description; if (op.operationId) return `Perform operation ${op.operationId}`; const verb = { get: 'Fetch', post: 'Create', put: 'Update', delete: 'Delete', patch: 'Modify', }[method.toLowerCase()] || 'Call'; const entity = path.split('/').filter(p => p && !p.startsWith('{')).pop() || 'resource'; return `${verb} ${entity}`; } function resolveRef(ref, spec, baseDir) { if (ref.startsWith('#/')) { return ref.split('/').slice(1).reduce((o, k) => o === null || o === void 0 ? void 0 : o[k], spec); } const [filePath, internal] = ref.split('#'); const fullPath = path.resolve(baseDir, filePath); if (!externalRefCache[fullPath]) { const content = fs.readFileSync(fullPath, 'utf8'); externalRefCache[fullPath] = (0, js_yaml_1.load)(content); } return internal ? internal.split('/').slice(1).reduce((o, k) => o === null || o === void 0 ? void 0 : o[k], externalRefCache[fullPath]) : externalRefCache[fullPath]; } function parseSchema(name, schema, spec, baseDir, depth = 0) { var _a, _b; if (depth > 3) return []; if (schema && typeof schema === 'object' && schema.$ref) { return parseSchema(name, resolveRef(schema.$ref, spec, baseDir), spec, baseDir, depth + 1); } if (schema && typeof schema === 'object' && schema.allOf) { return schema.allOf.flatMap((s) => parseSchema(name, s, spec, baseDir, depth + 1)); } if (schema && typeof schema === 'object' && (schema.oneOf || schema.anyOf)) { return [{ name: 'variant', type: `STRUCT(${name}_Union)`, required: 'OPTIONAL' }]; } // Handle primitive types - return empty array (no struct needed) if (schema && typeof schema === 'object' && schema.type && schema.type !== 'object' && schema.type !== 'array') { return []; } // Handle empty objects (no properties) - return empty array if (schema && typeof schema === 'object' && schema.type === 'object' && (!schema.properties || Object.keys(schema.properties).length === 0)) { return []; } const fields = []; if (schema && typeof schema === 'object' && ((_a = schema.discriminator) === null || _a === void 0 ? void 0 : _a.propertyName)) { fields.push({ name: schema.discriminator.propertyName, type: 'STRING', required: 'REQUIRED', }); } if (schema && typeof schema === 'object' && schema.type === 'object' && schema.properties) { for (const [key, prop] of Object.entries(schema.properties)) { let type = 'ANY'; if (prop && typeof prop === 'object' && prop.$ref) { type = `STRUCT(${prop.$ref.split('/').pop()})`; } else if (prop && typeof prop === 'object' && prop.type === 'array') { if (prop && typeof prop === 'object' && prop.items && prop.items.$ref) { type = `[]STRUCT(${prop.items.$ref.split('/').pop()})`; } else if (prop && typeof prop === 'object' && prop.items) { type = `[]${mapType((_b = prop.items) === null || _b === void 0 ? void 0 : _b.type)}`; } } else { type = mapType(prop.type, prop.format); } // Use the required field from the OpenAPI spec const required = (schema.required || []).includes(key) ? 'REQUIRED' : 'OPTIONAL'; fields.push({ name: key, type, required, }); } } return fields; } function generateStructName(operationId, method, path, suffix) { if (operationId) { return `${operationId}${suffix}`; } // Generate from path and method const pathParts = path.replace(/[\/{}]/g, '_').replace(/^_|_$/g, ''); return `${method}_${pathParts}${suffix}`; } function extractStructs(spec, baseDir) { const structs = {}; const definitions = spec.definitions || {}; // Extract schemas from definitions (OpenAPI v2 equivalent of components.schemas) for (const name in definitions) { const fields = parseSchema(name, definitions[name], spec, baseDir); // Always add the struct, even if empty structs[name] = fields; if (definitions[name] && typeof definitions[name] === 'object' && (definitions[name].oneOf || definitions[name].anyOf)) { structs[`${name}_Union`] = [{ name: 'value', type: 'ANY', required: 'OPTIONAL' }]; } } // Extract inline schemas from operations for (const [pathStr, methods] of Object.entries(spec.paths)) { for (const [method, op] of Object.entries(methods)) { const operationId = op.operationId || `${method}-${pathStr.replace(/[\/{}]/g, '-')}`; // Extract request body schemas (OpenAPI v2 uses parameters with in: body) if (op.parameters) { for (const param of op.parameters) { if (param && typeof param === 'object' && param.in === 'body' && param.schema && !param.schema.$ref) { // Inline schema - create a struct for it only if it has fields const requestStructName = generateStructName(operationId, method, pathStr, 'Request'); const fields = parseSchema(requestStructName, param.schema, spec, baseDir); if (fields.length > 0) { structs[requestStructName] = fields; } } } } // Extract response schemas (OpenAPI v2 has schema directly in response) if (op.responses) { for (const [code, response] of Object.entries(op.responses)) { if (response && typeof response === 'object' && response.schema && !response.schema.$ref) { // Inline schema - create a struct for it only if it has fields const responseStructName = generateStructName(operationId, method, pathStr, `Response${code}`); const fields = parseSchema(responseStructName, response.schema, spec, baseDir); if (fields.length > 0) { structs[responseStructName] = fields; } } } } } } return structs; } function getContentTypeAndBodyType(op) { var _a; // Check if there are formData parameters const hasFormData = (_a = op.parameters) === null || _a === void 0 ? void 0 : _a.some((param) => param && typeof param === 'object' && param.in === 'formData'); if (hasFormData) { return { contentType: 'multipart/form-data', bodyType: 'FORM' }; } // OpenAPI v2 determines content type from consumes array or defaults const consumes = op.consumes || ['application/json']; const contentType = consumes[0] || 'application/json'; let bodyType = 'RAW'; if (contentType === 'multipart/form-data') { bodyType = 'FORM'; } else if (contentType === 'application/x-www-form-urlencoded') { bodyType = 'FORM'; } else if (contentType === 'application/json') { bodyType = 'JSON'; } return { contentType, bodyType }; } function getHeadersForOperation(op, spec) { var _a; const { contentType } = getContentTypeAndBodyType(op); // Use a Map to prevent duplicate headers const headerMap = new Map(); const cookieMap = new Map(); // Add Content-Type header headerMap.set('Content-Type', contentType); // Add security headers based on the operation's security requirements const security = op.security || spec.security || []; for (const securityRequirement of security) { for (const [schemeName, scopes] of Object.entries(securityRequirement)) { const scheme = (_a = spec.securityDefinitions) === null || _a === void 0 ? void 0 : _a[schemeName]; if (scheme) { if (scheme.type === 'basic') { headerMap.set('Authorization', 'basic_auth'); } else if (scheme.type === 'apiKey') { if (scheme.in === 'header') { headerMap.set(scheme.name, `<${scheme.name.toUpperCase()}>`); } else if (scheme.in === 'query') { // Query parameters are handled in extractParameters } else if (scheme.in === 'cookie') { cookieMap.set(scheme.name, `<${scheme.name.toUpperCase()}>`); } } else if (scheme.type === 'oauth2') { headerMap.set('Authorization', 'bearer_token'); } } } } // Check if Authorization is used as a parameter but not defined in securityDefinitions if (op.parameters) { for (const param of op.parameters) { if (param && typeof param === 'object' && param.in === 'header' && param.name === 'Authorization' && !headerMap.has('Authorization')) { headerMap.set('Authorization', 'bearer_token'); } } } // Convert maps to arrays const headers = []; for (const [key, value] of headerMap) { headers.push({ [key]: value }); } for (const [key, value] of cookieMap) { headers.push({ [`Cookie-${key}`]: value }); } return headers; } function extractParameters(op, spec) { const inputParams = []; // Handle path, query, and header parameters if (op.parameters) { for (let param of op.parameters) { // Resolve parameter references if (param && typeof param === 'object' && param.$ref) { if (!spec.swaggerFile) { throw new Error("spec.swaggerFile is undefined. Please provide a valid baseDir when calling generateWrekenfile, or ensure all refs are internal."); } param = resolveRef(param.$ref, spec, path.dirname(spec.swaggerFile)); } // Skip body and formData parameters, they are handled in extractRequestBody if (param && typeof param === 'object' && (param.in === 'body' || param.in === 'formData')) { continue; } const paramType = param && typeof param === 'object' ? param.in || 'query' : 'query'; const paramName = param && typeof param === 'object' ? param.name : ''; const paramSchema = param && typeof param === 'object' ? param.schema || {} : {}; // Schema is sometimes at root of param const paramRequired = param && typeof param === 'object' ? param.required ? 'REQUIRED' : 'OPTIONAL' : 'OPTIONAL'; let type = 'STRING'; if (param && typeof param === 'object' && param.type) { type = mapType(param.type, param.format); } else if (paramSchema && typeof paramSchema === 'object' && paramSchema.type) { type = mapType(paramSchema.type, paramSchema.format); } inputParams.push({ name: paramName, type, required: paramRequired, location: paramType.toUpperCase(), // PATH, QUERY, HEADER }); } } return inputParams; } function extractRequestBody(op, operationId, method, path) { var _a; const inputParams = []; const bodyParam = (op.parameters || []).find((p) => p && typeof p === 'object' && p.in === 'body'); if (bodyParam) { let type; if (bodyParam && typeof bodyParam === 'object' && ((_a = bodyParam.schema) === null || _a === void 0 ? void 0 : _a.$ref)) { type = `STRUCT(${bodyParam.schema.$ref.split('/').pop()})`; } else { // Inline schema - use generated struct name const requestStructName = generateStructName(operationId, method, path, 'Request'); type = `STRUCT(${requestStructName})`; } inputParams.push({ name: 'body', type, required: bodyParam && typeof bodyParam === 'object' ? bodyParam.required ? 'REQUIRED' : 'OPTIONAL' : 'OPTIONAL', }); } // Handle formData for multipart/form-data if (op.parameters) { for (const param of op.parameters) { if (param && typeof param === 'object' && param.in === 'formData') { inputParams.push({ name: param && typeof param === 'object' ? param.name : '', type: param && typeof param === 'object' ? param.type === 'file' ? 'FILE' : mapType(param.type, param.format) : 'STRING', required: param && typeof param === 'object' ? param.required ? 'REQUIRED' : 'OPTIONAL' : 'OPTIONAL', }); } } } return inputParams; } function extractResponses(op, operationId, method, path) { var _a, _b; const returns = []; // Handle all response codes (success and error) for (const [code, response] of Object.entries(op.responses || {})) { let returnType = 'ANY'; if (response && typeof response === 'object' && response.schema) { if (response && typeof response === 'object' && response.schema.$ref) { returnType = `STRUCT(${response.schema.$ref.split('/').pop()})`; } else if (response && typeof response === 'object' && response.schema.type === 'array') { if (response && typeof response === 'object' && ((_a = response.schema.items) === null || _a === void 0 ? void 0 : _a.$ref)) { returnType = `[]STRUCT(${response.schema.items.$ref.split('/').pop()})`; } else if (response && typeof response === 'object' && response.schema.items) { returnType = `[]${mapType((_b = response.schema.items) === null || _b === void 0 ? void 0 : _b.type)}`; } } else if (response && typeof response === 'object' && response.schema.type === 'object') { // Inline schema - use generated struct name const responseStructName = generateStructName(operationId, method, path, `Response${code}`); returnType = `STRUCT(${responseStructName})`; } } else if (code === '204' || response && typeof response === 'object' && response.description === 'No Content') { returnType = 'VOID'; } returns.push({ RETURNTYPE: returnType, RETURNNAME: 'response', CODE: code === 'default' ? '500' : code, }); } return returns; } function extractInterfaces(spec) { var _a; const base = `${((_a = spec.schemes) === null || _a === void 0 ? void 0 : _a[0]) || 'https'}://${spec.host}${spec.basePath || ''}`; const interfaces = {}; // Valid HTTP methods const validMethods = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options', 'trace']; for (const [pathStr, methods] of Object.entries(spec.paths)) { for (const [method, op] of Object.entries(methods)) { // Skip extension fields (x-*) and only process valid HTTP methods if (method.startsWith('x-') || !validMethods.includes(method.toLowerCase())) { continue; } const operationId = op.operationId || `${method}-${pathStr.replace(/[\/{}]/g, '-')}`; const alias = operationId; const endpoint = pathStr.includes('{') ? `\`${base}${pathStr}\`` : `${base}${pathStr}`; // Check if operation is hidden from docs const isPrivate = op && typeof op === 'object' && op['x-hidden-from-docs'] === true; const visibility = isPrivate ? 'PRIVATE' : 'PUBLIC'; const { bodyType } = getContentTypeAndBodyType(op); const headers = getHeadersForOperation(op, spec); const pathQueryHeaderParams = extractParameters(op, spec); const bodyParams = extractRequestBody(op, operationId, method, pathStr); const inputParams = [...pathQueryHeaderParams, ...bodyParams]; const returns = extractResponses(op, operationId, method, pathStr); interfaces[alias] = { SUMMARY: op.summary || '', DESCRIPTION: op.description || '', DESC: generateDesc(op, method, pathStr), ENDPOINT: endpoint, VISIBILITY: visibility, HTTP: { METHOD: method.toUpperCase(), HEADERS: headers, BODYTYPE: bodyType, }, INPUTS: inputParams, RETURNS: returns, }; } } return interfaces; } function extractSecurityDefaults(spec) { const defs = []; const securityDefinitions = spec.securityDefinitions || {}; for (const [name, scheme] of Object.entries(securityDefinitions)) { if (scheme && typeof scheme === 'object' && scheme.type === 'basic') { defs.push({ basic_auth: 'Basic <BASE64>' }); } else if (scheme && typeof scheme === 'object' && scheme.type === 'apiKey') { if (scheme && typeof scheme === 'object' && scheme.in === 'header') { defs.push({ [scheme.name.toLowerCase()]: `<${scheme.name.toUpperCase()}>` }); } else if (scheme && typeof scheme === 'object' && scheme.in === 'query') { defs.push({ [`query_${scheme.name.toLowerCase()}`]: `<${scheme.name.toUpperCase()}>` }); } } else if (scheme && typeof scheme === 'object' && scheme.type === 'oauth2') { defs.push({ bearer_token: 'BEARER <ACCESS_TOKEN>' }); } } return defs; } function cleanYaml(yamlString) { return yamlString .replace(/\t/g, ' ') .replace(/[\u00A0]/g, ' ') .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '') .replace(/[ \t]+$/gm, '') .replace(/\r\n/g, '\n'); } function checkYamlForHiddenChars(yamlString) { const lines = yamlString.split('\n'); for (let i = 0; i < lines.length; i++) { const line = lines[i]; if (/\t/.test(line)) { throw new Error(`YAML contains a TAB character at line ${i + 1}:\n${line}`); } if (/\u00A0/.test(line)) { throw new Error(`YAML contains a non-breaking space (U+00A0) at line ${i + 1}:\n${line}`); } if (/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/.test(line)) { throw new Error(`YAML contains a non-printable character at line ${i + 1}:\n${line}`); } } } function validateYaml(yamlString) { try { (0, js_yaml_2.load)(yamlString); } catch (e) { throw new Error('Generated YAML is invalid: ' + e.message); } } function generateWrekenfile(spec, baseDir) { var _a; if (!spec || typeof spec !== 'object') { throw new Error("Argument 'spec' is required and must be an object"); } if (!baseDir || typeof baseDir !== 'string') { throw new Error("Argument 'baseDir' is required and must be a string"); } // Add swaggerFile path to spec for ref resolution spec.swaggerFile = baseDir; let yamlString = (0, js_yaml_1.dump)({ VERSION: '1.2', INIT: { DEFAULTS: [ ...extractSecurityDefaults(spec), { w_base_url: `${((_a = spec.schemes) === null || _a === void 0 ? void 0 : _a[0]) || 'https'}://${spec.host}${spec.basePath || ''}` }, ], }, INTERFACES: extractInterfaces(spec), STRUCTS: extractStructs(spec, baseDir), }, { noArrayIndent: true }); yamlString = cleanYaml(yamlString); checkYamlForHiddenChars(yamlString); validateYaml(yamlString); return yamlString; }