< Summary

Line coverage
100%
Covered lines: 17
Uncovered lines: 0
Coverable lines: 17
Total lines: 207
Line coverage: 100%
Branch coverage
100%
Covered branches: 6
Total branches: 6
Branch coverage: 100%
Method coverage
100%
Covered methods: 8
Fully covered methods: 8
Total methods: 8
Method coverage: 100%
Full method coverage: 100%

Metrics

File(s)

/home/runner/work/dotnet-validation/dotnet-validation/src/FluentValidator.cs

#LineLine coverage
 1using System.Text.RegularExpressions;
 2using ArturRios.Output;
 3using FluentValidation;
 4
 5namespace ArturRios.Validation;
 6
 7/// <summary>
 8/// Base validator that turns a FluentValidation result into the shapes an application consumes: a plain
 9/// array of messages, or an <see cref="ArturRios.Output"/> envelope.
 10/// </summary>
 11/// <typeparam name="T">The model type being validated.</typeparam>
 12/// <remarks>
 13/// Subclass it and declare the rules in the constructor exactly as with
 14/// <see cref="AbstractValidator{T}"/>; the helpers below come for free. Validation never throws for a model
 15/// that simply fails its rules — the failures arrive on the result, which is the family's convention.
 16/// </remarks>
 17public partial class FluentValidator<T> : AbstractValidator<T>, IFluentValidator<T>
 18{
 19    /// <summary>
 20    /// Upper bound, in milliseconds, on stripping the special characters from one message, so a pathological
 21    /// message can never spin.
 22    /// </summary>
 23    private const int MatchTimeoutMilliseconds = 100;
 24
 25    /// <inheritdoc />
 26    public string[] ValidateAndReturnErrors(T model, bool removeSpecialChars = false) =>
 6927        Messages(Validate(model), removeSpecialChars);
 28
 29    /// <inheritdoc />
 30    public ProcessOutput ValidateAndReturnProcessOutput(T model, bool removeSpecialChars = false) =>
 5531        ProcessOutput.New.WithErrors(ValidateAndReturnErrors(model, removeSpecialChars));
 32
 33    /// <summary>
 34    /// Validates <paramref name="model"/> and returns the failures as a <see cref="DataOutput{T}"/> envelope
 35    /// that also carries the model back.
 36    /// </summary>
 37    /// <param name="model">The model to validate.</param>
 38    /// <param name="removeSpecialChars">Strips the apostrophes and full stops from the messages when <see langword="tru
 39    /// <returns>
 40    /// An envelope carrying <paramref name="model"/> whether or not it is valid, so a caller can report the
 41    /// failures alongside what produced them.
 42    /// </returns>
 43    /// <remarks>
 44    /// This one is absent from <see cref="IFluentValidator{T}"/>: the interface is contravariant in
 45    /// <typeparamref name="T"/>, and a <see cref="DataOutput{T}"/> return puts the type parameter in an
 46    /// output position.
 47    /// </remarks>
 48    public DataOutput<T> ValidateAndReturnDataOutput(T model, bool removeSpecialChars = false) =>
 449        DataOutput<T>.New
 450            .WithData(model)
 451            .WithErrors(ValidateAndReturnErrors(model, removeSpecialChars));
 52
 53    /// <inheritdoc />
 54    public async Task<string[]> ValidateAndReturnErrorsAsync(
 55        T model,
 56        bool removeSpecialChars = false,
 57        CancellationToken cancellationToken = default) =>
 958        Messages(await ValidateAsync(model, cancellationToken).ConfigureAwait(false), removeSpecialChars);
 59
 60    /// <inheritdoc />
 61    public async Task<ProcessOutput> ValidateAndReturnProcessOutputAsync(
 62        T model,
 63        bool removeSpecialChars = false,
 64        CancellationToken cancellationToken = default) =>
 365        ProcessOutput.New.WithErrors(
 366            await ValidateAndReturnErrorsAsync(model, removeSpecialChars, cancellationToken).ConfigureAwait(false));
 67
 68    /// <summary>
 69    /// Asynchronously validates <paramref name="model"/> and returns the failures as a
 70    /// <see cref="DataOutput{T}"/> envelope that also carries the model back.
 71    /// </summary>
 72    /// <param name="model">The model to validate.</param>
 73    /// <param name="removeSpecialChars">Strips the apostrophes and full stops from the messages when <see langword="tru
 74    /// <param name="cancellationToken">Cancels the validation.</param>
 75    /// <returns>An envelope carrying <paramref name="model"/> whether or not it is valid.</returns>
 76    public async Task<DataOutput<T>> ValidateAndReturnDataOutputAsync(
 77        T model,
 78        bool removeSpecialChars = false,
 79        CancellationToken cancellationToken = default) =>
 180        DataOutput<T>.New
 181            .WithData(model)
 182            .WithErrors(await ValidateAndReturnErrorsAsync(model, removeSpecialChars, cancellationToken)
 183                .ConfigureAwait(false));
 84
 85    /// <summary>
 86    /// Projects a validation result onto its messages, optionally stripped of the default special characters.
 87    /// </summary>
 88    private static string[] Messages(FluentValidation.Results.ValidationResult result, bool removeSpecialChars)
 89    {
 15690        var messages = result.Errors.Select(error => error.ErrorMessage);
 91
 7692        if (removeSpecialChars)
 93        {
 1794            messages = messages.Select(message => SpecialChars().Replace(message, string.Empty));
 95        }
 96
 7697        return [.. messages];
 98    }
 99
 100    /// <summary>
 101    /// Matches the apostrophes and full stops FluentValidation puts in its default messages.
 102    /// </summary>
 103    [GeneratedRegex(@"['.]", RegexOptions.None, MatchTimeoutMilliseconds)]
 104    private static partial Regex SpecialChars();
 105}

/home/runner/work/dotnet-validation/dotnet-validation/src/obj/Release/net10.0/generated/System.Text.RegularExpressions.Generator/System.Text.RegularExpressions.Generator.RegexGenerator/RegexGenerator.g.cs

#LineLine coverage
 1// <auto-generated/>
 2#nullable enable
 3#pragma warning disable
 4
 5namespace ArturRios.Validation
 6{
 7    partial class FluentValidator<T>
 8    {
 9        /// <remarks>
 10        /// Pattern:<br/>
 11        /// <code>['.]</code><br/>
 12        /// Explanation:<br/>
 13        /// <code>
 14        /// ○ Match a character in the set ['.].<br/>
 15        /// </code>
 16        /// </remarks>
 17        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Text.RegularExpressions.Generator", "10.0.14.374
 1118        private static partial global::System.Text.RegularExpressions.Regex SpecialChars() => global::System.Text.Regula
 19    }
 20}
 21
 22namespace System.Text.RegularExpressions.Generated
 23{
 24    using System;
 25    using System.Buffers;
 26    using System.CodeDom.Compiler;
 27    using System.Collections;
 28    using System.ComponentModel;
 29    using System.Globalization;
 30    using System.Runtime.CompilerServices;
 31    using System.Text.RegularExpressions;
 32    using System.Threading;
 33
 34    /// <summary>Custom <see cref="Regex"/>-derived type for the SpecialChars method.</summary>
 35    [GeneratedCodeAttribute("System.Text.RegularExpressions.Generator", "10.0.14.37416")]
 36    file sealed class SpecialChars_0 : Regex
 37    {
 38        /// <summary>Cached, thread-safe singleton instance.</summary>
 39        internal static readonly SpecialChars_0 Instance = new();
 40
 41        /// <summary>Initializes the instance.</summary>
 42        private SpecialChars_0()
 43        {
 44            base.pattern = "['.]";
 45            base.roptions = RegexOptions.None;
 46            base.internalMatchTimeout = TimeSpan.FromMilliseconds(100);
 47            base.factory = new RunnerFactory();
 48            base.capsize = 1;
 49        }
 50
 51        /// <summary>Provides a factory for creating <see cref="RegexRunner"/> instances to be used by methods on <see c
 52        private sealed class RunnerFactory : RegexRunnerFactory
 53        {
 54            /// <summary>Creates an instance of a <see cref="RegexRunner"/> used by methods on <see cref="Regex"/>.</sum
 55            protected override RegexRunner CreateInstance() => new Runner();
 56
 57            /// <summary>Provides the runner that contains the custom logic implementing the specified regular expressio
 58            private sealed class Runner : RegexRunner
 59            {
 60                /// <summary>Scan the <paramref name="inputSpan"/> starting from base.runtextstart for the next match.</
 61                /// <param name="inputSpan">The text being scanned by the regular expression.</param>
 62                protected override void Scan(ReadOnlySpan<char> inputSpan)
 63                {
 64                    if (TryFindNextPossibleStartingPosition(inputSpan))
 65                    {
 66                        // The search in TryFindNextPossibleStartingPosition performed the entire match.
 67                        int start = base.runtextpos;
 68                        int end = base.runtextpos = start + 1;
 69                        base.Capture(0, start, end);
 70                    }
 71                }
 72
 73                /// <summary>Search <paramref name="inputSpan"/> starting from base.runtextpos for the next location a m
 74                /// <param name="inputSpan">The text being scanned by the regular expression.</param>
 75                /// <returns>true if a possible match was found; false if no more matches are possible.</returns>
 76                private bool TryFindNextPossibleStartingPosition(ReadOnlySpan<char> inputSpan)
 77                {
 78                    int pos = base.runtextpos;
 79
 80                    // Empty matches aren't possible.
 81                    if ((uint)pos < (uint)inputSpan.Length)
 82                    {
 83                        // The pattern begins with a character in the set ['.].
 84                        // Find the next occurrence. If it can't be found, there's no match.
 85                        int i = inputSpan.Slice(pos).IndexOfAny('\'', '.');
 86                        if (i >= 0)
 87                        {
 88                            base.runtextpos = pos + i;
 89                            return true;
 90                        }
 91                    }
 92
 93                    // No match found.
 94                    base.runtextpos = inputSpan.Length;
 95                    return false;
 96                }
 97            }
 98        }
 99
 100    }
 101
 102}