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();
}