-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add EventInfo wrapper type for serializing events (#957)
- Loading branch information
Showing
4 changed files
with
172 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
124 changes: 124 additions & 0 deletions
124
TeachingRecordSystem/src/TeachingRecordSystem.Core/EventInfo.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
using System.Text.Json; | ||
using System.Text.Json.Serialization; | ||
using TeachingRecordSystem.Core.DataStore.Postgres.Models; | ||
using TeachingRecordSystem.Core.Events; | ||
|
||
namespace TeachingRecordSystem.Core; | ||
|
||
/// <inheritdoc cref="EventInfo"/> | ||
public sealed class EventInfo<TEvent> : EventInfo | ||
where TEvent : EventBase | ||
{ | ||
public EventInfo(TEvent @event) | ||
: base(@event) | ||
{ | ||
} | ||
|
||
public static implicit operator TEvent(EventInfo<TEvent> @event) => @event.Event; | ||
|
||
public new TEvent Event => (TEvent)base.Event; | ||
} | ||
|
||
/// <summary> | ||
/// Wrapper type for serializing an event to JSON. | ||
/// </summary> | ||
public abstract class EventInfo | ||
{ | ||
private static readonly JsonSerializerOptions _serializerOptions = new JsonSerializerOptions() | ||
{ | ||
Converters = | ||
{ | ||
new EventInfoJsonConverter() | ||
} | ||
}; | ||
|
||
private protected EventInfo(EventBase @event) | ||
{ | ||
Event = @event; | ||
} | ||
|
||
public EventBase Event { get; } | ||
|
||
public static EventInfo<TEvent> Create<TEvent>(TEvent @event) where TEvent : EventBase => new EventInfo<TEvent>(@event); | ||
|
||
public static EventInfo Deserialize(string payload) => | ||
JsonSerializer.Deserialize<EventInfo>(payload, _serializerOptions)!; | ||
|
||
public static EventInfo<TEvent> Deserialize<TEvent>(string payload) where TEvent : EventBase => | ||
Deserialize(payload) is EventInfo<TEvent> typedEvent ? typedEvent : | ||
throw new InvalidCastException(); | ||
|
||
public string Serialize() => JsonSerializer.Serialize(this, typeof(EventInfo), _serializerOptions); | ||
} | ||
|
||
file class EventInfoJsonConverter : JsonConverter<EventInfo> | ||
{ | ||
public override EventInfo? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) | ||
{ | ||
if (reader.TokenType != JsonTokenType.StartObject) | ||
{ | ||
throw new JsonException(); | ||
} | ||
|
||
Type? eventType = null; | ||
|
||
while (reader.Read()) | ||
{ | ||
if (reader.TokenType != JsonTokenType.PropertyName) | ||
{ | ||
throw new JsonException(); | ||
} | ||
|
||
var propertyName = reader.GetString(); | ||
|
||
if (propertyName == "EventTypeName") | ||
{ | ||
reader.Read(); | ||
|
||
if (reader.TokenType != JsonTokenType.String) | ||
{ | ||
throw new JsonException(); | ||
} | ||
|
||
var eventTypeName = typeof(EventBase).Namespace + "." + reader.GetString()!; | ||
eventType = Type.GetType(eventTypeName); | ||
} | ||
else if (propertyName == "Event") | ||
{ | ||
reader.Read(); | ||
|
||
if (reader.TokenType != JsonTokenType.StartObject) | ||
{ | ||
throw new JsonException(); | ||
} | ||
|
||
if (eventType is null) | ||
{ | ||
throw new JsonException(); | ||
} | ||
|
||
var @event = JsonSerializer.Deserialize(ref reader, eventType, Event.JsonSerializerOptions); | ||
|
||
reader.Read(); | ||
|
||
var eventInfoType = typeof(EventInfo<>).MakeGenericType(eventType); | ||
return (EventInfo)Activator.CreateInstance(eventInfoType, [@event])!; | ||
} | ||
} | ||
|
||
throw new Exception(); | ||
} | ||
|
||
public override void Write(Utf8JsonWriter writer, EventInfo value, JsonSerializerOptions options) | ||
{ | ||
var eventType = value.Event.GetType(); | ||
var eventTypeName = eventType.Name; | ||
|
||
writer.WriteStartObject(); | ||
writer.WritePropertyName("EventTypeName"); | ||
writer.WriteStringValue(eventTypeName); | ||
writer.WritePropertyName("Event"); | ||
JsonSerializer.Serialize(writer, value.Event, eventType, Event.JsonSerializerOptions); | ||
writer.WriteEndObject(); | ||
} | ||
} |
43 changes: 43 additions & 0 deletions
43
TeachingRecordSystem/tests/TeachingRecordSystem.Core.Tests/EventInfoTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
using TeachingRecordSystem.Core.Events; | ||
|
||
namespace TeachingRecordSystem.Core.Tests; | ||
|
||
public class EventInfoTests | ||
{ | ||
[Fact] | ||
public void EventSerializesCorrectly() | ||
{ | ||
// Arrange | ||
var @e = new UserActivatedEvent() | ||
{ | ||
CreatedUtc = DateTime.UtcNow, | ||
SourceUserId = DataStore.Postgres.Models.User.SystemUserId, | ||
User = new() | ||
{ | ||
AzureAdUserId = "ad-user-id", | ||
Email = "[email protected]", | ||
Name = "Test User", | ||
Roles = ["Administrator"], | ||
UserId = Guid.NewGuid(), | ||
UserType = UserType.Person | ||
} | ||
}; | ||
|
||
var eventInfo = EventInfo.Create(@e); | ||
|
||
// Act | ||
var serialized = eventInfo.Serialize(); | ||
var deserialized = EventInfo.Deserialize(serialized); | ||
|
||
// Assert | ||
var roundTripped = Assert.IsType<EventInfo<UserActivatedEvent>>(deserialized); | ||
Assert.Equal(e.CreatedUtc, roundTripped.Event.CreatedUtc); | ||
Assert.Equal(e.SourceUserId, roundTripped.Event.SourceUserId); | ||
Assert.Equal(e.User.AzureAdUserId, roundTripped.Event.User.AzureAdUserId); | ||
Assert.Equal(e.User.Email, roundTripped.Event.User.Email); | ||
Assert.Equal(e.User.Name, roundTripped.Event.User.Name); | ||
Assert.Equal(e.User.Roles, roundTripped.Event.User.Roles); | ||
Assert.Equal(e.User.UserId, roundTripped.Event.User.UserId); | ||
Assert.Equal(e.User.UserType, roundTripped.Event.User.UserType); | ||
} | ||
} |