RequestBody Components

Create reusable request body components that can be shared across multiple endpoints.

Full source

File: [pwsh/tutorial/examples/10.3-OpenAPI-Component-RequestBody.ps1][10.3-OpenAPI-Component-RequestBody.ps1]

<#
    Sample: OpenAPI RequestBody Components
    Purpose: Demonstrate reusable request body components with multiple content types.
    File:    10.3-OpenAPI-Component-RequestBody.ps1
    Notes:   Shows class inheritance, component wrapping, and content type negotiation.
#>
param(
    [int]$Port = 5000,
    [IPAddress]$IPAddress = [IPAddress]::Loopback
)


# --- Logging / Server ---

New-KrLogger | Add-KrSinkConsole |
    Set-KrLoggerLevel -Value Debug |
    Register-KrLogger -Name 'console' -SetAsDefault

New-KrServer -Name 'OpenAPI RequestBody Component'

Add-KrEndpoint -Port $Port -IPAddress $IPAddress
# =========================================================
#                 TOP-LEVEL OPENAPI
# =========================================================

Add-KrOpenApiInfo -Title 'RequestBody Component API' `
    -Version '1.0.0' `
    -Description 'Demonstrates reusable request body components.'

# =========================================================
#                      COMPONENT SCHEMAS
# =========================================================

[OpenApiSchemaComponent(RequiredProperties = ('productName', 'price'))]
class Product {
    [OpenApiPropertyAttribute(Description = 'Unique product identifier', Format = 'int64', Example = 1)]
    [long]$id

    [OpenApiPropertyAttribute(Description = 'Product name', Example = 'Laptop')]
    [string]$productName

    [OpenApiPropertyAttribute(Description = 'Product price', Format = 'double', Example = 999.99)]
    [double]$price

    [OpenApiPropertyAttribute(Description = 'Product description', Example = 'A high-performance laptop')]
    [string]$description

    [OpenApiPropertyAttribute(Description = 'Stock quantity', Minimum = 0, Example = 50)]
    [int]$stock

    [OpenApiPropertyAttribute(Description = 'Creation timestamp', Format = 'date-time', Example = '2024-01-01T12:00:00Z')]
    [string]$createdAt

    [OpenApiPropertyAttribute(Description = 'Update timestamp', Example = '2024-01-01T12:00:00Z')]
    [datetime]$updatedAt
}

# =========================================================
#        COMPONENT REQUEST BODIES (Reusable)
# =========================================================

# CreateProductRequest: RequestBody component that wraps Product schema
[OpenApiRequestBodyComponent(
    Description = 'Product creation payload. Supports JSON and form data.',
    Required = $true,
    ContentType = ('application/json', 'application/x-www-form-urlencoded', 'application/xml', 'application/yaml'))]
[Product]$CreateProductRequest

# UpdateProductRequest: RequestBody component that wraps UpdateProduct schema
[OpenApiSchemaComponent(RequiredProperties = ('productName', 'price'))]
class UpdateProductRequest {
    [OpenApiPropertyAttribute(Description = 'Product name', Example = 'Laptop Pro')]
    [string]$productName

    [OpenApiPropertyAttribute(Description = 'Product price', Format = 'double', Example = 1299.99)]
    [double]$price

    [OpenApiPropertyAttribute(Description = 'Product description')]
    [string]$description

    [OpenApiPropertyAttribute(Description = 'Stock quantity', Minimum = 0)]
    [int]$stock
}


[OpenApiRequestBodyComponent(    Description = 'Product update payload.', Required = $true, ContentType = 'application/json')]
[OpenApiRequestBodyExampleRef(Key = 'general_entry', ReferenceId = 'BuyGeneralTicketsRequestExample', ContentType = 'application/json')]
[UpdateProductRequest]$UpdateProductRequest


# --- Component examples (stored under components/examples) ---
New-KrOpenApiExample -Summary 'General entry ticket' -Value ([ordered]@{
        ticketType = 'general'
        ticketDate = '2023-09-07'
        email = 'todd@example.com'
    }) | Add-KrOpenApiComponent -Name 'BuyGeneralTicketsRequestExample'



[OpenApiRequestBodyComponent( Description = 'A simple string payload.', Required = $true, ContentType = 'application/json')]
[ValidateLength(4, 100)]
[OpenApiString]$SimpleStringRequestBody

[OpenApiRequestBodyComponent( Description = 'A simple date payload.', Required = $true, ContentType = 'application/json')]
[OpenApiDate]$SimpleDateRequestBody
# =========================================================
#                 ROUTES / OPERATIONS
# =========================================================

Enable-KrConfiguration

Add-KrApiDocumentationRoute -DocumentType Swagger
Add-KrApiDocumentationRoute -DocumentType Redoc


<#
.SYNOPSIS
    Create a new product.
.DESCRIPTION
    Creates a new product using the reusable CreateProductRequest component.
.PARAMETER body
    Product creation request
#>
function createProduct {
    [OpenApiPath(HttpVerb = 'post', Pattern = '/products')]
    [OpenApiResponse(StatusCode = '201', Description = 'Product created successfully', Schema = [Product], ContentType = ('application/json', 'application/xml', 'application/x-www-form-urlencoded'))]
    [OpenApiResponse(StatusCode = '400', Description = 'Invalid input')]
    param(
        [OpenApiRequestBodyRef(ReferenceId = 'CreateProductRequest')]
        [Product]$body
    )

    if (-not $body.productName -or -not $body.price) {
        Write-KrResponse @{error = 'productName and price are required' } -StatusCode 400
        return
    }

    $response = [Product]@{
        id = 1
        productName = $body.productName
        price = $body.price
        description = $body.description
        stock = $body.stock -as [int]
        createdAt = (Get-Date).ToUniversalTime().ToString('o')
    }

    Write-KrResponse $response -StatusCode 201
}

<#
.SYNOPSIS
    Update an existing product.
.DESCRIPTION
    Updates product details using the reusable UpdateProductRequest component.
.PARAMETER productId
    The product ID to update
.PARAMETER body
    Product update request
#>
function updateProduct {
    [OpenApiPath(HttpVerb = 'put', Pattern = '/products/{productId}')]
    [OpenApiResponse(StatusCode = '200', Description = 'Product updated successfully', Schema = [Product], ContentType = ('application/json', 'application/xml', 'application/x-www-form-urlencoded'))]
    [OpenApiResponse(StatusCode = '400', Description = 'Invalid input')]
    [OpenApiResponse(StatusCode = '404', Description = 'Product not found')]
    param(
        [OpenApiParameter(In = [OaParameterLocation]::Path, Required = $true)]
        [int]$productId,
        [OpenApiRequestBodyRef(ReferenceId = 'UpdateProductRequest')]
        $body
    )

    if (-not $body.productName -or -not $body.price) {
        Write-KrJsonResponse @{error = 'productName and price are required' } -StatusCode 400
        return
    }

    $response = [Product]@{
        id = $productId
        productName = $body.productName
        price = $body.price
        description = $body.description
        stock = $body.stock -as [int]
        updatedAt = (Get-Date).ToUniversalTime().ToString('o')
    }

    Write-KrResponse $response -StatusCode 200
}

# =========================================================
#                OPENAPI DOC ROUTE / BUILD
# =========================================================

Add-KrOpenApiRoute

Build-KrOpenApiDocument
# Test and log OpenAPI document validation result
if (Test-KrOpenApiDocument) {
    Write-KrLog -Level Information -Message 'OpenAPI document built and validated successfully.'
} else {
    Write-KrLog -Level Error -Message 'OpenAPI document validation failed.'
}

# =========================================================
#                      RUN SERVER
# =========================================================


Start-KrServer -CloseLogsOnExit

Step-by-step

  1. Logging: Register console logger as default.
  2. Server: Create server named ‘OpenAPI RequestBody Component’.
  3. OpenAPI info: Add title and description.
  4. Define Product schema (id, productName, price, description, stock, createdAt).
  5. Create CreateProductRequest component wrapping Product (JSON + form data) via OpenApiRequestBodyComponent.
  6. Create UpdateProductRequest component (JSON) with required name/price via OpenApiRequestBodyComponent.
  7. POST endpoint: Accept CreateProductRequest (via OpenApiRequestBodyRef) and return Product (201) or 400.
  8. PUT endpoint: Accept UpdateProductRequest (via OpenApiRequestBodyRef) and return updated Product (200) or 400/404.
  9. Build and test OpenAPI document.

Try it

# Create product using CreateProductRequest component
curl -X POST http://127.0.0.1:5000/products `
  -H "Content-Type: application/json" `
  -d '{
    "productName": "Gaming Laptop",
    "price": 1599.99,
    "description": "High-performance laptop for gaming",
    "stock": 25
  }'

# Update product using UpdateProductRequest component
curl -X PUT http://127.0.0.1:5000/products/1 `
  -H "Content-Type: application/json" `
  -d '{
    "productName": "Gaming Laptop Pro",
    "price": 1899.99,
    "stock": 15
  }'

# View request body components in OpenAPI spec
curl http://127.0.0.1:5000/openapi/v1/openapi.json | jq '.components.requestBodies'

PowerShell create example:

$createPayload = @{
    productName = 'Gaming Laptop'
    price       = 1599.99
    description = 'High-performance laptop for gaming'
    stock       = 25
} | ConvertTo-Json

Invoke-WebRequest -Uri http://127.0.0.1:5000/products `
    -Method Post `
    -Headers @{ 'Content-Type' = 'application/json' } `
    -Body $createPayload

RequestBody Component Attributes

[OpenApiRequestBodyComponent(
    Description = 'Product creation payload',
    Required = $true,
    ContentType = ('application/json', 'application/x-www-form-urlencoded')
)]
[Product]$CreateProductRequest
  • Description: Human-readable description of the request body.
  • Required: Set to $true to mark the request body as required.
  • ContentType: Single or array of supported MIME types (e.g., 'application/json', 'application/xml').
  • Type: The variable type becomes the request body schema (typically a schema component type).

Key Concepts

  • Reusability: Define once, reference with OpenApiRequestBodyRef in multiple endpoints.
  • Multiple Content Types: Create supports JSON and form; update uses JSON.
  • Schema Inheritance: Use PowerShell class inheritance for schemas when needed (request bodies reference those schema types).
  • Typed responses: POST/PUT return the Product schema with server-generated fields (id, createdAt).
  • Write-KrResponse content type: Automatically chooses JSON/XML and respects [OpenApiResponse] ContentType when serializing responses.
  • Separation: Distinct request components for different operations (Create vs. Update).

Attribute decoration cheatsheet

  • [OpenApiPath]: Declares verb + route; drives OpenAPI operation generation.
  • [OpenApiResponse]: Documents status codes, descriptions, schemas, and content types.
  • [OpenApiRequestBodyRef] / [OpenApiRequestBody]: Reuse component bodies or document inline payloads.
  • [OpenApiSchemaComponent] / [OpenApiPropertyAttribute]: Define reusable schemas and property constraints (description, format, examples, validation).

Troubleshooting

Issue: Request body component not referenced correctly.

  • Solution: Ensure [OpenApiRequestBodyRef(ReferenceId = '...')] matches the request-body component name (the annotated variable name, or the Key override on [OpenApiRequestBodyComponent]).

Issue: Class inheritance not working with request body components.

  • Solution: Use colon syntax class CreateRequest:BaseSchema {} to inherit properties from base schema.

Issue: Multiple content types not being accepted.

  • Solution: Specify all content types in ContentType parameter: ContentType = ('application/json', 'application/xml').

References


Previous / Next

Previous: Component Schemas Next: Parameter Components