UNPKG

wrekenfile-converter

Version:

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

1,083 lines 51.8 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-v2-swagger-to-wrekenfile-v2.ts
// Converts OpenAPI v2 (Swagger) 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 a Swagger v2
 * `additionalProperties` value.
 */
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 to avoid dangling
        // STRUCT(Foo) references for schemas that will never produce 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 && 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)) {
        // 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) || constants_1.TYPE_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 (schema && typeof schema === 'object' && ((_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 && typeof schema === 'object' && schema.type && schema.type !== 'object' && schema.type !== 'array') {
        return [];
    }
    if (schema && typeof schema === 'object' && 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 a Swagger v2 error response whose schema is inline
 * (no `$ref`). When the response object itself is a `$ref` to `#/responses/X`,
 * the returned name is stable across all call sites so one definition is
 * 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}`;
        }
    }
    const opId = op.operationId || 'op';
    return `${opId}_Error${code}`;
}
function extractStructs(spec, baseDir) {
    const structs = {};
    const definitions = spec.definitions || {}; // OpenAPI v2 uses 'definitions' instead of 'components.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 definitions (OpenAPI v2)
    for (const name in definitions) {
        collectAllReferencedSchemas(definitions[name], name);
        const schema = definitions[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 response schemas from spec.responses under the same name
    // extractErrors uses for them, so `STRUCT(ErrorNNN)` references resolve.
    const topLevelResponses = spec.responses || {};
    for (const [key, rawResp] of Object.entries(topLevelResponses)) {
        if (!rawResp || !rawResp.schema)
            continue;
        const structName = /^[0-9]+$/.test(key) ? `Error${key}` : `Response_${key}`;
        if (rawResp.schema.$ref) {
            const refName = rawResp.schema.$ref.split('/').pop();
            if (refName)
                collectAllReferencedSchemas(resolveRef(rawResp.schema.$ref, spec, baseDir), refName);
        }
        else if (typeof rawResp.schema === 'object') {
            collectAllReferencedSchemas(rawResp.schema, structName);
        }
    }
    // Extract inline schemas from operations
    if (spec.paths && typeof spec.paths === 'object') {
        for (const [pathStr, pathMethods] of Object.entries(spec.paths)) {
            for (const [method, op] of Object.entries(pathMethods)) {
                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) {
                            if (param.schema && param.schema.$ref) {
                                const refName = param.schema.$ref.split('/').pop();
                                if (refName)
                                    collectAllReferencedSchemas(resolveRef(param.schema.$ref, spec, baseDir), refName);
                            }
                            else if (param.schema && typeof param.schema === 'object') {
                                const requestStructName = generateStructName(operationId, method, pathStr, 'Request');
                                collectAllReferencedSchemas(param.schema, requestStructName);
                            }
                        }
                    }
                }
                // Extract response schemas (OpenAPI v2 has schema directly in response)
                if (op.responses) {
                    for (const [code, response] of Object.entries(op.responses)) {
                        // Handle response references
                        let actualResponse = response;
                        if (response && typeof response === 'object' && response.$ref) {
                            actualResponse = resolveRef(response.$ref, spec, baseDir);
                        }
                        if (actualResponse && typeof actualResponse === 'object' && actualResponse.schema) {
                            if (actualResponse.schema && actualResponse.schema.$ref) {
                                const refName = actualResponse.schema.$ref.split('/').pop();
                                if (refName)
                                    collectAllReferencedSchemas(resolveRef(actualResponse.schema.$ref, spec, baseDir), refName);
                            }
                            else if (actualResponse.schema && typeof actualResponse.schema === 'object') {
                                // For error codes, use the same name extractErrors will emit
                                // so the STRUCT(...) reference resolves.
                                const statusCode = parseInt(code);
                                const responseStructName = statusCode >= 400
                                    ? getErrorStructName(response, op, code)
                                    : generateStructName(operationId, method, pathStr, `Response${code}`);
                                collectAllReferencedSchemas(actualResponse.schema, responseStructName);
                            }
                        }
                    }
                }
            }
        }
    }
    return structs;
}
function getContentTypeAndBodyType(op, spec) {
    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: constants_1.CONTENT_TYPE_FORM_DATA, bodyType: 'form-data' };
    }
    // OpenAPI v2 determines content type from consumes array or defaults
    const consumes = op.consumes || spec.consumes || [constants_1.CONTENT_TYPE_JSON];
    const contentType = consumes[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, spec) {
    // 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) {
            // OpenAPI v2 uses produces array
            const produces = op.produces || spec.produces || [constants_1.CONTENT_TYPE_JSON];
            if (produces.length > 0) {
                return produces[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;
    const { contentType } = getContentTypeAndBodyType(op, spec);
    // 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 = (_b = spec.securityDefinitions) === null || _b === void 0 ? void 0 : _b[schemeName]; // OpenAPI v2 uses securityDefinitions
            if (scheme) {
                if (scheme.type === 'basic') {
                    headerMap.set(constants_1.HEADER_AUTHORIZATION, constants_1.AUTH_BASIC_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);
                }
            }
        }
    }
    // Check if Authorization is used as a parameter but not defined in securityDefinitions
    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 = [];
    // Handle query parameters only
    // Path parameters are already in ENDPOINT (e.g., /users/{userId})
    // Header parameters are in HTTP.HEADERS
    // Body and formData parameters are handled in extractRequestBody
    if (op.parameters) {
        for (let param of op.parameters) {
            // Resolve parameter references
            if (param && typeof param === 'object' && param.$ref) {
                param = resolveRef(param.$ref, spec, baseDir);
            }
            // Skip body and formData parameters, they are handled in extractRequestBody
            if (param && typeof param === 'object' && (param.in === 'body' || param.in === 'formData')) {
                continue;
            }
            const paramIn = param && typeof param === 'object' ? param.in || 'query' : 'query';
            // v2.0.2: All parameters (path, query, header) must be in INPUTS with LOCATION
            // Don't skip any - include all with LOCATION
            const paramName = param && typeof param === 'object' ? param.name : '';
            const paramSchema = param && typeof param === 'object' ? param.schema || {} : {};
            // Query parameters default to false if not specified
            const isRequired = param && typeof param === 'object' ? param.required === true : false;
            const hasDefault = paramSchema && typeof paramSchema === 'object' ? paramSchema.default !== undefined : false;
            let type = 'STRING';
            if (param && typeof param === 'object' && param.type) {
                type = getTypeFromSchema({ type: param.type, format: param.format }, spec, baseDir);
            }
            else if (paramSchema && typeof paramSchema === 'object' && paramSchema.type) {
                type = getTypeFromSchema(paramSchema, spec, baseDir);
            }
            // v2.0.2: All INPUTS must have LOCATION field
            // Build input parameter with LOCATION
            if (isRequired && !hasDefault) {
                // Simple form with LOCATION
                const inputParam = {};
                inputParam[paramName] = {
                    TYPE: type,
                    LOCATION: paramIn,
                };
                inputParams.push(inputParam);
            }
            else {
                // Extended form with 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;
    const inputParams = [];
    // OpenAPI v2 uses parameters with in: body
    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 = getTypeFromSchema(bodyParam.schema, spec, baseDir);
        }
        else if (bodyParam && typeof bodyParam === 'object' && bodyParam.schema) {
            // Inline schema - use generated struct name
            const requestStructName = generateStructName(operationId, method, path, 'Request');
            type = `STRUCT(${requestStructName})`;
        }
        else {
            type = 'ANY';
        }
        // In OpenAPI v2, body parameters default to false (optional) if not specified
        const isRequired = bodyParam && typeof bodyParam === 'object' ? bodyParam.required === true : false;
        // 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);
        }
    }
    // Handle formData for multipart/form-data (OpenAPI v2)
    if (op.parameters) {
        for (const param of op.parameters) {
            if (param && typeof param === 'object' && param.in === 'formData') {
                const type = param.type === 'file' ? 'STRING' : getTypeFromSchema({ type: param.type, format: param.format }, spec, baseDir);
                // FormData parameters default to false (optional) if not specified
                const isRequired = param.required === true;
                const hasDefault = param.default !== undefined;
                const inputParam = {};
                // v2.0.2: All INPUTS must have LOCATION field
                if (isRequired && !hasDefault) {
                    // Simple form with LOCATION
                    inputParam[param.name] = {
                        TYPE: type,
                        LOCATION: 'body',
                    };
                }
                else {
                    // Extended form with LOCATION
                    inputParam[param.name] = {
                        TYPE: type,
                        REQUIRED: isRequired,
                        LOCATION: 'body',
                    };
                    if (hasDefault) {
                        inputParam[param.name].DEFAULT = param.default;
                    }
                }
                inputParams.push(inputParam);
            }
        }
    }
    return inputParams;
}
function extractResponses(op, operationId, method, path, spec, baseDir) {
    var _a;
    const returns = [];
    // Only include success responses (2xx) in RETURNS section
    // Error responses go in ERRORS section
    for (const [code, response] of Object.entries(op.responses || {})) {
        const statusCode = parseInt(code);
        // Only process 2xx success responses
        if (isNaN(statusCode) || statusCode < 200 || statusCode >= 300) {
            continue;
        }
        // Handle response references (OpenAPI v2)
        let actualResponse = response;
        if (response && typeof response === 'object' && response.$ref) {
            actualResponse = resolveRef(response.$ref, spec, baseDir);
        }
        let returnType = null;
        // 204 No Content - no response body
        if (code === '204') {
            continue;
        }
        if (actualResponse && typeof actualResponse === 'object' && actualResponse.schema) {
            const schema = actualResponse.schema;
            if (schema.$ref) {
                returnType = getTypeFromSchema(schema, spec, baseDir);
            }
            else if (schema.type === 'array') {
                if ((_a = schema.items) === null || _a === void 0 ? void 0 : _a.$ref) {
                    returnType = getTypeFromSchema(schema, spec, baseDir);
                }
                else {
                    returnType = getTypeFromSchema(schema, spec, baseDir);
                }
            }
            else if (schema.type === 'object') {
                // Inline schema - use generated struct name
                const responseStructName = generateStructName(operationId, method, path, `Response${code}`);
                returnType = `STRUCT(${responseStructName})`;
            }
            else {
                returnType = getTypeFromSchema(schema, spec, baseDir);
            }
        }
        else {
            // No schema - might be a header-only response
            const statusCode = parseInt(code);
            if (statusCode >= 200 && statusCode < 300) {
                continue; // Skip void success responses
            }
            returnType = 'ANY'; // Error responses without schema
        }
        // Only add to RETURNS if there's actually a return type
        if (returnType) {
            // Generate descriptive RETURNVAR name based on response code and operation
            const returnVarName = (0, response_utils_1.generateReturnVarName)(operationId, 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 (actualResponse && typeof actualResponse === 'object' && actualResponse.schema) {
                const schema = actualResponse.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, response] of Object.entries(op.responses || {})) {
        const statusCode = parseInt(code);
        if (isNaN(statusCode) && code !== 'default')
            continue;
        if (statusCode >= 400 || code === 'default') {
            // Handle response references
            let actualResponse = response;
            if (response && typeof response === 'object' && response.$ref) {
                actualResponse = resolveRef(response.$ref, spec, baseDir);
            }
            let errorType = constants_1.TYPE_ANY;
            let when = `HTTP ${code}`;
            if (actualResponse && typeof actualResponse === 'object' && actualResponse.schema) {
                const schema = actualResponse.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 — generate a struct name. Shared
                    // Swagger v2 responses (spec.responses.X) get a stable name so the
                    // corresponding struct registered by extractStructs is the same one
                    // extractErrors references.
                    const errorStructName = getErrorStructName(response, op, code);
                    errorType = `STRUCT(${errorStructName})`;
                }
            }
            // Generate descriptive WHEN clause with HTTP status code
            when = (0, response_utils_1.generateErrorWhen)(actualResponse, 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)) {
        // Handle path-level parameters (OpenAPI v2)
        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
            const allParams = [...pathLevelParams, ...(op.parameters || [])];
            const opWithMergedParams = Object.assign(Object.assign({}, op), { parameters: allParams });
            const { contentType, bodyType } = getContentTypeAndBodyType(opWithMergedParams, spec);
            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, spec);
            // 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;
    const defs = {};
    const securityDefinitions = spec.securityDefinitions || {}; // OpenAPI v2 uses securityDefinitions
    for (const [_name, scheme] of Object.entries(securityDefinitions)) {
        if (scheme && typeof scheme === 'object' && scheme.type === 'basic') {
            defs.basic_auth = constants_1.AUTH_TEMPLATE_BASIC;
        }
        else if (scheme && typeof scheme === 'object' && 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 && typeof scheme === 'object' && scheme.type === 'oauth2') {
            defs.bearer_token = constants_1.AUTH_TEMPLATE_BEARER_ACCESS;
        }
    }
    // Add base URL (OpenAPI v2 constructs from schemes, host, basePath)
    const scheme = ((_a = spec.schemes) === null || _a === void 0 ? void 0 : _a[0]) || 'https';
    const host = spec.host || '';
    const basePath = spec.basePath || '';
    const baseUrl = `${scheme}://${host}${basePath}`;
    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.validateOpenApiV2Spec)(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_ID
        updateReturnVarsUsingCanonicalId(methods);
        const wrekenfile = {
            VERSION: constants_1.WREKENFILE_VERSION,
        };
        // Add DEFAULTS if we have any
        if (Object.keys(defaults).length > 0) {
            wrekenfile.DEFAULTS = defaults;
        }
        // Add METHODS (mandatory) - use CANONICAL_ID as key when available
        const renamedMethods = renameMethodsToCanonicalId(methods);
        wrekenfile.METHODS = renamedMethods;
        // Add STRUCTS if we have any
        const preFilterStructCount = Object.keys(structs).length;
        if (preFilterStructCount > 0) {
            wrekenfile.STRUCTS = structs;
        }
        // Remove unused STRUCTS (keep only those referenced by METHODS)
        (0, struct_utils_1.filterStructsByUsage)(wrekenfile);
        // Generate YAML string using the standard pipeline
        return (0, yaml_utils_1.generateYamlString)(wrekenfile);
    }
    catch (err) {
        // Log error with context
        (0, error_utils_1.logError)(err, {
            converter: 'openapi-v2-to-wrekenfile',
            baseDir,
            specInfo: ((_b = spec === null || spec === void 0 ? void 0 : spec.info) === null || _b === void 0 ? void 0 : _b.title) || 'unknown',
            specVersion: (spec === null || spec === void 0 ? void 0 : spec.swagger) || 'unknown'
        });
        // Re-throw with additional context if it's not already a ConverterError
        if (err.code && (err.code.startsWith('INVALID_') || err.code.startsWith('MISSING_'))) {
            throw err;
        }
        throw (0, error_utils_1.createConverterError)(`Failed to generate Wrekenfile from OpenAPI v2 spec: ${err.message}`, "GENERATION_FAILED", {
            converter: 'openapi-v2-to-wrekenfile',
            baseDir,
            specInfo: ((_c = spec === null || spec === void 0 ? void 0 : spec.info) === null || _c === void 0 ? void 0 : _c.title) || 'unknown',
            specVersion: (spec === null || spec === void 0 ? void 0 : spec.swagger) || 'unknown'
        }, err);
    }
}
/**
 * Generate a Wrekenfile and return both the YAML string and conversion stats.
 */
function generateWrekenfileWithStats(spec, baseDir) {
    var _a, _b, _c;
    try {
        (0, error_utils_1.validateOpenApiV2Spec)(spec);
        (0, error_utils_1.validateBaseDir)(baseDir);
        const defaults = extractSecurityDefaults(spec);
        const methods = extractMethods(spec, baseDir);
        const structs = extractStructs(spec, baseDir);
        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);
        for (const [methodId, methodData] of Object.entries(methods)) {
            const canonicalId = canonicalIdMap.get(methodId);
            if (canonicalId) {
                methodData.CANONICAL_ID = canonicalId;
            }
        }
        updateReturnVarsUsingCanonicalId(methods);
        const wrekenfile = { VERSION: constants_1.WREKENFILE_VERSION };
        if (Object.keys(defaults).length > 0) {
            wrekenfile.DEFAULTS = defaults;
        }
        const renamedMethods = renameMethodsToCanonicalId(methods);
        wrekenfile.METHODS = renamedMethods;
        const preFilterStructCount = Object.keys(structs).length;
        if (preFilterStructCount > 0) {
            wrekenfile.STRUCTS = structs;
        }
        (0, struct_utils_1.filterStructsByUsage)(wrekenfile);
        const stats = (0, conversion_stats_1.computeConversionStats)(wrekenfile, preFilterStructCount);
        const yaml = (0, yaml_utils_1.generateYamlString)(wrekenfile);
        return { yaml, stats };
    }
    catch (err) {
        (0, error_utils_1.logError)(err, {
            converter: 'openapi-v2-to-wrekenfile',
            baseDir,
            specInfo: ((_b = spec === null || spec === void 0 ? void 0 : spec.info) === null || _b === void 0 ? void 0 : _b.title) || 'unknown',
            specVersion: (spec === null || spec === void 0 ? void 0 : spec.swagger) || 'unknown'
        });
        if (err.code && (err.code.startsWith('INVALID_') || err.code.startsWith('MISSING_'))) {
            throw err;
        }
        throw (0, error_utils_1.createConverterError)(`Failed to generate Wrekenfile from OpenAPI v2 spec: ${err.message}`, "GENERATION_FAILED", {
            converter: 'openapi-v2-to-wrekenfile',
            baseDir,
            specInfo: ((_c = spec === null || spec === void 0 ? void 0 : spec.info) === null || _c === void 0 ? void 0 : _c.title) || 'unknown',
            specVersion: (spec === null || spec === void 0 ? void 0 : spec.swagger) || 'unknown'
        }, err);
    }
}
//# sourceMappingURL=openapi-v2-to-wrekenfile.js.map