Skip to content

Commit

Permalink
Prevent duplicate validation of implemented interfaces (#109)
Browse files Browse the repository at this point in the history
  • Loading branch information
viceroypenguin authored Sep 10, 2024
1 parent 1571912 commit 036ed67
Show file tree
Hide file tree
Showing 69 changed files with 788 additions and 255 deletions.
17 changes: 12 additions & 5 deletions src/Immediate.Validations.Generators/Templates/Validations.sbntxt
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,15 @@ partial {{ c.type }} {{ c.name }}
partial {{ class.type }} {{ class.name }}
{
static ValidationResult IValidationTarget<{{ class.name }}>.Validate({{ class.name; if is_reference_type; "?"; end }} target) =>
Validate(target);
Validate(target, []);

public static {{ if class.type == "interface"; "new"; end }} ValidationResult Validate({{ class.name; if is_reference_type; "?"; end }} target)
static ValidationResult IValidationTarget<{{ class.name }}>.Validate({{ class.name; if is_reference_type; "?"; end }} target, ValidationResult errors) =>
Validate(target, errors);

public static {{ if class.type == "interface"; "new"; end }} ValidationResult Validate({{ class.name; if is_reference_type; "?"; end }} target) =>
Validate(target, []);

public static {{ if class.type == "interface"; "new"; end }} ValidationResult Validate({{ class.name; if is_reference_type; "?"; end }} target, ValidationResult errors)
{
{{~ if is_reference_type ~}}
if (target is not { } t)
Expand All @@ -31,11 +37,12 @@ partial {{ class.type }} {{ class.name }}
{{~ else ~}}
var t = target;
{{~ end ~}}

var errors = new ValidationResult();

if (!errors.VisitType(typeof({{ class.name }})))
return errors;

{{~ for bc in base_validation_targets ~}}
errors.AddRange({{ bc }}.Validate(t));
{{ bc }}.Validate(t, errors);
{{~ end ~}}

{{~ if !skip_self ~}}
Expand Down
19 changes: 19 additions & 0 deletions src/Immediate.Validations.Shared/IValidationTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,23 @@ public interface IValidationTarget<T>
Justification = "This is a static method to validate an instance of the self type."
)]
static abstract ValidationResult Validate(T? target);

/// <summary>
/// A method which can be used to validate instances of the type <typeparamref name="T"/>.
/// </summary>
/// <param name="target">
/// An instance of type <typeparamref name="T"/> which should be validated.
/// </param>
/// <param name="errors">
/// The operating <see cref="ValidationResult" /> to add validation entries to.
/// </param>
/// <returns>
/// The parameter <paramref name="errors"/>, for easier consumption.
/// </returns>
[SuppressMessage(
"Design",
"CA1000:Do not declare static members on generic types",
Justification = "This is a static method to validate an instance of the self type."
)]
static abstract ValidationResult Validate(T? target, ValidationResult errors);
}
13 changes: 13 additions & 0 deletions src/Immediate.Validations.Shared/ValidationResult.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.RegularExpressions;
Expand All @@ -16,6 +17,18 @@ public sealed partial class ValidationResult : IEnumerable<ValidationError>
private static partial Regex FormatRegex();

private List<ValidationError>? _errors;
private HashSet<string>? _types;

/// <summary>
/// Internal function used to support tracking which types have been visited as part of validation.
/// </summary>
/// <remarks>
/// Should not be used by consumers.
/// </remarks>
[EditorBrowsable(EditorBrowsableState.Never)]
[SuppressMessage("Design", "CA1062:Validate arguments of public methods")]
public bool VisitType(Type type) =>
(_types ??= []).Add(type.ToString());

/// <summary>
/// Indicates whether the validation was successful.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using Immediate.Validations.Shared;
using Xunit;

namespace Immediate.Validations.FunctionalTests.IntegrationTests;

public partial class DuplicateTypeVisitTests
{
[Validate]
public partial interface IBaseInterface : IValidationTarget<IBaseInterface>
{
public static int VisitCount { get; set; }

private static void AdditionalValidations(ValidationResult _, IBaseInterface __)
{
VisitCount++;
}
}

[Validate]
public partial interface ISubInterface : IBaseInterface, IValidationTarget<ISubInterface>;

[Validate]
public sealed partial record ValidateRecord : IBaseInterface, ISubInterface, IValidationTarget<ValidateRecord>;

[Fact]
public void TypeValidatorsAreVisitedAtMostOnce()
{
var record = new ValidateRecord();

_ = ValidateRecord.Validate(record);

Assert.Equal(1, IBaseInterface.VisitCount);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Immutable;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Diagnostics;
Expand All @@ -10,7 +11,7 @@ namespace Immediate.Validations.Tests.AnalyzerTests;
public static class AnalyzerTestHelpers
{
public static CSharpAnalyzerTest<TAnalyzer, DefaultVerifier> CreateAnalyzerTest<TAnalyzer>(
string inputSource
[StringSyntax("c#-test")] string inputSource
)
where TAnalyzer : DiagnosticAnalyzer, new()
{
Expand Down Expand Up @@ -105,7 +106,7 @@ params DiagnosticResult[] diagnostics
}

public static CSharpSuppressorTest<TSuppressor, DefaultVerifier> CreateSuppressorTest<TSuppressor>(
string inputSource
[StringSyntax("c#-test")] string inputSource
)
where TSuppressor : DiagnosticSuppressor, new()
{
Expand All @@ -128,7 +129,7 @@ string inputSource
}

public static CSharpSuppressorTest<TSuppressor, DefaultVerifier> CreateSuppressorTest<TSuppressor, TAnalyzer>(
string inputSource
[StringSyntax("c#-test")] string inputSource
)
where TSuppressor : DiagnosticSuppressor, new()
where TAnalyzer : DiagnosticAnalyzer, new()
Expand Down
Loading

0 comments on commit 036ed67

Please sign in to comment.