// 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) || 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) || 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) || 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) || 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) || (value.CompareTo(minValue) >= 0 && 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.");
}
}