Skip to content

Commit

Permalink
minor: Optional keep flag (#6)
Browse files Browse the repository at this point in the history
* test push

* minor: add optional keep flag to keep attribute

* fix: trie access modifiers
  • Loading branch information
maranmaran authored Jun 26, 2023
1 parent c294d79 commit f221686
Show file tree
Hide file tree
Showing 10 changed files with 294 additions and 68 deletions.
1 change: 1 addition & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
branches:
- main
workflow_dispatch:

jobs:
build-and-publish:
Expand Down
2 changes: 1 addition & 1 deletion source/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<PropertyGroup>
<Version>2.0.3</Version>
<Version>2.0.4-optional-keep</Version>
<Authors>Marko Urh</Authors>
<Company>Perun</Company>
<Copyright>Copyright (c) 2023 Marko Urh and other authors.</Copyright>
Expand Down
52 changes: 52 additions & 0 deletions source/Perun.Differ.Tests/DifferTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,54 @@ public void NestedComplexIterable_Diffs()
Assert.Equal(expectedPropsInDiffCount, actualPropsInDiffCount);
}

[Fact]
public void OptionalKeepDiff_Simple_NoSiblingChanges_Ignores()
{
var faker = new AutoFaker<SimpleOptionalKeepModel>();
var left = faker.UseSeed(1).Generate();

var diff = DifferDotNet.Diff(left, left).SingleOrDefault();

Assert.Null(diff);
}

[Fact]
public void OptionalKeepDiff_Simple_SiblingChanges_DoesNotIgnore()
{
var faker = new AutoFaker<SimpleOptionalKeepModel>();
var left = faker.UseSeed(1).Generate();
var right = faker.UseSeed(2).Generate();

var diffs = DifferDotNet.Diff(left, right);

Assert.Equal(2, diffs.Count());
}

[Fact]
public void OptionalKeepDiff_Complex_NoSiblingChange_Ignores()
{
var faker = new AutoFaker<ComplexOptionalKeepModel>();
var left = faker.UseSeed(1).Generate();

var diff = DifferDotNet.Diff(left, left).SingleOrDefault();

Assert.Null(diff);
}

[Fact]
public void OptionalKeepDiff_Complex_ChildChange_DoesNotIgnore()
{
var faker = new AutoFaker<ComplexOptionalKeepModel>();
var left = faker.UseSeed(1).Generate();
var right = faker.UseSeed(2).Generate();

right.NoDiff = left.NoDiff;

var diff = DifferDotNet.Diff(left, right);

Assert.NotNull(diff.SingleOrDefault());
}

[Fact]
public void KeepDiff_Simple_Keeps()
{
Expand All @@ -453,6 +501,7 @@ public void KeepDiff_Simple_Keeps()
Assert.Equal(left.NoDiffKeepMe, diff.LeftValue);
Assert.Equal(left.NoDiffKeepMe, diff.RightValue);
Assert.Equal(diff.LeftValue, diff.RightValue);
Assert.False(diff.IgnoreIfNoOtherDiff);
}

[Fact]
Expand All @@ -464,6 +513,7 @@ public void KeepDiff_IterableSimple_KeepsAllChildren()
var diffs = DifferDotNet.Diff(left, left).ToList();

Assert.Equal(left.NoDiffKeepMe.Count(), diffs.Count);
Assert.True(diffs.TrueForAll(x => !x.IgnoreIfNoOtherDiff));
}

[Fact]
Expand All @@ -475,6 +525,7 @@ public void KeepDiff_IterableComplex_KeepsAllChildren()
var diffs = DifferDotNet.Diff(left, left).ToList();

Assert.Equal(left.NoDiffKeepMe.Count(), diffs.Count);
Assert.True(diffs.TrueForAll(x => !x.IgnoreIfNoOtherDiff));
}

[Fact]
Expand All @@ -487,6 +538,7 @@ public void KeepDiff_Complex_KeepsAllChildren()

var expectedDiffCount = left.NoDiffKeepMe.GetType().GetProperties().Length;
Assert.Equal(expectedDiffCount, diffs.Count);
Assert.True(diffs.TrueForAll(x => !x.IgnoreIfNoOtherDiff));
}

[Fact]
Expand Down
16 changes: 16 additions & 0 deletions source/Perun.Differ.Tests/TestTypes/KeepAttributeModels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,20 @@ public class ComplexKeepModel

public ComplexType NoDiff { get; set; }
}

public class SimpleOptionalKeepModel
{
[KeepInDiff(IgnoreIfNoOtherDiff = true)]
public string NoDiffKeepMe { get; set; }

public string NoDiff { get; set; }
}

public class ComplexOptionalKeepModel
{
[KeepInDiff(IgnoreIfNoOtherDiff = true)]
public ComplexType NoDiffKeepMe { get; set; }

public ComplexType NoDiff { get; set; }
}
}
81 changes: 35 additions & 46 deletions source/Perun.Differ/AttributeApplier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,75 +7,64 @@ internal static class AttributeApplier
{
internal static DiffCollection ApplyAttributes(DiffCollection collection)
{
var keepPaths = collection.KeepPaths.ToList();
var ignorePaths = collection.IgnorePaths.ToList();
var trie = CreateTrie(collection);

AddDiffsToKeep(collection.Diffs, collection.KeepDiffs);
RemoveDiffsToIgnore(collection.Diffs, ignorePaths, keepPaths);
AddDiffsToKeep(collection.Diffs, collection.KeepDiffs, trie);
RemoveDiffsToIgnore(collection.Diffs, collection.IgnorePaths);

return collection;
}

private static Dictionary<string, Difference> AddDiffsToKeep(
Dictionary<string, Difference> differences,
List<Difference> keepDiffs
)
private static Trie<Difference> CreateTrie(DiffCollection collection)
{
foreach (var keepDiff in keepDiffs)
var hasOptional = collection.KeepDiffs.Any(x => x.IgnoreIfNoOtherDiff);
var hasIgnore = collection.IgnorePaths.Any();

var trie = new Trie<Difference>();
if (hasOptional || hasIgnore)
{
// relevant, keep it in diff
if (!differences.ContainsKey(keepDiff.FullPath))
foreach (var diff in collection.Diffs.Values)
{
differences.Add(keepDiff.FullPath, keepDiff);
trie.Add(diff.FieldPath, diff);
}
}

return differences;
return trie;
}

private static Dictionary<string, Difference> RemoveDiffsToIgnore(
private static void AddDiffsToKeep(
Dictionary<string, Difference> differences,
List<string> ignorePaths,
List<string> keepPaths
)
HashSet<Difference> keepDiffs,
Trie<Difference> trie
)
{
var ignoreKeys = new List<string>();
foreach (var ignorePath in ignorePaths)
foreach (var diff in keepDiffs)
{
var ignorePathSplit = ignorePath.Split('.');

ignoreKeys.AddRange(
differences.Keys.Where(key =>
{
var keySplit = key.Split('.');
var startsWith = true;
for (var i = 0; i < ignorePathSplit.Length; i++)
{
if (i >= keySplit.Length)
{
break;
}
var ignoreNode = ignorePathSplit[i];
var keyNode = keySplit[i];
if (diff.IgnoreIfNoOtherDiff && !trie.Retrieve(diff.FieldPath).Any())
{
continue;
}

startsWith &= ignoreNode?.ToLower() == keyNode?.ToLower();
}
var added = differences.TryAdd(diff.FullPath, diff);

return startsWith;
})
);
if (added) // modify trie for search on new records
{
trie.Add(diff.FullPath, diff);
}
}
}

ignoreKeys = ignoreKeys.Except(keepPaths).ToList();
private static void RemoveDiffsToIgnore(
Dictionary<string, Difference> differences,
HashSet<string> ignorePaths
)
{
var pathsToRemove = ignorePaths.Where(differences.ContainsKey);

foreach (var ignoreKey in ignoreKeys)
foreach (var path in pathsToRemove)
{
differences.Remove(ignoreKey);
differences.Remove(path);
}

return differences;
}
}
}
31 changes: 19 additions & 12 deletions source/Perun.Differ/Attributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,24 @@ namespace Differ.DotNet
/// Keeps property and subsequent children in audit diff even if no change was made.
/// </summary>
[PublicAPI]
[AttributeUsage(AttributeTargets.Property)]
[AttributeUsage(AttributeTargets.Property)]
public sealed class KeepInDiffAttribute : Attribute
{
{
/// <summary>
/// Gets or sets a value indicating whether attribute should be ignored if sibling or child diffs exist.
/// </summary>
/// <value>
/// If <c>true</c> attribute is ignored (not kept), if no sibling or child diffs exist.
/// If <c>false</c> values are always kept.
/// </value>
public bool IgnoreIfNoOtherDiff { get; set; }
}

/// <summary>
/// Ignores property and subsequent children in audit diff.
/// </summary>
[PublicAPI]
[AttributeUsage(AttributeTargets.Property)]
[AttributeUsage(AttributeTargets.Property)]
public sealed class IgnoreInDiffAttribute : Attribute
{
}
Expand All @@ -27,7 +35,7 @@ public sealed class IgnoreInDiffAttribute : Attribute
/// <see cref="Difference.CustomFieldName"/>
/// </summary>
[PublicAPI]
[AttributeUsage(AttributeTargets.Property)]
[AttributeUsage(AttributeTargets.Property)]
public sealed class DiffPropertyName : Attribute
{
public string Name { get; }
Expand All @@ -38,21 +46,20 @@ public DiffPropertyName(string name)
}
}

[Flags]
[Flags]
internal enum DiffActions
{
Default = 0,
Keep = 1,
Ignore = 2
Keep = 1,
Ignore = 2,
KeepOptional = 4,
}

internal sealed class DiffCollection
{
public Dictionary<string, Difference> Diffs { get; set; } = new Dictionary<string, Difference>();

public List<Difference> KeepDiffs { get; set; } = new List<Difference>();
public HashSet<string> KeepPaths { get; set; } = new HashSet<string>();
public Dictionary<string, Difference> Diffs { get; set; } = new(); // (FullPath, Diff)

public HashSet<string> IgnorePaths { get; set; } = new HashSet<string>();
public HashSet<Difference> KeepDiffs { get; set; } = new();
public HashSet<string> IgnorePaths { get; set; } = new(); // FullPath
}
}
15 changes: 8 additions & 7 deletions source/Perun.Differ/DifferDotNet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,11 @@ DiffActions actions
}

// Check for KeepInDiff attribute
if (prop.GetCustomAttribute<KeepInDiffAttribute>() != null)
if (prop.GetCustomAttribute<KeepInDiffAttribute>() is { } keepAtt)
{
if (!diffs.KeepPaths.Contains(fullPath))
diffs.KeepPaths.Add(fullPath);

actions |= DiffActions.Keep;
actions |= keepAtt.IgnoreIfNoOtherDiff
? DiffActions.KeepOptional
: DiffActions.Keep;
}

// Recurse on sub-objects
Expand Down Expand Up @@ -176,11 +175,13 @@ DiffActions actions
rightObj
);

if (actions.HasFlag(DiffActions.Keep))
if (actions.HasFlag(DiffActions.Keep) || actions.HasFlag(DiffActions.KeepOptional))
{
diff.IgnoreIfNoOtherDiff = actions.HasFlag(DiffActions.KeepOptional);
diff.Keep = true;
diffs.KeepDiffs.Add(diff);
}


var exitCond = actions.HasFlag(DiffActions.Ignore)
|| leftObj == null && rightObj == null
|| diffs.Diffs.ContainsKey(diff.FullPath)
Expand Down
10 changes: 8 additions & 2 deletions source/Perun.Differ/Difference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public sealed class Difference
public string CustomFieldName { get; set; }

public object LeftValue { get; set; }
public object RightValue { get; set; }
public object RightValue { get; set; }

public Difference()
{
Expand Down Expand Up @@ -48,6 +48,12 @@ public Difference(string fullPath, string customFullPath, object leftValue, obje
var fieldPath = string.Join(".", pathSplit.Take(pathSplit.Length - 1));

return (fullPath, fieldName, fieldPath);
}
}

/// assembly internals

internal bool IgnoreIfNoOtherDiff { get; set; }
internal bool Keep { get; set; }
internal bool Ignore { get; set; }
}
}
11 changes: 11 additions & 0 deletions source/Perun.Differ/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@ namespace Differ.DotNet
{
internal static class Extensions
{
internal static bool TryAdd<TKey, TValue>(this Dictionary<TKey, TValue> dict, TKey key, TValue value)
{
if (dict.ContainsKey(key))
{
dict.Add(key, value);
return true;
}

return false;
}

/// <summary>
/// Reference: https://stackoverflow.com/a/65079923
/// </summary>
Expand Down
Loading

0 comments on commit f221686

Please sign in to comment.