dotnet-cqrs/Svrnty.CQRS.Events.Abstractions/Models/SchemaInfo.cs

92 lines
3.8 KiB
C#

using System;
using Svrnty.CQRS.Events.Abstractions.Models;
using Svrnty.CQRS.Events.Abstractions.EventStore;
namespace Svrnty.CQRS.Events.Abstractions.Models;
/// <summary>
/// Represents schema information for a versioned event type.
/// </summary>
/// <remarks>
/// <para>
/// Schema information tracks the evolution of event types over time, enabling:
/// - Automatic upcasting from old versions to new versions
/// - JSON schema generation for external consumers
/// - Version compatibility checking
/// </para>
/// <para>
/// <strong>Example:</strong>
/// UserCreatedEventV1 → UserCreatedEventV2 (added Email property)
/// </para>
/// </remarks>
/// <param name="EventType">The fully qualified CLR type name of the event (e.g., "MyApp.UserCreatedEvent")</param>
/// <param name="Version">The semantic version of this schema (e.g., 1, 2, 3)</param>
/// <param name="ClrType">The .NET Type that represents this version</param>
/// <param name="JsonSchema">JSON Schema (Draft 7) describing the event structure (optional, for external consumers)</param>
/// <param name="UpcastFromType">The CLR type of the previous version (null for version 1)</param>
/// <param name="UpcastFromVersion">The version number this version can upcast from (null for version 1)</param>
/// <param name="RegisteredAt">When this schema was registered in the system</param>
public sealed record SchemaInfo(
string EventType,
int Version,
Type ClrType,
string? JsonSchema,
Type? UpcastFromType,
int? UpcastFromVersion,
DateTimeOffset RegisteredAt)
{
/// <summary>
/// Gets a value indicating whether this is the initial version of the event.
/// </summary>
public bool IsInitialVersion => Version == 1 && UpcastFromType == null;
/// <summary>
/// Gets a value indicating whether this schema can be upcast from a previous version.
/// </summary>
public bool SupportsUpcasting => UpcastFromType != null && UpcastFromVersion.HasValue;
/// <summary>
/// Gets the schema identifier (EventType:Version).
/// </summary>
public string SchemaId => $"{EventType}:v{Version}";
/// <summary>
/// Validates the schema information for correctness.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if the schema info is invalid.</exception>
public void Validate()
{
if (string.IsNullOrWhiteSpace(EventType))
throw new InvalidOperationException("EventType cannot be null or whitespace.");
if (Version < 1)
throw new InvalidOperationException($"Version must be >= 1, got {Version}.");
if (ClrType == null)
throw new InvalidOperationException("ClrType cannot be null.");
if (!ClrType.IsAssignableTo(typeof(ICorrelatedEvent)))
throw new InvalidOperationException($"ClrType {ClrType.FullName} must implement ICorrelatedEvent.");
// Version 1 should not have upcast information
if (Version == 1)
{
if (UpcastFromType != null)
throw new InvalidOperationException("Version 1 should not have UpcastFromType.");
if (UpcastFromVersion.HasValue)
throw new InvalidOperationException("Version 1 should not have UpcastFromVersion.");
}
else
{
// Versions > 1 should have upcast information
if (UpcastFromType == null)
throw new InvalidOperationException($"Version {Version} must specify UpcastFromType.");
if (!UpcastFromVersion.HasValue)
throw new InvalidOperationException($"Version {Version} must specify UpcastFromVersion.");
if (UpcastFromVersion.Value != Version - 1)
throw new InvalidOperationException(
$"Version {Version} must upcast from version {Version - 1}, got {UpcastFromVersion.Value}.");
}
}
}