Response Components

Create reusable response components for consistent response structures across multiple endpoints.

Full source

File: [pwsh/tutorial/examples/10.5-OpenAPI-Component-Response.ps1][10.5-OpenAPI-Component-Response.ps1]

<#
    Sample: OpenAPI Response Components
    Purpose: Demonstrate reusable response components with multiple response types per component.
    File:    10.5-OpenAPI-Component-Response.ps1
    Notes:   Shows inline responses, generic object responses, and schema references within attributes.
#>
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 Response Component' -PassThru

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

Add-KrOpenApiInfo -Title 'Response Component API' `
    -Version '1.0.0' `
    -Description 'Demonstrates reusable response components.'


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

[OpenApiSchemaComponent(Required = ('statusCode', 'message'))]
class ErrorResponse {
    [OpenApiPropertyAttribute(Description = 'HTTP status code', Example = 400)]
    [int]$statusCode

    [OpenApiPropertyAttribute(Description = 'Error message', Example = 'Invalid request')]
    [string]$message

    [OpenApiPropertyAttribute(Description = 'Error code identifier', Example = 'INVALID_INPUT')]
    [string]$code

    [OpenApiPropertyAttribute(Description = 'Additional error details')]
    [string]$details
}

[OpenApiSchemaComponent(Required = ('id', 'title', 'content'))]
class Article {
    [OpenApiPropertyAttribute(Description = 'Article ID', Format = 'int64', Example = 1)]
    [long]$id

    [OpenApiPropertyAttribute(Description = 'Article title', Example = 'Getting Started with OpenAPI')]
    [string]$title

    [OpenApiPropertyAttribute(Description = 'Article content', Example = 'This article covers...')]
    [string]$content

    [OpenApiPropertyAttribute(Description = 'Publication date', Format = 'date', Example = '2025-01-15')]
    [string]$publishedAt

    [OpenApiPropertyAttribute(Description = 'Author name', Example = 'John Doe')]
    [string]$author
}

[OpenApiSchemaComponent(Required = ('id', 'message', 'timestamp'))]
class SuccessResponse {
    [OpenApiPropertyAttribute(Description = 'Operation ID', Format = 'uuid')]
    [string]$id

    [OpenApiPropertyAttribute(Description = 'Success message', Example = 'Resource created successfully')]
    [string]$message

    [OpenApiPropertyAttribute(Description = 'Operation timestamp', Format = 'date-time')]
    [string]$timestamp
}

# =========================================================
#          COMPONENT RESPONSES (Reusable)
# =========================================================

# Response component for common success responses (200, 201)
[OpenApiResponseComponent(JoinClassName = '-', Description = 'Success responses')]
class SuccessResponses {
    [OpenApiResponseAttribute(Description = 'Operation completed successfully', ContentType = ('application/json', 'application/xml'))]
    [SuccessResponse]$OK

    [OpenApiResponseAttribute(Description = 'Resource created successfully' , ContentType = ('application/json', 'application/xml'))]
    [SuccessResponse]$Created
}


# Response component for common error responses (400, 404)
[OpenApiResponseComponent(JoinClassName = '-', Description = 'Error responses')]
class ErrorResponses {
    [OpenApiResponseAttribute(Description = 'Bad request - validation failed', ContentType = ('application/json', 'application/xml'))]
    [ErrorResponse]$BadRequest

    [OpenApiResponseAttribute(Description = 'Resource not found', ContentType = ('application/json', 'application/xml'))]
    [ErrorResponse]$NotFound
}

# Response component for article responses
[OpenApiResponseComponent( Description = 'Article responses')]
class ArticleResponses {
    [OpenApiResponseAttribute(Description = 'Article retrieved successfully', ContentType = ('application/json', 'application/xml'), inline = $true)]
    [Article]$ArticleResponsesOK

    [OpenApiResponseAttribute(Description = 'Article not found', ContentType = ('application/json', 'application/xml'))]
    [ErrorResponse] $ArticleResponsesNotFound

    [OpenApiResponseAttribute()]
    $objectResponse
}

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

Enable-KrConfiguration

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


<#
.SYNOPSIS
    Get article by ID.
.DESCRIPTION
    Retrieves a single article. Returns Article on success or ErrorResponse on failure.
.PARAMETER articleId
    The article ID to retrieve
.NOTES
    GET article endpoint
#>
function getArticle {
    [OpenApiPath(HttpVerb = 'get', Pattern = '/articles/{articleId}')]
    [OpenApiResponseRefAttribute(StatusCode = '200', ReferenceId = 'ArticleResponsesOK')]
    [OpenApiResponseRefAttribute(StatusCode = '404', ReferenceId = 'ArticleResponsesNotFound')]
    param(
        [OpenApiParameter(In = [OaParameterLocation]::Path, Required = $true, Description = 'Article ID to retrieve')]
        [int]$articleId
    )

    # Validate ID
    if ($articleId -le 0) {
        $myError = @{
            statusCode = 400
            message = 'Invalid article ID'
            code = 'INVALID_ID'
            details = 'Article ID must be a positive integer'
        }
        Write-KrJsonResponse $myError -StatusCode 400
        return
    }

    # Mock article data
    $article = [Article]@{
        id = $articleId
        title = 'Getting Started with OpenAPI'
        content = 'OpenAPI is a specification for building APIs...'
        publishedAt = '2025-01-15'
        author = 'John Doe'
    }

    Write-KrResponse $article -StatusCode 200
}



<#
.SYNOPSIS
    Create a new article.
.DESCRIPTION
    Creates a new article and returns success response or error.
.PARAMETER body
    Article data (title and content required)
.NOTES
    POST article endpoint
#>
function createArticle {
    [OpenApiPath(HttpVerb = 'post', Pattern = '/articles')]
    [OpenApiResponseRefAttribute(StatusCode = '201', ReferenceId = 'SuccessResponses-Created')]
    [OpenApiResponseRefAttribute(StatusCode = '400', ReferenceId = 'ErrorResponses-BadRequest')]
    param(
        [OpenApiRequestBody(ContentType = ('application/json', 'application/xml', 'application/x-www-form-urlencoded'))]
        [Article]$body
    )

    # Validate
    if (-not $body.title -or -not $body.content) {
        $myError = @{
            statusCode = 400
            message = 'Validation failed'
            code = 'VALIDATION_ERROR'
            details = 'title and content are required'
        }
        Write-KrJsonResponse $myError -StatusCode 400
        return
    }

    # Success response
    $success = [SuccessResponse]@{
        id = [System.Guid]::NewGuid().ToString()
        message = 'Article created successfully'
        timestamp = (Get-Date).ToUniversalTime().ToString('o')
    }

    Write-KrResponse $success -StatusCode 201
}

# DELETE article endpoint
<#
.SYNOPSIS
    Delete an article.
.DESCRIPTION
    Deletes an article and returns success or error response.
.PARAMETER articleId
    The article ID to delete
#>
function deleteArticle {
    [OpenApiPath(HttpVerb = 'delete', Pattern = '/articles/{articleId}')]
    [OpenApiResponseRefAttribute(StatusCode = '200', ReferenceId = 'SuccessResponses-OK')]
    [OpenApiResponseRefAttribute(StatusCode = '404', ReferenceId = 'ErrorResponses-NotFound')]
    param(
        [OpenApiParameter(In = [OaParameterLocation]::Path, Required = $true, Description = 'Article ID to delete')]
        [int]$articleId
    )

    $success = [SuccessResponse]@{
        id = [System.Guid]::NewGuid().ToString()
        message = "Article $articleId deleted successfully"
        timestamp = (Get-Date).ToUniversalTime().ToString('o')
    }

    Write-KrResponse $success -StatusCode 200
}

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

Add-KrOpenApiRoute

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

Start-KrServer -Server $srv -CloseLogsOnExit


Step-by-step

  1. Logging: Register console logger as default.
  2. Server: Create server named ‘OpenAPI Response Component’ and add endpoint.
  3. OpenAPI info: Add title and description.
  4. Schema components: Define ErrorResponse, Article, and SuccessResponse schema classes.
  5. Response component: Define ArticleResponses component with multiple response types:
    • $OK with inline response (inline = $true)
    • $NotFound with schema specified in attribute (Schema = [ErrorResponse])
    • $objectResponse with generic object (no type)
  6. Success response component: Define SuccessResponses component wrapping SuccessResponse.
  7. Error response component: Define ErrorResponses component wrapping ErrorResponse.
  8. GET endpoint: Reference response component properties using [OpenApiResponseRefAttribute].
  9. POST endpoint: Create article with success/error response references.
  10. DELETE endpoint: Delete article with success/error response references.
  11. Build and test: Generate and validate OpenAPI document.

Try it

# Get article (success response)
curl -i http://127.0.0.1:5000/articles/1

# Get article with invalid ID (error response)
curl -i http://127.0.0.1:5000/articles/-1

# Create article (success response)
curl -X POST http://127.0.0.1:5000/articles `
  -H "Content-Type: application/json" `
  -d '{
    "title": "My Article",
    "content": "Article content here"
  }'

# Create with missing fields (error response)
curl -X POST http://127.0.0.1:5000/articles `
  -H "Content-Type: application/json" `
  -d '{"title": "Missing content"}'

# Delete article (success response)
curl -X DELETE http://127.0.0.1:5000/articles/1

PowerShell create example:

$body = @{
    title   = 'My Article'
    content = 'Article content here'
} | ConvertTo-Json

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

Response Component Attributes

Basic Pattern:

[OpenApiResponseComponent(Description = 'Article responses')]
class ArticleResponses {
    [OpenApiResponseAttribute(Description = 'Article retrieved successfully', ContentType = ('application/json', 'application/xml'), Inline = $true)]
    [Article]$OK

    [OpenApiResponseAttribute(Description = 'Article not found', ContentType = ('application/json', 'application/xml'))]
    [ErrorResponse]$NotFound

    [OpenApiResponseAttribute()]
    $objectResponse
}

Attribute Properties:

  • [OpenApiResponseComponent()]: Marks a class as a reusable response component.
  • [OpenApiResponseAttribute(...)]: Decorates response properties with metadata.
    • Description: Human-readable response description.
    • ContentType: MIME types the response supports (e.g., ('application/json', 'application/xml')).
    • Inline: If $true, includes response inline; otherwise references the component.
    • Schema: Specifies schema type directly in attribute (alternative to property type).

Schema Definition Options:

  1. Type on property: [ErrorResponse]$NotFound — schema inferred from property type.
  2. Schema in attribute: Schema = [ErrorResponse] — schema specified in [OpenApiResponseAttribute].
  3. Generic object: $objectResponse — no type specified; represents untyped response.

Reference Pattern in Functions:

function getArticle {
    [OpenApiPath(HttpVerb = 'get', Pattern = '/articles/{articleId}')]
    [OpenApiResponseRefAttribute(StatusCode = '200', ReferenceId = 'ArticleResponsesOK')]
    [OpenApiResponseRefAttribute(StatusCode = '404', ReferenceId = 'ArticleResponsesNotFound')]
    param([int]$articleId)
}

Note: When JoinClassName = '-' is omitted, property names are used directly (e.g., ArticleResponsesOK). When included, property names are joined with - (e.g., ArticleResponses-OK).

Common Response Patterns

# Success confirmation (ID, message, timestamp)
[OpenApiSchemaComponent(Required = ('id', 'message', 'timestamp'))]
class SuccessResponse {
    [string]$id
    [string]$message
    [string]$timestamp
}

# Detailed error (code, message, field, details)
[OpenApiSchemaComponent(Required = ('code', 'message'))]
class ErrorDetail {
    [string]$code
    [string]$message
    [string]$field
    [string]$details
}

# Data response (with metadata)
[OpenApiSchemaComponent(Required = ('id', 'data'))]
class DataResponse {
    [string]$id
    [object]$data
    [string]$timestamp
}

Key Concepts

  • Response Components: Group related response types (success, error, specific) into reusable component classes.
  • Multiple Responses: Define multiple response types in a single component (OK, Created, NotFound, etc.).
  • Inline Responses: Use Inline = $true to include response definition inline instead of referencing a component.
  • Schema Definition Methods:
    • Type on property: [Article]$OK — schema inferred from PowerShell class type.
    • Attribute-based: Schema = [ErrorResponse] — schema specified in attribute.
    • Generic: $objectResponse — untyped response for flexible structures.
  • Content Negotiation: Specify multiple content types; Write-KrResponse auto-selects based on Accept header.
  • Reference Naming: Without JoinClassName = '-', use full property path in ReferenceId (e.g., ArticleResponsesOK).
  • Consistency: Reusable response components ensure uniform error handling and success patterns across endpoints.
  • Documentation: Response components automatically appear in OpenAPI components.responses section.

Attribute Decoration Cheatsheet

Purpose Attribute Usage
Response component class [OpenApiResponseComponent()] Marks class as reusable response definition
Response property [OpenApiResponseAttribute(...)] Decorates response property with metadata
Response reference [OpenApiResponseRefAttribute(StatusCode = '...', ReferenceId = '...')] References response component property in function
Inline response Inline = $true Includes response inline instead of referencing
Response schema [Article]$OK or Schema = [Article] Defines schema type for response
Generic response $objectResponse (no type) Represents untyped/flexible response
Content types ContentType = ('application/json', 'application/xml') MIME types supported by response

Troubleshooting

Issue: Response component reference not found.

  • Solution: Check ReferenceId matches pattern ClassName-PropertyName (with JoinClassName) or ClassNamePropertyName (without).

Issue: Schema not defined for response.

  • Solution: Either specify property type [SchemaClass]$Property or use Schema = [SchemaClass] in [OpenApiResponseAttribute].

Issue: Inline responses not appearing correctly.

  • Solution: Set Inline = $true in [OpenApiResponseAttribute] to embed response definition instead of referencing component.

References


Previous / Next

Previous: Parameter Components Next: Complete Components