Core/Configuration.psm1
|
<#
.SYNOPSIS CCF Configuration Manager - Deep Merge & Layered (Optimized) .DESCRIPTION Gestiona la configuración del framework y aplicaciones basadas en CCF. Incluye un sistema de caché en memoria para alto rendimiento. #> $script:ConfigCache = New-Object -TypeName 'System.Collections.Concurrent.ConcurrentDictionary[string,object]' # --- Auxiliar: Fusión Recursiva (Deep Merge) --- function Merge-CCFConfig { param( [Parameter(Mandatory = $true)] [PSCustomObject]$Base, [Parameter(Mandatory = $true)] [PSCustomObject]$Override ) foreach ($prop in $Override.PSObject.Properties) { if ($null -eq $Base.PSObject.Properties[$prop.Name]) { $Base | Add-Member -NotePropertyName $prop.Name -NotePropertyValue $prop.Value -Force } else { $baseVal = $Base.($prop.Name) $overVal = $prop.Value if ($baseVal -is [PSCustomObject] -and $overVal -is [PSCustomObject]) { Merge-CCFConfig -Base $baseVal -Override $overVal } elseif ($baseVal -is [array] -and $overVal -is [array]) { $Base.($prop.Name) = $baseVal + $overVal } else { $Base.($prop.Name) = $overVal } } } return $Base } # --- Carga de Configuración con Caché --- function Get-CCFConfig { param( [Parameter(Mandatory = $true)] [string]$ConfigName, [string[]]$ActivePresets = @(), [switch]$ForceRefresh ) # 1. Verificar Caché (Si no hay ForceRefresh) $cacheKey = "$ConfigName" + ($ActivePresets -join ",") $cachedVal = $null if (-not $ForceRefresh -and $script:ConfigCache.TryGetValue($cacheKey, [ref]$cachedVal)) { Write-CCFLogDebug "Retornando configuración desde caché para: $ConfigName" return $cachedVal } # SEGURIDAD: Sanitización de ConfigName (Anti-PathTraversal) if ($ConfigName -match "[\\/:]" -or $ConfigName -match "\.\.") { Log-Error "BLOQUEO DE SEGURIDAD: Intento de Path Traversal o nombre de configuración inválido: $ConfigName" return $null } $configRoot = Get-CCFPath -Target "Configs" $factoryPath = Join-Path $configRoot "Factory\$ConfigName.json" $presetsPath = Join-Path $configRoot "Presets" $userPath = Join-Path $configRoot "User\$ConfigName.json" $finalConfig = [PSCustomObject]@{} # 1. Cargar Factory if (Test-Path $factoryPath) { try { $finalConfig = Get-Content $factoryPath -Raw | ConvertFrom-Json Log-Debug "Base cargada para: $ConfigName" } catch { Log-Error "Fallo crítico al cargar Factory Config ${ConfigName}: $($_.Exception.Message)" return $null } } # 2. Cargar Presets foreach ($preset in $ActivePresets) { $pPath = Join-Path $presetsPath "$preset.json" if (Test-Path $pPath) { try { $pContent = Get-Content $pPath -Raw | ConvertFrom-Json if ($pContent.PSObject.Properties[$ConfigName]) { $finalConfig = Merge-CCFConfig -Base $finalConfig -Override $pContent.$ConfigName } } catch { Log-Warn "Fallo al aplicar preset '$preset'" } } } # 3. Cargar User if (Test-Path $userPath) { try { $uContent = Get-Content $userPath -Raw | ConvertFrom-Json $finalConfig = Merge-CCFConfig -Base $finalConfig -Override $uContent } catch { Log-Warn "Fallo al cargar User Config para $ConfigName" } } # Guardar en Caché $null = $script:ConfigCache.TryAdd($cacheKey, $finalConfig) return $finalConfig } # --- Guardado e Invalidación de Caché --- function Set-CCFUserConfig { param( [Parameter(Mandatory = $true)] [string]$ConfigName, [Parameter(Mandatory = $true)] [PSCustomObject]$ConfigObject ) $userDir = Join-Path (Get-CCFPath -Target "Configs") "User" if (-not (Test-Path $userDir)) { New-Item -ItemType Directory -Path $userDir -Force | Out-Null } $userPath = Join-Path $userDir "$ConfigName.json" try { $ConfigObject | ConvertTo-Json -Depth 10 | Set-Content $userPath -Force Log-Success "Configuración guardada en ${userPath}" # Invalidar todas las entradas de caché para este ConfigName $keysToClear = $script:ConfigCache.Keys | Where-Object { $_ -like "$ConfigName*" } foreach ($k in $keysToClear) { $removed = $null $null = $script:ConfigCache.TryRemove($k, [ref]$removed) } Write-CCFLogDebug "Caché invalidada para: $ConfigName" } catch { Log-Error "No se pudo guardar en $userPath" } } # --- Limpieza de Caché --- function Remove-CCFConfigCache { $script:ConfigCache.Clear() Write-CCFLogInfo "Caché de configuración vaciada." } # --- Validación de Esquiva (Schema-lite) --- function Test-CCFConfigSchema { param( [Parameter(Mandatory = $true)] [PSCustomObject]$Config, [Parameter(Mandatory = $true)] [hashtable]$Schema ) $errors = New-Object System.Collections.Generic.List[string] foreach ($key in $Schema.Keys) { $expectedType = $Schema[$key] $val = $Config.$key if ($null -eq $val) { $errors.Add("Campo faltante: $key") } elseif (-not ($val -is $expectedType)) { $errors.Add("Tipo incorrecto para $key. Esperado: $($expectedType.Name), Obtenido: $($val.GetType().Name)") } } if ($errors.Count -gt 0) { foreach ($err in $errors) { Log-Error "Validación de Config: $err" } return $false } return $true } # --- Alias de Retrocompatibilidad --- New-Alias -Name Clear-CCFConfigCache -Value Remove-CCFConfigCache Export-ModuleMember -Function Get-CCFConfig, Set-CCFUserConfig, Remove-CCFConfigCache, Test-CCFConfigSchema -Alias * |