UNPKG

wrekenfile-converter

Version:

Convert OpenAPI and Postman specs into Wrekenfiles, with chunking for vector database storage

1,045 lines 55.4 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; exports.generateWrekenfileWithStats = generateWrekenfileWithStats; // openapi-to-wrekenfile-v2.ts // Converts OpenAPI v3 specifications to Wrekenfile v2.0.1 format const fs = __importStar(require("fs")); const path = __importStar(require("path")); const js_yaml_1 = require("js-yaml"); const yaml_utils_1 = require("./utils/yaml-utils"); const constants_1 = require("./utils/constants"); const response_utils_1 = require("./utils/response-utils"); const type_utils_1 = require("./utils/type-utils"); const summary_utils_1 = require("./utils/summary-utils"); const error_utils_1 = require("./utils/error-utils"); const canonical_id_1 = require("./utils/canonical-id"); const struct_utils_1 = require("./utils/struct-utils"); const conversion_stats_1 = require("./utils/conversion-stats"); const externalRefCache = {}; // Re-export for backward compatibility const mapType = type_utils_1.mapOpenApiType; const generateSummary = summary_utils_1.generateOpenApiSummary; function resolveRef(ref, spec, baseDir) { if (!ref || typeof ref !== 'string') { throw (0, error_utils_1.createConverterError)(`Invalid $ref: must be a non-empty string`, "INVALID_REF", { ref, refType: typeof ref }); } if (ref.startsWith('#/')) { const pathParts = ref.split('/').slice(1); let result = spec; for (const part of pathParts) { if (result === undefined || result === null) { throw (0, error_utils_1.createConverterError)(`Failed to resolve $ref: ${ref} - path segment '${part}' not found`, "REF_RESOLUTION_FAILED", { ref, pathParts, currentPath: pathParts.slice(0, pathParts.indexOf(part) + 1) }); } result = result[part]; } return result; } const [filePath, internal] = ref.split('#'); if (!filePath) { throw (0, error_utils_1.createConverterError)(`Invalid external $ref: missing file path in ${ref}`, "INVALID_EXTERNAL_REF", { ref }); } const fullPath = path.resolve(baseDir, filePath); if (!fs.existsSync(fullPath)) { throw (0, error_utils_1.createConverterError)(`External $ref file not found: ${fullPath}`, "EXTERNAL_REF_FILE_NOT_FOUND", { ref, filePath, baseDir, fullPath }); } try { if (!externalRefCache[fullPath]) { const content = fs.readFileSync(fullPath, 'utf8'); externalRefCache[fullPath] = (0, js_yaml_1.load)(content); } if (internal) { const internalPath = internal.split('/').slice(1); let result = externalRefCache[fullPath]; for (const part of internalPath) { if (result === undefined || result === null) { throw (0, error_utils_1.createConverterError)(`Failed to resolve internal $ref: ${internal} in file ${fullPath}`, "INTERNAL_REF_RESOLUTION_FAILED", { ref, internal, filePath, internalPath }); } result = result[part]; } return result; } return externalRefCache[fullPath]; } catch (err) { if (err.code && err.code.startsWith('REF_')) { throw err; } throw (0, error_utils_1.createConverterError)(`Error loading external $ref file: ${fullPath}`, "EXTERNAL_REF_LOAD_ERROR", { ref, filePath, fullPath }, err); } } /** * Build a Wrekenfile map value-type string from an `additionalProperties` value. * Handles scalar, array-of-scalar, array-of-ref, and ref value types so we can * render `map[STRING]X` for objects defined only by `additionalProperties`. */ function mapSchemaToMapType(ap, spec, baseDir) { if (ap === true || !ap || typeof ap !== 'object') { return 'map[STRING]ANY'; } if (ap.$ref) { const resolved = resolveRef(ap.$ref, spec, baseDir); if (resolved && resolved.type && resolved.type !== 'object') { return `map[STRING]${mapType(resolved.type, resolved.format)}`; } return `map[STRING]STRUCT(${ap.$ref.split('/').pop()})`; } if (ap.type === 'array' && ap.items) { if (ap.items.$ref) { const resolvedItems = resolveRef(ap.items.$ref, spec, baseDir); if (resolvedItems && resolvedItems.type && resolvedItems.type !== 'object') { return `map[STRING][]${mapType(resolvedItems.type, resolvedItems.format)}`; } return `map[STRING][]STRUCT(${ap.items.$ref.split('/').pop()})`; } if (ap.items.type) { return `map[STRING][]${mapType(ap.items.type, ap.items.format)}`; } return 'map[STRING][]ANY'; } if (ap.type) { return `map[STRING]${mapType(ap.type, ap.format)}`; } return 'map[STRING]ANY'; } function getTypeFromSchema(schema, spec, baseDir) { if (!schema || typeof schema !== 'object') { return 'ANY'; } if (schema.$ref) { const resolvedSchema = resolveRef(schema.$ref, spec, baseDir); if (resolvedSchema && resolvedSchema.type && resolvedSchema.type !== 'object') { return mapType(resolvedSchema.type, resolvedSchema.format); } // Resolve propertyless object schemas at the $ref site so we don't emit // dangling STRUCT(Foo) references for schemas that will never be registered // (since they have no properties to parse into struct fields). if (resolvedSchema && resolvedSchema.type === 'object' && !resolvedSchema.properties) { if (resolvedSchema.additionalProperties) { return mapSchemaToMapType(resolvedSchema.additionalProperties, spec, baseDir); } return 'OBJECT'; } const refName = schema.$ref.split('/').pop(); return `STRUCT(${refName})`; } if (schema.type === 'array') { if (schema.items && schema.items.$ref) { const resolvedItems = resolveRef(schema.items.$ref, spec, baseDir); if (resolvedItems && resolvedItems.type && resolvedItems.type !== 'object') { return `[]${mapType(resolvedItems.type, resolvedItems.format)}`; } const refName = schema.items.$ref.split('/').pop(); return `[]STRUCT(${refName})`; } else if (schema.items) { return `[]${mapType(schema.items.type, schema.items.format)}`; } else { return '[]ANY'; } } if (schema.type === 'object') { // Check if it has properties or is a generic object if (schema.properties || schema.additionalProperties) { // If it has additionalProperties, it's a map if (schema.additionalProperties) { const valueType = typeof schema.additionalProperties === 'object' && schema.additionalProperties.type ? mapType(schema.additionalProperties.type, schema.additionalProperties.format) : 'ANY'; return `map[STRING]${valueType}`; } // Otherwise it's a struct (will be defined in STRUCTS) return 'OBJECT'; } // Generic object without properties return 'OBJECT'; } if (schema.type && schema.type !== 'object') { return mapType(schema.type, schema.format); } return 'ANY'; } function parseSchema(name, schema, spec, baseDir, depth = 0) { var _a, _b; if (depth > 3) return []; if (schema.$ref) return parseSchema(name, resolveRef(schema.$ref, spec, baseDir), spec, baseDir, depth + 1); if (schema.allOf) return schema.allOf.flatMap((s) => parseSchema(name, s, spec, baseDir, depth + 1)); if (schema.oneOf || schema.anyOf) { // Enumerate union variants with their actual types const variants = schema.oneOf || schema.anyOf; const fields = []; // Add discriminator field if present if ((_a = schema.discriminator) === null || _a === void 0 ? void 0 : _a.propertyName) { fields.push({ name: schema.discriminator.propertyName, type: 'STRING', REQUIRED: true, }); } for (let i = 0; i < variants.length; i++) { const variant = variants[i]; if (variant && typeof variant === 'object' && variant.$ref) { const refName = typeof variant.$ref === 'string' ? variant.$ref.split('/').pop() : undefined; const variantType = getTypeFromSchema(variant, spec, baseDir) || 'ANY'; fields.push({ name: refName ? `variant_${refName}` : `variant_${i}`, type: variantType, REQUIRED: false, }); } else if (variant && typeof variant === 'object' && variant.type && variant.type !== 'object') { fields.push({ name: `variant_${i}`, type: mapType(variant.type, variant.format), REQUIRED: false, }); } else { fields.push({ name: `variant_${i}`, type: 'ANY', REQUIRED: false, }); } } return fields.length > 0 ? fields : [{ name: 'value', type: 'ANY', REQUIRED: false, }]; } const fields = []; if ((_b = schema.discriminator) === null || _b === void 0 ? void 0 : _b.propertyName) { fields.push({ name: schema.discriminator.propertyName, type: 'STRING', REQUIRED: true, }); } // Handle simple types (string, integer, etc.) - these should not create structs if (schema.type && schema.type !== 'object') { return []; } if (schema.type === 'object' && schema.properties) { for (const [key, prop] of Object.entries(schema.properties)) { const type = getTypeFromSchema(prop, spec, baseDir); // Use the required field from the OpenAPI spec const required = (schema.required || []).includes(key); const field = { name: key, type, REQUIRED: required, }; // Add comment if description exists if (prop && typeof prop === 'object' && prop.description) { field.comment = prop.description; } fields.push(field); } } return fields; } function generateStructName(_operationId, method, path, suffix) { // Use canonical ID as the base for inline request/response struct names const canonicalId = (0, canonical_id_1.computeCanonicalId)('api', method.toUpperCase(), path); return `${canonicalId}${suffix}`; } /** * Pick a struct name for an error response whose content schema is inline * (no `$ref`). When the response object itself is a `$ref` to * `#/components/responses/X`, the returned name is stable across all call * sites so the struct can be defined once and referenced from every operation. */ function getErrorStructName(rawResponse, op, code) { if (rawResponse && rawResponse.$ref && typeof rawResponse.$ref === 'string') { const key = rawResponse.$ref.split('/').pop() || ''; if (/^[0-9]+$/.test(key)) { return `Error${key}`; } if (key) { return `Response_${key}`; } } // Truly inline per-operation error schema — scope the name to the operation const opId = op.operationId || 'op'; return `${opId}_Error${code}`; } function extractStructs(spec, baseDir) { var _a, _b, _c; const structs = {}; const schemas = ((_a = spec.components) === null || _a === void 0 ? void 0 : _a.schemas) || {}; // Helper to recursively collect all referenced schemas function collectAllReferencedSchemas(schema, name) { if (!schema || typeof schema !== 'object' || !name || structs[name]) return; const resolved = schema.$ref ? resolveRef(schema.$ref, spec, baseDir) : schema; const fields = parseSchema(name, resolved, spec, baseDir); // Only add struct if it has at least one field if (fields.length > 0) { structs[name] = fields; } // Traverse all properties if (resolved && resolved.type === 'object' && resolved.properties && typeof resolved.properties === 'object') { for (const [propName, prop] of Object.entries(resolved.properties)) { if (prop && typeof prop === 'object' && prop.$ref) { const refName = prop.$ref.split('/').pop(); if (refName) collectAllReferencedSchemas(resolveRef(prop.$ref, spec, baseDir), refName); } else if (prop && typeof prop === 'object' && prop.type === 'array' && prop.items) { if (prop.items && typeof prop.items === 'object' && prop.items.$ref) { const refName = prop.items.$ref.split('/').pop(); if (refName) collectAllReferencedSchemas(resolveRef(prop.items.$ref, spec, baseDir), refName); } else if (prop.items && typeof prop.items === 'object' && (prop.items.type === 'object' || prop.items.properties || prop.items.allOf || prop.items.oneOf || prop.items.anyOf)) { collectAllReferencedSchemas(prop.items, name + '_' + propName + '_Item'); } } else if (prop && typeof prop === 'object' && (prop.type === 'object' || prop.properties || prop.allOf || prop.oneOf || prop.anyOf)) { collectAllReferencedSchemas(prop, name + '_' + propName); } } } // Traverse array items at root if (resolved && resolved.type === 'array' && resolved.items) { if (resolved.items && typeof resolved.items === 'object' && resolved.items.$ref) { const refName = resolved.items.$ref.split('/').pop(); if (refName) collectAllReferencedSchemas(resolveRef(resolved.items.$ref, spec, baseDir), refName); } else if (resolved.items && typeof resolved.items === 'object' && (resolved.items.type === 'object' || resolved.items.properties || resolved.items.allOf || resolved.items.oneOf || resolved.items.anyOf)) { collectAllReferencedSchemas(resolved.items, name + '_Item'); } } // Traverse allOf/oneOf/anyOf for (const combiner of ['allOf', 'oneOf', 'anyOf']) { if (resolved && Array.isArray(resolved[combiner])) { for (const subSchema of resolved[combiner]) { if (subSchema && typeof subSchema === 'object' && subSchema.$ref) { const refName = subSchema.$ref.split('/').pop(); if (refName) collectAllReferencedSchemas(resolveRef(subSchema.$ref, spec, baseDir), refName); } else if (subSchema && typeof subSchema === 'object') { collectAllReferencedSchemas(subSchema, name + '_' + combiner); } } } } } // Extract schemas from components for (const name in schemas) { collectAllReferencedSchemas(schemas[name], name); const schema = schemas[name]; if (schema && (schema.oneOf || schema.anyOf)) { // Build union struct with actual variant types const unionFields = parseSchema(`${name}_Union`, schema, spec, baseDir); structs[`${name}_Union`] = unionFields.length > 0 ? unionFields : [{ name: 'value', type: 'ANY', REQUIRED: false }]; } } // Register shared error-response schemas from components.responses under the // same name extractErrors uses for them, so `STRUCT(ErrorNNN)` references // resolve to an actual definition. const componentResponses = ((_b = spec.components) === null || _b === void 0 ? void 0 : _b.responses) || {}; for (const [key, rawResp] of Object.entries(componentResponses)) { if (!rawResp || !rawResp.content) continue; const jsonContent = rawResp.content[constants_1.CONTENT_TYPE_JSON]; const schema = jsonContent === null || jsonContent === void 0 ? void 0 : jsonContent.schema; if (!schema) continue; const structName = /^[0-9]+$/.test(key) ? `Error${key}` : `Response_${key}`; if (schema.$ref) { const refName = schema.$ref.split('/').pop(); if (refName) collectAllReferencedSchemas(resolveRef(schema.$ref, spec, baseDir), refName); } else if (typeof schema === 'object') { collectAllReferencedSchemas(schema, structName); } } // Extract inline schemas from operations if (spec.paths && typeof spec.paths === 'object') { 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 if ((_c = op.requestBody) === null || _c === void 0 ? void 0 : _c.content) { for (const [_contentType, content] of Object.entries(op.requestBody.content)) { if (content && content.schema) { if (content.schema && content.schema.$ref) { const refName = content.schema.$ref.split('/').pop(); if (refName) collectAllReferencedSchemas(resolveRef(content.schema.$ref, spec, baseDir), refName); } else if (content.schema && typeof content.schema === 'object') { const requestStructName = generateStructName(operationId, method, pathStr, 'Request'); collectAllReferencedSchemas(content.schema, requestStructName); } } } } // Extract response schemas if (op.responses) { for (const [code, rawResp] of Object.entries(op.responses)) { // Resolve $ref on the response object itself (e.g. $ref: '#/components/responses/...') const response = (rawResp === null || rawResp === void 0 ? void 0 : rawResp.$ref) ? resolveRef(rawResp.$ref, spec, baseDir) : rawResp; if (response && response.content) { for (const [_contentType, content] of Object.entries(response.content)) { if (content && content.schema) { const schema = content.schema; if (schema.$ref) { const refName = schema.$ref.split('/').pop(); if (refName) collectAllReferencedSchemas(resolveRef(schema.$ref, spec, baseDir), refName); } else if (schema.type === 'array' && schema.items) { // For array responses, extract the item type if (schema.items.$ref) { const refName = schema.items.$ref.split('/').pop(); if (refName) collectAllReferencedSchemas(resolveRef(schema.items.$ref, spec, baseDir), refName); } else if (schema.items.type === 'object') { // Inline object in array - create struct for the item const responseStructName = generateStructName(operationId, method, pathStr, `Response${code}Item`); collectAllReferencedSchemas(schema.items, responseStructName); } } else if (schema.type === 'object' && typeof schema === 'object') { // Inline object schema - create struct. Error codes use // the same naming extractErrors picks so the STRUCT(...) // reference resolves to this definition. const statusCode = parseInt(code); const responseStructName = statusCode >= 400 ? getErrorStructName(rawResp, op, code) : generateStructName(operationId, method, pathStr, `Response${code}`); collectAllReferencedSchemas(schema, responseStructName); } } } } } } } } } return structs; } function getContentTypeAndBodyType(op) { const requestBody = op.requestBody; if (!(requestBody === null || requestBody === void 0 ? void 0 : requestBody.content)) { return { contentType: constants_1.CONTENT_TYPE_JSON, bodyType: constants_1.BODYTYPE_RAW }; } const contentTypes = Object.keys(requestBody.content); const contentType = contentTypes[0] || constants_1.CONTENT_TYPE_JSON; let bodyType = constants_1.BODYTYPE_RAW; if (contentType === constants_1.CONTENT_TYPE_FORM_DATA) { bodyType = 'form-data'; } else if (contentType === constants_1.CONTENT_TYPE_URLENCODED) { bodyType = 'x-www-form-urlencoded'; } return { contentType, bodyType }; } function getAcceptContentType(op) { // Get the first content type from the first success response (2xx) for (const [code, response] of Object.entries(op.responses || {})) { const statusCode = parseInt(code); if (statusCode >= 200 && statusCode < 300 && response.content) { const contentTypes = Object.keys(response.content); if (contentTypes.length > 0) { return contentTypes[0]; } } } // Default to JSON if no response content type found return constants_1.CONTENT_TYPE_JSON; } function getHeadersForOperation(op, spec, method, baseDir) { var _a, _b, _c; const { contentType } = getContentTypeAndBodyType(op); // Use a Map to prevent duplicate headers const headerMap = new Map(); // Add Content-Type header for POST/PUT/PATCH requests const httpMethod = (method === null || method === void 0 ? void 0 : method.toLowerCase()) || ((_a = op.method) === null || _a === void 0 ? void 0 : _a.toLowerCase()) || ''; if (constants_1.HTTP_METHODS_WITH_BODY.includes(httpMethod)) { headerMap.set(constants_1.HEADER_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 = (_c = (_b = spec.components) === null || _b === void 0 ? void 0 : _b.securitySchemes) === null || _c === void 0 ? void 0 : _c[schemeName]; if (scheme) { if (scheme.type === 'http') { if (scheme.scheme === 'bearer') { headerMap.set(constants_1.HEADER_AUTHORIZATION, constants_1.AUTH_BEARER_TOKEN); } else if (scheme.scheme === 'basic') { headerMap.set(constants_1.HEADER_AUTHORIZATION, constants_1.AUTH_BASIC_AUTH); } else if (scheme.scheme === 'digest') { headerMap.set(constants_1.HEADER_AUTHORIZATION, constants_1.AUTH_DIGEST_AUTH); } else { headerMap.set(constants_1.HEADER_AUTHORIZATION, `<${scheme.scheme}_auth>`); } } else if (scheme.type === 'apiKey') { if (scheme.in === 'header') { headerMap.set(scheme.name, scheme.name.toLowerCase()); } } else if (scheme.type === 'oauth2') { headerMap.set(constants_1.HEADER_AUTHORIZATION, constants_1.AUTH_BEARER_TOKEN); } else if (scheme.type === 'openIdConnect') { headerMap.set(constants_1.HEADER_AUTHORIZATION, constants_1.AUTH_ID_TOKEN); } } } } // Check if Authorization is used as a parameter but not defined in securitySchemes if (op.parameters) { for (let param of op.parameters) { // Resolve $ref if present if (param && typeof param === 'object' && param.$ref) { param = resolveRef(param.$ref, spec, baseDir || ''); } if (param && typeof param === 'object' && param.in === 'header' && param.name === constants_1.HEADER_AUTHORIZATION && !headerMap.has(constants_1.HEADER_AUTHORIZATION)) { headerMap.set(constants_1.HEADER_AUTHORIZATION, constants_1.AUTH_BEARER_TOKEN); } } } // Convert Map to object const headers = {}; for (const [key, value] of headerMap.entries()) { headers[key] = value; } return headers; } function extractParameters(op, spec, baseDir) { const inputParams = []; // v2.0.2: All parameters (path, query, header) must be in INPUTS with LOCATION // Path parameters are also in ENDPOINT (e.g., /tasks/{taskId}) // Header parameters are also in HTTP.HEADERS // Body parameters are handled separately in extractRequestBody for (let param of op.parameters || []) { // Resolve $ref if present if (param.$ref) { param = resolveRef(param.$ref, spec, baseDir); } const paramIn = param.in || 'query'; // Skip body parameters - they're handled in extractRequestBody if (paramIn === 'body' || paramIn === 'formData') { continue; } const paramName = param.name; const paramSchema = param.schema || {}; let type = 'STRING'; if (paramSchema.type) { type = getTypeFromSchema(paramSchema, spec, baseDir); } else if (paramSchema.$ref) { type = getTypeFromSchema(paramSchema, spec, baseDir); } const isRequired = param.required === true; const hasDefault = paramSchema.default !== undefined; // v2.0.2: All INPUTS must have LOCATION field // Build input parameter with LOCATION if (isRequired && !hasDefault) { // Simple form: - paramName: TYPE (but we need LOCATION, so use extended form) const inputParam = {}; inputParam[paramName] = { TYPE: type, LOCATION: paramIn, }; inputParams.push(inputParam); } else { // Extended form: - paramName: { TYPE: ..., REQUIRED: ..., DEFAULT: ..., LOCATION: ... } const inputParam = {}; inputParam[paramName] = { TYPE: type, REQUIRED: isRequired, LOCATION: paramIn, }; if (hasDefault) { inputParam[paramName].DEFAULT = paramSchema.default; } inputParams.push(inputParam); } } return inputParams; } function extractRequestBody(op, operationId, method, path, spec, baseDir) { var _a, _b, _c; const inputParams = []; const requestBody = op.requestBody; if (!(requestBody === null || requestBody === void 0 ? void 0 : requestBody.content)) { return inputParams; } const contentTypes = Object.keys(requestBody.content); const contentType = contentTypes[0]; if (contentType === constants_1.CONTENT_TYPE_JSON && ((_a = requestBody.content[contentType]) === null || _a === void 0 ? void 0 : _a.schema)) { const bodySchema = requestBody.content[contentType].schema; let type; if (bodySchema && bodySchema.$ref) { type = getTypeFromSchema(bodySchema, spec, baseDir); } else if (bodySchema) { const requestStructName = generateStructName(operationId, method, path, 'Request'); type = `STRUCT(${requestStructName})`; } else { type = 'ANY'; } const isRequired = requestBody.required === true; // v2.0.2: All INPUTS must have LOCATION field if (isRequired) { // Simple form with LOCATION const inputParam = {}; inputParam.body = { TYPE: type, LOCATION: 'body', }; inputParams.push(inputParam); } else { // Extended form with LOCATION const inputParam = {}; inputParam.body = { TYPE: type, REQUIRED: false, LOCATION: 'body', }; inputParams.push(inputParam); } } else if (contentType === 'multipart/form-data' && ((_b = requestBody.content[contentType]) === null || _b === void 0 ? void 0 : _b.schema)) { const bodySchema = requestBody.content[contentType].schema; if (bodySchema && bodySchema.properties) { for (const [key, prop] of Object.entries(bodySchema.properties)) { const type = prop && prop.format === 'binary' ? 'STRING' : getTypeFromSchema(prop, spec, baseDir); const required = (bodySchema.required || []).includes(key); const hasDefault = prop && prop.default !== undefined; const inputParam = {}; // v2.0.2: All INPUTS must have LOCATION field if (required && !hasDefault) { // Simple form with LOCATION inputParam[key] = { TYPE: type, LOCATION: 'body', }; } else { // Extended form with LOCATION inputParam[key] = { TYPE: type, REQUIRED: required, LOCATION: 'body', }; if (hasDefault) { inputParam[key].DEFAULT = prop.default; } } inputParams.push(inputParam); } } } else if (contentType === 'application/x-www-form-urlencoded' && ((_c = requestBody.content[contentType]) === null || _c === void 0 ? void 0 : _c.schema)) { const bodySchema = requestBody.content[contentType].schema; if (bodySchema && bodySchema.properties) { for (const [key, prop] of Object.entries(bodySchema.properties)) { const type = getTypeFromSchema(prop, spec, baseDir); const required = (bodySchema.required || []).includes(key); const hasDefault = prop && prop.default !== undefined; const inputParam = {}; // v2.0.2: All INPUTS must have LOCATION field if (required && !hasDefault) { // Simple form with LOCATION inputParam[key] = { TYPE: type, LOCATION: 'body', }; } else { // Extended form with LOCATION inputParam[key] = { TYPE: type, REQUIRED: required, LOCATION: 'body', }; if (hasDefault) { inputParam[key].DEFAULT = prop.default; } } inputParams.push(inputParam); } } } return inputParams; } function extractResponses(op, operationId, method, path, spec, baseDir) { const returns = []; // Only include success responses (2xx) in RETURNS section // Error responses go in ERRORS section for (const [code, rawResponse] of Object.entries(op.responses || {})) { const statusCode = parseInt(code); // Only process 2xx success responses if (statusCode < 200 || statusCode >= 300) { continue; } // Resolve $ref on the response object itself (e.g. $ref: '#/components/responses/...') const response = rawResponse.$ref ? resolveRef(rawResponse.$ref, spec, baseDir) : rawResponse; const content = response.content; let returnType = null; // 204 No Content - no response body if (code === '204') { // Skip void responses - they should not have RETURNS section continue; } if (content) { const jsonContent = content[constants_1.CONTENT_TYPE_JSON]; if (jsonContent === null || jsonContent === void 0 ? void 0 : jsonContent.schema) { const schema = jsonContent.schema; // Use getTypeFromSchema to handle arrays, $refs, and inline schemas correctly returnType = getTypeFromSchema(schema, spec, baseDir); // If it's an inline object schema (not array, not $ref), we need to create a struct if (returnType === 'OBJECT' && !schema.$ref && schema.type === 'object') { const responseStructName = generateStructName(operationId, method, path, `Response${code}`); returnType = `STRUCT(${responseStructName})`; } } else { // No schema but has content - might be empty body returnType = 'ANY'; } } else { // No content - skip void success responses continue; } // Only add to RETURNS if there's actually a return type if (returnType) { // Generate descriptive RETURNVAR name based on response code and operation const operationName = operationId || method.toLowerCase() + path.replace(/[\/{}]/g, '_'); const returnVarName = (0, response_utils_1.generateReturnVarName)(operationName, code); // v2.0.2: STATUS code is required in RETURNS const returnItem = { RETURNTYPE: returnType, RETURNVAR: returnVarName, STATUS: statusCode, }; // Check for pagination hints in response schema if (content) { const jsonContent = content[constants_1.CONTENT_TYPE_JSON]; if (jsonContent === null || jsonContent === void 0 ? void 0 : jsonContent.schema) { const schema = jsonContent.schema; const resolvedSchema = schema.$ref ? resolveRef(schema.$ref, spec, baseDir) : schema; if (resolvedSchema && resolvedSchema.properties) { // Look for common pagination fields if (resolvedSchema.properties.next_cursor || resolvedSchema.properties.cursor) { returnItem.PAGINATION = { TYPE: 'cursor', CURSOR_FIELD: resolvedSchema.properties.next_cursor ? 'next_cursor' : 'cursor', }; } else if (resolvedSchema.properties.offset !== undefined || resolvedSchema.properties.skip !== undefined) { returnItem.PAGINATION = { TYPE: 'offset', OFFSET_FIELD: resolvedSchema.properties.offset !== undefined ? 'offset' : 'skip', }; } else if (resolvedSchema.properties.page !== undefined || resolvedSchema.properties.pageNumber !== undefined) { returnItem.PAGINATION = { TYPE: 'page', PAGE_SIZE_FIELD: (resolvedSchema.properties.pageSize !== undefined && resolvedSchema.properties.pageSize !== null) ? String(resolvedSchema.properties.pageSize) : 'limit', }; } } } } returns.push(returnItem); } } return returns; } function extractErrors(op, spec, baseDir) { const errors = []; // Extract error responses (4xx, 5xx) for (const [code, rawResponse] of Object.entries(op.responses || {})) { const statusCode = parseInt(code); if (isNaN(statusCode) && code !== 'default') continue; if (statusCode >= 400 || code === 'default') { // Resolve $ref on the response object itself const response = rawResponse.$ref ? resolveRef(rawResponse.$ref, spec, baseDir) : rawResponse; const content = response.content; let errorType = constants_1.TYPE_ANY; let when = `HTTP ${code}`; if (content) { const jsonContent = content[constants_1.CONTENT_TYPE_JSON]; if (jsonContent === null || jsonContent === void 0 ? void 0 : jsonContent.schema) { const schema = jsonContent.schema; if (schema.$ref) { errorType = getTypeFromSchema(schema, spec, baseDir); } else if (schema.type && schema.type !== 'object') { // Primitive / array error schema — emit the primitive type // directly instead of wrapping in a dangling STRUCT(...). errorType = getTypeFromSchema(schema, spec, baseDir); } else { // Inline object error schema. Name it so extractStructs can // register the matching definition. Shared components.responses // entries get a stable name (Error{code} or Response_{key}); // truly inline per-operation schemas get an operation-specific // name so two different 400 bodies don't collide on `Error400`. const errorStructName = getErrorStructName(rawResponse, op, code); errorType = `STRUCT(${errorStructName})`; } } } // Generate descriptive WHEN clause with HTTP status code when = (0, response_utils_1.generateErrorWhen)(response, code); // v2.0.2: STATUS code is required in ERRORS const errorItem = { TYPE: errorType, STATUS: statusCode || (code === 'default' ? 500 : parseInt(code)), WHEN: when, }; errors.push(errorItem); } } return errors; } function generateMethodAlias(operationId, method, path) { if (operationId) { // Convert operationId to kebab-case if needed return operationId.replace(/_/g, '-').toLowerCase(); } // Generate from path and method const pathParts = path.replace(/[\/{}]/g, '-').replace(/^-|-$/g, ''); return `${method.toLowerCase()}-${pathParts}`; } function extractMethods(spec, baseDir) { const methods = {}; // Valid HTTP methods const validMethods = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options', 'trace']; // Check if paths exists and is an object if (!spec.paths || typeof spec.paths !== 'object') { return methods; } for (const [pathStr, pathMethods] of Object.entries(spec.paths)) { const pathLevelParams = pathMethods.parameters || []; for (const [method, op] of Object.entries(pathMethods)) { // 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 = generateMethodAlias(operationId, method, pathStr); const summary = generateSummary(op, method, pathStr); const endpoint = pathStr; // Merge path-level and operation-level parameters (OpenAPI v3) const allParams = [...pathLevelParams, ...(op.parameters || [])]; const opWithMergedParams = Object.assign(Object.assign({}, op), { parameters: allParams }); const { contentType, bodyType } = getContentTypeAndBodyType(opWithMergedParams); const headers = getHeadersForOperation(opWithMergedParams, spec, method, baseDir); const pathQueryHeaderParams = extractParameters(opWithMergedParams, spec, baseDir); const bodyParams = extractRequestBody(opWithMergedParams, operationId, method, pathStr, spec, baseDir); const inputParams = [...pathQueryHeaderParams, ...bodyParams]; const returns = extractResponses(opWithMergedParams, operationId, method, pathStr, spec, baseDir); const errors = extractErrors(opWithMergedParams, spec, baseDir); // Get accept content type from responses const acceptContentType = getAcceptContentType(opWithMergedParams); // Build method in v2.0.2 format const methodDef = { SUMMARY: summary, }; // Add DESC if description exists if (op.description) { methodDef.DESC = op.description; } // HTTP section (mandatory for API methods) methodDef.HTTP = { METHOD: method.toUpperCase(), ENDPOINT: endpoint, HEADERS: headers, CONTENT_TYPE: contentType, ACCEPT: acceptContentType, }; // v2.0.2: BODY.TYPE should be STRUCT(...) format if (bodyParams.length > 0 && bodyParams[0].body) { const bodyTypeValue = bodyParams[0].body.TYPE || bodyParams[0].body; methodDef.HTTP.BODY = { TYPE: bodyTypeValue, }; } if (bodyType !== constants_1.BODYTYPE_RAW) { methodDef.HTTP.BODYTYPE = bodyType; } // EXECUTION section (mandatory) - v2.0.2 requires KIND methodDef.EXECUTION = { KIND: 'http', MODE: constants_1.EXECUTION_MODE_SYNC, // REST APIs are synchronous request/response }; // INPUTS section (optional) if (inputParams.length > 0) { methodDef.INPUTS = inputParams; } // RETURNS section (optional - omit for void) if (returns.length > 0) { methodDef.RETURNS = returns; } // ERRORS section (optional) if (errors.length > 0) { methodDef.ERRORS = errors; } methods[alias] = methodDef; } } return methods; } function extractSecurityDefaults(spec) { var _a, _b, _c; const defs = {}; const securitySchemes = ((_a = spec.components) === null || _a === void 0 ? void 0 : _a.securitySchemes) || {}; for (const [_name, scheme] of Object.entries(securitySchemes)) { if (scheme.type === 'http') { if (scheme.scheme === 'bearer') { defs.bearer_token = constants_1.AUTH_TEMPLATE_BEARER; } else if (scheme.scheme === 'basic') { defs.basic_auth = constants_1.AUTH_TEMPLATE_BASIC; } else if (scheme.scheme === 'digest') { defs.digest_auth = constants_1.AUTH_TEMPLATE_DIGEST; } else { defs[`${scheme.scheme}_auth`] = `<${scheme.scheme.toUpperCase()}_CREDENTIALS>`; } } else if (scheme.type === 'apiKey') { if (scheme.in === 'header') { defs[scheme.name.toLowerCase()] = `<${scheme.name.toUpperCase()}>`; } else if (scheme.in === 'query') { defs[`query_${scheme.name.toLowerCase()}`] = `<${scheme.name.toUpperCase()}>`; } else if (scheme.in === 'cookie') { defs[`cookie_${scheme.name.toLowerCase()}`] = `<${scheme.name.toUpperCase()}>`; } } else if (scheme.type === 'oauth2') { defs.bearer_token = constants_1.AUTH_TEMPLATE_BEARER_ACCESS; } else if (scheme.type === 'openIdConnect') { defs.id_token = constants_1.AUTH_TEMPLATE_ID_TOKEN; } } // Add base URL const baseUrl = ((_c = (_b = spec.servers) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.url) || constants_1.DEFAULT_BASE_URL; defs.w_base_url = baseUrl.replace(/\/$/, ''); // Remove trailing slash return defs; } function updateReturnVarsUsingCanonicalId(methods) { for (const methodData of Object.values(methods)) { const canonicalId = methodData.CANONICAL_ID; if (!canonicalId || !Array.isArray(methodData.RETURNS)) continue; const baseVar = canonicalId.replace(/\./g, '_'); for (const ret of methodData.RETURNS) { const status = ret.STATUS; if (status === 200 || status === '200') { ret.RETURNVAR = baseVar; } else if (status !== undefined && status !== null) { ret.RETURNVAR = `${baseVar}_${status}`; } else { ret.RETURNVAR = baseVar; } } } } function renameMethodsToCanonicalId(methods) { const renamed = {}; for (const [oldId, methodData] of Object.entries(methods)) { const canonicalId = methodData.CANONICAL_ID; const key = canonicalId || oldId; renamed[key] = methodData; } return renamed; } function generateWrekenfile(spec, baseDir) { var _a, _b, _c; try { // Validate inputs (0, error_utils_1.validateOpenApiV3Spec)(spec); (0, error_utils_1.validateBaseDir)(baseDir); const defaults = extractSecurityDefaults(spec); const methods = extractMethods(spec, baseDir); const structs = extractStructs(spec, baseDir); // Resolve canonical IDs for all methods const canonicalInputs = Object.entries(methods).map(([methodId, methodData]) => { var _a, _b; return ({ methodId, httpMethod: (_a = methodData.HTTP) === null || _a === void 0 ? void 0 : _a.METHOD, endpoint: (_b = methodData.HTTP) === null || _b === void 0 ? void 0 : _b.ENDPOINT, existingCanonicalId: methodData.CANONICAL_ID, }); }); const libraryName = ((_a = spec === null || spec === void 0 ? void 0 : spec.info) === null || _a === void 0 ? void 0 : _a.title) || 'unknown'; const canonicalIdMap = (0, canonical_id_1.resolveCanonicalIds)(canonicalInputs, libraryName); // Add CANONICAL_ID to each method for (const [methodId, methodData] of Object.entries(methods)) { const canonicalId = canonicalIdMap.get(methodId); if (canonicalId) { methodData.CANONICAL_ID = canonicalId; } } // Update RETURNVARs to be derived from CANONICAL_I