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