using SQLInsight.Core.Collection; using SQLInsight.Core.Findings; using SQLInsight.Rules.Engine; namespace SQLInsight.Rules.Servers; /// /// A rule about something that is not a SQL Server instance (a Windows server, a synthetic check). It produces the /// same findings, explanations and outcomes as the instance rules, against its own kind of context. /// public interface IScopedRule { /// Stable identifier, e.g. SQLI-WIN-001. Never reused for a different check. string Id { get; } string Title { get; } RuleEvaluation Evaluate(TContext context); } public abstract class ScopedRule : IScopedRule { public abstract string Id { get; } public abstract string Title { get; } public abstract RuleEvaluation Evaluate(TContext context); protected Finding CreateFinding(Severity severity, string title, string explanation, string recommendation, IEnumerable? evidence = null, string? subject = null, params Reference[] references) => new() { RuleId = Id, Severity = severity, Title = title, Subject = subject, Explanation = explanation, Recommendation = recommendation, Evidence = evidence?.ToList() ?? [], References = references, }; protected static RuleEvaluation Result(IEnumerable findings) { var list = findings.ToArray(); return list.Length == 0 ? RuleEvaluation.Passed() : RuleEvaluation.Found(list); } } /// Runs scoped rules. Like : a rule that cannot run is not measurable (with the reason) or an error, never silently passed. public static class ScopedRuleEngine { public static IReadOnlyList Evaluate(IEnumerable> rules, TContext context) { ArgumentNullException.ThrowIfNull(rules); return rules.Select(rule => Evaluate(rule, context)).ToList(); } private static RuleResult Evaluate(IScopedRule rule, TContext context) { try { var evaluation = rule.Evaluate(context); return new RuleResult { RuleId = rule.Id, RuleTitle = rule.Title, Outcome = evaluation.Outcome, Findings = evaluation.Findings, Reason = evaluation.Reason }; } catch (DataUnavailableException ex) { return new RuleResult { RuleId = rule.Id, RuleTitle = rule.Title, Outcome = ex.NotApplicable ? RuleOutcome.NotApplicable : RuleOutcome.NotMeasurable, Reason = ex.Message, }; } #pragma warning disable CA1031 // One faulty rule must not stop the others; the failure is reported, not hidden. catch (Exception ex) #pragma warning restore CA1031 { return new RuleResult { RuleId = rule.Id, RuleTitle = rule.Title, Outcome = RuleOutcome.Error, Reason = $"{ex.GetType().Name}: {ex.Message}" }; } } } /// Every rule about Windows servers and synthetic checks, for rule listings. public static class ServerRuleCatalog { public static IReadOnlyList<(string Id, string Title)> All => WindowsServerRules.Create().Select(r => (r.Id, r.Title)) .Concat(SyntheticCheckRules.Create().Select(r => (r.Id, r.Title))) .ToList(); }