@bramato/openrouter-mock-generator
Version:
AI-powered mock data generator using OpenRouter API with JSON mode support
357 lines • 13.8 kB
JavaScript
;
/**
* Microservizio per sostituire URLs nei dati JSON
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.UrlReplacer = void 0;
class UrlReplacer {
/**
* Sostituisce URLs Picsum con nuovi URLs nei dati JSON
*/
static async replaceUrls(data, urlMappings, cdnMappings, options = {}) {
const opts = this.normalizeOptions(options);
const result = {
success: false,
replacedCount: 0,
failedCount: 0,
mappings: [],
modifiedData: opts.backupOriginal ? this.deepClone(data) : data,
errors: [],
};
try {
// Crea backup se richiesto
const originalData = opts.backupOriginal ? this.deepClone(data) : null;
// Esegui sostituzioni
await this.performReplacements(result.modifiedData, '', urlMappings, cdnMappings, result, opts, 0);
// Valida URLs se richiesto
if (opts.validateUrls) {
await this.validateReplacementUrls(result);
}
result.success = result.failedCount === 0 || result.replacedCount > 0;
// Log risultati se richiesto
if (opts.logReplacements) {
this.logReplacementResults(result);
}
}
catch (error) {
result.errors.push(`Replacement failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
result.success = false;
}
return result;
}
/**
* Crea mappature URL da risultati di elaborazione immagini
*/
static createUrlMappings(imageResults) {
const urlMappings = new Map();
const cdnMappings = new Map();
const failedUrls = [];
imageResults.forEach(result => {
if (result.success && result.newUrl) {
// Caso di successo: usa nuova URL generata
urlMappings.set(result.originalUrl, result.newUrl);
if (result.cdnUrl) {
cdnMappings.set(result.originalUrl, result.cdnUrl);
}
}
else if (!result.success && result.newUrl) {
// Caso di fallback: mantieni URL originale (già impostata nel newUrl)
urlMappings.set(result.originalUrl, result.newUrl);
}
else {
// Caso di errore completo senza URL di fallback
failedUrls.push(result.originalUrl);
}
});
return { urlMappings, cdnMappings, failedUrls };
}
/**
* Esegue le sostituzioni ricorsivamente
*/
static async performReplacements(obj, currentPath, urlMappings, cdnMappings, result, options, itemIndex) {
if (!obj || typeof obj !== 'object')
return;
if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; i++) {
const newPath = currentPath ? `${currentPath}[${i}]` : `[${i}]`;
await this.performReplacements(obj[i], newPath, urlMappings, cdnMappings, result, options, i);
}
}
else {
for (const [key, value] of Object.entries(obj)) {
const newPath = currentPath ? `${currentPath}.${key}` : key;
if (typeof value === 'string' && this.isPicsumUrl(value)) {
await this.replaceUrl(obj, key, value, newPath, urlMappings, cdnMappings, result, options, itemIndex);
}
else if (typeof value === 'object' && value !== null) {
await this.performReplacements(value, newPath, urlMappings, cdnMappings, result, options, itemIndex);
}
}
}
}
/**
* Sostituisce un singolo URL
*/
static async replaceUrl(obj, key, originalUrl, path, urlMappings, cdnMappings, result, options, itemIndex) {
try {
// Normalizza URL per la ricerca
const normalizedUrl = this.normalizeUrl(originalUrl);
// Cerca mappatura diretta
let newUrl = urlMappings.get(originalUrl) || urlMappings.get(normalizedUrl);
let replacementType = 'direct';
// Se non trovata, cerca mappature variant (stesso seed/dimensioni)
if (!newUrl) {
newUrl = this.findVariantMapping(originalUrl, urlMappings);
replacementType = 'variant';
}
// Se ancora non trovata e preserveOriginalOnFailure è false, usa fallback
if (!newUrl && !options.preserveOriginalOnFailure) {
newUrl = this.generateFallbackUrl(originalUrl);
replacementType = 'fallback';
}
if (newUrl) {
// Usa CDN URL se disponibile e preferito
const finalUrl = options.preferCdnUrls && cdnMappings?.has(originalUrl)
? cdnMappings.get(originalUrl)
: newUrl;
// Sostituisci URL
obj[key] = finalUrl;
// Registra mapping
result.mappings.push({
originalUrl,
newUrl: finalUrl,
cdnUrl: cdnMappings?.get(originalUrl),
path,
fieldName: key,
itemIndex,
replacementType,
});
result.replacedCount++;
}
else {
// Fallimento - mantieni originale se richiesto
if (options.preserveOriginalOnFailure) {
result.errors.push(`No replacement found for URL: ${originalUrl} at ${path}`);
}
result.failedCount++;
}
}
catch (error) {
result.errors.push(`Failed to replace URL ${originalUrl}: ${error instanceof Error ? error.message : 'Unknown error'}`);
result.failedCount++;
}
}
/**
* Cerca mappatura per varianti (stesso contenuto, dimensioni diverse)
*/
static findVariantMapping(originalUrl, urlMappings) {
const originalSeed = this.extractSeed(originalUrl);
const originalDimensions = this.extractDimensions(originalUrl);
if (!originalSeed && !originalDimensions)
return undefined;
// Cerca URLs con stesso seed o dimensioni simili
for (const [mappedOriginal, mappedNew] of urlMappings.entries()) {
const mappedSeed = this.extractSeed(mappedOriginal);
const mappedDimensions = this.extractDimensions(mappedOriginal);
// Stesso seed
if (originalSeed && mappedSeed === originalSeed) {
return mappedNew;
}
// Dimensioni identiche
if (originalDimensions &&
mappedDimensions &&
originalDimensions.width === mappedDimensions.width &&
originalDimensions.height === mappedDimensions.height) {
return mappedNew;
}
}
return undefined;
}
/**
* Genera URL di fallback per casi non mappati
*/
static generateFallbackUrl(originalUrl) {
// Per ora mantieni URL Picsum come fallback
// In futuro potresti voler usare un'immagine placeholder
return originalUrl;
}
/**
* Valida URLs dopo la sostituzione
*/
static async validateReplacementUrls(result) {
const validationPromises = result.mappings.map(async (mapping) => {
try {
const response = await fetch(mapping.newUrl, {
method: 'HEAD',
signal: AbortSignal.timeout(this.URL_VALIDATION_TIMEOUT),
});
if (!response.ok) {
result.errors.push(`URL validation failed for ${mapping.newUrl}: ${response.status}`);
}
}
catch (error) {
result.errors.push(`URL validation error for ${mapping.newUrl}: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
});
await Promise.allSettled(validationPromises);
}
/**
* Verifica se una stringa è un URL Picsum
*/
static isPicsumUrl(url) {
return /https?:\/\/picsum\.photos/.test(url);
}
/**
* Normalizza URL per il confronto
*/
static normalizeUrl(url) {
return url.replace(/\?.*$/, '').toLowerCase().replace(/\/$/, '');
}
/**
* Estrae seed da URL Picsum
*/
static extractSeed(url) {
const match = url.match(/picsum\.photos\/seed\/([^\/\?]+)/);
return match ? match[1] : undefined;
}
/**
* Estrae dimensioni da URL Picsum
*/
static extractDimensions(url) {
// Pattern con larghezza e altezza
let match = url.match(/picsum\.photos\/(?:seed\/[^\/]+\/)?(\d+)\/(\d+)/);
if (match) {
return {
width: parseInt(match[1]),
height: parseInt(match[2]),
};
}
// Pattern solo larghezza (quadrata)
match = url.match(/picsum\.photos\/(?:seed\/[^\/]+\/)?(\d+)(?:\?|$)/);
if (match) {
const size = parseInt(match[1]);
return { width: size, height: size };
}
return undefined;
}
/**
* Clona profondamente un oggetto
*/
static deepClone(obj) {
if (obj === null || typeof obj !== 'object')
return obj;
if (obj instanceof Date)
return new Date(obj);
if (obj instanceof Array)
return obj.map(item => this.deepClone(item));
if (typeof obj === 'object') {
const cloned = {};
Object.keys(obj).forEach(key => {
cloned[key] = this.deepClone(obj[key]);
});
return cloned;
}
return obj;
}
/**
* Normalizza opzioni con valori di default
*/
static normalizeOptions(options) {
return {
preferCdnUrls: options.preferCdnUrls ?? true,
preserveOriginalOnFailure: options.preserveOriginalOnFailure ?? false,
validateUrls: options.validateUrls ?? false,
backupOriginal: options.backupOriginal ?? true,
logReplacements: options.logReplacements ?? true,
};
}
/**
* Log dei risultati di sostituzione
*/
static logReplacementResults(result) {
console.log(`\n🔄 URL Replacement Results:`);
console.log(` ✅ Successfully replaced: ${result.replacedCount} URLs`);
console.log(` ❌ Failed to replace: ${result.failedCount} URLs`);
if (result.errors.length > 0) {
console.log(` ⚠️ Errors encountered:`);
result.errors.slice(0, 5).forEach(error => {
console.log(` • ${error}`);
});
if (result.errors.length > 5) {
console.log(` • ... and ${result.errors.length - 5} more errors`);
}
}
// Raggruppa per tipo di sostituzione
const typeGroups = result.mappings.reduce((acc, mapping) => {
acc[mapping.replacementType] = (acc[mapping.replacementType] || 0) + 1;
return acc;
}, {});
if (Object.keys(typeGroups).length > 0) {
console.log(` 📊 Replacement types:`);
Object.entries(typeGroups).forEach(([type, count]) => {
console.log(` • ${type}: ${count}`);
});
}
}
/**
* Crea statistiche di sostituzione
*/
static getReplacementStats(result) {
const successRate = (result.replacedCount / (result.replacedCount + result.failedCount)) * 100;
const typeDistribution = result.mappings.reduce((acc, mapping) => {
acc[mapping.replacementType] = (acc[mapping.replacementType] || 0) + 1;
return acc;
}, {});
const avgUrlLength = result.mappings.length > 0
? result.mappings.reduce((sum, m) => sum + m.newUrl.length, 0) / result.mappings.length
: 0;
const summary = `Sostituiti ${result.replacedCount}/${result.replacedCount + result.failedCount} URLs (${Math.round(successRate)}% successo)`;
return {
summary,
successRate,
typeDistribution,
avgUrlLength: Math.round(avgUrlLength),
};
}
/**
* Rollback delle sostituzioni (usa backup)
*/
static rollbackReplacements(originalData, result) {
if (!result.modifiedData) {
throw new Error('No modified data available for rollback');
}
// Ripristina URLs originali
result.mappings.forEach(mapping => {
this.setValueAtPath(result.modifiedData, mapping.path, mapping.originalUrl);
});
return result.modifiedData;
}
/**
* Imposta valore a un percorso specifico
*/
static setValueAtPath(obj, path, value) {
const parts = path.split(/[\.\[\]]/).filter(Boolean);
let current = obj;
for (let i = 0; i < parts.length - 1; i++) {
const part = parts[i];
const isArrayIndex = /^\d+$/.test(part);
if (isArrayIndex) {
current = current[parseInt(part)];
}
else {
current = current[part];
}
}
const lastPart = parts[parts.length - 1];
const isArrayIndex = /^\d+$/.test(lastPart);
if (isArrayIndex) {
current[parseInt(lastPart)] = value;
}
else {
current[lastPart] = value;
}
}
}
exports.UrlReplacer = UrlReplacer;
UrlReplacer.URL_VALIDATION_TIMEOUT = 5000;
//# sourceMappingURL=url-replacer.js.map