diff --git a/docs/events/protection.md b/docs/events/protection.md index adc0ba446f..a831808b3a 100644 --- a/docs/events/protection.md +++ b/docs/events/protection.md @@ -17,11 +17,11 @@ builder.Services.AddMarten(opts => { opts.Connection(builder.Configuration.GetConnectionString("marten")); - // By a single, concrete type - opts.Events.AddMaskingRuleForProtectedInformation(x => + // By a single, concrete type (class or record) + opts.Events.AddMaskingRuleForProtectedInformation(x => x with { // I'm only masking a single property here, but you could do as much as you want - x.Name = "****"; + Name = "****" }); // Maybe you have an interface that multiple event types implement that would help diff --git a/src/EventSourcingTests/removing_protected_information.cs b/src/EventSourcingTests/removing_protected_information.cs index d0a8825098..8aaca8f880 100644 --- a/src/EventSourcingTests/removing_protected_information.cs +++ b/src/EventSourcingTests/removing_protected_information.cs @@ -38,7 +38,23 @@ public void match_exactly_on_event_type() theEvents.TryMask(@event).ShouldBeTrue(); started.Name.ShouldBe("****"); + } + + private record AccountChangedRecord(string FirstName, string LastName); + + [Fact] + public void match_exactly_on_event_type_when_record() + { + theEvents.AddMaskingRuleForProtectedInformation(x => x with { LastName = "****" }); + + var started = new AccountChangedRecord("John", "Doe"); + var @event = new Event(started); + + theEvents.TryMask(@event).ShouldBeTrue(); + + started.FirstName.ShouldBe("John"); + started.LastName.ShouldBe("****"); } [Fact] @@ -442,8 +458,6 @@ await theStore.Advanced.ApplyEventDataMasking(x => } } - - public interface IAccountEvent { string Name { get; set; } diff --git a/src/Marten/Events/EventGraph.Masking.cs b/src/Marten/Events/EventGraph.Masking.cs index c65dbcc8cb..b252cbcda6 100644 --- a/src/Marten/Events/EventGraph.Masking.cs +++ b/src/Marten/Events/EventGraph.Masking.cs @@ -13,12 +13,25 @@ public partial class EventGraph /// for an event type "T" or series of event types that can be cast /// to "T" /// - /// + /// Action to mask the current object /// public void AddMaskingRuleForProtectedInformation(Action action) { ArgumentNullException.ThrowIfNull(action); - _maskers.Add(new Masker(action)); + _maskers.Add(new ActionMasker(action)); + } + + /// + /// Register a policy for how to remove or mask protected information + /// for an event type "T" or series of event types that can be cast + /// to "T" + /// + /// Function to replace the event with a masked event + /// + public void AddMaskingRuleForProtectedInformation(Func func) + { + ArgumentNullException.ThrowIfNull(func); + _maskers.Add(new FuncMasker(func)); } internal bool TryMask(IEvent e) @@ -38,11 +51,11 @@ internal interface IMasker bool TryMask(IEvent @event); } -internal class Masker : IMasker where T : notnull +internal class ActionMasker : IMasker where T : notnull { private readonly Action _masking; - public Masker(Action masking) + public ActionMasker(Action masking) { _masking = masking; } @@ -58,3 +71,24 @@ public bool TryMask(IEvent @event) return false; } } + +internal class FuncMasker : IMasker where T : notnull +{ + private readonly Func _masking; + + public FuncMasker(Func masking) + { + _masking = masking; + } + + public bool TryMask(IEvent @event) + { + if (@event is IEvent e) + { + e.WithData(_masking(e.Data)); + return true; + } + + return false; + } +}