// ---------------------------------------------------------------------------- // UpdateOrderStatusEndpoint // // Demonstrates: // - PATCH operation for partial updates // - Path parameters with request body // - Write authorization claims // - State machine validation // ---------------------------------------------------------------------------- using Examples.OrderService.Models; using Microsoft.Extensions.Logging; using StellaOps.Microservice; namespace Examples.OrderService.Endpoints; /// /// Request to update order status. /// public sealed record UpdateOrderStatusRequest { public required string OrderId { get; init; } public required OrderStatus NewStatus { get; init; } public string? Reason { get; init; } public string? TrackingNumber { get; init; } } /// /// Response after status update. /// public sealed record UpdateOrderStatusResponse { public required string OrderId { get; init; } public required OrderStatus PreviousStatus { get; init; } public required OrderStatus CurrentStatus { get; init; } public required DateTimeOffset UpdatedAt { get; init; } public string? Message { get; init; } } /// /// Endpoint for updating order status with state machine validation. /// [StellaEndpoint("PATCH", "/orders/{id}/status", TimeoutSeconds = 10, RequiredClaims = ["orders:write"])] [ValidateSchema( ValidateRequest = true, ValidateResponse = true, Summary = "Update order status", Description = "Updates the status of an order. Status transitions are validated against allowed state machine rules.", Tags = ["orders", "write", "status"])] public sealed class UpdateOrderStatusEndpoint : IStellaEndpoint { private readonly ILogger _logger; // Valid state transitions private static readonly Dictionary ValidTransitions = new() { [OrderStatus.Pending] = [OrderStatus.Confirmed, OrderStatus.Cancelled], [OrderStatus.Confirmed] = [OrderStatus.Processing, OrderStatus.Cancelled], [OrderStatus.Processing] = [OrderStatus.Shipped, OrderStatus.Cancelled], [OrderStatus.Shipped] = [OrderStatus.Delivered], [OrderStatus.Delivered] = [OrderStatus.Refunded], [OrderStatus.Cancelled] = [], [OrderStatus.Refunded] = [] }; public UpdateOrderStatusEndpoint(ILogger logger) { _logger = logger; } public Task HandleAsync( UpdateOrderStatusRequest request, CancellationToken cancellationToken) { // Simulate current status lookup var currentStatus = OrderStatus.Processing; _logger.LogInformation( "Updating order {OrderId} status: {CurrentStatus} -> {NewStatus}", request.OrderId, currentStatus, request.NewStatus); // Validate state transition if (!IsValidTransition(currentStatus, request.NewStatus)) { _logger.LogWarning( "Invalid status transition for order {OrderId}: {Current} -> {New}", request.OrderId, currentStatus, request.NewStatus); // In real implementation, throw an exception that maps to 422 return Task.FromResult(new UpdateOrderStatusResponse { OrderId = request.OrderId, PreviousStatus = currentStatus, CurrentStatus = currentStatus, // Unchanged UpdatedAt = DateTimeOffset.UtcNow, Message = $"Invalid transition: {currentStatus} cannot transition to {request.NewStatus}" }); } return Task.FromResult(new UpdateOrderStatusResponse { OrderId = request.OrderId, PreviousStatus = currentStatus, CurrentStatus = request.NewStatus, UpdatedAt = DateTimeOffset.UtcNow, Message = request.NewStatus == OrderStatus.Shipped ? $"Shipped with tracking: {request.TrackingNumber}" : null }); } private static bool IsValidTransition(OrderStatus current, OrderStatus target) { return ValidTransitions.TryGetValue(current, out var allowed) && allowed.Contains(target); } }