Component Schemas

Define reusable request and response schemas using PowerShell classes decorated with OpenAPI attributes.

Full source

File: [pwsh/tutorial/examples/10.2-OpenAPI-Component-Schema.ps1][10.2-OpenAPI-Component-Schema.ps1]

<#
    Sample: OpenAPI Component Schemas
    Purpose: Demonstrate reusable component schemas for request and response payloads.
    File:    10.2-OpenAPI-Component-Schema.ps1
    Notes:   Shows schema component definition with required fields, property attributes, 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

$srv = New-KrServer -Name 'OpenAPI Component Schema' -PassThru

Add-KrEndpoint -Port $Port -IPAddress $IPAddress

# =========================================================
#                 TOP-LEVEL OPENAPI
# =========================================================

Add-KrOpenApiInfo -Title 'Component Schema API' `
    -Version '1.0.0' `
    -Description 'Demonstrates reusable component schemas passed as parameters and returned as responses.'

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

# Request schema: User input for creating a user
[OpenApiSchemaComponent(Required = ('firstName', 'lastName', 'email'))]
class CreateUserRequest {
    [OpenApiPropertyAttribute(Description = 'First name of the user', Example = 'John')]
    [string]$firstName

    [OpenApiPropertyAttribute(Description = 'Last name of the user', Example = 'Doe')]
    [string]$lastName

    [OpenApiPropertyAttribute(Description = 'Email address', Format = 'email', Example = 'john.doe@example.com')]
    [string]$email

    [OpenApiPropertyAttribute(Description = 'User age', Minimum = 0, Maximum = 150, Example = 30)]
    [int]$age
}

# Response schema: User data returned from server
[OpenApiSchemaComponent(Required = ('id', 'firstName', 'lastName', 'email'))]
class UserResponse {
    [OpenApiPropertyAttribute(Description = 'Unique user identifier', Format = 'int64', Example = 1)]
    [long]$id

    [OpenApiPropertyAttribute(Description = 'First name', Example = 'John')]
    [string]$firstName

    [OpenApiPropertyAttribute(Description = 'Last name', Example = 'Doe')]
    [string]$lastName

    [OpenApiPropertyAttribute(Description = 'Email address', Format = 'email', Example = 'john.doe@example.com')]
    [string]$email

    [OpenApiPropertyAttribute(Description = 'User age', Example = 30)]
    [int]$age

    [OpenApiPropertyAttribute(Description = 'ISO 8601 creation timestamp', Format = 'date-time')]
    [string]$createdAt
}

# =========================================================
#                 ROUTES / OPERATIONS
# =========================================================

Enable-KrConfiguration

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

# POST endpoint: Accept CreateUserRequest, return UserResponse
<#
.SYNOPSIS
    Create a new user.
.DESCRIPTION
    Accepts user information and returns the created user with an assigned ID.
.PARAMETER body
    User creation request payload
#>
function createUser {
    [OpenApiPath(HttpVerb = 'post', Pattern = '/users')]
    [OpenApiResponse(StatusCode = '201', Description = 'User created successfully', Schema = [UserResponse], ContentType = ('application/json', 'application/xml', 'application/yaml'))]
    [OpenApiResponse(StatusCode = '400', Description = 'Invalid input')]
    param(
        [OpenApiRequestBody(ContentType = ('application/json', 'application/xml', 'application/yaml', 'application/x-www-form-urlencoded'))]
        [CreateUserRequest]$body
    )

    # Simple validation
    if (-not $body.firstName -or -not $body.lastName -or -not $body.email) {
        Write-KrJsonResponse @{error = 'firstName, lastName, and email are required' } -StatusCode 400
        return
    }

    # Create response
    $response = @{
        id = 1
        firstName = $body.firstName
        lastName = $body.lastName
        email = $body.email
        age = $body.age -as [int]
        createdAt = (Get-Date).ToUniversalTime().ToString('o')
    }

    Write-KrResponse $response -StatusCode 201
}

# GET endpoint: Return a user by ID as UserResponse
<#
.SYNOPSIS
    Get user by ID.
.DESCRIPTION
    Retrieves a user resource by its identifier.
.PARAMETER userId
    The user ID to retrieve
#>
function getUser {
    [OpenApiPath(HttpVerb = 'get', Pattern = '/users/{userId}')]
    [OpenApiResponse(StatusCode = '200', Description = 'User found', Schema = [UserResponse], ContentType = ('application/json', 'application/xml', 'application/yaml'))]
    [OpenApiResponse(StatusCode = '404', Description = 'User not found')]
    param(
        [OpenApiParameter(In = [OaParameterLocation]::Path, Required = $true)]
        [int]$userId
    )

    # Mock user data
    [UserResponse]$response = [UserResponse]::new()
    $response.id = $userId
    $response.firstName = 'John'
    $response.lastName = 'Doe'
    $response.email = 'john.doe@example.com'
    $response.age = 30
    $response.createdAt = (Get-Date).AddDays(-1).ToUniversalTime().ToString('o')

    Write-KrResponse $response -StatusCode 200
}

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

Add-KrOpenApiRoute

Build-KrOpenApiDocument
Test-KrOpenApiDocument

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


Start-KrServer -Server $srv -CloseLogsOnExit


Step-by-step

  1. Logging: Register console logger as default.
  2. Server: Create server named ‘OpenAPI Component Schema’.
  3. OpenAPI info: Add title, version, and description.
  4. Define CreateUserRequest schema with required fields (firstName, lastName, email).
  5. Define UserResponse schema with all user fields including timestamps.
  6. POST endpoint: Accept CreateUserRequest body (JSON/XML/YAML/form) and return UserResponse (201) or 400.
  7. GET endpoint: Return UserResponse (200) for a given user ID or 404 when missing.
  8. Build and test OpenAPI document, then start server.

Try it

# Create a new user (POST with CreateUserRequest)
curl -X POST http://127.0.0.1:5000/users `
  -H "Content-Type: application/json" `
  -d '{
    "firstName": "Jane",
    "lastName": "Smith",
    "email": "jane.smith@example.com",
    "age": 28
  }'

# Get user by ID (returns UserResponse)
curl -i http://127.0.0.1:5000/users/1

# View the OpenAPI specification
curl http://127.0.0.1:5000/openapi/v1/openapi.json | ConvertFrom-Json | ConvertTo-Json

PowerShell equivalent for POST:

$body = @{
    firstName = 'Jane'
    lastName  = 'Smith'
    email     = 'jane.smith@example.com'
    age       = 28
} | ConvertTo-Json

Invoke-WebRequest -Uri http://127.0.0.1:5000/users `
    -Method Post `
    -Headers @{ 'Content-Type' = 'application/json' } `
    -Body $body | Select-Object StatusCode, Content

Schema Component Attributes

[OpenApiSchemaComponent(Required = ('firstName', 'lastName', 'email'))]
class CreateUserRequest {
    [OpenApiPropertyAttribute(Description = 'First name', Example = 'John')]
    [string]$firstName
}
  • Required: Comma-separated field names that must be present.
  • Description: Markdown-formatted component description.
  • OpenApiPropertyAttribute: Decorates individual properties with constraints (Example, Format, MinLength, MaxLength, Pattern, etc.).

Key Concepts

  • Request Schema: Defines the structure of incoming data (POST/PUT bodies).
  • Response Schema: Defines the structure of outgoing data.
  • Multiple content types: POST supports application/json, application/xml, application/yaml, and form data.
  • Reusability: Schemas are registered as OpenAPI components and can be referenced across multiple endpoints.
  • Validation: Use Required, ValidateLength, ValidateRange, ValidateSet on properties for constraints.
  • Examples: Populate Example attribute for clear documentation.
  • Write-KrResponse content type: Automatically chooses JSON/XML/YAML and supports application/x-www-form-urlencoded, respecting [OpenApiResponse] ContentType.

Attribute decoration cheatsheet

  • [OpenApiPath]: Declares the route/verb and wires the function into OpenAPI generation.
  • [OpenApiResponse]: Documents each status code, description, schema, and content types.
  • [OpenApiRequestBody]: Documents inline bodies (or use Ref variants to point to components).
  • [OpenApiSchemaComponent] / [OpenApiPropertyAttribute]: Define reusable schemas and property metadata (description, format, examples, validation ranges/sets).

Troubleshooting

Issue: Schema component not appearing in OpenAPI spec.

  • Solution: Ensure the class has [OpenApiSchemaComponent()] attribute and is referenced in a request body or response.

Issue: Required fields validation not working.

  • Solution: Add Required = ('field1', 'field2') parameter to [OpenApiSchemaComponent()] attribute.

Issue: Write-KrResponse returns wrong content type.

  • Solution: Verify Accept header in request matches one of the ContentType values in [OpenApiResponse] attribute.

References


Previous / Next

Previous: Hello World Next: RequestBody Components