diff --git a/docs/mdsource/serializer-settings.source.md b/docs/mdsource/serializer-settings.source.md index 59ad45a583..0dbea591c7 100644 --- a/docs/mdsource/serializer-settings.source.md +++ b/docs/mdsource/serializer-settings.source.md @@ -195,6 +195,21 @@ Result: snippet: SerializationTests.IgnoreType.verified.txt +## Scrub a type + +To scrub all members that match a certain type: + +snippet: AddScrubType + +Or globally: + +snippet: AddScrubTypeGlobal + +Result: + +snippet: SerializationTests.ScrubType.verified.txt + + ## Ignoring a instance To ignore instances of a type based on delegate: @@ -210,6 +225,21 @@ Result: snippet: SerializationTests.AddIgnoreInstance.verified.txt +## Scrub a instance + +To scrub instances of a type based on delegate: + +snippet: AddScrubInstance + +Or globally: + +snippet: AddScrubInstanceGlobal + +Result: + +snippet: SerializationTests.AddScrubInstance.verified.txt + + ## Obsolete members ignored Members with an [ObsoleteAttribute](https://docs.microsoft.com/en-us/dotnet/api/system.obsoleteattribute) are ignored: @@ -251,6 +281,21 @@ Result: snippet: SerializationTests.IgnoreMemberByExpression.verified.txt +## Scrub member by expressions + +To scrub members of a certain type using an expression: + +snippet: ScrubMemberByExpression + +Or globally + +snippet: ScrubMemberByExpressionGlobal + +Result: + +snippet: SerializationTests.ScrubMemberByExpression.verified.txt + + ## Ignore member by name To ignore members of a certain type using type and name: @@ -266,6 +311,21 @@ Result: snippet: SerializationTests.IgnoreMemberByName.verified.txt +## Scrub member by name + +To scrub members of a certain type using type and name: + +snippet: ScrubMemberByName + +Or globally: + +snippet: ScrubMemberByNameGlobal + +Result: + +snippet: SerializationTests.ScrubMemberByName.verified.txt + + ## Members that throw Members that throw exceptions can be excluded from serialization based on the exception type or properties. diff --git a/docs/named-tuples.md b/docs/named-tuples.md index 6c50bc719c..3e564d2321 100644 --- a/docs/named-tuples.md +++ b/docs/named-tuples.md @@ -19,7 +19,7 @@ Given a method that returns a named tuple: static (bool Member1, string Member2, string Member3) MethodWithNamedTuple() => (true, "A", "B"); ``` -snippet source | anchor +snippet source | anchor Can be verified: @@ -29,7 +29,7 @@ Can be verified: ```cs await VerifyTuple(() => MethodWithNamedTuple()); ``` -snippet source | anchor +snippet source | anchor Resulting in: diff --git a/docs/scrubbers.md b/docs/scrubbers.md index 42db666122..44702a0983 100644 --- a/docs/scrubbers.md +++ b/docs/scrubbers.md @@ -59,7 +59,7 @@ For example remove lines containing `text`: ```cs verifySettings.ScrubLines(line => line.Contains("text")); ``` -snippet source | anchor +snippet source | anchor @@ -74,7 +74,7 @@ For example remove lines containing `text1` or `text2` ```cs verifySettings.ScrubLinesContaining("text1", "text2"); ``` -snippet source | anchor +snippet source | anchor Case insensitive by default (StringComparison.OrdinalIgnoreCase). @@ -86,7 +86,7 @@ Case insensitive by default (StringComparison.OrdinalIgnoreCase). ```cs verifySettings.ScrubLinesContaining(StringComparison.Ordinal, "text1", "text2"); ``` -snippet source | anchor +snippet source | anchor @@ -101,7 +101,7 @@ For example converts lines to upper case: ```cs verifySettings.ScrubLinesWithReplace(line => line.ToUpper()); ``` -snippet source | anchor +snippet source | anchor @@ -114,7 +114,7 @@ Replaces `Environment.MachineName` with `TheMachineName`. ```cs verifySettings.ScrubMachineName(); ``` -snippet source | anchor +snippet source | anchor @@ -127,7 +127,7 @@ Replaces `Environment.UserName` with `TheUserName`. ```cs verifySettings.ScrubUserName(); ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/serializer-settings.md b/docs/serializer-settings.md index fd49c79d21..357228d23a 100644 --- a/docs/serializer-settings.md +++ b/docs/serializer-settings.md @@ -100,7 +100,7 @@ VerifierSettings .AddExtraSettings(_ => _.TypeNameHandling = TypeNameHandling.All); ``` -snippet source | anchor +snippet source | anchor @@ -120,7 +120,7 @@ public Task AddExtraSettings() return Verify("Value", settings); } ``` -snippet source | anchor +snippet source | anchor @@ -136,7 +136,7 @@ public Task AddExtraSettingsFluent() => _ => _.Error += (sender, args) => Console.WriteLine(args.ErrorContext.Member)); ``` -snippet source | anchor +snippet source | anchor @@ -156,7 +156,7 @@ To disable this behavior globally use: ```cs VerifierSettings.DontIgnoreEmptyCollections(); ``` -snippet source | anchor +snippet source | anchor @@ -178,7 +178,7 @@ var target = new GuidTarget await Verify(target); ``` -snippet source | anchor +snippet source | anchor Results in the following: @@ -203,7 +203,7 @@ Strings containing inline Guids can also be scrubbed. To enable this behavior, u ```cs VerifierSettings.ScrubInlineGuids(); ``` -snippet source | anchor +snippet source | anchor @@ -218,7 +218,7 @@ var settings = new VerifySettings(); settings.DontScrubGuids(); await Verify(target, settings); ``` -snippet source | anchor +snippet source | anchor Or with the fluent api: @@ -229,7 +229,7 @@ Or with the fluent api: await Verify(target) .DontScrubGuids(); ``` -snippet source | anchor +snippet source | anchor To disable this behavior globally use: @@ -239,7 +239,7 @@ To disable this behavior globally use: ```cs VerifierSettings.DontScrubGuids(); ``` -snippet source | anchor +snippet source | anchor @@ -267,7 +267,7 @@ var target = new DateTimeTarget await Verify(target); ``` -snippet source | anchor +snippet source | anchor Results in the following: @@ -305,7 +305,7 @@ settings.DontScrubDateTimes(); return Verify(target, settings); ``` -snippet source | anchor +snippet source | anchor Or using the fluent api use: @@ -321,7 +321,7 @@ var target = new return Verify(target) .DontScrubDateTimes(); ``` -snippet source | anchor +snippet source | anchor Or globally use: @@ -331,7 +331,7 @@ Or globally use: ```cs VerifierSettings.DontScrubDateTimes(); ``` -snippet source | anchor +snippet source | anchor @@ -474,7 +474,7 @@ public Task ScopedSerializerFluent() .AddExtraSettings(_ => _.TypeNameHandling = TypeNameHandling.All); } ``` -snippet source | anchor +snippet source | anchor Result: @@ -602,7 +602,7 @@ public Task IgnoreTypeFluent() .IgnoreMembersWithType(); } ``` -snippet source | anchor +snippet source | anchor Or globally: @@ -612,7 +612,7 @@ Or globally: ```cs VerifierSettings.IgnoreMembersWithType(); ``` -snippet source | anchor +snippet source | anchor Result: @@ -639,6 +639,161 @@ Result: +## Scrub a type + +To scrub all members that match a certain type: + + + +```cs +[Fact] +public Task ScrubType() +{ + var target = new IgnoreTypeTarget + { + ToIgnore = new() + { + Property = "Value" + }, + ToIgnoreNullable = new() + { + Property = "Value" + }, + ToIgnoreByInterface = new() + { + Property = "Value" + }, + ToIgnoreByBase = new() + { + Property = "Value" + }, + ToIgnoreByBaseGeneric = new() + { + Property = "Value" + }, + ToIgnoreByType = new() + { + Property = "Value" + }, + ToInclude = new() + { + Property = "Value" + }, + ToIncludeNullable = new() + { + Property = "Value" + }, + ToIgnoreStruct = new("Value"), + ToIgnoreStructNullable = new("Value"), + ToIncludeStruct = new("Value"), + ToIncludeStructNullable = new("Value") + }; + var settings = new VerifySettings(); + settings.ScrubMembersWithType(); + settings.ScrubMembersWithType(); + settings.ScrubMembersWithType(); + settings.ScrubMembersWithType(); + settings.ScrubMembersWithType(typeof(BaseToIgnoreGeneric<>)); + settings.ScrubMembersWithType(); + return Verify(target, settings); +} + +[Fact] +public Task ScrubTypeFluent() +{ + var target = new IgnoreTypeTarget + { + ToIgnore = new() + { + Property = "Value" + }, + ToIgnoreNullable = new() + { + Property = "Value" + }, + ToIgnoreByInterface = new() + { + Property = "Value" + }, + ToIgnoreByBase = new() + { + Property = "Value" + }, + ToIgnoreByBaseGeneric = new() + { + Property = "Value" + }, + ToIgnoreByType = new() + { + Property = "Value" + }, + ToInclude = new() + { + Property = "Value" + }, + ToIncludeNullable = new() + { + Property = "Value" + }, + ToIgnoreStruct = new("Value"), + ToIgnoreStructNullable = new("Value"), + ToIncludeStruct = new("Value"), + ToIncludeStructNullable = new("Value") + }; + return Verify(target) + .ScrubMembersWithType() + .ScrubMembersWithType() + .ScrubMembersWithType() + .ScrubMembersWithType() + .ScrubMembersWithType(typeof(BaseToIgnoreGeneric<>)) + .ScrubMembersWithType(); +} +``` +snippet source | anchor + + +Or globally: + + + +```cs +VerifierSettings.ScrubMembersWithType(); +``` +snippet source | anchor + + +Result: + + + +```txt +{ + ToIgnore: {Scrubbed}, + ToIgnoreByType: {Scrubbed}, + ToIgnoreByInterface: {Scrubbed}, + ToIgnoreByBase: {Scrubbed}, + ToIgnoreByBaseGeneric: {Scrubbed}, + ToIgnoreNullable: {Scrubbed}, + ToIgnoreStruct: {Scrubbed}, + ToIgnoreStructNullable: {Scrubbed}, + ToInclude: { + Property: Value + }, + ToIncludeNullable: { + Property: Value + }, + ToIncludeStruct: { + Property: Value + }, + ToIncludeStructNullable: { + Property: Value + } +} +``` +snippet source | anchor + + + ## Ignoring a instance To ignore instances of a type based on delegate: @@ -683,7 +838,7 @@ public Task AddIgnoreInstanceFluent() .IgnoreInstance(_ => _.Property == "Ignore"); } ``` -snippet source | anchor +snippet source | anchor Or globally: @@ -693,7 +848,7 @@ Or globally: ```cs VerifierSettings.IgnoreInstance(_ => _.Property == "Ignore"); ``` -snippet source | anchor +snippet source | anchor Result: @@ -711,6 +866,79 @@ Result: +## Scrub a instance + +To scrub instances of a type based on delegate: + + + +```cs +[Fact] +public Task AddScrubInstance() +{ + var target = new IgnoreInstanceTarget + { + ToIgnore = new() + { + Property = "Ignore" + }, + ToInclude = new() + { + Property = "Include" + } + }; + var settings = new VerifySettings(); + settings.ScrubInstance(_ => _.Property == "Ignore"); + return Verify(target, settings); +} + +[Fact] +public Task AddScrubInstanceFluent() +{ + var target = new IgnoreInstanceTarget + { + ToIgnore = new() + { + Property = "Ignore" + }, + ToInclude = new() + { + Property = "Include" + } + }; + return Verify(target) + .ScrubInstance(_ => _.Property == "Ignore"); +} +``` +snippet source | anchor + + +Or globally: + + + +```cs +VerifierSettings.ScrubInstance(_ => _.Property == "Ignore"); +``` +snippet source | anchor + + +Result: + + + +```txt +{ + ToIgnore: {Scrubbed}, + ToInclude: { + Property: Include + } +} +``` +snippet source | anchor + + + ## Obsolete members ignored Members with an [ObsoleteAttribute](https://docs.microsoft.com/en-us/dotnet/api/system.obsoleteattribute) are ignored: @@ -736,7 +964,7 @@ public Task WithObsoleteProp() return Verify(target); } ``` -snippet source | anchor +snippet source | anchor Result: @@ -784,7 +1012,7 @@ public Task WithObsoletePropIncludedFluent() .IncludeObsoletes(); } ``` -snippet source | anchor +snippet source | anchor Or globally: @@ -794,7 +1022,7 @@ Or globally: ```cs VerifierSettings.IncludeObsoletes(); ``` -snippet source | anchor +snippet source | anchor Result: @@ -855,7 +1083,7 @@ public Task IgnoreMemberByExpressionFluent() _ => _.PropertyThatThrows); } ``` -snippet source | anchor +snippet source | anchor Or globally @@ -870,7 +1098,7 @@ VerifierSettings.IgnoreMembers( _ => _.GetOnlyProperty, _ => _.PropertyThatThrows); ``` -snippet source | anchor +snippet source | anchor Result: @@ -886,6 +1114,86 @@ Result: +## Scrub member by expressions + +To scrub members of a certain type using an expression: + + + +```cs +[Fact] +public Task ScrubMemberByExpression() +{ + var target = new IgnoreExplicitTarget + { + Include = "Value", + Field = "Value", + Property = "Value", + PropertyWithPropertyName = "Value" + }; + var settings = new VerifySettings(); + settings.ScrubMembers( + _ => _.Property, + _ => _.PropertyWithPropertyName, + _ => _.Field, + _ => _.GetOnlyProperty, + _ => _.PropertyThatThrows); + return Verify(target, settings); +} + +[Fact] +public Task ScrubMemberByExpressionFluent() +{ + var target = new IgnoreExplicitTarget + { + Include = "Value", + Field = "Value", + Property = "Value" + }; + return Verify(target) + .ScrubMembers( + _ => _.Property, + _ => _.Field, + _ => _.GetOnlyProperty, + _ => _.PropertyThatThrows); +} +``` +snippet source | anchor + + +Or globally + + + +```cs +VerifierSettings.ScrubMembers( + _ => _.Property, + _ => _.PropertyWithPropertyName, + _ => _.Field, + _ => _.GetOnlyProperty, + _ => _.PropertyThatThrows); +``` +snippet source | anchor + + +Result: + + + +```txt +{ + Include: Value, + Field: {Scrubbed}, + Property: {Scrubbed}, + _Custom: {Scrubbed}, + GetOnlyProperty: {Scrubbed}, + PropertyThatThrows: {Scrubbed} +} +``` +snippet source | anchor + + + ## Ignore member by name To ignore members of a certain type using type and name: @@ -944,7 +1252,7 @@ public Task IgnoreMemberByNameFluent() .IgnoreMember(_ => _.PropertyThatThrows); } ``` -snippet source | anchor +snippet source | anchor Or globally: @@ -964,7 +1272,7 @@ VerifierSettings.IgnoreMember("Field"); // For a specific type with expression VerifierSettings.IgnoreMember(_ => _.PropertyThatThrows); ``` -snippet source | anchor +snippet source | anchor Result: @@ -981,6 +1289,104 @@ Result: +## Scrub member by name + +To scrub members of a certain type using type and name: + + + +```cs +[Fact] +public Task ScrubMemberByName() +{ + var target = new IgnoreExplicitTarget + { + Include = "Value", + Field = "Value", + Property = "Value", + PropertyByName = "Value" + }; + var settings = new VerifySettings(); + + // For all types + settings.ScrubMember("PropertyByName"); + + // For a specific type + settings.ScrubMember(typeof(IgnoreExplicitTarget), "Property"); + + // For a specific type generic + settings.ScrubMember("Field"); + + // For a specific type with expression + settings.ScrubMember(_ => _.PropertyThatThrows); + + return Verify(target, settings); +} + +[Fact] +public Task ScrubMemberByNameFluent() +{ + var target = new IgnoreExplicitTarget + { + Include = "Value", + Field = "Value", + Property = "Value", + PropertyByName = "Value" + }; + return Verify(target) + // For all types + .ScrubMember("PropertyByName") + + // For a specific type + .ScrubMember(typeof(IgnoreExplicitTarget), "Property") + + // For a specific type generic + .ScrubMember("Field") + + // For a specific type with expression + .ScrubMember(_ => _.PropertyThatThrows); +} +``` +snippet source | anchor + + +Or globally: + + + +```cs +// For all types +VerifierSettings.ScrubMember("PropertyByName"); + +// For a specific type +VerifierSettings.ScrubMember(typeof(IgnoreExplicitTarget), "Property"); + +// For a specific type generic +VerifierSettings.ScrubMember("Field"); + +// For a specific type with expression +VerifierSettings.ScrubMember(_ => _.PropertyThatThrows); +``` +snippet source | anchor + + +Result: + + + +```txt +{ + Include: Value, + Field: {Scrubbed}, + Property: {Scrubbed}, + GetOnlyProperty: asd, + PropertyThatThrows: {Scrubbed} +} +``` +snippet source | anchor + + + ## Members that throw Members that throw exceptions can be excluded from serialization based on the exception type or properties. @@ -1011,7 +1417,7 @@ public Task CustomExceptionPropFluent() .IgnoreMembersThatThrow(); } ``` -snippet source | anchor +snippet source | anchor Or globally: @@ -1021,7 +1427,7 @@ Or globally: ```cs VerifierSettings.IgnoreMembersThatThrow(); ``` -snippet source | anchor +snippet source | anchor Result: @@ -1058,7 +1464,7 @@ public Task ExceptionMessagePropFluent() .IgnoreMembersThatThrow(_ => _.Message == "Ignore"); } ``` -snippet source | anchor +snippet source | anchor Or globally: @@ -1068,7 +1474,7 @@ Or globally: ```cs VerifierSettings.IgnoreMembersThatThrow(_ => _.Message == "Ignore"); ``` -snippet source | anchor +snippet source | anchor Result: @@ -1234,7 +1640,7 @@ public Task MemberConverterByExpression() return Verify(input); } ``` -snippet source | anchor +snippet source | anchor diff --git a/readme.md b/readme.md index 5fc38e65fb..8e77d93916 100644 --- a/readme.md +++ b/readme.md @@ -355,7 +355,7 @@ public Task VerifyJsonJToken() return VerifyJson(target); } ``` -snippet source | anchor +snippet source | anchor Results in: diff --git a/src/Verify.Tests/Serialization/SerializationTests.AddScrubInstance.verified.txt b/src/Verify.Tests/Serialization/SerializationTests.AddScrubInstance.verified.txt new file mode 100644 index 0000000000..3ff5444e8e --- /dev/null +++ b/src/Verify.Tests/Serialization/SerializationTests.AddScrubInstance.verified.txt @@ -0,0 +1,6 @@ +{ + ToIgnore: {Scrubbed}, + ToInclude: { + Property: Include + } +} \ No newline at end of file diff --git a/src/Verify.Tests/Serialization/SerializationTests.AddScrubInstanceFluent.verified.txt b/src/Verify.Tests/Serialization/SerializationTests.AddScrubInstanceFluent.verified.txt new file mode 100644 index 0000000000..3ff5444e8e --- /dev/null +++ b/src/Verify.Tests/Serialization/SerializationTests.AddScrubInstanceFluent.verified.txt @@ -0,0 +1,6 @@ +{ + ToIgnore: {Scrubbed}, + ToInclude: { + Property: Include + } +} \ No newline at end of file diff --git a/src/Verify.Tests/Serialization/SerializationTests.JTokenScrub.verified.txt b/src/Verify.Tests/Serialization/SerializationTests.JTokenScrub.verified.txt new file mode 100644 index 0000000000..a0031ffc51 --- /dev/null +++ b/src/Verify.Tests/Serialization/SerializationTests.JTokenScrub.verified.txt @@ -0,0 +1,3 @@ +{ + Include: 1 +} \ No newline at end of file diff --git a/src/Verify.Tests/Serialization/SerializationTests.ScrubDictionaryKeyByName.verified.txt b/src/Verify.Tests/Serialization/SerializationTests.ScrubDictionaryKeyByName.verified.txt new file mode 100644 index 0000000000..9547bb8454 --- /dev/null +++ b/src/Verify.Tests/Serialization/SerializationTests.ScrubDictionaryKeyByName.verified.txt @@ -0,0 +1,6 @@ +{ + Include: { + Key1: Value2 + }, + Key2: Value4 +} \ No newline at end of file diff --git a/src/Verify.Tests/Serialization/SerializationTests.ScrubJTokenByName.verified.txt b/src/Verify.Tests/Serialization/SerializationTests.ScrubJTokenByName.verified.txt new file mode 100644 index 0000000000..11bd9fd4a0 --- /dev/null +++ b/src/Verify.Tests/Serialization/SerializationTests.ScrubJTokenByName.verified.txt @@ -0,0 +1,8 @@ +{ + short: { + key: { + code: 0, + msg: No action taken + } + } +} \ No newline at end of file diff --git a/src/Verify.Tests/Serialization/SerializationTests.ScrubMemberByExpression.verified.txt b/src/Verify.Tests/Serialization/SerializationTests.ScrubMemberByExpression.verified.txt new file mode 100644 index 0000000000..0b85fd8e34 --- /dev/null +++ b/src/Verify.Tests/Serialization/SerializationTests.ScrubMemberByExpression.verified.txt @@ -0,0 +1,8 @@ +{ + Include: Value, + Field: {Scrubbed}, + Property: {Scrubbed}, + _Custom: {Scrubbed}, + GetOnlyProperty: {Scrubbed}, + PropertyThatThrows: {Scrubbed} +} \ No newline at end of file diff --git a/src/Verify.Tests/Serialization/SerializationTests.ScrubMemberByExpressionFluent.verified.txt b/src/Verify.Tests/Serialization/SerializationTests.ScrubMemberByExpressionFluent.verified.txt new file mode 100644 index 0000000000..3940c6e307 --- /dev/null +++ b/src/Verify.Tests/Serialization/SerializationTests.ScrubMemberByExpressionFluent.verified.txt @@ -0,0 +1,7 @@ +{ + Include: Value, + Field: {Scrubbed}, + Property: {Scrubbed}, + GetOnlyProperty: {Scrubbed}, + PropertyThatThrows: {Scrubbed} +} \ No newline at end of file diff --git a/src/Verify.Tests/Serialization/SerializationTests.ScrubMemberByName.verified.txt b/src/Verify.Tests/Serialization/SerializationTests.ScrubMemberByName.verified.txt new file mode 100644 index 0000000000..a8e75c6d84 --- /dev/null +++ b/src/Verify.Tests/Serialization/SerializationTests.ScrubMemberByName.verified.txt @@ -0,0 +1,7 @@ +{ + Include: Value, + Field: {Scrubbed}, + Property: {Scrubbed}, + GetOnlyProperty: asd, + PropertyThatThrows: {Scrubbed} +} \ No newline at end of file diff --git a/src/Verify.Tests/Serialization/SerializationTests.ScrubMemberByNameFluent.verified.txt b/src/Verify.Tests/Serialization/SerializationTests.ScrubMemberByNameFluent.verified.txt new file mode 100644 index 0000000000..a8e75c6d84 --- /dev/null +++ b/src/Verify.Tests/Serialization/SerializationTests.ScrubMemberByNameFluent.verified.txt @@ -0,0 +1,7 @@ +{ + Include: Value, + Field: {Scrubbed}, + Property: {Scrubbed}, + GetOnlyProperty: asd, + PropertyThatThrows: {Scrubbed} +} \ No newline at end of file diff --git a/src/Verify.Tests/Serialization/SerializationTests.ScrubMembersNullable.verified.txt b/src/Verify.Tests/Serialization/SerializationTests.ScrubMembersNullable.verified.txt new file mode 100644 index 0000000000..25c4bec55f --- /dev/null +++ b/src/Verify.Tests/Serialization/SerializationTests.ScrubMembersNullable.verified.txt @@ -0,0 +1,3 @@ +{ + Property: {Scrubbed} +} \ No newline at end of file diff --git a/src/Verify.Tests/Serialization/SerializationTests.ScrubMembersNullableNested.verified.txt b/src/Verify.Tests/Serialization/SerializationTests.ScrubMembersNullableNested.verified.txt new file mode 100644 index 0000000000..26cde96b93 --- /dev/null +++ b/src/Verify.Tests/Serialization/SerializationTests.ScrubMembersNullableNested.verified.txt @@ -0,0 +1,3 @@ +{ + ToIgnoreStruct: {Scrubbed} +} \ No newline at end of file diff --git a/src/Verify.Tests/Serialization/SerializationTests.ScrubType.verified.txt b/src/Verify.Tests/Serialization/SerializationTests.ScrubType.verified.txt new file mode 100644 index 0000000000..0d678d8899 --- /dev/null +++ b/src/Verify.Tests/Serialization/SerializationTests.ScrubType.verified.txt @@ -0,0 +1,22 @@ +{ + ToIgnore: {Scrubbed}, + ToIgnoreByType: {Scrubbed}, + ToIgnoreByInterface: {Scrubbed}, + ToIgnoreByBase: {Scrubbed}, + ToIgnoreByBaseGeneric: {Scrubbed}, + ToIgnoreNullable: {Scrubbed}, + ToIgnoreStruct: {Scrubbed}, + ToIgnoreStructNullable: {Scrubbed}, + ToInclude: { + Property: Value + }, + ToIncludeNullable: { + Property: Value + }, + ToIncludeStruct: { + Property: Value + }, + ToIncludeStructNullable: { + Property: Value + } +} \ No newline at end of file diff --git a/src/Verify.Tests/Serialization/SerializationTests.ScrubTypeFluent.verified.txt b/src/Verify.Tests/Serialization/SerializationTests.ScrubTypeFluent.verified.txt new file mode 100644 index 0000000000..0d678d8899 --- /dev/null +++ b/src/Verify.Tests/Serialization/SerializationTests.ScrubTypeFluent.verified.txt @@ -0,0 +1,22 @@ +{ + ToIgnore: {Scrubbed}, + ToIgnoreByType: {Scrubbed}, + ToIgnoreByInterface: {Scrubbed}, + ToIgnoreByBase: {Scrubbed}, + ToIgnoreByBaseGeneric: {Scrubbed}, + ToIgnoreNullable: {Scrubbed}, + ToIgnoreStruct: {Scrubbed}, + ToIgnoreStructNullable: {Scrubbed}, + ToInclude: { + Property: Value + }, + ToIncludeNullable: { + Property: Value + }, + ToIncludeStruct: { + Property: Value + }, + ToIncludeStructNullable: { + Property: Value + } +} \ No newline at end of file diff --git a/src/Verify.Tests/Serialization/SerializationTests.cs b/src/Verify.Tests/Serialization/SerializationTests.cs index 3b5631363c..c4a19bec34 100644 --- a/src/Verify.Tests/Serialization/SerializationTests.cs +++ b/src/Verify.Tests/Serialization/SerializationTests.cs @@ -148,6 +148,24 @@ public Task JTokenIgnore() .IgnoreMembers("Ignore", "Memory Info"); } + [Fact] + public Task JTokenScrub() + { + var jToken = JToken.Parse(@"{ + Include: 1, + Ignore: 2, + ""Memory Info"": { + fragmentedBytes: 208, + heapSizeBytes: 2479536, + highMemoryLoadThresholdBytes: 30821986713, + memoryLoadBytes: 14041127280, + totalAvailableMemoryBytes: 34246651904 + } +}"); + return Verify(jToken) + .ScrubMembers("Ignore", "Memory Info"); + } + [Fact] public Task JObjectIgnore() { @@ -375,12 +393,14 @@ public void SettingsIsCloned() Assert.True(clone.GetShouldIgnoreInstance(GetType(), out var shouldIgnores)); var shouldIgnore = shouldIgnores.Single(); - Assert.True(shouldIgnore(this)); - Assert.False(shouldIgnore("notIgnored")); - Assert.True(clone.ShouldIgnoreForMemberOfType(GetType(), "ignored")); - Assert.False(clone.ShouldIgnoreForMemberOfType(GetType(), "notIgnored")); - Assert.True(clone.ShouldIgnoreByName("ignored")); - Assert.False(clone.ShouldIgnoreByName("notIgnored")); + Assert.Equal(ScrubOrIgnore.Ignore, shouldIgnore(this)); + Assert.Null(shouldIgnore("notIgnored")); + Assert.True(clone.TryGetScrubOrIgnoreByMemberOfType(GetType(), "ignored", out var scrubOrIgnore)); + Assert.Equal(ScrubOrIgnore.Ignore, scrubOrIgnore); + Assert.False(clone.TryGetScrubOrIgnoreByMemberOfType(GetType(), "notIgnored", out scrubOrIgnore)); + Assert.True(clone.TryGetScrubOrIgnoreByName("ignored", out scrubOrIgnore)); + Assert.Equal(ScrubOrIgnore.Ignore, scrubOrIgnore); + Assert.False(clone.TryGetScrubOrIgnoreByName("notIgnored", out scrubOrIgnore)); } [Fact] @@ -1683,6 +1703,11 @@ void AddIgnoreInstanceGlobal() VerifierSettings.IgnoreInstance(_ => _.Property == "Ignore"); + #endregion + #region AddScrubInstanceGlobal + + VerifierSettings.ScrubInstance(_ => _.Property == "Ignore"); + #endregion } @@ -1727,6 +1752,47 @@ public Task AddIgnoreInstanceFluent() #endregion + #region AddScrubInstance + + [Fact] + public Task AddScrubInstance() + { + var target = new IgnoreInstanceTarget + { + ToIgnore = new() + { + Property = "Ignore" + }, + ToInclude = new() + { + Property = "Include" + } + }; + var settings = new VerifySettings(); + settings.ScrubInstance(_ => _.Property == "Ignore"); + return Verify(target, settings); + } + + [Fact] + public Task AddScrubInstanceFluent() + { + var target = new IgnoreInstanceTarget + { + ToIgnore = new() + { + Property = "Ignore" + }, + ToInclude = new() + { + Property = "Include" + } + }; + return Verify(target) + .ScrubInstance(_ => _.Property == "Ignore"); + } + + #endregion + [Fact] public Task AddIgnoreInstanceInList() { @@ -1762,6 +1828,11 @@ void AddIgnoreTypeGlobal() VerifierSettings.IgnoreMembersWithType(); + #endregion + #region AddScrubTypeGlobal + + VerifierSettings.ScrubMembersWithType(); + #endregion } @@ -1872,6 +1943,114 @@ public Task IgnoreTypeFluent() #endregion + + #region AddScrubType + + [Fact] + public Task ScrubType() + { + var target = new IgnoreTypeTarget + { + ToIgnore = new() + { + Property = "Value" + }, + ToIgnoreNullable = new() + { + Property = "Value" + }, + ToIgnoreByInterface = new() + { + Property = "Value" + }, + ToIgnoreByBase = new() + { + Property = "Value" + }, + ToIgnoreByBaseGeneric = new() + { + Property = "Value" + }, + ToIgnoreByType = new() + { + Property = "Value" + }, + ToInclude = new() + { + Property = "Value" + }, + ToIncludeNullable = new() + { + Property = "Value" + }, + ToIgnoreStruct = new("Value"), + ToIgnoreStructNullable = new("Value"), + ToIncludeStruct = new("Value"), + ToIncludeStructNullable = new("Value") + }; + var settings = new VerifySettings(); + settings.ScrubMembersWithType(); + settings.ScrubMembersWithType(); + settings.ScrubMembersWithType(); + settings.ScrubMembersWithType(); + settings.ScrubMembersWithType(typeof(BaseToIgnoreGeneric<>)); + settings.ScrubMembersWithType(); + return Verify(target, settings); + } + + [Fact] + public Task ScrubTypeFluent() + { + var target = new IgnoreTypeTarget + { + ToIgnore = new() + { + Property = "Value" + }, + ToIgnoreNullable = new() + { + Property = "Value" + }, + ToIgnoreByInterface = new() + { + Property = "Value" + }, + ToIgnoreByBase = new() + { + Property = "Value" + }, + ToIgnoreByBaseGeneric = new() + { + Property = "Value" + }, + ToIgnoreByType = new() + { + Property = "Value" + }, + ToInclude = new() + { + Property = "Value" + }, + ToIncludeNullable = new() + { + Property = "Value" + }, + ToIgnoreStruct = new("Value"), + ToIgnoreStructNullable = new("Value"), + ToIncludeStruct = new("Value"), + ToIncludeStructNullable = new("Value") + }; + return Verify(target) + .ScrubMembersWithType() + .ScrubMembersWithType() + .ScrubMembersWithType() + .ScrubMembersWithType() + .ScrubMembersWithType(typeof(BaseToIgnoreGeneric<>)) + .ScrubMembersWithType(); + } + + #endregion + [Fact] public Task IgnoreMembersNullable() { @@ -1881,6 +2060,15 @@ public Task IgnoreMembersNullable() .IgnoreMembers(_ => _.Property); } + [Fact] + public Task ScrubMembersNullable() + { + ToIgnoreStruct? toIgnoreStruct = new ToIgnoreStruct("Value"); + + return Verify(toIgnoreStruct) + .ScrubMembers(_ => _.Property); + } + [Fact] public Task IgnoreMembersNullableNested() { @@ -1893,6 +2081,18 @@ public Task IgnoreMembersNullableNested() .IgnoreMembers(_ => _.ToIgnoreStruct); } + [Fact] + public Task ScrubMembersNullableNested() + { + var target = new IgnoreMembersNullableNestedTarget + { + ToIgnoreStruct = new ToIgnoreStruct("Value") + }; + + return Verify(target) + .ScrubMembers(_ => _.ToIgnoreStruct); + } + class IgnoreMembersNullableNestedTarget { public ToIgnoreStruct? ToIgnoreStruct { get; set; } @@ -2048,6 +2248,16 @@ void IgnoreMemberByExpressionGlobal() _ => _.PropertyThatThrows); #endregion + #region ScrubMemberByExpressionGlobal + + VerifierSettings.ScrubMembers( + _ => _.Property, + _ => _.PropertyWithPropertyName, + _ => _.Field, + _ => _.GetOnlyProperty, + _ => _.PropertyThatThrows); + + #endregion } #region IgnoreMemberByExpression @@ -2091,6 +2301,47 @@ public Task IgnoreMemberByExpressionFluent() #endregion + #region ScrubMemberByExpression + + [Fact] + public Task ScrubMemberByExpression() + { + var target = new IgnoreExplicitTarget + { + Include = "Value", + Field = "Value", + Property = "Value", + PropertyWithPropertyName = "Value" + }; + var settings = new VerifySettings(); + settings.ScrubMembers( + _ => _.Property, + _ => _.PropertyWithPropertyName, + _ => _.Field, + _ => _.GetOnlyProperty, + _ => _.PropertyThatThrows); + return Verify(target, settings); + } + + [Fact] + public Task ScrubMemberByExpressionFluent() + { + var target = new IgnoreExplicitTarget + { + Include = "Value", + Field = "Value", + Property = "Value" + }; + return Verify(target) + .ScrubMembers( + _ => _.Property, + _ => _.Field, + _ => _.GetOnlyProperty, + _ => _.PropertyThatThrows); + } + + #endregion + #region MemberConverter [Fact] @@ -2139,6 +2390,21 @@ void IgnoreMemberByNameGlobal() // For a specific type with expression VerifierSettings.IgnoreMember(_ => _.PropertyThatThrows); + #endregion + #region ScrubMemberByNameGlobal + + // For all types + VerifierSettings.ScrubMember("PropertyByName"); + + // For a specific type + VerifierSettings.ScrubMember(typeof(IgnoreExplicitTarget), "Property"); + + // For a specific type generic + VerifierSettings.ScrubMember("Field"); + + // For a specific type with expression + VerifierSettings.ScrubMember(_ => _.PropertyThatThrows); + #endregion } @@ -2197,6 +2463,61 @@ public Task IgnoreMemberByNameFluent() #endregion + #region ScrubMemberByName + + [Fact] + public Task ScrubMemberByName() + { + var target = new IgnoreExplicitTarget + { + Include = "Value", + Field = "Value", + Property = "Value", + PropertyByName = "Value" + }; + var settings = new VerifySettings(); + + // For all types + settings.ScrubMember("PropertyByName"); + + // For a specific type + settings.ScrubMember(typeof(IgnoreExplicitTarget), "Property"); + + // For a specific type generic + settings.ScrubMember("Field"); + + // For a specific type with expression + settings.ScrubMember(_ => _.PropertyThatThrows); + + return Verify(target, settings); + } + + [Fact] + public Task ScrubMemberByNameFluent() + { + var target = new IgnoreExplicitTarget + { + Include = "Value", + Field = "Value", + Property = "Value", + PropertyByName = "Value" + }; + return Verify(target) + // For all types + .ScrubMember("PropertyByName") + + // For a specific type + .ScrubMember(typeof(IgnoreExplicitTarget), "Property") + + // For a specific type generic + .ScrubMember("Field") + + // For a specific type with expression + .ScrubMember(_ => _.PropertyThatThrows); + } + + #endregion + public class IgnoreTargetBase { public string Property { get; set; } @@ -2230,6 +2551,24 @@ public Task IgnoreJTokenByName() var target = JToken.Parse(json); return Verify(target).IgnoreMember("Ignore1"); } + [Fact] + public Task ScrubJTokenByName() + { + var json = @"{ + 'short': { + 'key': { + 'code': 0, + 'msg': 'No action taken' + }, + 'Ignore1': { + 'code': 2, + 'msg': 'ignore this' + } + } +}"; + var target = JToken.Parse(json); + return Verify(target).ScrubMember("Ignore1"); + } [Fact] public Task VerifyJsonGuid() @@ -2324,6 +2663,25 @@ public Task IgnoreDictionaryKeyByName() .IgnoreMember("Ignore"); } + [Fact] + public Task ScrubDictionaryKeyByName() + { + var target = new Dictionary + { + { + "Include", new Dictionary + { + {"Ignore", "Value1"}, + {"Key1", "Value2"} + } + }, + {"Ignore", "Value3"}, + {"Key2", "Value4"} + }; + return Verify(target) + .ScrubMember("Ignore"); + } + class IgnoreExplicitTarget { public string Include; @@ -2452,7 +2810,6 @@ class WithNotSupportedException public Guid NotImplementedExceptionProperty => throw new NotSupportedException(); } - void WithObsoletePropIncludedGlobally() { #region WithObsoletePropIncludedGlobally diff --git a/src/Verify/Extensions.cs b/src/Verify/Extensions.cs index 6405947fc5..13d5c1a17b 100644 --- a/src/Verify/Extensions.cs +++ b/src/Verify/Extensions.cs @@ -5,6 +5,12 @@ static class Extensions public static List Clone(this List original) => new(original); + public static Dictionary Clone(this Dictionary original) + where TValue : struct where TKey : notnull => new(original); + + public static Dictionary Clone(this Dictionary original) + where TValue : struct where TKey : notnull => new(original); + public static string TrimEnd(this string input, string suffixToRemove, StringComparison comparisonType = StringComparison.CurrentCulture) { if (input.EndsWith(suffixToRemove, comparisonType)) diff --git a/src/Verify/Serialization/ArrayConverter.cs b/src/Verify/Serialization/ArrayConverter.cs index 534ddf20e9..c40e1ce15d 100644 --- a/src/Verify/Serialization/ArrayConverter.cs +++ b/src/Verify/Serialization/ArrayConverter.cs @@ -5,9 +5,18 @@ public override bool CanConvert(Type objectType) => public override void Write(VerifyJsonWriter writer, object value) { - if (!writer.serialization.ShouldSerialize(value)) + if (writer.serialization.TryGetScrubOrIgnoreByInstance(value, out var scrubOrIgnore)) { - return; + if (scrubOrIgnore == ScrubOrIgnore.Ignore) + { + return; + } + + if (scrubOrIgnore == ScrubOrIgnore.Scrub) + { + writer.WriteRaw("{Scrubbed}"); + return; + } } Type? type = null; diff --git a/src/Verify/Serialization/Converters/DictionaryConverter.cs b/src/Verify/Serialization/Converters/DictionaryConverter.cs index 1db7db77c9..7e858dec65 100644 --- a/src/Verify/Serialization/Converters/DictionaryConverter.cs +++ b/src/Verify/Serialization/Converters/DictionaryConverter.cs @@ -32,7 +32,12 @@ public override void Write(VerifyJsonWriter writer, object value) var valueType = genericArguments.Last(); var keyType = genericArguments.First(); var definition = type.GetGenericTypeDefinition(); - Func shouldIgnoreByName = _ => writer.serialization.ShouldIgnoreByName(_); + Func shouldIgnoreByName = _ => + { + writer.serialization.TryGetScrubOrIgnoreByName(_, out var scrubOrIgnore); + return scrubOrIgnore; + }; + if (definition == typeof(SortedDictionary<,>) || definition.Name == "ImmutableSortedDictionary`2") { diff --git a/src/Verify/Serialization/CustomContractResolver.cs b/src/Verify/Serialization/CustomContractResolver.cs index 80e016f3b6..3652578561 100644 --- a/src/Verify/Serialization/CustomContractResolver.cs +++ b/src/Verify/Serialization/CustomContractResolver.cs @@ -103,6 +103,21 @@ protected override JsonProperty CreateProperty(MemberInfo member, MemberSerializ return property; } + if (settings.TryGetScrubOrIgnore(member, out var scrubOrIgnore)) + { + if (scrubOrIgnore == ScrubOrIgnore.Ignore) + { + property.Ignored = true; + } + else + { + property.PropertyType = typeof(string); + property.ValueProvider = new ScrubbedProvider(); + } + + return property; + } + if (member.Name == "Message") { if (member.DeclaringType == typeof(ArgumentException)) @@ -121,21 +136,12 @@ protected override JsonProperty CreateProperty(MemberInfo member, MemberSerializ property.DefaultValueHandling = DefaultValueHandling.Include; } - if (settings.ShouldIgnore(member)) - { - property.Ignored = true; - return property; - } - property.ValueProvider = new CustomValueProvider( valueProvider, memberType, exception => settings.ShouldIgnoreException(exception), - VerifierSettings.GetMemberConverter(member)); - if (settings.TryGetShouldSerialize(memberType, property.ValueProvider.GetValue, out var shouldSerialize)) - { - property.ShouldSerialize = shouldSerialize; - } + VerifierSettings.GetMemberConverter(member), + settings); return property; } diff --git a/src/Verify/Serialization/CustomValueProvider.cs b/src/Verify/Serialization/CustomValueProvider.cs index 2209bb86ad..f0c77f3183 100644 --- a/src/Verify/Serialization/CustomValueProvider.cs +++ b/src/Verify/Serialization/CustomValueProvider.cs @@ -5,17 +5,19 @@ Type memberType; Func shouldIgnoreException; ConvertTargetMember? membersConverter; + SerializationSettings settings; - public CustomValueProvider( - IValueProvider inner, + public CustomValueProvider(IValueProvider inner, Type memberType, Func shouldIgnoreException, - ConvertTargetMember? membersConverter) + ConvertTargetMember? membersConverter, + SerializationSettings settings) { this.inner = inner; this.memberType = memberType; this.shouldIgnoreException = shouldIgnoreException; this.membersConverter = membersConverter; + this.settings = settings; } public void SetValue(object target, object? value) => @@ -31,7 +33,20 @@ public void SetValue(object target, object? value) => try { - return inner.GetValue(target); + var value = inner.GetValue(target); + if (value != null && + settings.TryGetScrubOrIgnoreByInstance(value, out var scrubOrIgnore)) + { + if (scrubOrIgnore == ScrubOrIgnore.Ignore) + { + return null; + } + if (scrubOrIgnore == ScrubOrIgnore.Scrub) + { + return "{Scrubbed}"; + } + } + return value; } catch (Exception exception) { diff --git a/src/Verify/Serialization/ScrubOrIgnore.cs b/src/Verify/Serialization/ScrubOrIgnore.cs new file mode 100644 index 0000000000..08bcc7fa6a --- /dev/null +++ b/src/Verify/Serialization/ScrubOrIgnore.cs @@ -0,0 +1,5 @@ +enum ScrubOrIgnore +{ + Scrub, + Ignore +} \ No newline at end of file diff --git a/src/Verify/Serialization/ScrubbedProvider.cs b/src/Verify/Serialization/ScrubbedProvider.cs new file mode 100644 index 0000000000..08f4527593 --- /dev/null +++ b/src/Verify/Serialization/ScrubbedProvider.cs @@ -0,0 +1,8 @@ +class ScrubbedProvider : IValueProvider +{ + public void SetValue(object target, object? value) => + throw new NotImplementedException(); + + public object GetValue(object target) => + "{Scrubbed}"; +} \ No newline at end of file diff --git a/src/Verify/Serialization/Scrubbers/DictionaryWrappers/OrderedStringDictionaryWrapper.cs b/src/Verify/Serialization/Scrubbers/DictionaryWrappers/OrderedStringDictionaryWrapper.cs index df88859da0..14dce5c490 100644 --- a/src/Verify/Serialization/Scrubbers/DictionaryWrappers/OrderedStringDictionaryWrapper.cs +++ b/src/Verify/Serialization/Scrubbers/DictionaryWrappers/OrderedStringDictionaryWrapper.cs @@ -1,13 +1,35 @@ class OrderedStringDictionaryWrapper : - Dictionary, + Dictionary, IDictionaryWrapper where TInner : IDictionary { - public OrderedStringDictionaryWrapper(Func shouldIgnore, TInner inner) : - base(inner - .Where(_ => !shouldIgnore(_.Key)) - .OrderBy(_ => _.Key, StringComparer.OrdinalIgnoreCase) - .ToDictionary(_ => _.Key, _ => _.Value)) + public OrderedStringDictionaryWrapper(Func shouldIgnore, TInner inner) : + base(BuildInner(shouldIgnore, inner)) { } + + static Dictionary BuildInner(Func shouldIgnore, TInner inner) + { + var dictionary = new Dictionary(); + foreach (var pair in inner + .OrderBy(_ => _.Key, StringComparer.OrdinalIgnoreCase)) + { + var key = pair.Key; + var scrubOrIgnore = shouldIgnore(key); + if (scrubOrIgnore == ScrubOrIgnore.Ignore) + { + continue; + } + + if (scrubOrIgnore == ScrubOrIgnore.Scrub) + { + dictionary.Add(key, "{Scrubbed}"); + continue; + } + + dictionary.Add(key, pair.Value); + } + + return dictionary; + } } \ No newline at end of file diff --git a/src/Verify/Serialization/Scrubbers/DictionaryWrappers/StringDictionaryWrapper.cs b/src/Verify/Serialization/Scrubbers/DictionaryWrappers/StringDictionaryWrapper.cs index 5525ff6b3a..85b24443aa 100644 --- a/src/Verify/Serialization/Scrubbers/DictionaryWrappers/StringDictionaryWrapper.cs +++ b/src/Verify/Serialization/Scrubbers/DictionaryWrappers/StringDictionaryWrapper.cs @@ -1,12 +1,32 @@ class StringDictionaryWrapper : - Dictionary, + Dictionary, IDictionaryWrapper where TInner : IDictionary { - public StringDictionaryWrapper(Func shouldIgnore, TInner inner) : - base(inner - .Where(_ => !shouldIgnore(_.Key)) + public StringDictionaryWrapper(Func shouldIgnore, TInner inner) : + base(BuildInner(shouldIgnore, inner) .ToDictionary(_ => _.Key, _ => _.Value)) { } + + static IEnumerable<(string Key, object? Value)> BuildInner(Func shouldIgnore, TInner inner) + { + foreach (var pair in inner) + { + var key = pair.Key; + var scrubOrIgnore = shouldIgnore(key); + if (scrubOrIgnore == ScrubOrIgnore.Ignore) + { + continue; + } + + if (scrubOrIgnore == ScrubOrIgnore.Scrub) + { + yield return (key, "{Scrubbed}"); + continue; + } + + yield return (key, pair.Value); + } + } } \ No newline at end of file diff --git a/src/Verify/Serialization/SerializationSettings_IgnoreByName.cs b/src/Verify/Serialization/SerializationSettings_IgnoreByName.cs index b1e9b7b9d6..5f94cd352b 100644 --- a/src/Verify/Serialization/SerializationSettings_IgnoreByName.cs +++ b/src/Verify/Serialization/SerializationSettings_IgnoreByName.cs @@ -2,7 +2,7 @@ partial class SerializationSettings { - List ignoredByNameMembers = new(); + Dictionary ignoredByNameMembers = new(); public void IgnoreStackTrace() => IgnoreMember("StackTrace"); @@ -10,7 +10,12 @@ public void IgnoreStackTrace() => public void IgnoreMember(string name) { Guard.AgainstNullOrEmpty(name, nameof(name)); - ignoredByNameMembers.Add(name); + ignoredByNameMembers[name] = ScrubOrIgnore.Ignore; + } + public void ScrubMember(string name) + { + Guard.AgainstNullOrEmpty(name, nameof(name)); + ignoredByNameMembers[name] = ScrubOrIgnore.Ignore; } public void IgnoreMembers(params string[] names) @@ -22,6 +27,15 @@ public void IgnoreMembers(params string[] names) } } - internal bool ShouldIgnoreByName(string name) => - ignoredByNameMembers.Contains(name); + public void ScrubMembers(params string[] names) + { + Guard.AgainstNullOrEmpty(names, nameof(names)); + foreach (var name in names) + { + ScrubMember(name); + } + } + + internal bool TryGetScrubOrIgnoreByName(string name, [NotNullWhen(true)] out ScrubOrIgnore? scrubOrIgnore) => + ignoredByNameMembers.TryGetValue(name, out scrubOrIgnore); } \ No newline at end of file diff --git a/src/Verify/Serialization/SerializationSettings_IgnoreForMemberOfType.cs b/src/Verify/Serialization/SerializationSettings_IgnoreForMemberOfType.cs index c44d1c7e93..3cd6ffb1cb 100644 --- a/src/Verify/Serialization/SerializationSettings_IgnoreForMemberOfType.cs +++ b/src/Verify/Serialization/SerializationSettings_IgnoreForMemberOfType.cs @@ -2,7 +2,7 @@ partial class SerializationSettings { - Dictionary> ignoredMembers = new(); + Dictionary> ignoredMembers = new(); public void IgnoreMembers(params Expression>[] expressions) where T : notnull @@ -13,7 +13,24 @@ public void IgnoreMembers(params Expression>[] expressions) } } + public void ScrubMembers(params Expression>[] expressions) + where T : notnull + { + foreach (var expression in expressions) + { + ScrubMember(expression); + } + } + public void IgnoreMember(Expression> expression) + where T : notnull => + IgnoreMember(expression, ScrubOrIgnore.Ignore); + + public void ScrubMember(Expression> expression) + where T : notnull => + IgnoreMember(expression, ScrubOrIgnore.Scrub); + + void IgnoreMember(Expression> expression, ScrubOrIgnore scrubOrIgnore) where T : notnull { var member = expression.FindMember(); @@ -24,17 +41,25 @@ public void IgnoreMember(Expression> expression) To ignore specific members for T, create a custom converter."); } - IgnoreMember(declaringType, member.Name); + IgnoreMember(declaringType, member.Name,scrubOrIgnore); } public void IgnoreMembers(params string[] names) where T : notnull => IgnoreMembers(typeof(T), names); + public void ScrubMembers(params string[] names) + where T : notnull => + ScrubMembers(typeof(T), names); + public void IgnoreMember(string name) where T : notnull => IgnoreMember(typeof(T), name); + public void ScrubMember(string name) + where T : notnull => + ScrubMember(typeof(T), name); + public void IgnoreMembers(Type declaringType, params string[] names) { Guard.AgainstNullOrEmpty(names, nameof(names)); @@ -44,7 +69,22 @@ public void IgnoreMembers(Type declaringType, params string[] names) } } - public void IgnoreMember(Type declaringType, string name) + public void ScrubMembers(Type declaringType, params string[] names) + { + Guard.AgainstNullOrEmpty(names, nameof(names)); + foreach (var name in names) + { + ScrubMember(declaringType, name); + } + } + + public void IgnoreMember(Type declaringType, string name) => + IgnoreMember(declaringType, name, ScrubOrIgnore.Ignore); + + public void ScrubMember(Type declaringType, string name) => + IgnoreMember(declaringType, name, ScrubOrIgnore.Scrub); + + void IgnoreMember(Type declaringType, string name, ScrubOrIgnore scrubOrIgnore) { Guard.AgainstNullOrEmpty(name, nameof(name)); Guard.AgainstNullable(declaringType, nameof(name)); @@ -53,10 +93,24 @@ public void IgnoreMember(Type declaringType, string name) ignoredMembers[declaringType] = list = new(); } - list.Add(name); + list[name] = scrubOrIgnore; } - internal bool ShouldIgnoreForMemberOfType(Type declaringType, string name) => - ignoredMembers.Where(_ => _.Value.Contains(name)) - .Any(_ => _.Key.IsAssignableFrom(declaringType)); + internal bool TryGetScrubOrIgnoreByMemberOfType(Type declaringType, string name, [NotNullWhen(true)] out ScrubOrIgnore? scrubOrIgnore) + { + foreach (var typeIgnores in ignoredMembers) + { + if (typeIgnores.Key.IsAssignableFrom(declaringType)) + { + if (typeIgnores.Value.TryGetValue(name, out var innerScrubOrIgnore)) + { + scrubOrIgnore = innerScrubOrIgnore; + return true; + } + } + } + + scrubOrIgnore = null; + return false; + } } \ No newline at end of file diff --git a/src/Verify/Serialization/SerializationSettings_IgnoreInstance.cs b/src/Verify/Serialization/SerializationSettings_IgnoreInstance.cs index be6d0099b7..c022ad195e 100644 --- a/src/Verify/Serialization/SerializationSettings_IgnoreInstance.cs +++ b/src/Verify/Serialization/SerializationSettings_IgnoreInstance.cs @@ -1,6 +1,6 @@ partial class SerializationSettings { - Dictionary> ignoredInstances = new(); + Dictionary>> ignoredInstances = new(); public void IgnoreInstance(Func shouldIgnore) where T : notnull @@ -22,9 +22,48 @@ public void IgnoreInstance(Type type, ShouldIgnore shouldIgnore) ignoredInstances[type] = list = new(); } - list.Add(shouldIgnore); + list.Add(_ => + { + if (shouldIgnore(_)) + { + return ScrubOrIgnore.Ignore; + } + + return null; + }); + } + + public void ScrubInstance(Func shouldScrub) + where T : notnull + { + var type = typeof(T); + ScrubInstance( + type, + target => + { + var arg = (T) target; + return shouldScrub(arg); + }); + } + + public void ScrubInstance(Type type, ShouldScrub shouldScrub) + { + if (!ignoredInstances.TryGetValue(type, out var list)) + { + ignoredInstances[type] = list = new(); + } + + list.Add(_ => + { + if (shouldScrub(_)) + { + return ScrubOrIgnore.Scrub; + } + + return null; + }); } - internal bool GetShouldIgnoreInstance(Type memberType, [NotNullWhen(true)] out List? funcs) => + internal bool GetShouldIgnoreInstance(Type memberType, [NotNullWhen(true)] out List>? funcs) => ignoredInstances.TryGetValue(memberType, out funcs); } \ No newline at end of file diff --git a/src/Verify/Serialization/SerializationSettings_IgnoreType.cs b/src/Verify/Serialization/SerializationSettings_IgnoreType.cs index c62f56e743..37be8dd09b 100644 --- a/src/Verify/Serialization/SerializationSettings_IgnoreType.cs +++ b/src/Verify/Serialization/SerializationSettings_IgnoreType.cs @@ -2,32 +2,43 @@ partial class SerializationSettings { - List ignoredTypes = new(); + Dictionary ignoredTypes = new(); + + public void ScrubMembersWithType() + where T : notnull => + ScrubMembersWithType(typeof(T)); + + public void ScrubMembersWithType(Type type) => + ignoredTypes[type]= ScrubOrIgnore.Scrub; public void IgnoreMembersWithType() where T : notnull => IgnoreMembersWithType(typeof(T)); public void IgnoreMembersWithType(Type type) => - ignoredTypes.Add(type); + ignoredTypes[type]= ScrubOrIgnore.Ignore; - bool ShouldIgnoreType(Type memberType) + bool TryGetScrubOrIgnoreByType(Type memberType, [NotNullWhen(true)]out ScrubOrIgnore? scrubOrIgnore) { - if (ignoredTypes.Any(memberType.InheritsFrom)) + foreach (var member in ignoredTypes) { - return true; + if (memberType.InheritsFrom(member.Key)) + { + scrubOrIgnore = member.Value; + return true; + } } - var typeFromNullable = Nullable.GetUnderlyingType(memberType); - - if (typeFromNullable != null) + foreach (var member in ignoredTypes) { - if (ignoredTypes.Any(_ => _.IsAssignableFrom(typeFromNullable))) + if (member.Key.IsAssignableFrom(typeFromNullable)) { + scrubOrIgnore = member.Value; return true; } } + scrubOrIgnore = null; return false; } } \ No newline at end of file diff --git a/src/Verify/Serialization/SerializationSettings_ShouldIgnore.cs b/src/Verify/Serialization/SerializationSettings_ShouldIgnore.cs index fa69621aab..c19037dd27 100644 --- a/src/Verify/Serialization/SerializationSettings_ShouldIgnore.cs +++ b/src/Verify/Serialization/SerializationSettings_ShouldIgnore.cs @@ -1,94 +1,52 @@ partial class SerializationSettings { - internal bool ShouldIgnore(MemberInfo member) + internal bool TryGetScrubOrIgnore(MemberInfo member, [NotNullWhen(true)] out ScrubOrIgnore? scrubOrIgnore) { if (ShouldIgnoreIfObsolete(member)) { + scrubOrIgnore = ScrubOrIgnore.Ignore; return true; } - return ShouldIgnore(member.DeclaringType!, member.MemberType(), member.Name); + return TryGetScrubOrIgnore(member.DeclaringType!, member.MemberType(), member.Name, out scrubOrIgnore); } - internal bool ShouldIgnore(Type declaringType, Type memberType, string name) - { - if (ShouldIgnoreType(memberType)) - { - return true; - } + internal bool TryGetScrubOrIgnore(Type declaringType, Type memberType, string name, [NotNullWhen(true)] out ScrubOrIgnore? scrubOrIgnore) => + TryGetScrubOrIgnoreByType(memberType, out scrubOrIgnore) || + TryGetScrubOrIgnoreByName(name, out scrubOrIgnore) || + TryGetScrubOrIgnoreByMemberOfType(declaringType, name, out scrubOrIgnore); - if (ShouldIgnoreByName(name)) - { - return true; - } - - if (ShouldIgnoreForMemberOfType(declaringType, name)) - { - return true; - } - - return false; - } - - internal bool ShouldSerialize(object value) + internal bool TryGetScrubOrIgnoreByInstance(object value, [NotNullWhen(true)] out ScrubOrIgnore? scrubOrIgnore) { var memberType = value.GetType(); if (GetShouldIgnoreInstance(memberType, out var funcs)) { - return funcs.All(func => !func(value)); - } - - if (IsIgnoredCollection(memberType)) - { - // since inside IsCollection, it is safe to use IEnumerable - var collection = (IEnumerable) value; - - return collection.HasMembers(); - } - - return true; - } - - internal bool TryGetShouldSerialize(Type memberType, Func getValue, out Predicate? shouldSerialize) - { - if (GetShouldIgnoreInstance(memberType, out var funcs)) - { - shouldSerialize = declaringInstance => + foreach (var func in funcs) { - var instance = getValue(declaringInstance); - - if (instance is null) + ScrubOrIgnore? orIgnore = func(value); + if (orIgnore != null) { - return false; + scrubOrIgnore = orIgnore; + return true; } + } - return funcs.All(func => !func(instance)); - }; - - return true; + scrubOrIgnore = null; + return false; } if (IsIgnoredCollection(memberType)) { - shouldSerialize = declaringInstance => + // since inside IsCollection, it is safe to use IEnumerable + var collection = (IEnumerable) value; + if (!collection.HasMembers()) { - var instance = getValue(declaringInstance); - - if (instance is null) - { - return false; - } - - // since inside IsCollection, it is safe to use IEnumerable - var collection = (IEnumerable) instance; - - return collection.HasMembers(); - }; - - return true; + scrubOrIgnore = ScrubOrIgnore.Ignore; + return true; + } } - shouldSerialize = null; + scrubOrIgnore = null; return false; } } \ No newline at end of file diff --git a/src/Verify/Serialization/ShouldScrub.cs b/src/Verify/Serialization/ShouldScrub.cs new file mode 100644 index 0000000000..3585b1685c --- /dev/null +++ b/src/Verify/Serialization/ShouldScrub.cs @@ -0,0 +1,3 @@ +namespace VerifyTests; + +public delegate bool ShouldScrub(object instance); \ No newline at end of file diff --git a/src/Verify/Serialization/VerifierSettings_SerializationMaps.cs b/src/Verify/Serialization/VerifierSettings_SerializationMaps.cs index 1a82b02f92..24f37f6d94 100644 --- a/src/Verify/Serialization/VerifierSettings_SerializationMaps.cs +++ b/src/Verify/Serialization/VerifierSettings_SerializationMaps.cs @@ -30,47 +30,89 @@ public static void IgnoreMembers(params Expression>[] expres where T : notnull => serialization.IgnoreMembers(expressions); + public static void ScrubMembers(params Expression>[] expressions) + where T : notnull => + serialization.ScrubMembers(expressions); + public static void IgnoreMember(Expression> expression) where T : notnull => serialization.IgnoreMembers(expression); + public static void ScrubMember(Expression> expression) + where T : notnull => + serialization.IgnoreMembers(expression); + public static void IgnoreMembers(params string[] names) where T : notnull => serialization.IgnoreMembers(names); + public static void ScrubMembers(params string[] names) + where T : notnull => + serialization.IgnoreMembers(names); + public static void IgnoreMember(string name) where T : notnull => serialization.IgnoreMember(name); + public static void ScrubMember(string name) + where T : notnull => + serialization.IgnoreMember(name); + public static void IgnoreMembers(Type declaringType, params string[] names) => serialization.IgnoreMembers(declaringType, names); + public static void ScrubMembers(Type declaringType, params string[] names) => + serialization.IgnoreMembers(declaringType, names); + public static void IgnoreMember(Type declaringType, string name) => serialization.IgnoreMember(declaringType, name); + public static void ScrubMember(Type declaringType, string name) => + serialization.ScrubMember(declaringType, name); + public static void IgnoreStackTrace() => serialization.IgnoreStackTrace(); public static void IgnoreMember(string name) => serialization.IgnoreMember(name); + public static void ScrubMember(string name) => + serialization.ScrubMember(name); + public static void IgnoreMembers(params string[] names) => serialization.IgnoreMembers( names); + public static void ScrubMembers(params string[] names) => + serialization.IgnoreMembers( names); + public static void IgnoreInstance(Func shouldIgnore) where T : notnull => serialization.IgnoreInstance( shouldIgnore); + public static void ScrubInstance(Func shouldIgnore) + where T : notnull => + serialization.ScrubInstance( shouldIgnore); + public static void IgnoreInstance(Type type, ShouldIgnore shouldIgnore) => serialization.IgnoreInstance(type, shouldIgnore); + public static void ScrubInstance(Type type, ShouldScrub shouldScrub) => + serialization.ScrubInstance(type, shouldScrub); + public static void IgnoreMembersWithType() where T : notnull => serialization.IgnoreMembersWithType(); + public static void ScrubMembersWithType() + where T : notnull => + serialization.ScrubMembersWithType(); + public static void IgnoreMembersWithType(Type type) => serialization.IgnoreMembersWithType(type); + public static void ScrubMembersWithType(Type type) => + serialization.ScrubMembersWithType(type); + public static void IgnoreMembersThatThrow() where T : Exception => serialization.IgnoreMembersThatThrow(); diff --git a/src/Verify/Serialization/VerifyJsonWriter.cs b/src/Verify/Serialization/VerifyJsonWriter.cs index f2f5e04580..8184b04e58 100644 --- a/src/Verify/Serialization/VerifyJsonWriter.cs +++ b/src/Verify/Serialization/VerifyJsonWriter.cs @@ -176,13 +176,29 @@ public void WriteMember(object target, object? value, string name) var declaringType = target.GetType(); var memberType = value.GetType(); - if (serialization.ShouldIgnore(declaringType, memberType, name)) + if (serialization.TryGetScrubOrIgnore(declaringType, memberType, name, out var scrubOrIgnore)) { + if (scrubOrIgnore == ScrubOrIgnore.Ignore) + { + return; + } + + WritePropertyName(name); + WriteRawValue("{Scrubbed}"); + return; } - if (!serialization.ShouldSerialize(value)) + if (serialization.TryGetScrubOrIgnoreByInstance(value, out scrubOrIgnore)) { + if (scrubOrIgnore == ScrubOrIgnore.Ignore) + { + return; + } + + WritePropertyName(name); + WriteRawValue("{Scrubbed}"); + return; } diff --git a/src/Verify/Serialization/VerifySettings_SerializationMaps.cs b/src/Verify/Serialization/VerifySettings_SerializationMaps.cs index a919775e62..dcfcba8f91 100644 --- a/src/Verify/Serialization/VerifySettings_SerializationMaps.cs +++ b/src/Verify/Serialization/VerifySettings_SerializationMaps.cs @@ -43,6 +43,13 @@ public void IgnoreMembers(params Expression>[] expressions) serialization.IgnoreMembers(expressions); } + public void ScrubMembers(params Expression>[] expressions) + where T : notnull + { + CloneSettings(); + serialization.ScrubMembers(expressions); + } + public void IgnoreMember(Expression> expression) where T : notnull { @@ -50,6 +57,13 @@ public void IgnoreMember(Expression> expression) serialization.IgnoreMembers(expression); } + public void ScrubMember(Expression> expression) + where T : notnull + { + CloneSettings(); + serialization.ScrubMembers(expression); + } + public void IgnoreMembers(params string[] names) where T : notnull { @@ -57,6 +71,13 @@ public void IgnoreMembers(params string[] names) serialization.IgnoreMembers(names); } + public void ScrubMembers(params string[] names) + where T : notnull + { + CloneSettings(); + serialization.ScrubMembers(names); + } + public void IgnoreMember(string name) where T : notnull { @@ -64,30 +85,61 @@ public void IgnoreMember(string name) serialization.IgnoreMember(name); } + public void ScrubMember(string name) + where T : notnull + { + CloneSettings(); + serialization.ScrubMember(name); + } + public void IgnoreMembers(Type declaringType, params string[] names) { CloneSettings(); serialization.IgnoreMembers(declaringType, names); } + public void ScrubMembers(Type declaringType, params string[] names) + { + CloneSettings(); + serialization.ScrubMembers(declaringType, names); + } + public void IgnoreMember(Type declaringType, string name) { CloneSettings(); serialization.IgnoreMember(declaringType, name); } + public void ScrubMember(Type declaringType, string name) + { + CloneSettings(); + serialization.ScrubMember(declaringType, name); + } + public void IgnoreMember(string name) { CloneSettings(); serialization.IgnoreMember(name); } + public void ScrubMember(string name) + { + CloneSettings(); + serialization.ScrubMember(name); + } + public void IgnoreMembers(params string[] names) { CloneSettings(); serialization.IgnoreMembers(names); } + public void ScrubMembers(params string[] names) + { + CloneSettings(); + serialization.ScrubMembers(names); + } + public void IgnoreInstance(Func shouldIgnore) where T : notnull { @@ -95,12 +147,25 @@ public void IgnoreInstance(Func shouldIgnore) serialization.IgnoreInstance(shouldIgnore); } + public void ScrubInstance(Func shouldScrub) + where T : notnull + { + CloneSettings(); + serialization.ScrubInstance(shouldScrub); + } + public void IgnoreInstance(Type type, ShouldIgnore shouldIgnore) { CloneSettings(); serialization.IgnoreInstance(type, shouldIgnore); } + public void ScrubInstance(Type type, ShouldScrub shouldScrub) + { + CloneSettings(); + serialization.ScrubInstance(type, shouldScrub); + } + public void IgnoreMembersWithType() where T : notnull { @@ -108,12 +173,25 @@ public void IgnoreMembersWithType() serialization.IgnoreMembersWithType(); } + public void ScrubMembersWithType() + where T : notnull + { + CloneSettings(); + serialization.ScrubMembersWithType(); + } + public void IgnoreMembersWithType(Type type) { CloneSettings(); serialization.IgnoreMembersWithType(type); } + public void ScrubMembersWithType(Type type) + { + CloneSettings(); + serialization.ScrubMembersWithType(type); + } + public void IgnoreMembersThatThrow() where T : Exception { diff --git a/src/Verify/SettingsTask_SerializationMaps.cs b/src/Verify/SettingsTask_SerializationMaps.cs index 79aaa87035..ad4281235a 100644 --- a/src/Verify/SettingsTask_SerializationMaps.cs +++ b/src/Verify/SettingsTask_SerializationMaps.cs @@ -33,6 +33,13 @@ public SettingsTask IgnoreMembers(params Expression>[] expre return this; } + public SettingsTask ScrubMembers(params Expression>[] expressions) + where T : notnull + { + CurrentSettings.ScrubMembers(expressions); + return this; + } + public SettingsTask IgnoreMember(Expression> expression) where T : notnull { @@ -40,6 +47,13 @@ public SettingsTask IgnoreMember(Expression> expression) return this; } + public SettingsTask ScrubMember(Expression> expression) + where T : notnull + { + CurrentSettings.ScrubMembers(expression); + return this; + } + public SettingsTask IgnoreMembers(params string[] names) where T : notnull { @@ -47,6 +61,13 @@ public SettingsTask IgnoreMembers(params string[] names) return this; } + public SettingsTask ScrubMembers(params string[] names) + where T : notnull + { + CurrentSettings.ScrubMembers(names); + return this; + } + public SettingsTask IgnoreMember(string name) where T : notnull { @@ -54,30 +75,61 @@ public SettingsTask IgnoreMember(string name) return this; } + public SettingsTask ScrubMember(string name) + where T : notnull + { + CurrentSettings.ScrubMember(name); + return this; + } + public SettingsTask IgnoreMembers(Type declaringType, params string[] names) { CurrentSettings.IgnoreMembers(declaringType, names); return this; } + public SettingsTask ScrubMembers(Type declaringType, params string[] names) + { + CurrentSettings.ScrubMembers(declaringType, names); + return this; + } + public SettingsTask IgnoreMember(Type declaringType, string name) { CurrentSettings.IgnoreMember(declaringType, name); return this; } + public SettingsTask ScrubMember(Type declaringType, string name) + { + CurrentSettings.ScrubMember(declaringType, name); + return this; + } + public SettingsTask IgnoreMember(string name) { CurrentSettings.IgnoreMember(name); return this; } + public SettingsTask ScrubMember(string name) + { + CurrentSettings.ScrubMember(name); + return this; + } + public SettingsTask IgnoreMembers(params string[] names) { CurrentSettings.IgnoreMembers(names); return this; } + public SettingsTask ScrubMembers(params string[] names) + { + CurrentSettings.ScrubMembers(names); + return this; + } + public SettingsTask IgnoreInstance(Func shouldIgnore) where T : notnull { @@ -85,12 +137,25 @@ public SettingsTask IgnoreInstance(Func shouldIgnore) return this; } + public SettingsTask ScrubInstance(Func shouldScrub) + where T : notnull + { + CurrentSettings.ScrubInstance(shouldScrub); + return this; + } + public SettingsTask IgnoreInstance(Type type, ShouldIgnore shouldIgnore) { CurrentSettings.IgnoreInstance(type, shouldIgnore); return this; } + public SettingsTask ScrubInstance(Type type, ShouldScrub shouldScrub) + { + CurrentSettings.ScrubInstance(type, shouldScrub); + return this; + } + public SettingsTask IgnoreMembersWithType() where T : notnull { @@ -98,12 +163,25 @@ public SettingsTask IgnoreMembersWithType() return this; } + public SettingsTask ScrubMembersWithType() + where T : notnull + { + CurrentSettings.ScrubMembersWithType(); + return this; + } + public SettingsTask IgnoreMembersWithType(Type type) { CurrentSettings.IgnoreMembersWithType(type); return this; } + public SettingsTask ScrubMembersWithType(Type type) + { + CurrentSettings.ScrubMembersWithType(type); + return this; + } + public SettingsTask IgnoreMembersThatThrow() where T : Exception {