Private/New-HDTErrorRecord.ps1
|
function New-HDTErrorRecord { <# .SYNOPSIS Builds the ErrorRecord every HDT configuration failure is thrown as. .DESCRIPTION HDT classifies a failure as Transient, Configuration or Environment, and requires a Configuration failure to fail fast and point at the file and the line. This builds that record so every caller produces the same shape: with -Path and -Line : "{Path}({Line}): {Message}" with -Path only : "{Path}: {Message}" with neither : "{Message}" -Path also becomes the TargetObject, so a log reader and the console can both recover which file was at fault without parsing prose. Callers throw it with $PSCmdlet.ThrowTerminatingError(), never with `throw "message"`: a bare string throw discards the error id and the target object, which are exactly what make a configuration failure greppable in a log and machine-readable by the console. Thrown that way the record's FullyQualifiedErrorId is "<ErrorId>,<FunctionName>", so 'HDTConfigurationError*' matches every one of them regardless of which function raised it. The line number is only available where the caller knows one. YAML parse errors carry a line; authoring-rule violations, which are detected after parsing, carry the offending rule instead - the object graph the parser returns has no line information on it. .PARAMETER Message The sentence an administrator reads, without the file prefix. .PARAMETER Path The file at fault. Becomes both the message prefix and the TargetObject. .PARAMETER Line The 1-based line at fault. Only used when -Path is supplied. .PARAMETER ErrorId The error id. Defaults to HDTConfigurationError; the dependency gate in ConvertFrom-HDTYaml uses HDTDependencyError. .PARAMETER TargetObject The thing at fault, when it is not a file. The refusals are about a DISK, so Select-HDTTargetDisk carries the disk number here and a console recovers it without parsing prose. Supplied, it wins over -Path; omitted, -Path is still the target. .PARAMETER Category The ErrorCategory. Defaults to InvalidData. .PARAMETER InnerException An exception to preserve underneath the one this record carries. Omitted where the underlying exception is a third-party type that must not escape the engine. .OUTPUTS System.Management.Automation.ErrorRecord .EXAMPLE $PSCmdlet.ThrowTerminatingError((New-HDTErrorRecord -Message 'schemaVersion is missing.' -Path $Path)) The canonical call: fail fast, name the file, keep the error id. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Builds an ErrorRecord object; it changes no state.')] [CmdletBinding()] [OutputType([System.Management.Automation.ErrorRecord])] param( [Parameter(Mandatory = $true, Position = 0)] [ValidateNotNullOrEmpty()] [string] $Message, [Parameter()] [string] $Path, [Parameter()] [int] $Line, [Parameter()] [ValidateNotNullOrEmpty()] [string] $ErrorId = 'HDTConfigurationError', [Parameter()] [AllowNull()] [object] $TargetObject, [Parameter()] [System.Management.Automation.ErrorCategory] $Category = [System.Management.Automation.ErrorCategory]::InvalidData, [Parameter()] [System.Exception] $InnerException ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $text = $Message if (-not [string]::IsNullOrWhiteSpace($Path)) { if ($PSBoundParameters.ContainsKey('Line') -and $Line -gt 0) { $text = '{0}({1}): {2}' -f $Path, $Line, $Message } else { $text = '{0}: {1}' -f $Path, $Message } } if ($PSBoundParameters.ContainsKey('InnerException') -and $null -ne $InnerException) { $exception = New-Object -TypeName System.Exception -ArgumentList $text, $InnerException } else { $exception = New-Object -TypeName System.Exception -ArgumentList $text } $target = $null if ($PSBoundParameters.ContainsKey('TargetObject')) { $target = $TargetObject } elseif (-not [string]::IsNullOrWhiteSpace($Path)) { $target = $Path } return (New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $exception, $ErrorId, $Category, $target) } |