// Copyright (c) The Geekeey Authors // SPDX-License-Identifier: EUPL-1.2 using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; namespace Geekeey.Request.Validation; /// /// Provides built-in validators for common validation scenarios. /// public static class RuleBuilderExtensions { private static bool IsNull([NotNullWhen(false)] TProperty? value) { object? boxed = value; return boxed is null; } /// /// Adds a rule to ensure that the property value is not null for reference types. /// /// The type of the object being validated. /// The type of the property being validated. Must be a nullable reference type. /// The rule builder to which the condition is applied. /// The updated rule builder with the not-null condition added. public static IPropertyRuleBuilder NotNull( this IPropertyRuleBuilder rule) where TProperty : class? { ArgumentNullException.ThrowIfNull(rule); return rule.Must(static value => value is not null, "Value is required."); } /// /// Adds a rule to ensure that the property value is not null for nullable value types. /// /// The type of the object being validated. /// The underlying non-nullable value type of the property being validated. /// The rule builder to which the condition is applied. /// The updated rule builder with the not-null condition added. public static IPropertyRuleBuilder NotNull( this IPropertyRuleBuilder rule) where TProperty : struct { ArgumentNullException.ThrowIfNull(rule); return rule.Must(static value => value.HasValue, "Value is required."); } /// /// Adds a rule to ensure that the string property value is not null, empty, or whitespace. /// /// The type of the object being validated. /// The rule builder to which the condition is applied. /// The updated rule builder with the not-empty condition added. public static IPropertyRuleBuilder NotEmpty(this IPropertyRuleBuilder rule) { ArgumentNullException.ThrowIfNull(rule); return rule.Must(static value => !string.IsNullOrWhiteSpace(value), "Value is required."); } /// /// Adds a rule to ensure that the collection property value is not null and contains at least one element. /// /// The type of the object being validated. /// The type of the elements in the collection being validated. /// The rule builder to which the condition is applied. /// The updated rule builder with the not-empty condition added. public static IPropertyRuleBuilder?> NotEmpty( this IPropertyRuleBuilder?> rule) { ArgumentNullException.ThrowIfNull(rule); return rule.Must(static value => value is not null && value.Any(), "Value is required."); } /// /// Removes the current rule property path from emitted validation problems. /// /// The type of the object being validated. /// The type of the property being validated. /// The rule builder to which the property path transform is applied. /// The updated rule builder with the property path removed. public static IPropertyRuleBuilder WithoutPropertyPath( this IPropertyRuleBuilder rule) { ArgumentNullException.ThrowIfNull(rule); return rule.WithPropertyPath(static _ => string.Empty); } /// /// Adds a rule to ensure that the string property value meets the specified minimum length. /// /// The type of the object being validated. /// The rule builder to which the condition is applied. /// The minimum allowed number of characters. /// The updated rule builder with the minimum length condition added. public static IPropertyRuleBuilder MinLength( this IPropertyRuleBuilder rule, int minLength) { ArgumentNullException.ThrowIfNull(rule); ArgumentOutOfRangeException.ThrowIfNegative(minLength); return rule.Must(value => IsNull(value) || value.Length >= minLength, $"Value must be at least {minLength} characters long."); } /// /// Adds a rule to ensure that the string property value does not exceed the specified maximum length. /// /// The type of the object being validated. /// The rule builder to which the condition is applied. /// The maximum allowed number of characters. /// The updated rule builder with the maximum length condition added. public static IPropertyRuleBuilder MaxLength( this IPropertyRuleBuilder rule, int maxLength) { ArgumentNullException.ThrowIfNull(rule); ArgumentOutOfRangeException.ThrowIfNegative(maxLength); return rule.Must(value => IsNull(value) || value.Length <= maxLength, $"Value must be at most {maxLength} characters long."); } /// /// Adds a rule to ensure that the string property value falls within the specified inclusive length range. /// /// The type of the object being validated. /// The rule builder to which the condition is applied. /// The minimum allowed number of characters. /// The maximum allowed number of characters. /// The updated rule builder with the length range condition added. public static IPropertyRuleBuilder Length( this IPropertyRuleBuilder rule, int minLength, int maxLength) { ArgumentNullException.ThrowIfNull(rule); ArgumentOutOfRangeException.ThrowIfNegative(minLength); ArgumentOutOfRangeException.ThrowIfNegative(maxLength); if (maxLength < minLength) { throw new ArgumentOutOfRangeException(nameof(maxLength), "Maximum length must be greater than or equal to minimum length."); } return rule.Must(value => IsNull(value) || (value.Length >= minLength && value.Length <= maxLength), $"Value must be between {minLength} and {maxLength} characters long."); } /// /// Adds a rule to ensure that the property value is greater than the specified comparison value. /// /// The type of the object being validated. /// The type of the property being validated. /// The rule builder to which the condition is applied. /// The value that the property must be greater than. /// The updated rule builder with the greater-than condition added. public static IPropertyRuleBuilder GreaterThan( this IPropertyRuleBuilder rule, TProperty comparisonValue) where TProperty : IComparable? { ArgumentNullException.ThrowIfNull(rule); return rule.Must(value => IsNull(value) || IsNull(comparisonValue) || value.CompareTo(comparisonValue) > 0, $"Value must be greater than {comparisonValue}."); } /// /// Adds a rule to ensure that the property value is greater than or equal to the specified comparison value. /// /// The type of the object being validated. /// The type of the property being validated. /// The rule builder to which the condition is applied. /// The value that the property must be greater than or equal to. /// The updated rule builder with the greater-than-or-equal condition added. public static IPropertyRuleBuilder GreaterThanOrEqualTo( this IPropertyRuleBuilder rule, TProperty comparisonValue) where TProperty : IComparable? { ArgumentNullException.ThrowIfNull(rule); return rule.Must(value => IsNull(value) || IsNull(comparisonValue) || value.CompareTo(comparisonValue) >= 0, $"Value must be greater than or equal to {comparisonValue}."); } /// /// Adds a rule to ensure that the property value is less than the specified comparison value. /// /// The type of the object being validated. /// The type of the property being validated. /// The rule builder to which the condition is applied. /// The value that the property must be less than. /// The updated rule builder with the less-than condition added. public static IPropertyRuleBuilder LessThan( this IPropertyRuleBuilder rule, TProperty comparisonValue) where TProperty : IComparable? { ArgumentNullException.ThrowIfNull(rule); return rule.Must(value => IsNull(value) || IsNull(comparisonValue) || value.CompareTo(comparisonValue) < 0, $"Value must be less than {comparisonValue}."); } /// /// Adds a rule to ensure that the property value is less than or equal to the specified comparison value. /// /// The type of the object being validated. /// The type of the property being validated. /// The rule builder to which the condition is applied. /// The value that the property must be less than or equal to. /// The updated rule builder with the less-than-or-equal condition added. public static IPropertyRuleBuilder LessThanOrEqualTo( this IPropertyRuleBuilder rule, TProperty comparisonValue) where TProperty : IComparable? { ArgumentNullException.ThrowIfNull(rule); return rule.Must(value => IsNull(value) || IsNull(comparisonValue) || value.CompareTo(comparisonValue) <= 0, $"Value must be less than or equal to {comparisonValue}."); } /// /// Adds a rule to ensure that the property value falls within the specified inclusive range. /// /// The type of the object being validated. /// The type of the property being validated. /// The rule builder to which the condition is applied. /// The minimum allowed value. /// The maximum allowed value. /// The updated rule builder with the range condition added. public static IPropertyRuleBuilder Between( this IPropertyRuleBuilder rule, TProperty minValue, TProperty maxValue) where TProperty : IComparable? { ArgumentNullException.ThrowIfNull(rule); if (minValue is not null && maxValue is not null && minValue.CompareTo(maxValue) > 0) { throw new ArgumentOutOfRangeException(nameof(maxValue), "Maximum value must be greater than or equal to minimum value."); } return rule.Must(value => IsNull(value) || ((minValue is null || value.CompareTo(minValue) >= 0) && (maxValue is null || value.CompareTo(maxValue) <= 0)), $"Value must be between {minValue} and {maxValue}."); } /// /// Adds a rule to ensure that the property value is equal to the specified comparison value. /// /// The type of the object being validated. /// The type of the property being validated. /// The rule builder to which the condition is applied. /// The required value for the property. /// The updated rule builder with the equality condition added. public static IPropertyRuleBuilder Equal( this IPropertyRuleBuilder rule, TProperty comparisonValue) where TProperty : IEquatable? { ArgumentNullException.ThrowIfNull(rule); return rule.Must(value => EqualityComparer.Default.Equals(value, comparisonValue), $"Value must be equal to {comparisonValue}."); } /// /// Adds a rule to ensure that the property value is not equal to the specified disallowed value. /// /// The type of the object being validated. /// The type of the property being validated. /// The rule builder to which the condition is applied. /// The value that the property must not equal. /// The updated rule builder with the inequality condition added. public static IPropertyRuleBuilder NotEqual( this IPropertyRuleBuilder rule, TProperty disallowedValue) where TProperty : IEquatable? { ArgumentNullException.ThrowIfNull(rule); return rule.Must(value => !EqualityComparer.Default.Equals(value, disallowedValue), $"Value must not be equal to {disallowedValue}."); } /// /// Adds a rule to ensure that the string property value matches the specified regular expression pattern. /// /// The type of the object being validated. /// The rule builder to which the condition is applied. /// The regular expression pattern that the property value must match. /// The updated rule builder with the pattern-matching condition added. public static IPropertyRuleBuilder Matches( this IPropertyRuleBuilder rule, string pattern) { ArgumentNullException.ThrowIfNull(rule); ArgumentException.ThrowIfNullOrEmpty(pattern); return rule.Must(value => IsNull(value) || Regex.IsMatch(value, pattern), "Value is not in the correct format."); } /// /// Adds a rule to ensure that the string property value matches the specified regular expression. /// /// The type of the object being validated. /// The rule builder to which the condition is applied. /// The regular expression that the property value must match. /// The updated rule builder with the pattern-matching condition added. public static IPropertyRuleBuilder Matches( this IPropertyRuleBuilder rule, Regex regex) { ArgumentNullException.ThrowIfNull(rule); ArgumentNullException.ThrowIfNull(regex); return rule.Must(value => IsNull(value) || regex.IsMatch(value), "Value is not in the correct format."); } }