Private/Output/Get-AvmFailureDetail.ps1

function Get-AvmPrimaryIssue {
    <#
    .SYNOPSIS
        Pick the single most actionable issue on a result.

    .DESCRIPTION
        A failing result often carries a bare status issue (for example
        terraform's "test run 'apply' fail", which has no line number) ahead of
        the diagnostic that names the file, position and cause. The headline a
        user sees - the console summary, the GitHub Actions annotation and the
        terminating error message - must be the one that points at the problem,
        so an issue carrying a line number wins over one that does not.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [object] $Result
    )

    Set-StrictMode -Version 3.0

    $issuesProperty = $Result.PSObject.Properties['Issues']
    if ($null -eq $issuesProperty) {
        return $null
    }

    $issues = @(@($issuesProperty.Value) | Where-Object { $null -ne $_ })
    if ($issues.Count -eq 0) {
        return $null
    }

    $preferred = $issues | Where-Object {
        $lineProperty = $_.PSObject.Properties['Line']
        $null -ne $lineProperty -and [int]$lineProperty.Value -gt 0
    } | Select-Object -First 1

    if ($null -ne $preferred) {
        return $preferred
    }

    return $issues[0]
}

function Get-AvmIssuePosition {
    [CmdletBinding()]
    [OutputType([hashtable])]
    param(
        [AllowNull()]
        [object] $Issue
    )

    Set-StrictMode -Version 3.0

    if ($null -eq $Issue) {
        return $null
    }

    $fileProperty = $Issue.PSObject.Properties['File']
    if ($null -eq $fileProperty -or [string]::IsNullOrWhiteSpace([string]$fileProperty.Value)) {
        return $null
    }

    $lineProperty = $Issue.PSObject.Properties['Line']
    $columnProperty = $Issue.PSObject.Properties['Column']

    return @{
        File   = [string]$fileProperty.Value
        Line   = if ($null -ne $lineProperty) { [int]$lineProperty.Value } else { 0 }
        Column = if ($null -ne $columnProperty) { [int]$columnProperty.Value } else { 0 }
    }
}

function Get-AvmPrimaryIssueLine {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory)]
        [object] $Result
    )

    Set-StrictMode -Version 3.0

    $chosen = Get-AvmPrimaryIssue -Result $Result
    if ($null -eq $chosen) {
        return ''
    }

    $lines = @(ConvertTo-AvmIssueLine -Result ([pscustomobject]@{ Issues = @($chosen) }))
    if ($lines.Count -gt 0) {
        return $lines[0]
    }

    return ''
}

function Get-AvmFailureDetail {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory)]
        [object] $Result
    )

    Set-StrictMode -Version 3.0

    $stepsProperty = $Result.PSObject.Properties['Steps']
    if ($null -ne $stepsProperty) {
        foreach ($step in @($stepsProperty.Value)) {
            if ([string]$step.Status -notin @('fail', 'error')) {
                continue
            }
            $errorProperty = $step.PSObject.Properties['Error']
            if ($null -ne $errorProperty -and -not [string]::IsNullOrWhiteSpace([string]$errorProperty.Value)) {
                return "Step '$($step.Step)': $($errorProperty.Value)"
            }
            $resultProperty = $step.PSObject.Properties['Result']
            if ($null -ne $resultProperty -and $null -ne $resultProperty.Value) {
                $issueLine = Get-AvmPrimaryIssueLine -Result $resultProperty.Value
                if (-not [string]::IsNullOrWhiteSpace($issueLine)) {
                    return "Step '$($step.Step)': $issueLine"
                }
            }
            return "Step '$($step.Step)' reported Status '$($step.Status)'."
        }
    }

    return (Get-AvmPrimaryIssueLine -Result $Result)
}

function Get-AvmFailurePosition {
    <#
    .SYNOPSIS
        Locate the file, line and column behind the detail Get-AvmFailureDetail
        returns, so the GitHub Actions annotation can be anchored on it.

    .DESCRIPTION
        The walk deliberately mirrors Get-AvmFailureDetail step for step: the
        annotation must point at the issue whose text is being reported, so a
        branch that yields a positionless detail must yield a null position.
    #>

    [CmdletBinding()]
    [OutputType([hashtable])]
    param(
        [Parameter(Mandatory)]
        [object] $Result
    )

    Set-StrictMode -Version 3.0

    $stepsProperty = $Result.PSObject.Properties['Steps']
    if ($null -ne $stepsProperty) {
        foreach ($step in @($stepsProperty.Value)) {
            if ([string]$step.Status -notin @('fail', 'error')) {
                continue
            }
            $errorProperty = $step.PSObject.Properties['Error']
            if ($null -ne $errorProperty -and -not [string]::IsNullOrWhiteSpace([string]$errorProperty.Value)) {
                return $null
            }
            $resultProperty = $step.PSObject.Properties['Result']
            if ($null -ne $resultProperty -and $null -ne $resultProperty.Value) {
                return (Get-AvmIssuePosition -Issue (Get-AvmPrimaryIssue -Result $resultProperty.Value))
            }
            return $null
        }
    }

    return (Get-AvmIssuePosition -Issue (Get-AvmPrimaryIssue -Result $Result))
}