using System;
using System.Linq;
namespace Svrnty.CQRS.Events.Abstractions.Schema;
///
/// Marks an event type with version information for schema evolution.
///
///
///
/// This attribute enables automatic schema versioning and upcasting.
/// Use it to track event evolution over time and specify upcast relationships.
///
///
/// Example:
///
/// // Version 1 (initial)
/// [EventVersion(1)]
/// public record UserCreatedEventV1 : CorrelatedEvent
/// {
/// public string Name { get; init; }
/// }
///
/// // Version 2 (added Email property)
/// [EventVersion(2, UpcastFrom = typeof(UserCreatedEventV1))]
/// public record UserCreatedEventV2 : CorrelatedEvent
/// {
/// public string Name { get; init; }
/// public string Email { get; init; }
///
/// // Static upcaster method (convention-based)
/// public static UserCreatedEventV2 UpcastFrom(UserCreatedEventV1 v1)
/// {
/// return new UserCreatedEventV2
/// {
/// EventId = v1.EventId,
/// CorrelationId = v1.CorrelationId,
/// OccurredAt = v1.OccurredAt,
/// Name = v1.Name,
/// Email = "unknown@example.com" // Default value for new property
/// };
/// }
/// }
///
///
///
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]
public sealed class EventVersionAttribute : Attribute
{
///
/// Gets the version number of this event schema.
///
///
/// Version numbers should start at 1 and increment sequentially.
/// Version 1 represents the initial event schema.
///
public int Version { get; }
///
/// Gets the type of the previous version this event can upcast from.
///
///
///
/// Should be null for version 1 (initial version).
/// For versions > 1, specify the immediate previous version.
///
///
/// Multi-hop upcasting is automatic: V1 → V2 → V3
/// You only need to specify the immediate previous version.
///
///
public Type? UpcastFrom { get; init; }
///
/// Gets or sets the event type name used for schema identification.
///
///
///
/// If not specified, defaults to the class name without version suffix.
/// Example: "UserCreatedEventV2" → "UserCreatedEvent"
///
///
/// All versions of the same event should use the same EventTypeName.
///
///
public string? EventTypeName { get; init; }
///
/// Initializes a new instance of the class.
///
/// The version number (must be >= 1).
/// Thrown if version is less than 1.
public EventVersionAttribute(int version)
{
if (version < 1)
throw new ArgumentOutOfRangeException(nameof(version), "Version must be >= 1.");
Version = version;
}
///
/// Gets the normalized event type name from a CLR type.
///
/// The CLR type of the event.
/// The normalized event type name (without version suffix).
///
/// Removes common version suffixes: V1, V2, V3, etc.
/// Example: "UserCreatedEventV2" → "UserCreatedEvent"
///
public static string GetEventTypeName(Type eventType)
{
var attribute = eventType.GetCustomAttributes(typeof(EventVersionAttribute), false)
.FirstOrDefault() as EventVersionAttribute;
if (attribute?.EventTypeName != null)
return attribute.EventTypeName;
// Remove version suffix (V1, V2, etc.) from type name
var typeName = eventType.Name;
var versionSuffixIndex = typeName.LastIndexOf('V');
if (versionSuffixIndex > 0 && versionSuffixIndex < typeName.Length - 1)
{
var suffix = typeName.Substring(versionSuffixIndex + 1);
if (int.TryParse(suffix, out _))
{
return typeName.Substring(0, versionSuffixIndex);
}
}
return typeName;
}
}