124 lines
3.5 KiB
TypeScript
124 lines
3.5 KiB
TypeScript
|
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<string, string>,
|
||
|
context: IRouteContext
|
||
|
): Record<string, string> {
|
||
|
const result: Record<string, string> = {};
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
}
|