Public/Route/Add-KrMapRoute.ps1

<#
    .SYNOPSIS
        Adds a new map route to the Kestrun server.
    .DESCRIPTION
        This function allows you to add a new map route to the Kestrun server by specifying the route path and the script block or code to be executed when the route is accessed.
    .PARAMETER Server
        The Kestrun server instance to which the route will be added.
        If not specified, the function will attempt to resolve the current server context.
    .PARAMETER Options
        An instance of `Kestrun.Hosting.Options.MapRouteOptions` that contains the configuration for the route.
        This parameter is used to specify various options for the route, such as HTTP verbs, path, authorization schemes, and more.
    .PARAMETER Verbs
        The HTTP verbs (GET, POST, etc.) that the route should respond to.
    .PARAMETER Pattern
        The URL path for the new route.
    .PARAMETER ScriptBlock
        The script block to be executed when the route is accessed.
    .PARAMETER Code
        The code to be executed when the route is accessed, used in conjunction with the Language parameter.
    .PARAMETER Language
        The scripting language of the code to be executed.
    .PARAMETER CodeFilePath
        The file path to the code to be executed when the route is accessed.
    .PARAMETER AuthorizationSchema
        An optional array of authorization schemes that the route requires.
    .PARAMETER AuthorizationPolicy
        An optional array of authorization policies that the route requires.
    .PARAMETER ExtraImports
        An optional array of additional namespaces to import for the route.
    .PARAMETER ExtraRefs
        An optional array of additional assemblies to reference for the route.
    .PARAMETER AllowDuplicate
        If specified, allows the addition of duplicate routes with the same path and HTTP verb.
    .PARAMETER Arguments
        An optional hashtable of arguments to pass to the script block or code.
    .PARAMETER DuplicateAction
        Specifies the action to take if a duplicate route is detected. Options are 'Throw', 'Skip', 'Allow', or 'Warn'.
        Default is 'Throw', which will raise an error if a duplicate route is found.
    .PARAMETER PassThru
        If specified, the function will return the created route object.
    .OUTPUTS
        Returns the Kestrun server instance with the new route added.
    .EXAMPLE
        Add-KrMapRoute -Server $myServer -Path "/myroute" -ScriptBlock { Write-Host "Hello, World!" }
        Adds a new map route to the specified Kestrun server with the given path and script block.
    .EXAMPLE

        Add-KrMapRoute -Server $myServer -Path "/myroute" -Code "Write-Host 'Hello, World!'" -Language PowerShell
        Adds a new map route to the specified Kestrun server with the given path and code.
    .EXAMPLE
        Get-KrServer | Add-KrMapRoute -Path "/myroute" -ScriptBlock { Write-Host "Hello, World!" } -PassThru
        Adds a new map route to the current Kestrun server and returns the route object.
    .NOTES
        This function is part of the Kestrun PowerShell module and is used to manage routes
#>

function Add-KrMapRoute {
    [KestrunRuntimeApi('Definition')]
    [CmdletBinding(defaultParameterSetName = 'ScriptBlock', PositionalBinding = $true)]
    [OutputType([Kestrun.Hosting.KestrunHost])]
    param(
        [Parameter(Mandatory = $false, ValueFromPipeline = $true)]
        [Kestrun.Hosting.KestrunHost]$Server,

        [Parameter(Mandatory = $true, ParameterSetName = 'Options', ValueFromPipeline = $true)]
        [Kestrun.Hosting.Options.MapRouteOptions]$Options,

        [Parameter(ParameterSetName = 'ScriptBlock')]
        [Parameter(ParameterSetName = 'Code')]
        [Parameter(ParameterSetName = 'CodeFilePath')]
        [Kestrun.Utilities.HttpVerb[]]$Verbs = @([Kestrun.Utilities.HttpVerb]::Get),

        [Parameter(ParameterSetName = 'ScriptBlock')]
        [Parameter(ParameterSetName = 'Code')]
        [Parameter(ParameterSetName = 'CodeFilePath')]
        [ValidatePattern('^/')]
        [alias('Path')]
        [string]$Pattern = '/',

        [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'ScriptBlock')]
        [scriptblock]$ScriptBlock,

        [Parameter(Mandatory = $true, ParameterSetName = 'Code')]
        [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')]
        [string[]]$AuthorizationSchema = $null,

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

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

        [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()]
        [switch]$AllowDuplicate,

        [Parameter()]
        [ValidateSet('Throw', 'Skip', 'Allow', 'Warn')]
        [string]$DuplicateAction = 'Throw',

        [Parameter()]
        [switch]$PassThru

    )
    process {
        # Ensure the server instance is resolved
        $Server = Resolve-KestrunServer -Server $Server

        $exists = Test-KrRoute -Path $Pattern -Verb $Verbs

        if ($exists) {
            if ($AllowDuplicate -or $DuplicateAction -eq 'Allow') {
                Write-KrWarningLog -Message "Route '{Path}' ({Verbs}) already exists; adding another." -Values $Pattern, ($Verbs -join ',')
            } elseif ($DuplicateAction -eq 'Skip') {
                Write-KrVerboseLog -Message "Route '{Path}' ({Verbs}) exists; skipping." -Values $Pattern, ($Verbs -join ',')
                return
            } elseif ($DuplicateAction -eq 'Warn') {
                Write-KrWarningLog -Message "Route '{Path}' ({Verbs}) already exists." -Values $Pattern, ($Verbs -join ',')
            } else {
                throw [System.InvalidOperationException]::new(
                    "Route '$Pattern' with method(s) $($Verbs -join ',') already exists.")
            }
        }

        # -- if Options parameter is used, we can skip the rest of the parameters
        if ($PSCmdlet.ParameterSetName -ne 'Options') {
            $Options = [Kestrun.Hosting.Options.MapRouteOptions]::new()
            $Options.HttpVerbs = $Verbs
            $Options.Pattern = $Pattern
            $Options.ExtraImports = $ExtraImports
            $Options.ExtraRefs = $ExtraRefs
            if ($null -ne $AuthorizationSchema) {
                $Options.RequireSchemes = $AuthorizationSchema
            }
            if ($null -ne $AuthorizationPolicy) {
                $Options.RequirePolicies = $AuthorizationPolicy
            }

            if ($null -ne $Arguments) {
                $dict = [System.Collections.Generic.Dictionary[string, object]]::new()
                foreach ($key in $Arguments.Keys) {
                    $dict[$key] = $Arguments[$key]
                }
                $Options.Arguments = $dict
            }
            switch ($PSCmdlet.ParameterSetName) {
                'ScriptBlock' {
                    $Options.Language = [Kestrun.Scripting.ScriptLanguage]::PowerShell
                    $Options.Code = $ScriptBlock.ToString()
                }
                'Code' {
                    $Options.Language = $Language
                    $Options.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' {
                            $Options.ValidateCodeSettings.Language = [Kestrun.Scripting.ScriptLanguage]::PowerShell
                        }
                        '.cs' {
                            $Options.ValidateCodeSettings.Language = [Kestrun.Scripting.ScriptLanguage]::CSharp
                        }
                        '.vb' {
                            $Options.ValidateCodeSettings.Language = [Kestrun.Scripting.ScriptLanguage]::VisualBasic
                        }
                        default {
                            throw "Unsupported '$extension' code file extension."
                        }
                    }
                    $Options.ValidateCodeSettings.Code = Get-Content -Path $CodeFilePath -Raw
                }
            }
        } else {
            Write-Verbose 'Using provided MapRouteOptions instance.'
        }
        if ($Server.RouteGroupStack.Count -gt 0) {
            $grp = $Server.RouteGroupStack.Peek()
            $Options = _KrMerge-MRO -Parent $grp -Child $Options
        }
        [Kestrun.Hosting.KestrunHostMapExtensions]::AddMapRoute($Server, $Options) | Out-Null

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