Custom Error Handler
Use OpenAPI error schemas with a custom PowerShell runtime error response script for OpenAPI routes.
Full source
File: pwsh/tutorial/examples/10.26-OpenAPI-Custom-Error-Handler.ps1
<#
Sample: OpenAPI Custom Error Handler
Purpose: Demonstrate OpenAPI error schema + custom PowerShell runtime error response script.
File: 10.26-OpenAPI-Custom-Error-Handler.ps1
#>
param(
[int]$Port = 5000,
[IPAddress]$IPAddress = [IPAddress]::Loopback
)
New-KrLogger | Add-KrSinkConsole |
Set-KrLoggerLevel -Value Debug |
Register-KrLogger -Name 'console' -SetAsDefault
New-KrServer -Name 'OpenAPI Custom Error Handler'
Add-KrEndpoint -Port $Port -IPAddress $IPAddress
# OpenAPI metadata
Add-KrOpenApiInfo -Title 'Orders API - Custom Error Handler' -Version '1.0.0' -Description 'OpenAPI routes with a custom runtime error response script.'
Set-KrOpenApiErrorSchema -Name 'ApiError' -ContentType @('application/problem+json', 'application/json')
# Runtime custom error response script
Set-KrPowerShellErrorResponse -ScriptBlock {
$payload = [ordered]@{
status = $StatusCode
title = 'Request failed'
detail = $ErrorMessage
path = [string]$Context.Request.Path
timestamp = (Get-Date).ToUniversalTime().ToString('o')
}
Write-KrJsonResponse -InputObject $payload -StatusCode $StatusCode -ContentType 'application/problem+json'
}
[OpenApiSchemaComponent(RequiredProperties = ('id', 'sku', 'quantity'))]
class OrderDto {
[OpenApiProperty(Description = 'Order identifier', Example = 1001)]
[int]$id
[OpenApiProperty(Description = 'Item sku', Example = 'SKU-RED-001')]
[string]$sku
[OpenApiProperty(Description = 'Quantity requested', Example = 2)]
[int]$quantity
}
[OpenApiSchemaComponent(RequiredProperties = ('status', 'title', 'detail', 'path', 'timestamp'))]
class ApiError {
[OpenApiProperty(Description = 'HTTP status code', Example = 500)]
[int]$status
[OpenApiProperty(Description = 'Short error summary', Example = 'Request failed')]
[string]$title
[OpenApiProperty(Description = 'Detailed error message', Example = 'Unhandled exception')]
[string]$detail
[OpenApiProperty(Description = 'Request path', Example = '/orders/13')]
[string]$path
[OpenApiProperty(Description = 'UTC timestamp', Example = '2026-02-17T10:11:12.0000000Z')]
[OpenApiDateTime]$timestamp
}
[OpenApiSchemaComponent(RequiredProperties = ('sku', 'quantity'))]
class CreateOrderRequest {
[OpenApiProperty(Description = 'Item sku', Example = 'SKU-RED-001')]
[string]$sku
[OpenApiProperty(Description = 'Quantity requested', Example = 2)]
[int]$quantity
}
Enable-KrConfiguration
<#
.SYNOPSIS
Get an order by ID.
.DESCRIPTION
Returns a sample order payload and raises a runtime exception for order 13 to demonstrate
the custom PowerShell error response handler on an OpenAPI route.
.PARAMETER orderId
The order identifier from the route path.
#>
function getOrder {
[OpenApiPath(HttpVerb = 'get', Pattern = '/orders/{orderId}', Tags = ('orders'))]
[OpenApiResponse(StatusCode = '200', Description = 'Order found', Schema = [OrderDto], ContentType = 'application/json')]
[OpenApiResponse(StatusCode = '500', Description = 'Server error', Schema = [ApiError], ContentType = ('application/problem+json', 'application/json'))]
param(
[OpenApiParameter(In = [OaParameterLocation]::Path, Required = $true)]
[int]$orderId
)
if ($orderId -eq 13) {
throw [System.InvalidOperationException]::new('Order service unavailable for order 13.')
}
Write-KrResponse -InputObject @{
id = $orderId
sku = 'SKU-RED-001'
quantity = 2
} -StatusCode 200
}
<#
.SYNOPSIS
Create a new order.
.DESCRIPTION
Accepts an OpenAPI-described JSON request body and returns a created order. Invalid quantity
raises a runtime exception to exercise the custom error response script.
.PARAMETER body
The order creation payload bound from the request body.
#>
function createOrder {
[OpenApiPath(HttpVerb = 'post', Pattern = '/orders', Tags = ('orders'))]
[OpenApiResponse(StatusCode = '201', Description = 'Order created', Schema = [OrderDto], ContentType = 'application/json')]
[OpenApiResponse(StatusCode = '415', Description = 'Unsupported media type', Schema = [ApiError], ContentType = ('application/problem+json', 'application/json'))]
[OpenApiResponse(StatusCode = '500', Description = 'Server error', Schema = [ApiError], ContentType = ('application/problem+json', 'application/json'))]
param(
[OpenApiRequestBody(Description = 'Order payload', Required = $true, ContentType = 'application/json')]
[CreateOrderRequest]$body
)
if ($body.quantity -le 0) {
throw [System.ArgumentOutOfRangeException]::new('quantity', 'Quantity must be greater than zero.')
}
Write-KrResponse -InputObject @{
id = 2001
sku = $body.sku
quantity = $body.quantity
} -StatusCode 201
}
Add-KrOpenApiRoute
Add-KrApiDocumentationRoute -DocumentType Swagger
Build-KrOpenApiDocument | Out-Null
Test-KrOpenApiDocument | Out-Null
Start-KrServer -CloseLogsOnExit
Step-by-step
- Server and logger: Create the server, endpoint, and console logger.
- OpenAPI metadata: Add OpenAPI info and set the error schema contract with
Set-KrOpenApiErrorSchema. - Custom runtime handler: Register
Set-KrPowerShellErrorResponsewith a scriptblock that builds a consistent ProblemDetails-style payload. - Schema components: Define
OrderDto,CreateOrderRequest, andApiErroras reusable OpenAPI components. - GET route: Add
/orders/{orderId}with OpenAPI responses and throw for a specific ID to trigger the custom error path. - POST route: Add
/orderswith JSON request body and OpenAPI 415/500 error responses. - OpenAPI routes: Expose
/openapi/*and Swagger docs, then build and validate the document. - Run: Start the server and verify both success and error behavior.
Try it
# Success path
curl -i http://127.0.0.1:5000/orders/1
# Runtime exception path (custom 500 payload)
curl -i http://127.0.0.1:5000/orders/13
# Media type contract error (custom 415 payload)
curl -i -X POST http://127.0.0.1:5000/orders \
-H "Content-Type: text/plain" \
-d "bad"
# OpenAPI document
curl -i http://127.0.0.1:5000/openapi/v3.1/openapi.json
PowerShell equivalent for POST:
Invoke-WebRequest -Uri http://127.0.0.1:5000/orders `
-Method Post `
-ContentType 'text/plain' `
-Body 'bad' `
-SkipHttpErrorCheck | Select-Object StatusCode, Content
Key points
Set-KrOpenApiErrorSchemacontrols the OpenAPI contract for generated/standardized error responses.Set-KrPowerShellErrorResponsecontrols runtime payload shape when PowerShell route execution hits an error path.- This pattern is OpenAPI-focused: your route docs and runtime errors stay aligned around a shared
ApiErrorschema.
Troubleshooting
- 415 does not return JSON: send
Accept: application/jsonin your request while validating payload shape. - Custom payload not applied: ensure
Set-KrPowerShellErrorResponseruns beforeStart-KrServerand the scriptblock does not throw. - OpenAPI missing error schema: verify
Set-KrOpenApiErrorSchemais configured beforeBuild-KrOpenApiDocument.
References
- Add-KrOpenApiInfo
- Set-KrOpenApiErrorSchema
- Set-KrPowerShellErrorResponse
- OpenApiPath
- OpenApiRequestBody
- OpenApiResponse
- OpenApiSchemaComponent
- Add-KrOpenApiRoute
- Build-KrOpenApiDocument
- Test-KrOpenApiDocument
- Add-KrApiDocumentationRoute
- OpenAPI Runtime Error Payload Customization
- OpenAPI Guide
Previous / Next
Previous: Additional and Pattern Properties Next: Razor