using Greencore.platform.rules; namespace Greencore.platform.validators; /// Mensaje asociado a una aparición; se resuelve utilizando evidencia ya capturada. public sealed class RuleDiagnosticDefinition { private readonly Func _message; public string Code { get; } public RuleDiagnosticDefinition(string code, Func message) { ArgumentException.ThrowIfNullOrWhiteSpace(code); ArgumentNullException.ThrowIfNull(message); Code = code; _message = message; } public RuleDiagnosticDefinition(string code, string message) : this(code, _ => message) { ArgumentException.ThrowIfNullOrWhiteSpace(message); } public string Format(RuleEvaluation evaluation) { var message = _message(evaluation); ArgumentException.ThrowIfNullOrWhiteSpace(message); return message; } } public sealed record RuleDiagnostic(RuleEvaluation Evaluation, string Code, string Message, IReadOnlyList Children) { public string? Path => Evaluation.Path; public bool IsGroup => Evaluation.Kind != RuleNodeKind.Leaf; } /// Proyección neutral; las alternativas exitosas nunca publican sus fallos parciales. public sealed class RuleDiagnostics { private readonly IReadOnlyDictionary _definitions; internal RuleDiagnosticDefinition? Find(string nodeId) => _definitions.GetValueOrDefault(nodeId); public RuleDiagnostics(IEnumerable> definitions) { ArgumentNullException.ThrowIfNull(definitions); var copy = new Dictionary(StringComparer.Ordinal); foreach (var pair in definitions) { ArgumentException.ThrowIfNullOrWhiteSpace(pair.Key); ArgumentNullException.ThrowIfNull(pair.Value); copy.Add(pair.Key, pair.Value); } _definitions = copy; } public IReadOnlyList Project(RuleEvaluation evaluation) { ArgumentNullException.ThrowIfNull(evaluation); if (evaluation.Status != RuleStatus.Unsatisfied) return []; var children = evaluation.Kind == RuleNodeKind.Not ? [] : evaluation.Children.SelectMany(Project).ToArray(); if (_definitions.TryGetValue(evaluation.NodeId, out var definition)) return [new(evaluation, definition.Code, definition.Format(evaluation), Array.AsReadOnly(children))]; if (evaluation.Kind is RuleNodeKind.Leaf or RuleNodeKind.AnyOf or RuleNodeKind.Not) throw new InvalidOperationException($"Missing diagnostic for node '{evaluation.NodeId}'."); return Array.AsReadOnly(children); } /// Rechaza antes de evaluar las ramas que pueden fallar sin diagnóstico. public void Validate(RuleNode tree) { ArgumentNullException.ThrowIfNull(tree); if (tree.Kind is RuleNodeKind.Leaf or RuleNodeKind.AnyOf or RuleNodeKind.Not && !_definitions.ContainsKey(tree.NodeId)) throw new ArgumentException($"Missing diagnostic for node '{tree.NodeId}'.", nameof(tree)); if (tree.Kind == RuleNodeKind.Not) return; var children = tree.Kind == RuleNodeKind.When ? tree.Children.Skip(1) : tree.Children; foreach (var child in children) Validate(child); } }