File: src\Shared\Data.Validation\TimeSpanAttribute.cs
Web Access
Project: src\src\Libraries\Microsoft.Extensions.Diagnostics.Probes\Microsoft.Extensions.Diagnostics.Probes.csproj (Microsoft.Extensions.Diagnostics.Probes)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using Microsoft.Shared.Diagnostics;
 
#pragma warning disable CA1716
namespace Microsoft.Shared.Data.Validation;
#pragma warning restore CA1716
 
/// <summary>
/// Provides boundary validation for <see cref="TimeSpan"/>.
/// </summary>
#if !SHARED_PROJECT
[ExcludeFromCodeCoverage]
#endif
 
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
[SuppressMessage("Design", "CA1019:Define accessors for attribute arguments", Justification = "Indirectly we are.")]
internal sealed class TimeSpanAttribute : ValidationAttribute
{
    /// <summary>
    /// Gets the lower bound for time span.
    /// </summary>
    public TimeSpan Minimum => _minMs.HasValue ? TimeSpan.FromMilliseconds((double)_minMs) : TimeSpan.Parse(_min!, CultureInfo.InvariantCulture);
 
    /// <summary>
    /// Gets the upper bound for time span.
    /// </summary>
    public TimeSpan? Maximum
    {
        get
        {
            if (_maxMs.HasValue)
            {
                return TimeSpan.FromMilliseconds((double)_maxMs);
            }
            else
            {
                return _max == null ? null : TimeSpan.Parse(_max, CultureInfo.InvariantCulture);
            }
        }
    }
 
    /// <summary>
    /// Gets or sets a value indicating whether the time span validation should exclude the minimum and maximum values.
    /// </summary>
    /// <value>
    /// The default value is <c>false</c>.
    /// </value>
    public bool Exclusive { get; set; }
 
    private readonly int? _minMs;
    private readonly int? _maxMs;
    private readonly string? _min;
    private readonly string? _max;
 
    /// <summary>
    /// Initializes a new instance of the <see cref="TimeSpanAttribute"/> class.
    /// </summary>
    /// <param name="minMs">Minimum in milliseconds.</param>
    public TimeSpanAttribute(int minMs)
    {
        _minMs = minMs;
        _maxMs = null;
    }
 
    /// <summary>
    /// Initializes a new instance of the <see cref="TimeSpanAttribute"/> class.
    /// </summary>
    /// <param name="minMs">Minimum in milliseconds.</param>
    /// <param name="maxMs">Maximum in milliseconds.</param>
    public TimeSpanAttribute(int minMs, int maxMs)
    {
        _minMs = minMs;
        _maxMs = maxMs;
    }
 
    /// <summary>
    /// Initializes a new instance of the <see cref="TimeSpanAttribute"/> class.
    /// </summary>
    /// <param name="min">Minimum represented as time span string.</param>
    public TimeSpanAttribute(string min)
    {
        _ = Throw.IfNullOrWhitespace(min);
 
        _min = min;
        _max = null;
    }
 
    /// <summary>
    /// Initializes a new instance of the <see cref="TimeSpanAttribute"/> class.
    /// </summary>
    /// <param name="min">Minimum represented as time span string.</param>
    /// <param name="max">Maximum represented as time span string.</param>
    public TimeSpanAttribute(string min, string max)
    {
        _ = Throw.IfNullOrWhitespace(min);
        _ = Throw.IfNullOrWhitespace(max);
 
        _min = min;
        _max = max;
    }
 
    /// <summary>
    /// Validates that a given value represents an in-range TimeSpan value.
    /// </summary>
    /// <param name="value">The value to validate.</param>
    /// <param name="validationContext">Additional context for this validation.</param>
    /// <returns>A value indicating success or failure.</returns>
    protected override ValidationResult IsValid(object? value, ValidationContext? validationContext)
    {
        var min = Minimum;
        var max = Maximum;
 
        if (min >= max)
        {
            throw new InvalidOperationException($"{nameof(TimeSpanAttribute)} requires that the minimum value be less than the maximum value (see field {validationContext.GetDisplayName()})");
        }
 
        if (value == null)
        {
            // use the [Required] attribute to force presence
            return ValidationResult.Success!;
        }
 
        if (value is TimeSpan ts)
        {
            if (Exclusive && ts <= min)
            {
                return new ValidationResult($"The field {validationContext.GetDisplayName()} must be > to {min}.", validationContext.GetMemberName());
            }
 
            if (ts < min)
            {
                return new ValidationResult($"The field {validationContext.GetDisplayName()} must be >= to {min}.", validationContext.GetMemberName());
            }
 
            if (max.HasValue)
            {
                if (Exclusive && ts >= max.Value)
                {
                    return new ValidationResult($"The field {validationContext.GetDisplayName()} must be < to {max}.", validationContext.GetMemberName());
                }
 
                if (ts > max.Value)
                {
                    return new ValidationResult($"The field {validationContext.GetDisplayName()} must be <= to {max}.", validationContext.GetMemberName());
                }
            }
 
            return ValidationResult.Success!;
        }
 
        throw new InvalidOperationException($"{nameof(TimeSpanAttribute)} can only be used with fields of type TimeSpan (see field {validationContext.GetDisplayName()})");
    }
}