Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
- Implemented comprehensive unit tests for RabbitMqTransportServer, covering constructor, disposal, connection management, event handlers, and exception handling. - Added configuration tests for RabbitMqTransportServer to validate SSL, durable queues, auto-recovery, and custom virtual host options. - Created unit tests for UdpFrameProtocol, including frame parsing and serialization, header size validation, and round-trip data preservation. - Developed tests for UdpTransportClient, focusing on connection handling, event subscriptions, and exception scenarios. - Established tests for UdpTransportServer, ensuring proper start/stop behavior, connection state management, and event handling. - Included tests for UdpTransportOptions to verify default values and modification capabilities. - Enhanced service registration tests for Udp transport services in the dependency injection container.
427 lines
15 KiB
Markdown
427 lines
15 KiB
Markdown
# JSON Schema Validation
|
|
|
|
This document describes the JSON Schema validation feature in the StellaOps Router/Microservice SDK.
|
|
|
|
## Overview
|
|
|
|
The StellaOps Microservice SDK provides compile-time JSON Schema generation and runtime request/response validation. Schemas are automatically generated from your C# types using a source generator, then transmitted to the Gateway via the HELLO payload where they power both runtime validation and OpenAPI documentation.
|
|
|
|
### Key Features
|
|
|
|
- **Compile-time schema generation**: JSON Schema draft 2020-12 generated from C# types
|
|
- **Runtime validation**: Request bodies validated against schema before reaching handlers
|
|
- **OpenAPI 3.1.0 compatibility**: Native JSON Schema support in OpenAPI 3.1.0
|
|
- **Automatic documentation**: Schemas flow to Gateway for unified OpenAPI documentation
|
|
- **External schema support**: Override generated schemas with embedded resource files
|
|
|
|
### Benefits
|
|
|
|
| Benefit | Description |
|
|
|---------|-------------|
|
|
| Type safety | Contract enforcement between clients and services |
|
|
| Early error detection | Invalid requests rejected with 422 before handler execution |
|
|
| Documentation automation | No manual schema maintenance required |
|
|
| Interoperability | Standard JSON Schema works with any tooling |
|
|
|
|
---
|
|
|
|
## Quick Start
|
|
|
|
### 1. Add the ValidateSchema Attribute
|
|
|
|
```csharp
|
|
using StellaOps.Microservice;
|
|
|
|
[StellaEndpoint("POST", "/invoices")]
|
|
[ValidateSchema]
|
|
public sealed class CreateInvoiceEndpoint : IStellaEndpoint<CreateInvoiceRequest, CreateInvoiceResponse>
|
|
{
|
|
public Task<CreateInvoiceResponse> HandleAsync(
|
|
CreateInvoiceRequest request,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
// Request is already validated against JSON Schema
|
|
return Task.FromResult(new CreateInvoiceResponse
|
|
{
|
|
InvoiceId = Guid.NewGuid().ToString(),
|
|
CreatedAt = DateTime.UtcNow,
|
|
Status = "draft"
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2. Define Your Request/Response Types
|
|
|
|
```csharp
|
|
public sealed record CreateInvoiceRequest
|
|
{
|
|
public required string CustomerId { get; init; }
|
|
public required decimal Amount { get; init; }
|
|
public string? Description { get; init; }
|
|
public List<LineItem> LineItems { get; init; } = [];
|
|
}
|
|
|
|
public sealed record CreateInvoiceResponse
|
|
{
|
|
public required string InvoiceId { get; init; }
|
|
public required DateTime CreatedAt { get; init; }
|
|
public required string Status { get; init; }
|
|
}
|
|
```
|
|
|
|
### 3. Build Your Project
|
|
|
|
The source generator automatically creates JSON Schema definitions at compile time. These schemas are included in the HELLO payload when your microservice connects to the Gateway.
|
|
|
|
---
|
|
|
|
## Attribute Reference
|
|
|
|
### ValidateSchemaAttribute
|
|
|
|
Enables JSON Schema validation for an endpoint.
|
|
|
|
```csharp
|
|
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
|
public sealed class ValidateSchemaAttribute : Attribute
|
|
```
|
|
|
|
### Properties
|
|
|
|
| Property | Type | Default | Description |
|
|
|----------|------|---------|-------------|
|
|
| `ValidateRequest` | `bool` | `true` | Enable request body validation |
|
|
| `ValidateResponse` | `bool` | `false` | Enable response body validation |
|
|
| `RequestSchemaResource` | `string?` | `null` | Embedded resource path to external request schema |
|
|
| `ResponseSchemaResource` | `string?` | `null` | Embedded resource path to external response schema |
|
|
| `Summary` | `string?` | `null` | OpenAPI operation summary |
|
|
| `Description` | `string?` | `null` | OpenAPI operation description |
|
|
| `Tags` | `string[]?` | `null` | OpenAPI tags for grouping endpoints |
|
|
| `Deprecated` | `bool` | `false` | Mark endpoint as deprecated in OpenAPI |
|
|
|
|
### Validation Properties
|
|
|
|
#### ValidateRequest (default: true)
|
|
|
|
Controls whether incoming request bodies are validated against the generated schema.
|
|
|
|
```csharp
|
|
// Request validation enabled (default)
|
|
[ValidateSchema(ValidateRequest = true)]
|
|
|
|
// Disable request validation
|
|
[ValidateSchema(ValidateRequest = false)]
|
|
```
|
|
|
|
#### ValidateResponse (default: false)
|
|
|
|
Enables response body validation. Useful for debugging or strict contract enforcement, but adds overhead.
|
|
|
|
```csharp
|
|
// Enable response validation
|
|
[ValidateSchema(ValidateResponse = true)]
|
|
```
|
|
|
|
#### External Schema Files
|
|
|
|
Override generated schemas with embedded resource files when you need custom schema definitions:
|
|
|
|
```csharp
|
|
[ValidateSchema(RequestSchemaResource = "Schemas.create-order.json")]
|
|
public sealed class CreateOrderEndpoint : IStellaEndpoint<CreateOrderRequest, CreateOrderResponse>
|
|
```
|
|
|
|
The schema file must be embedded as a resource in your assembly.
|
|
|
|
### Documentation Properties
|
|
|
|
#### Summary and Description
|
|
|
|
Provide OpenAPI documentation for the operation:
|
|
|
|
```csharp
|
|
[ValidateSchema(
|
|
Summary = "Create a new invoice",
|
|
Description = "Creates a draft invoice for the specified customer. The invoice must be finalized before it can be sent.")]
|
|
public sealed class CreateInvoiceEndpoint : IStellaEndpoint<CreateInvoiceRequest, CreateInvoiceResponse>
|
|
```
|
|
|
|
#### Tags
|
|
|
|
Override the default service-based tag grouping:
|
|
|
|
```csharp
|
|
[ValidateSchema(Tags = ["Billing", "Invoices"])]
|
|
public sealed class CreateInvoiceEndpoint : IStellaEndpoint<CreateInvoiceRequest, CreateInvoiceResponse>
|
|
```
|
|
|
|
#### Deprecated
|
|
|
|
Mark an endpoint as deprecated in OpenAPI documentation:
|
|
|
|
```csharp
|
|
[ValidateSchema(Deprecated = true, Description = "Use /v2/invoices instead")]
|
|
public sealed class CreateInvoiceV1Endpoint : IStellaEndpoint<CreateInvoiceRequest, CreateInvoiceResponse>
|
|
```
|
|
|
|
---
|
|
|
|
## Schema Discovery Endpoints
|
|
|
|
The Gateway exposes endpoints to discover and retrieve schemas from connected microservices.
|
|
|
|
### Discovery Endpoint
|
|
|
|
```http
|
|
GET /.well-known/openapi
|
|
```
|
|
|
|
Returns metadata about the OpenAPI document including available format URLs:
|
|
|
|
```json
|
|
{
|
|
"openapi_json": "/openapi.json",
|
|
"openapi_yaml": "/openapi.yaml",
|
|
"etag": "\"a1b2c3d4\"",
|
|
"generated_at": "2025-01-15T10:30:00.000Z"
|
|
}
|
|
```
|
|
|
|
### OpenAPI Document (JSON)
|
|
|
|
```http
|
|
GET /openapi.json
|
|
Accept: application/json
|
|
```
|
|
|
|
Returns the full OpenAPI 3.1.0 specification in JSON format. Supports ETag-based caching:
|
|
|
|
```http
|
|
GET /openapi.json
|
|
If-None-Match: "a1b2c3d4"
|
|
```
|
|
|
|
Returns `304 Not Modified` if the document hasn't changed.
|
|
|
|
### OpenAPI Document (YAML)
|
|
|
|
```http
|
|
GET /openapi.yaml
|
|
Accept: application/yaml
|
|
```
|
|
|
|
Returns the full OpenAPI 3.1.0 specification in YAML format.
|
|
|
|
### Caching Behavior
|
|
|
|
All OpenAPI endpoints support HTTP caching:
|
|
|
|
| Header | Value | Description |
|
|
|--------|-------|-------------|
|
|
| `Cache-Control` | `public, max-age=60` | Client-side caching for 60 seconds |
|
|
| `ETag` | `"<hash>"` | Content hash for conditional requests |
|
|
|
|
---
|
|
|
|
## Examples
|
|
|
|
### Basic Endpoint with Validation
|
|
|
|
```csharp
|
|
[StellaEndpoint("POST", "/invoices")]
|
|
[ValidateSchema]
|
|
public sealed class CreateInvoiceEndpoint : IStellaEndpoint<CreateInvoiceRequest, CreateInvoiceResponse>
|
|
{
|
|
private readonly ILogger<CreateInvoiceEndpoint> _logger;
|
|
|
|
public CreateInvoiceEndpoint(ILogger<CreateInvoiceEndpoint> logger)
|
|
{
|
|
_logger = logger;
|
|
}
|
|
|
|
public Task<CreateInvoiceResponse> HandleAsync(
|
|
CreateInvoiceRequest request,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
_logger.LogInformation(
|
|
"Creating invoice for customer {CustomerId} with amount {Amount}",
|
|
request.CustomerId,
|
|
request.Amount);
|
|
|
|
return Task.FromResult(new CreateInvoiceResponse
|
|
{
|
|
InvoiceId = $"INV-{Guid.NewGuid():N}"[..16].ToUpperInvariant(),
|
|
CreatedAt = DateTime.UtcNow,
|
|
Status = "draft"
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
### Endpoint with Full Documentation
|
|
|
|
```csharp
|
|
[StellaEndpoint("POST", "/invoices", TimeoutSeconds = 30)]
|
|
[ValidateSchema(
|
|
Summary = "Create invoice",
|
|
Description = "Creates a new draft invoice for the specified customer. Line items are optional but recommended for itemized billing.",
|
|
Tags = ["Billing", "Invoices"])]
|
|
public sealed class CreateInvoiceEndpoint : IStellaEndpoint<CreateInvoiceRequest, CreateInvoiceResponse>
|
|
{
|
|
// Implementation...
|
|
}
|
|
```
|
|
|
|
### Deprecated Endpoint
|
|
|
|
```csharp
|
|
[StellaEndpoint("POST", "/v1/invoices")]
|
|
[ValidateSchema(
|
|
Deprecated = true,
|
|
Summary = "Create invoice (deprecated)",
|
|
Description = "This endpoint is deprecated. Use POST /v2/invoices instead.")]
|
|
public sealed class CreateInvoiceV1Endpoint : IStellaEndpoint<CreateInvoiceRequest, CreateInvoiceResponse>
|
|
{
|
|
// Implementation...
|
|
}
|
|
```
|
|
|
|
### Request with Complex Types
|
|
|
|
```csharp
|
|
public sealed record CreateInvoiceRequest
|
|
{
|
|
/// <summary>
|
|
/// The customer identifier.
|
|
/// </summary>
|
|
public required string CustomerId { get; init; }
|
|
|
|
/// <summary>
|
|
/// The invoice amount in the default currency.
|
|
/// </summary>
|
|
public required decimal Amount { get; init; }
|
|
|
|
/// <summary>
|
|
/// Optional description for the invoice.
|
|
/// </summary>
|
|
public string? Description { get; init; }
|
|
|
|
/// <summary>
|
|
/// Line items for itemized billing.
|
|
/// </summary>
|
|
public List<LineItem> LineItems { get; init; } = [];
|
|
}
|
|
|
|
public sealed record LineItem
|
|
{
|
|
public required string Description { get; init; }
|
|
public required decimal Amount { get; init; }
|
|
public int Quantity { get; init; } = 1;
|
|
}
|
|
```
|
|
|
|
The source generator produces JSON Schema that includes:
|
|
- Required property validation (`required` keyword)
|
|
- Type validation (`type` keyword)
|
|
- Nullable property handling (`null` in type union)
|
|
- Nested object schemas
|
|
|
|
---
|
|
|
|
## Architecture
|
|
|
|
### Schema Flow Diagram
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────┐
|
|
│ COMPILE TIME │
|
|
├─────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ C# Types Source Generator Generated Code │
|
|
│ ───────── ──────────────── ────────────── │
|
|
│ CreateInvoice ──► Analyzes types ──► JSON Schema defs │
|
|
│ Request Extracts metadata GetSchemaDefinitions│
|
|
│ CreateInvoice Generates schemas EndpointDescriptor │
|
|
│ Response with SchemaInfo │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────────┐
|
|
│ RUNTIME │
|
|
├─────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ Microservice Start HELLO Payload Gateway Storage │
|
|
│ ───────────────── ───────────── ─────────────── │
|
|
│ RouterConnection ──► Instance info ──► ConnectionState │
|
|
│ Manager Endpoints[] Schemas dictionary │
|
|
│ Schemas{} OpenApiInfo │
|
|
│ OpenApiInfo │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────────┐
|
|
│ HTTP EXPOSURE │
|
|
├─────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ OpenApiDocument Document Cache HTTP Endpoints │
|
|
│ Generator ───────────── ────────────── │
|
|
│ ────────────── │
|
|
│ Aggregates all ──► TTL-based cache ──► GET /openapi.json │
|
|
│ connected services ETag generation GET /openapi.yaml │
|
|
│ Prefixes schemas Invalidation on GET /.well-known/ │
|
|
│ by service name connection change openapi │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Schema Naming Convention
|
|
|
|
Schemas are prefixed with the service name to avoid conflicts:
|
|
|
|
- Service: `Billing`
|
|
- Type: `CreateInvoiceRequest`
|
|
- Schema ID: `Billing_CreateInvoiceRequest`
|
|
|
|
This allows multiple services to define types with the same name without collisions.
|
|
|
|
---
|
|
|
|
## Error Handling
|
|
|
|
### Validation Errors
|
|
|
|
When request validation fails, the endpoint returns a `422 Unprocessable Entity` response with details:
|
|
|
|
```json
|
|
{
|
|
"type": "https://tools.ietf.org/html/rfc4918#section-11.2",
|
|
"title": "Validation Error",
|
|
"status": 422,
|
|
"errors": [
|
|
{
|
|
"path": "$.customerId",
|
|
"message": "Required property 'customerId' is missing"
|
|
},
|
|
{
|
|
"path": "$.amount",
|
|
"message": "Value must be a number"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Invalid Schemas
|
|
|
|
If a schema cannot be parsed (e.g., malformed JSON in external schema file), the schema is skipped and a warning is logged. The endpoint will still function but without schema validation.
|
|
|
|
---
|
|
|
|
## See Also
|
|
|
|
- [OpenAPI Aggregation](openapi-aggregation.md) - How schemas become OpenAPI documentation
|
|
- [API Overview](../../api/overview.md) - General API conventions
|
|
- [Router Architecture](architecture.md) - Router system overview
|