// SPDX-License-Identifier: Apache-2.0
// Copyright 2024-2026 Vorion LLC
import fp from 'fastify-plugin';
import { createLogger } from '../../common/logger.js';
const versionLogger = createLogger({ component: 'api-versioning' });
/**
 * Supported API versions
 */
export const API_VERSIONS = ['v1'];
/**
 * Current (latest) API version
 */
export const CURRENT_VERSION = 'v1';
/**
 * Deprecated API versions with sunset dates
 */
export const DEPRECATED_VERSIONS = {
// Example: 'v0': { sunsetDate: '2025-06-01', message: 'Please migrate to v1' }
};
/**
 * Extract API version from request URL path
 *
 * Looks for /api/v{N}/ pattern in the URL
 */
export function extractVersionFromUrl(url) {
    const match = url.match(/\/api\/(v\d+)\//);
    if (match && API_VERSIONS.includes(match[1])) {
        return match[1];
    }
    return null;
}
/**
 * Extract API version from Accept header
 *
 * Supports formats:
 * - application/vnd.vorion.v1+json
 * - application/json; version=v1
 */
export function extractVersionFromAcceptHeader(acceptHeader) {
    if (!acceptHeader)
        return null;
    // Check for vendor-specific media type: application/vnd.vorion.v1+json
    const vendorMatch = acceptHeader.match(/application\/vnd\.vorion\.(v\d+)\+json/);
    if (vendorMatch && API_VERSIONS.includes(vendorMatch[1])) {
        return vendorMatch[1];
    }
    // Check for version parameter: application/json; version=v1
    const paramMatch = acceptHeader.match(/version=(v\d+)/);
    if (paramMatch && API_VERSIONS.includes(paramMatch[1])) {
        return paramMatch[1];
    }
    return null;
}
/**
 * Extract version from request using URL-first, then Accept header fallback
 */
export function extractVersion(request, defaultVersion = CURRENT_VERSION) {
    // 1. Try URL-based version (most explicit)
    const urlVersion = extractVersionFromUrl(request.url);
    if (urlVersion) {
        return createVersionInfo(urlVersion, 'url');
    }
    // 2. Try Accept header (fallback for version negotiation)
    const headerVersion = extractVersionFromAcceptHeader(request.headers.accept);
    if (headerVersion) {
        return createVersionInfo(headerVersion, 'header');
    }
    // 3. Default to latest version (with deprecation warning recommended)
    return createVersionInfo(defaultVersion, 'default');
}
/**
 * Create version info object with deprecation details
 */
function createVersionInfo(version, source) {
    const deprecation = DEPRECATED_VERSIONS[version];
    return {
        version,
        source,
        isDeprecated: !!deprecation,
        deprecationMessage: deprecation?.message,
        sunsetDate: deprecation?.sunsetDate,
    };
}
/**
 * Check if a version is supported
 */
export function isVersionSupported(version) {
    return API_VERSIONS.includes(version);
}
/**
 * Get deprecation warning message for default version usage
 */
export function getDefaultVersionWarning() {
    return `API version not specified. Defaulting to ${CURRENT_VERSION}. ` +
        'For stability, explicitly specify the API version in the URL path (e.g., /api/v1/...) ' +
        'or via Accept header (application/vnd.vorion.v1+json).';
}
/**
 * API Versioning Plugin
 *
 * Adds version extraction and deprecation headers to all requests.
 * Registers version info on request object for route handlers.
 */
const versioningPluginAsync = async (fastify, opts) => {
    const { defaultVersion = CURRENT_VERSION, includeDeprecationHeaders = true, basePath = '/api', } = opts;
    // Decorate request with apiVersion property
    fastify.decorateRequest('apiVersion', null);
    // Add version extraction hook
    fastify.addHook('onRequest', async (request, reply) => {
        // Skip non-API routes (health checks, metrics, etc.)
        if (!request.url.startsWith(basePath)) {
            return;
        }
        const versionInfo = extractVersion(request, defaultVersion);
        request.apiVersion = versionInfo;
        // Add version to response headers
        reply.header('X-API-Version', versionInfo.version);
        // Add deprecation headers if applicable
        if (includeDeprecationHeaders) {
            if (versionInfo.isDeprecated) {
                reply.header('Deprecation', 'true');
                if (versionInfo.sunsetDate) {
                    reply.header('Sunset', versionInfo.sunsetDate);
                }
                reply.header('X-Deprecation-Notice', versionInfo.deprecationMessage ?? 'This API version is deprecated');
                versionLogger.warn({
                    version: versionInfo.version,
                    source: versionInfo.source,
                    url: request.url,
                    sunsetDate: versionInfo.sunsetDate,
                }, 'Deprecated API version used');
            }
            // Warn when no version specified (using default)
            if (versionInfo.source === 'default') {
                reply.header('Warning', `299 - "${getDefaultVersionWarning()}"`);
                versionLogger.debug({
                    defaultVersion: versionInfo.version,
                    url: request.url,
                }, 'Request using default API version');
            }
        }
    });
};
/**
 * API Versioning Plugin (wrapped with fastify-plugin for proper encapsulation)
 */
export const versioningPlugin = fp(versioningPluginAsync, {
    name: 'api-versioning',
    fastify: '>=4.x',
});
/**
 * Create a version-specific error response
 */
export function createVersionedError(code, message, versionInfo) {
    const error = {
        error: {
            code,
            message,
        },
    };
    if (versionInfo) {
        error.error.version = versionInfo.version;
        if (versionInfo.isDeprecated) {
            error.error.deprecationNotice = versionInfo.deprecationMessage;
        }
    }
    return error;
}
/**
 * Create unsupported version error response
 */
export function createUnsupportedVersionError(requestedVersion) {
    return {
        error: {
            code: 'UNSUPPORTED_API_VERSION',
            message: `API version '${requestedVersion}' is not supported. Supported versions: ${API_VERSIONS.join(', ')}`,
        },
    };
}
/**
 * Route prefix generator for versioned routes
 */
export function getVersionedPrefix(version) {
    return `/api/${version}`;
}
/**
 * Check if request is for versioned API
 */
export function isVersionedRequest(request) {
    return !!request.apiVersion;
}
export default versioningPlugin;
//# sourceMappingURL=index.js.map