// Copyright (c) The Geekeey Authors // SPDX-License-Identifier: EUPL-1.2 using System.Linq.Expressions; namespace Geekeey.Request.Validation; internal abstract record Rule { protected Rule(Expression expression) { PropertyPath = PropertyPath.FromExpression(expression); } protected PropertyPath GetPropertyPath() { return PropertyPathTransform(PropertyPath); } public PropertyPath PropertyPath { get; } public Severity Severity { get; init; } = Severity.Error; public string? Code { get; init; } public Func PropertyPathTransform { get; init; } = static path => path; public abstract IEnumerable Validate(ValidationContext context); } internal abstract record Rule : Rule { protected Rule(Expression expression) : base(expression) { } public IReadOnlyList> Steps { get; init; } = []; public override IEnumerable Validate(ValidationContext context) { if (context.Instance is T instance) { return Validate(instance, context); } if (context.Instance is null && default(T) is null) { return Validate((T)context.Instance!, context); } var actualType = context.Instance?.GetType().FullName ?? "null"; throw new InvalidOperationException( $"Expected validation context instance of type '{typeof(T).FullName}', but got '{actualType}'."); } protected abstract IEnumerable Validate(T instance, ValidationContext context); } internal sealed record PropertyRule : Rule { private readonly Func _accessor; public PropertyRule(Expression> expression) : base(expression) { _accessor = expression.Compile(); } protected override IEnumerable Validate(T instance, ValidationContext context) { if (Steps.Count is 0) { return []; } var value = _accessor(instance); var propertyPath = GetPropertyPath(); return Steps.SelectMany(step => step.Validate(value, context, propertyPath, Code, Severity)); } } internal sealed record CollectionRule : Rule { private readonly Func> _accessor; public CollectionRule(Expression>> expression) : base(expression) { _accessor = expression.Compile(); } protected override IEnumerable Validate(T instance, ValidationContext context) { if (Steps.Count is 0) { yield break; } if (_accessor(instance) is not { } collection) { yield break; } var index = 0; foreach (var element in collection) { var propertyPath = GetPropertyPath() + index; foreach (var step in Steps) { foreach (var problem in step.Validate(element, context, propertyPath, Code, Severity)) { yield return problem; } } index++; } } }