# 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 { public Task 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 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 ``` 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 ``` #### Tags Override the default service-based tag grouping: ```csharp [ValidateSchema(Tags = ["Billing", "Invoices"])] public sealed class CreateInvoiceEndpoint : IStellaEndpoint ``` #### Deprecated Mark an endpoint as deprecated in OpenAPI documentation: ```csharp [ValidateSchema(Deprecated = true, Description = "Use /v2/invoices instead")] public sealed class CreateInvoiceV1Endpoint : IStellaEndpoint ``` --- ## 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` | `""` | Content hash for conditional requests | --- ## Examples ### Basic Endpoint with Validation ```csharp [StellaEndpoint("POST", "/invoices")] [ValidateSchema] public sealed class CreateInvoiceEndpoint : IStellaEndpoint { private readonly ILogger _logger; public CreateInvoiceEndpoint(ILogger logger) { _logger = logger; } public Task 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 { // 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 { // Implementation... } ``` ### Request with Complex Types ```csharp public sealed record CreateInvoiceRequest { /// /// The customer identifier. /// public required string CustomerId { get; init; } /// /// The invoice amount in the default currency. /// public required decimal Amount { get; init; } /// /// Optional description for the invoice. /// public string? Description { get; init; } /// /// Line items for itemized billing. /// public List 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