using System; using Svrnty.CQRS.Events.Abstractions.Models; using Svrnty.CQRS.Events.Abstractions.EventStore; namespace Svrnty.CQRS.Events.Abstractions.Models; /// /// Represents schema information for a versioned event type. /// /// /// /// 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 /// /// /// Example: /// UserCreatedEventV1 → UserCreatedEventV2 (added Email property) /// /// /// The fully qualified CLR type name of the event (e.g., "MyApp.UserCreatedEvent") /// The semantic version of this schema (e.g., 1, 2, 3) /// The .NET Type that represents this version /// JSON Schema (Draft 7) describing the event structure (optional, for external consumers) /// The CLR type of the previous version (null for version 1) /// The version number this version can upcast from (null for version 1) /// When this schema was registered in the system public sealed record SchemaInfo( string EventType, int Version, Type ClrType, string? JsonSchema, Type? UpcastFromType, int? UpcastFromVersion, DateTimeOffset RegisteredAt) { /// /// Gets a value indicating whether this is the initial version of the event. /// public bool IsInitialVersion => Version == 1 && UpcastFromType == null; /// /// Gets a value indicating whether this schema can be upcast from a previous version. /// public bool SupportsUpcasting => UpcastFromType != null && UpcastFromVersion.HasValue; /// /// Gets the schema identifier (EventType:Version). /// public string SchemaId => $"{EventType}:v{Version}"; /// /// Validates the schema information for correctness. /// /// Thrown if the schema info is invalid. 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}."); } } }