Utilities/Test-ExpressionCacheProviderSpec.ps1
|
<#
.SYNOPSIS Validates a single ExpressionCache provider spec and normalizes it to a hashtable. .DESCRIPTION Enforces the strict provider descriptor contract used by Initialize-ExpressionCache: - Spec must include Name (string), GetOrCreate (command-name string), and Config (hashtable/IDictionary). - Optional Initialize, ClearCache, and Teardown hooks must be command-name strings. - Function-name strings are validated with Get-Command -Name <string>. - Returns a normalized **hashtable** (not PSCustomObject). .PARAMETER Spec Provider descriptor to validate. Accepts hashtable, ordered hashtable, or PSCustomObject. .OUTPUTS [hashtable] A normalized provider spec (Name, GetOrCreate, [Initialize], [ClearCache], [Teardown], Config as hashtable). .EXAMPLE $spec = @{ Name = 'InMemoryCache' GetOrCreate = 'Get-InMemory-CachedValue' Initialize = 'Initialize-InMemoryCache' ClearCache = 'Clear-InMemory-Cache' Config = @{ DefaultMaxAge = (New-TimeSpan -Minutes 10) } } $valid = Test-ExpressionCacheProviderSpec -Spec $spec #> function Test-ExpressionCacheProviderSpec { param([Parameter(Mandatory)][object]$Spec) function ConvertTo-Hashtable { param([Parameter(Mandatory)][object]$InputObject) # Handle accidental arrays (caused by stray outputs upstream) if ($InputObject -is [array]) { # Pick the single spec-like object if present $candidates = $InputObject | Where-Object { $_ -is [System.Collections.IDictionary] -or $_ -is [pscustomobject] } if ($candidates.Count -eq 1) { $InputObject = $candidates[0] } else { $types = ($InputObject | ForEach-Object { $_.GetType().FullName }) -join ', ' throw "ExpressionCache: Provider spec must be a hashtable/IDictionary or PSCustomObject. Got array: $types" } } if ($InputObject -is [System.Collections.IDictionary]) { return $InputObject } if ($InputObject -is [pscustomobject]) { $ht = @{} foreach ($p in $InputObject.PSObject.Properties) { $ht[$p.Name] = $p.Value } return $ht } throw "ExpressionCache: Provider spec must be a hashtable/IDictionary or PSCustomObject. Got: $($InputObject.GetType().FullName)" } function Confirm-HookCommand { param( [Parameter(Mandatory)][object]$Value, [Parameter(Mandatory)][string]$PropName, [Parameter(Mandatory)][string]$ProviderName ) if ($Value -isnot [string] -or [string]::IsNullOrWhiteSpace($Value)) { throw "ExpressionCache: Provider '$ProviderName': '$PropName' must be a non-empty command-name string." } $command = Get-Command -Name $Value -CommandType Function, Cmdlet, ExternalScript -ErrorAction SilentlyContinue if (-not $command) { Write-Warning "ExpressionCache: Provider '$ProviderName': command '$Value' (from '$PropName') was not found in the current scope. It must be available at call time." return } if ($PropName -eq 'GetOrCreate') { foreach ($requiredParameter in 'Key', 'ScriptBlock') { if (-not $command.Parameters.ContainsKey($requiredParameter)) { throw "ExpressionCache: Provider '$ProviderName': GetOrCreate command '$Value' must declare a '$requiredParameter' parameter." } } } } # Normalize to plain hashtable (and strip accidental array wrappers) $specHt = ConvertTo-Hashtable $Spec foreach ($req in 'Name','GetOrCreate','Config') { if (-not ($specHt.Keys -contains $req) -or -not $specHt[$req]) { throw "ExpressionCache: Provider spec missing required property '$req'." } } if ($specHt['Name'] -isnot [string] -or [string]::IsNullOrWhiteSpace($specHt['Name'])) { throw "ExpressionCache: Provider 'Name' must be a non-empty string." } $name = [string]$specHt['Name'] # Validate functors (silent on success) Confirm-HookCommand -Value $specHt['GetOrCreate'] -PropName 'GetOrCreate' -ProviderName $name foreach ($opt in 'Initialize', 'ClearCache', 'Teardown') { if ($specHt.Keys -contains $opt -and $null -ne $specHt[$opt]) { Confirm-HookCommand -Value $specHt[$opt] -PropName $opt -ProviderName $name } } # Config -> hashtable (and handle PSCO) $cfg = $specHt['Config'] if ($cfg -isnot [System.Collections.IDictionary] -and $cfg -isnot [pscustomobject]) { throw "ExpressionCache: Provider '$name': Config must be a hashtable or PSCustomObject." } $specHt['Config'] = ConvertTo-Hashtable $cfg return $specHt # only output; no stray $true’s } |