///|
pub(all) enum ValidationKind {
EmptyName
InvalidTarget
InvalidWindow
InvalidRule
EmptyCollection
} derive(Eq, Debug)
///|
pub(all) struct ValidationIssue {
code : String
message : String
kind : ValidationKind
} derive(Eq, Debug)
///|
pub(all) struct ValidationReport {
mut valid : Bool
mut checked : Int
issues : Array[ValidationIssue]
} derive(Eq, Debug)
///|
fn empty_report() -> ValidationReport {
{ valid: true, checked: 0, issues: [] }
}
///|
fn issue(
report : ValidationReport,
kind : ValidationKind,
code : String,
message : String,
) -> ValidationReport {
let issues = report.issues
issues.push({ code, message, kind })
{ ..report, valid: false, issues }
}
///|
fn checked(report : ValidationReport) -> ValidationReport {
{ ..report, checked: report.checked + 1 }
}
///|
pub fn ValidationReport::has_code(
self : ValidationReport,
code : String,
) -> Bool {
for item in self.issues {
if item.code == code {
return true
}
}
false
}
///|
pub fn ValidationReport::issue_count(self : ValidationReport) -> Int {
self.issues.length()
}
///|
pub fn ValidationReport::summary(self : ValidationReport) -> String {
if self.valid {
"valid: \{self.checked} checks"
} else {
"invalid: \{self.issues.length()} issues across \{self.checked} checks"
}
}
///|
pub fn validate_target(target : SloTarget) -> ValidationReport {
let report = checked(empty_report())
let report = if target.name.length() == 0 {
issue(report, EmptyName, "empty_name", "SLO target name must not be empty")
} else {
report
}
if target.target_bp < 0 ||
target.target_bp > 10000 ||
target.period_minutes <= 0 {
issue(
report,
InvalidTarget,
"invalid_target",
"target basis points and period must be in range",
)
} else {
report
}
}
///|
pub fn validate_window(window : RequestWindow) -> ValidationReport {
let report = checked(empty_report())
let report = if window.name.length() == 0 {
issue(
report,
EmptyName,
"empty_name",
"request window name must not be empty",
)
} else {
report
}
if window.minutes <= 0 ||
window.total < 0 ||
window.good < 0 ||
window.good > window.total {
issue(
report,
InvalidWindow,
"invalid_window",
"window counts and duration are inconsistent",
)
} else {
report
}
}
///|
pub fn validate_rule(rule : BurnRule) -> ValidationReport {
let report = checked(empty_report())
if rule.name.length() == 0 || rule.threshold_x100 <= 0 {
issue(
report,
InvalidRule,
"invalid_rule",
"burn rule needs a name and positive threshold",
)
} else {
report
}
}
///|
pub fn validate_samples(samples : Array[TrafficSample]) -> ValidationReport {
let mut report = empty_report()
if samples.length() == 0 {
report = issue(
report,
EmptyCollection,
"empty_samples",
"at least one traffic sample is required",
)
report
} else {
for sample in samples {
let _ = checked(report)
if sample.total < 0 || sample.good < 0 || sample.good > sample.total {
report.valid = false
report.issues.push({
code: "invalid_sample",
message: "sample good count must be between zero and total",
kind: InvalidWindow,
})
}
}
report
}
}
///|
pub fn validate_config(config : SloConfig) -> ValidationReport {
let target_report = validate_target(config.target)
let mut report = target_report
if config.windows.length() == 0 {
report = issue(
report,
EmptyCollection,
"empty_windows",
"configuration needs at least one window",
)
ignore(report)
} else {
for policy in config.windows {
let _ = checked(report)
if policy.minutes <= 0 || policy.name.length() == 0 {
report.valid = false
report.issues.push({
code: "invalid_policy_window",
message: "window policy needs a name and positive duration",
kind: InvalidWindow,
})
}
}
}
if config.rules.length() == 0 {
report = issue(
report,
EmptyCollection,
"empty_rules",
"configuration needs at least one burn rule",
)
ignore(report)
} else {
for rule in config.rules {
let rule_report = validate_rule(rule)
report.checked = report.checked + rule_report.checked
for item in rule_report.issues {
report.issues.push(item)
}
if !rule_report.valid {
report.valid = false
}
}
}
report
}
///|
pub fn validate_windows(windows : Array[RequestWindow]) -> ValidationReport {
let mut report = empty_report()
if windows.length() == 0 {
report = issue(
report,
EmptyCollection,
"empty_windows",
"at least one request window is required",
)
ignore(report)
} else {
for window in windows {
let item = validate_window(window)
report.checked = report.checked + item.checked
if !item.valid {
report.valid = false
}
for problem in item.issues {
report.issues.push(problem)
}
}
}
report
}
///|
pub fn merge_validation(
a : ValidationReport,
b : ValidationReport,
) -> ValidationReport {
let result = {
valid: a.valid && b.valid,
checked: a.checked + b.checked,
issues: [],
}
for item in a.issues {
result.issues.push(item)
}
for item in b.issues {
result.issues.push(item)
}
result
}