Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add IfExists parameter for Routes to define if a second Route should Error, Overwrite or be Skipped #1042

Merged
merged 2 commits into from
Dec 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions docs/Tutorials/Routes/Overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,71 @@ Get-PodeRoute -EndpointName Admin

The [`Get-PodeStaticRoute`](../../../Functions/Routes/Get-PodeStaticRoute) function works in the same way as above - but with no `-Method` parameter.

## If Exists Preference

By default when you try and add a Route with the same Method and Path twice, Pode will throw an error when attempting to add the second Route.

You can alter this behaviour by using the `-IfExists` parameter on several of the Route functions:

* [`Add-PodeRoute`](../../../Functions/Routes/Add-PodeRoute)
* [`Add-PodeStaticRoute`](../../../Functions/Routes/Add-PodeStaticRoute)
* [`Add-PodeSignalRoute`](../../../Functions/Routes/Add-PodeSignalRoute)
* [`Add-PodeRouteGroup`](../../../Functions/Routes/Add-PodeRouteGroup)
* [`Add-PodeStaticRouteGroup`](../../../Functions/Routes/Add-PodeStaticRouteGroup)
* [`Add-PodeSignalRouteGroup`](../../../Functions/Routes/Add-PodeSignalRouteGroup)
* [`Use-PodeRoutes`](../../../Functions/Routes/Use-PodeRoutes)

Or you can alter the global default preference for all Routes using [`Set-PodeRouteIfExistsPreference`](../../../Functions/Routes/Set-PodeRouteIfExistsPreference).

This parameter accepts the following options:

| Option | Description |
| ------ | ----------- |
| Default | This will use the `-IfExists` value from higher up the hierarchy (as defined see below) - if none defined, Error is the final default |
| Error | Throw an error if the Route already exists |
| Overwrite | Delete the existing Route if one exists, and then recreate the Route with the new definition |
| Skip | Skip over adding the Route if it already exists |

and the following hierarchy is used when deciding which behaviour to use. At each step if the value defined is `Default` then check the next value in the hierarchy:

1. Use the value defined directly on the Route, such as [`Add-PodeRoute`](../../../Functions/Routes/Add-PodeRoute)
2. Use the value defined on a Route Group, such as [`Add-PodeRouteGroup`](../../../Functions/Routes/Add-PodeRouteGroup)
3. Use the value defined on [`Use-PodeRoutes`](../../../Functions/Routes/Use-PodeRoutes)
4. Use the value defined from [`Set-PodeRouteIfExistsPreference`](../../../Functions/Routes/Set-PodeRouteIfExistsPreference)
5. Throw an error if the Route already exists

For example, the following will now skip attempting to add the second Route because it already exists; meaning the value returned from `http://localhost:8080` is `1` not `2`:

```powershell
Start-PodeServer {
Add-PodeEndpoint -Address * -Port 8080 -Protocol Http

Add-PodeRoute -Method Get -Path '/' -ScriptBlock {
Write-PodeJsonResponse -Value @{ Result = 1 }
}

Add-PodeRoute -Method Get -Path '/' -IfExists Skip -ScriptBlock {
Write-PodeJsonResponse -Value @{ Result = 2 }
}
}
```

Or, we could use Overwrite and the value returned will now be `2` not `1`:

```powershell
Start-PodeServer {
Add-PodeEndpoint -Address * -Port 8080 -Protocol Http

Add-PodeRoute -Method Get -Path '/' -ScriptBlock {
Write-PodeJsonResponse -Value @{ Result = 1 }
}

Add-PodeRoute -Method Get -Path '/' -IfExists Overwrite -ScriptBlock {
Write-PodeJsonResponse -Value @{ Result = 2 }
}
}
```

## Grouping

If you have a number of Routes that all share the same base path, middleware, authentication, or other parameters, then you can add these Routes within a Route Group (via [`Add-PodeRouteGroup`](../../../Functions/Routes/Add-PodeRouteGroup)) to share the parameter values:
Expand Down
4 changes: 4 additions & 0 deletions src/Pode.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@
'Add-PodeRouteGroup',
'Add-PodeStaticRouteGroup',
'Add-PodeSignalRouteGroup',
'Set-PodeRouteIfExistsPreference',
'Test-PodeRoute',
'Test-PodeStaticRoute',
'Test-PodeSignalRoute',

# handlers
'Add-PodeHandler',
Expand Down
7 changes: 7 additions & 0 deletions src/Private/Context.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,13 @@ function New-PodeContext
IsDynamic = $false
}

# pode default preferences
$ctx.Server.Preferences = @{
Routes = @{
IfExists = $null
}
}

# routes for pages and api
$ctx.Server.Routes = @{
'delete' = [ordered]@{}
Expand Down
61 changes: 51 additions & 10 deletions src/Private/Routes.ps1
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
function Test-PodeRoute
function Test-PodeRouteFromRequest
{
param (
[Parameter(Mandatory=$true)]
Expand Down Expand Up @@ -395,9 +395,9 @@ function Get-PodeStaticRouteDefaults
)
}

function Test-PodeRouteAndError
function Test-PodeRouteInternal
{
param (
param(
[Parameter(Mandatory=$true)]
[string]
$Method,
Expand All @@ -412,15 +412,34 @@ function Test-PodeRouteAndError

[Parameter()]
[string]
$Address
$Address,

[switch]
$ThrowError
)

$found = @($PodeContext.Server.Routes[$Method][$Path])
# check the routes
$found = $false
$routes = @($PodeContext.Server.Routes[$Method][$Path])

if (($found | Where-Object { ($_.Endpoint.Protocol -ieq $Protocol) -and ($_.Endpoint.Address -ieq $Address) } | Measure-Object).Count -eq 0) {
return
foreach ($route in $routes) {
if (($route.Endpoint.Protocol -ieq $Protocol) -and ($route.Endpoint.Address -ieq $Address)) {
$found = $true
break
}
}

# skip if not found
if (!$found) {
return $false
}

# do we want to throw an error if found, or skip?
if (!$ThrowError) {
return $true
}

# throw error
$_url = $Protocol
if (![string]::IsNullOrEmpty($_url) -and ![string]::IsNullOrWhiteSpace($Address)) {
$_url = "$($_url)://$($Address)"
Expand All @@ -432,9 +451,8 @@ function Test-PodeRouteAndError
if ([string]::IsNullOrEmpty($_url)) {
throw "[$($Method)] $($Path): Already defined"
}
else {
throw "[$($Method)] $($Path): Already defined for $($_url)"
}

throw "[$($Method)] $($Path): Already defined for $($_url)"
}

function Convert-PodeFunctionVerbToHttpMethod
Expand Down Expand Up @@ -587,4 +605,27 @@ function ConvertTo-PodeMiddleware
})

return $converted
}

function Get-PodeRouteIfExistsPreference
{
# from route groups
$groupPref = $RouteGroup.IfExists
if (![string]::IsNullOrWhiteSpace($groupPref) -and ($groupPref -ine 'default')) {
return $groupPref
}

# from Use-PodeRoute
if (![string]::IsNullOrWhiteSpace($RouteIfExists) -and ($RouteIfExists -ine 'default')) {
return $RouteIfExists
}

# global preference
$globalPref = $PodeContext.Server.Preferences.Routes.IfExists
if (![string]::IsNullOrWhiteSpace($globalPref) -and ($globalPref -ine 'default')) {
return $globalPref
}

# final global default
return 'Error'
}
Loading