import type { IRouteContext } from '../models/route-context.js'; /** * Utility class for resolving template variables in strings */ export class TemplateUtils { /** * Resolve template variables in a string using the route context * Supports variables like {domain}, {path}, {clientIp}, etc. * * @param template The template string with {variables} * @param context The route context with values * @returns The resolved string */ public static resolveTemplateVariables(template: string, context: IRouteContext): string { if (!template) { return template; } // Replace variables with values from context return template.replace(/\{([a-zA-Z0-9_\.]+)\}/g, (match, varName) => { // Handle nested properties with dot notation (e.g., {headers.host}) if (varName.includes('.')) { const parts = varName.split('.'); let current: any = context; // Traverse nested object structure for (const part of parts) { if (current === undefined || current === null) { return match; // Return original if path doesn't exist } current = current[part]; } // Return the resolved value if it exists if (current !== undefined && current !== null) { return TemplateUtils.convertToString(current); } return match; } // Direct property access const value = context[varName as keyof IRouteContext]; if (value === undefined) { return match; // Keep the original {variable} if not found } // Convert value to string return TemplateUtils.convertToString(value); }); } /** * Safely convert a value to a string * * @param value Any value to convert to string * @returns String representation or original match for complex objects */ private static convertToString(value: any): string { if (value === null || value === undefined) { return ''; } if (typeof value === 'string') { return value; } if (typeof value === 'number' || typeof value === 'boolean') { return value.toString(); } if (Array.isArray(value)) { return value.join(','); } if (typeof value === 'object') { try { return JSON.stringify(value); } catch (e) { return '[Object]'; } } return String(value); } /** * Resolve template variables in header values * * @param headers Header object with potential template variables * @param context Route context for variable resolution * @returns New header object with resolved values */ public static resolveHeaderTemplates( headers: Record, context: IRouteContext ): Record { const result: Record = {}; for (const [key, value] of Object.entries(headers)) { // Skip special directive headers (starting with !) if (value.startsWith('!')) { result[key] = value; continue; } // Resolve template variables in the header value result[key] = TemplateUtils.resolveTemplateVariables(value, context); } return result; } /** * Check if a string contains template variables * * @param str String to check for template variables * @returns True if string contains template variables */ public static containsTemplateVariables(str: string): boolean { return !!str && /\{([a-zA-Z0-9_\.]+)\}/g.test(str); } }