Public/Middleware/Enable-KrExceptionHandling.ps1

<#
.SYNOPSIS
    Enables exception handling middleware for a Kestrun server instance.
.DESCRIPTION
    This cmdlet configures the exception handling middleware for a Kestrun server instance,
    allowing for customizable error handling and response generation.
.PARAMETER Server
    The Kestrun server instance (resolved if omitted via Resolve-KestrunServer).
.PARAMETER ExceptionHandlingPath
    The path to re-execute when an exception occurs (e.g., '/error').
.PARAMETER CreateScopeForErrors
    If specified, creates a new scope for handling errors.
.PARAMETER AllowStatusCode404Response
    If specified, allows handling of 404 status code responses.
.PARAMETER IncludeDetailsInDevelopment
    If specified, includes detailed error information when the environment is set to Development.
.PARAMETER UseProblemDetails
    If specified, formats error responses using the Problem Details standard (RFC 7807).
.PARAMETER Compress
    If specified, compress the json response for error handling.
.PARAMETER DeveloperExceptionPage
    If specified, enables the Developer Exception Page middleware with default options.
.PARAMETER SourceCodeLineCount
    The number of source code lines to display around the error line in the Developer Exception Page.
.PARAMETER SourceCodePath
    The base path to use for locating source code files in the Developer Exception Page.
.PARAMETER LanguageOptions
    A LanguageOptions object defining the scripting language, code, references, imports, and arguments
    for custom error handling logic.
.PARAMETER ScriptBlock
    A PowerShell script block to execute for custom error handling logic.
.PARAMETER Code
    A string containing the code to execute for custom error handling logic.
.PARAMETER Language
    The scripting language of the provided code (e.g., PowerShell, CSharp, VisualBasic).
.PARAMETER CodeFilePath
    The file path to a script file containing the code to execute for custom error handling logic.
.PARAMETER ExtraRefs
    An array of additional assemblies to reference when executing the custom error handling code.
.PARAMETER Arguments
    A hashtable of arguments to pass to the custom error handling code.
.PARAMETER ExtraImports
    An array of additional namespaces to import when executing the custom error handling code.
.PARAMETER PassThru
    If specified, returns the modified Kestrun server instance.
.OUTPUTS
    The modified Kestrun server instance if the PassThru parameter is specified; otherwise, no output.
.EXAMPLE
    Enable-KrExceptionHandling -ExceptionHandlingPath '/error' -CreateScopeForErrors -AllowStatusCode404Response -PassThru
    Enables exception handling middleware on the default Kestrun server instance,
    re-executing requests to '/error' when exceptions occur, creating a scope for error handling,
    and allowing handling of 404 status code responses. The modified server instance is returned.
.EXAMPLE
    Enable-KrExceptionHandling -Server $myServer -DeveloperExceptionPage -SourceCodeLineCount 10 -SourceCodePath 'C:\MyApp'
    Enables the Developer Exception Page middleware on the specified Kestrun server instance,
    displaying 10 lines of source code around the error line and using 'C:\MyApp' as the base path for source files.
.EXAMPLE
    $langOptions = [Kestrun.Hosting.Options.LanguageOptions]::new()
    $langOptions.Language = [Kestrun.Scripting.ScriptLanguage]::PowerShell
    $langOptions.Code = { param($exception) "An error occurred: $($exception.Message)" }
    Enable-KrExceptionHandling -Server $myServer -LanguageOptions $langOptions
    Enables custom exception handling on the specified Kestrun server instance,
    executing the provided PowerShell code block when an exception occurs. The code block receives the exception object as a parameter.
    The custom error handling logic can be defined using the LanguageOptions, ScriptBlock, Code, or CodeFilePath parameters.
.NOTES
    This function is part of the Kestrun PowerShell module and is used to manage Kestrun servers
    and their middleware components.
#>

function Enable-KrExceptionHandling {
    [KestrunRuntimeApi('Definition')]
    [CmdletBinding(DefaultParameterSetName = 'Default')]
    [OutputType([Kestrun.Hosting.KestrunHost])]
    param(
        [Parameter(ValueFromPipeline = $true)]
        [Kestrun.Hosting.KestrunHost] $Server,

        # Redirect
        [Parameter(Mandatory = $true, ParameterSetName = 'ExceptionHandlingPath')]
        [string] $ExceptionHandlingPath,
        [Parameter(ParameterSetName = 'LanguageOptions')]
        [Parameter(ParameterSetName = 'ScriptBlock')]
        [Parameter(ParameterSetName = 'Code')]
        [Parameter(ParameterSetName = 'CodeFilePath')]
        [Parameter(ParameterSetName = 'ExceptionHandlingPath')]
        [switch] $CreateScopeForErrors,
        [Parameter(ParameterSetName = 'LanguageOptions')]
        [Parameter(ParameterSetName = 'ScriptBlock')]
        [Parameter(ParameterSetName = 'Code')]
        [Parameter(ParameterSetName = 'CodeFilePath')]
        [Parameter(ParameterSetName = 'ExceptionHandlingPath')]
        [switch] $AllowStatusCode404Response,

        [Parameter(ParameterSetName = 'Json')]
        [switch] $IncludeDetailsInDevelopment,
        [Parameter(ParameterSetName = 'Json')]
        [switch] $UseProblemDetails,
        [Parameter(ParameterSetName = 'Json')]
        [switch] $Compress,

        [Parameter(Mandatory = $true, ParameterSetName = 'DeveloperExceptionPage')]
        [switch] $DeveloperExceptionPage,
        [Parameter(ParameterSetName = 'DeveloperExceptionPage')]
        [int] $SourceCodeLineCount,
        [Parameter(ParameterSetName = 'DeveloperExceptionPage')]
        [string] $SourceCodePath,

        # Custom via LanguageOptions or ScriptBlock
        [Parameter(ParameterSetName = 'LanguageOptions')]
        [Kestrun.Hosting.Options.LanguageOptions] $LanguageOptions,

        [Parameter(ParameterSetName = 'ScriptBlock')]
        [scriptblock] $ScriptBlock,

        [Parameter(Mandatory = $true, ParameterSetName = 'Code')]
        [Alias('CodeBlock')]
        [string]$Code,
        [Parameter(Mandatory = $true, ParameterSetName = 'Code')]
        [Kestrun.Scripting.ScriptLanguage]$Language,

        [Parameter(Mandatory = $true, ParameterSetName = 'CodeFilePath')]
        [string]$CodeFilePath,

        [Parameter(ParameterSetName = 'ScriptBlock')]
        [Parameter(ParameterSetName = 'Code')]
        [Parameter(ParameterSetName = 'CodeFilePath')]
        [System.Reflection.Assembly[]]$ExtraRefs = $null,

        [Parameter(ParameterSetName = 'ScriptBlock')]
        [Parameter(ParameterSetName = 'Code')]
        [Parameter(ParameterSetName = 'CodeFilePath')]
        [hashtable]$Arguments,

        [Parameter(ParameterSetName = 'ScriptBlock')]
        [Parameter(ParameterSetName = 'Code')]
        [Parameter(ParameterSetName = 'CodeFilePath')]
        [string[]]$ExtraImports = $null,

        [switch] $PassThru
    )
    begin {
        $Server = Resolve-KestrunServer -Server $Server
    }
    process {
        $exceptionOptions = [Kestrun.Hosting.Options.ExceptionOptions]::new($Server)

        # Map direct ExceptionOptions properties from parameters
        if ($PSBoundParameters.ContainsKey('CreateScopeForErrors')) {
            $exceptionOptions.CreateScopeForErrors = $CreateScopeForErrors.IsPresent
        }
        if ($PSBoundParameters.ContainsKey('AllowStatusCode404Response')) {
            $exceptionOptions.AllowStatusCode404Response = $AllowStatusCode404Response.IsPresent
        }
        if ($PSCmdlet.ParameterSetName -eq 'ExceptionHandlingPath') {
            $exceptionOptions.ExceptionHandlingPath = [Microsoft.AspNetCore.Http.PathString]::new($ExceptionHandlingPath)
        } elseif ($PSCmdlet.ParameterSetName -eq 'Json') {
            # If using JSON fallback, set the options accordingly
            # If using the JSON fallback, set up the built-in JSON handler
            $exceptionOptions.UseJsonExceptionHandler($UseProblemDetails.IsPresent, $IncludeDetailsInDevelopment.IsPresent, $Compress.IsPresent)
        } elseif ($PSCmdlet.ParameterSetName -eq 'LanguageOptions') {
            # If using LanguageOptions, assign it directly
            $exceptionOptions.LanguageOptions = $LanguageOptions
        } elseif ($PSCmdlet.ParameterSetName -eq 'DeveloperExceptionPage') {
            # Warn if not in Development environment
            if (-not (Test-KrDebugContext)) {
                if (Test-KrLogger) {
                    Write-KrLog -Level Warning -Message 'DeveloperExceptionPage is typically used in Development environment. Current environment does not appear to be Development.'
                } else {
                    Write-Warning -Message 'DeveloperExceptionPage is typically used in Development environment. Current environment does not appear to be Development.'
                }
            }
            # If using Developer Exception Page, set up the options accordingly
            $exceptionOptions.DeveloperExceptionPageOptions = [Microsoft.AspNetCore.Builder.DeveloperExceptionPageOptions]::new()
            # Configure Developer Exception Page options if specified
            if ($PSBoundParameters.ContainsKey('SourceCodeLineCount')) {
                $exceptionOptions.DeveloperExceptionPageOptions.SourceCodeLineCount = $SourceCodeLineCount
            }
            if ($PSBoundParameters.ContainsKey('SourceCodePath')) {
                $exceptionOptions.DeveloperExceptionPageOptions.FileProvider = [Microsoft.Extensions.FileProviders.PhysicalFileProvider]::new($SourceCodePath)
            }
        } elseif ($PSCmdlet.ParameterSetName -ne 'Default') {
            $lo = [Kestrun.Hosting.Options.LanguageOptions]::new()
            $lo.ExtraImports = $ExtraImports
            $lo.ExtraRefs = $ExtraRefs
            if ($null -ne $Arguments) {
                $dict = [System.Collections.Generic.Dictionary[string, object]]::new()
                foreach ($key in $Arguments.Keys) {
                    $dict[$key] = $Arguments[$key]
                }
                $lo.Arguments = $dict
            }

            switch ($PSCmdlet.ParameterSetName) {
                'ScriptBlock' {
                    $lo.Language = [Kestrun.Scripting.ScriptLanguage]::PowerShell
                    $lo.Code = $ScriptBlock.ToString()
                }
                'Code' {
                    $lo.Language = $Language
                    $lo.Code = $Code
                }
                'CodeFilePath' {
                    if (-not (Test-Path -Path $CodeFilePath)) {
                        throw "The specified code file path does not exist: $CodeFilePath"
                    }
                    $extension = Split-Path -Path $CodeFilePath -Extension
                    switch ($extension) {
                        '.ps1' {
                            $lo.Language = [Kestrun.Scripting.ScriptLanguage]::PowerShell
                        }
                        '.cs' {
                            $lo.Language = [Kestrun.Scripting.ScriptLanguage]::CSharp
                        }
                        '.vb' {
                            $lo.Language = [Kestrun.Scripting.ScriptLanguage]::VisualBasic
                        }
                        default {
                            throw "Unsupported '$extension' code file extension."
                        }
                    }
                    $lo.Code = Get-Content -Path $CodeFilePath -Raw
                }
                default {
                    throw "Unrecognized ParameterSetName: $($PSCmdlet.ParameterSetName)"
                }
            }
            $exceptionOptions.LanguageOptions = $lo
        }


        # Assign the configured options to the server instance
        $Server.ExceptionOptions = $exceptionOptions

        if ($PassThru.IsPresent) {
            # if the PassThru switch is specified, return the modified server instance
            return $Server
        }
    }
}