Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/events/protection.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ builder.Services.AddMarten(opts =>
{
opts.Connection(builder.Configuration.GetConnectionString("marten"));

// By a single, concrete type
opts.Events.AddMaskingRuleForProtectedInformation<AccountChanged>(x =>
// By a single, concrete type (class or record)
opts.Events.AddMaskingRuleForProtectedInformation<AccountChanged>(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
Expand Down
18 changes: 16 additions & 2 deletions src/EventSourcingTests/removing_protected_information.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<AccountChangedRecord>(x => x with { LastName = "****" });

var started = new AccountChangedRecord("John", "Doe");

var @event = new Event<AccountChangedRecord>(started);

theEvents.TryMask(@event).ShouldBeTrue();

started.FirstName.ShouldBe("John");
started.LastName.ShouldBe("****");
}

[Fact]
Expand Down Expand Up @@ -442,8 +458,6 @@ await theStore.Advanced.ApplyEventDataMasking(x =>
}
}



public interface IAccountEvent
{
string Name { get; set; }
Expand Down
42 changes: 38 additions & 4 deletions src/Marten/Events/EventGraph.Masking.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,25 @@ public partial class EventGraph
/// for an event type "T" or series of event types that can be cast
/// to "T"
/// </summary>
/// <param name="action"></param>
/// <param name="action">Action to mask the current object</param>
/// <typeparam name="T"></typeparam>
public void AddMaskingRuleForProtectedInformation<T>(Action<T> action)
{
ArgumentNullException.ThrowIfNull(action);
_maskers.Add(new Masker<T>(action));
_maskers.Add(new ActionMasker<T>(action));
}

/// <summary>
/// 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"
/// </summary>
/// <param name="func">Function to replace the event with a masked event</param>
/// <typeparam name="T"></typeparam>
public void AddMaskingRuleForProtectedInformation<T>(Func<T,T> func)
{
ArgumentNullException.ThrowIfNull(func);
_maskers.Add(new FuncMasker<T>(func));
}

internal bool TryMask(IEvent e)
Expand All @@ -38,11 +51,11 @@ internal interface IMasker
bool TryMask(IEvent @event);
}

internal class Masker<T> : IMasker where T : notnull
internal class ActionMasker<T> : IMasker where T : notnull
{
private readonly Action<T> _masking;

public Masker(Action<T> masking)
public ActionMasker(Action<T> masking)
{
_masking = masking;
}
Expand All @@ -58,3 +71,24 @@ public bool TryMask(IEvent @event)
return false;
}
}

internal class FuncMasker<T> : IMasker where T : notnull
{
private readonly Func<T,T> _masking;

public FuncMasker(Func<T,T> masking)
{
_masking = masking;
}

public bool TryMask(IEvent @event)
{
if (@event is IEvent<T> e)
{
e.WithData(_masking(e.Data));
return true;
}

return false;
}
}
Loading