src/private/Invoke-GitHubApi-BodyEmpty.ps1

#!/usr/bin/env pwsh
$ErrorActionPreference = "Stop"
Set-StrictMode -Version Latest


function Invoke-GitHubApi-BodyEmpty {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        # [ValidateScript({ $_ -in [System.Net.Http.HttpMethod]::Get, [System.Net.Http.HttpMethod]::Post, [System.Net.Http.HttpMethod]::Put, [System.Net.Http.HttpMethod]::Patch, [System.Net.Http.HttpMethod]::Delete, [System.Net.Http.HttpMethod]::Head, [System.Net.Http.HttpMethod]::Options, [System.Net.Http.HttpMethod]::Trace, [System.Net.Http.HttpMethod]::Connect})]
        [System.Net.Http.HttpMethod] $Method,

        [Parameter(Mandatory = $true, Position = 1)]
        # [ValidateNotNullOrEmpty()]
        [string] $Route,

        [Parameter(Mandatory = $false, Position = 2)]
        [hashtable] $Query = @{},

        [Parameter(Mandatory = $false, Position = 11)]
        # [ValidateNotNull()]
        [hashtable] $Headers = @{},

        [Parameter(Mandatory = $false)]
        # [ValidateNotNullOrEmpty()]
        [string] $Hostname = $null,

        [Parameter(Mandatory = $false)]
        # [ValidateNotNullOrEmpty()]
        [string] $jq = $null,

        [Parameter(Mandatory = $false)]
        [switch] $Paginate,

        [Parameter(Mandatory = $false)]
        # [ValidateNotNullOrEmpty()]
        [string[]] $Previews = @(),

        [Parameter(Mandatory = $false, Position = 16)]
        [switch] $OutputRaw,

        [Parameter(Mandatory = $false)]
        [switch] $OutputAsHashtable,

        [Parameter(Mandatory = $false)]
        # [ValidateRange(0, [int]::MaxValue)]
        [Nullable[int]] $OutputDepth,

        [Parameter(Mandatory = $false)]
        [switch] $OutputNoEnumerate
    )

    Begin {
        if ($OutputRaw) {
            if ($OutputAsHashtable) {
                throw "OutputRaw and OutputAsHashtable cannot be used together."
            }
            if ($OutputDepth) {
                throw "OutputRaw and OutputDepth cannot be used together."
            }
            if ($OutputNoEnumerate) {
                throw "OutputRaw and OutputNoEnumerate cannot be used together."
            }
        }

        # [PSCommand] $command
        $ghCommand = [System.Management.Automation.PSCommand]::new()
        $ghCommand.AddCommand("gh")
        # --cache duration Cache the response, e.g. "3600s", "60m", "1h"
        # -F, --field key=value Add a typed parameter in key=value format
        # -H, --header key:value Add a HTTP request header in key:value format
        # --hostname string The GitHub hostname for the request (default "github.com")
        # -i, --include Include HTTP response status line and headers in the output
        # --input file The file to use as body for the HTTP request (use "-" to read from standard input)
        # -q, --jq string Query to select values from the response using jq syntax
        # -X, --method string The HTTP method for the request (default "GET")
        # --paginate Make additional HTTP requests to fetch all pages of results
        # -p, --preview names GitHub API preview names to request (without the "-preview" suffix)
        # -f, --raw-field key=value Add a string parameter in key=value format
        # --silent Do not print the response body
        # -t, --template string Format JSON output using a Go template; see "gh help formatting"
        # --verbose Include full HTTP request and response in the output

        $ghCommand = $ghCommand.AddArgument("api")

        [System.Text.StringBuilder] $routeFinal = [System.Text.StringBuilder]::new()
        $routeFinal.Append($Route)
        [bool] $firstQueryStringParameter = $true
        foreach ($key in $Query.Keys) {
            if ($firstQueryStringParameter) {
                $routeFinal.Append("?")
                $firstQueryStringParameter = $false
            } else {
                $routeFinal.Append("&")
            }
            $routeFinal.Append([System.Uri]::EscapeDataString($key))
            $routeFinal.Append("=")
            $routeFinal.Append([System.Uri]::EscapeDataString($Query[$key]))
        }
        $ghCommand = $ghCommand.AddArgument($routeFinal.ToString())

        if ($Method -ne [System.Net.Http.HttpMethod]::Get) {
            $ghCommand = $ghCommand.AddArgument("-X")
            $ghCommand = $ghCommand.AddArgument($Method.ToString().ToUpperInvariant())
        }

        [hashtable] $headersFinal = @{
            "Accept" = "application/vnd.github+json";
        } + $headers
        foreach ($headerKey in $headersFinal.Keys) {
            $ghCommand = $ghCommand.AddArgument("--header")
            [string] $escapedHeaderKey = [System.Uri]::EscapeDataString($headerKey)
            [string] $escapedHeaderValue = [System.Uri]::EscapeDataString($headersFinal[$headerKey])
            $ghCommand = $ghCommand.AddArgument("${escapedHeaderKey}: ${escapedHeaderValue}")
        }

        if ($Hostname) {
            $ghCommand = $ghCommand.AddArgument("--hostname")
            $ghCommand = $ghCommand.AddArgument($Hostname)
        }

        if ($jq) {
            $ghCommand = $ghCommand.AddArgument("--jq")
            $ghCommand = $ghCommand.AddArgument($jq)
        }

        if ($Paginate) {
            $ghCommand = $ghCommand.AddArgument("--paginate")
        }

        if ($Previews) {
            $ghCommand = $ghCommand.AddArgument("--preview")
            $ghCommand = $ghCommand.AddArgument($Previews -join ",")
        }

        $outputHandlingCommand = $null -as [System.Management.Automation.PSCommand]
        if (-not $OutputRaw) {
            $outputHandlingCommand = [System.Management.Automation.PSCommand]::new()
            $outputHandlingCommand = $outputHandlingCommand.AddCommand("ConvertFrom-Json")

            if ($OutputAsHashtable) {
                $outputHandlingCommand = $outputHandlingCommand.AddArgument("-AsHashtable")
            }

            if ($OutputDepth) {
                $outputHandlingCommand = $outputHandlingCommand.AddArgument("-Depth")
                $outputHandlingCommand = $outputHandlingCommand.AddArgument($OutputDepth)
            }

            if ($OutputNoEnumerate) {
                $outputHandlingCommand = $outputHandlingCommand.AddArgument("-NoEnumerate")
            }
        }
    }

    Process {
        # https://learn.microsoft.com/en-us/dotnet/api/system.management.automation.runspaces.command?view=powershellsdk-7.3.0
        # https://learn.microsoft.com/en-us/dotnet/api/system.management.automation.pscommand?view=powershellsdk-7.3.0
        function AccumulatePSCommand {
            param(
                [System.Management.Automation.PSCommand] $accumulate,
                [System.Management.Automation.Runspaces.Command] $subcommand
            )
            [System.Management.Automation.PSCommand] $result = $accumulate.Clone()
            $result = $result.AddCommand($subcommand.CommandText)
            foreach ($subcommandParameter in $subcommand.Parameters) {
                Write-Host "A parameter!"
                if ($subcommandParameter.IsScript) {
                    $result = $result.AddScript($subcommandParameter.Value, $subcommand.UseLocalScope)
                } else {
                    $result = $result.AddParameter($subcommandParameter.Name, $subcommandParameter.Value)
                }
            }
            if ($subcommand.IsEndOfStatement) {
                $result = $result.AddStatement()
            }
            return $result
        }
        function ExpressCommandPart {
            param(
                [System.Management.Automation.Runspaces.Command] $subcommand  # should be a stringbuilder
            )
            [System.Text.StringBuilder] $result = [System.Text.StringBuilder]::new()
            $result.Append($subcommand.CommandText)
            foreach ($subcommandParameter in $subcommand.Parameters) {
                $result.Append(" ")
                $result.Append($subcommandParameter.Name)
                if ($subcommandParameter.Value) {
                    $result.Append(" ")
                    $result.Append($subcommandParameter.Value)
                }
            }
            if ($subcommand.MergeUnclaimedPreviousCommandResults -ne 0) {
                $result.Append(" 2>&1")
            }
            return $result.ToString()
        }

        [System.Management.Automation.PSCommand] $commandFinal = $ghCommand.Clone()

        if ($null -ne $outputHandlingCommand) {
            foreach ($outputHandlingCommandCommand in $outputHandlingCommand.Commands) {
                $commandFinal = AccumulatePSCommand -accumulate $commandFinal -subcommand $outputHandlingCommandCommand
            }
        }

        # don't use the above loop.
        # instead, express them out into a stringbuilder. interleave with pipes and semicolons. (if not is first command in statement, use pipe, unless IsEndOfStatement then instead use semicolon and mark as first command in statement again.)
        # then invoke-expression the stringbuilder.

        # with these in place, backport changes to original script.

        # if manually expressing the string don't work right, remember that this script here does not need to support the stringifying of the command. (that's just me practicing for later.)
        # instead, we can execute the command against a PowerShell instance and a Runspace.
        # see the examples on the methods, here: https://learn.microsoft.com/en-us/dotnet/api/system.management.automation.pscommand?view=powershellsdk-7.3.0

        [string] $commandFinalCommandText = ($commandFinal.Commands | Select-Object -ExpandProperty CommandText) -join " | "
        Write-Host "Executing: ${commandFinalCommandText}"
        Invoke-Expression $commandFinalCommandText
    }

    End {
    }
}