///|
pub(all) enum AuditSeverity {
Info
Warning
Error
} derive(Debug, Eq)
///|
pub(all) struct AuditIssue {
key : String
code : String
severity : AuditSeverity
message : String
} derive(Debug, Eq)
///|
pub(all) struct AuditReport {
total_flags : Int
errors : Int
warnings : Int
infos : Int
issues : Array[AuditIssue]
} derive(Debug)
///|
fn issue(
issues : Array[AuditIssue],
key : String,
code : String,
severity : AuditSeverity,
message : String,
) -> Unit {
issues.push({ key, code, severity, message })
}
///|
fn add_validation_issues(
issues : Array[AuditIssue],
flag : FlagDefinition,
) -> Unit {
for code in flag.validation_errors() {
issue(issues, flag.key, code, Error, "flag definition is invalid: " + code)
}
match flag.rollout_percentage {
Some(0) =>
issue(
issues,
flag.key,
"zero_rollout",
Info,
"rollout is intentionally disabled at 0%",
)
Some(10000) =>
issue(
issues,
flag.key,
"full_rollout",
Info,
"rollout is configured for all targeting keys",
)
Some(_) => ()
None => ()
}
if flag.has_target() && flag.is_rollout() {
issue(
issues,
flag.key,
"target_before_rollout",
Warning,
"target matching is evaluated before percentage rollout",
)
}
if !flag.enabled {
issue(
issues,
flag.key,
"disabled_flag",
Info,
"flag is disabled and returns the caller default",
)
}
}
///|
pub fn Provider::audit(self : Provider) -> AuditReport {
let issues = []
for _, flag in self.flags {
add_validation_issues(issues, flag)
}
let mut errors = 0
let mut warnings = 0
let mut infos = 0
for item in issues {
match item.severity {
Error => errors += 1
Warning => warnings += 1
Info => infos += 1
}
}
{ total_flags: self.flags.length(), errors, warnings, infos, issues }
}
///|
pub fn AuditReport::is_clean(self : AuditReport) -> Bool {
self.errors == 0
}
///|
pub fn AuditReport::has_warnings(self : AuditReport) -> Bool {
self.warnings > 0
}
///|
pub fn AuditReport::issue_count(
self : AuditReport,
severity : AuditSeverity,
) -> Int {
match severity {
Error => self.errors
Warning => self.warnings
Info => self.infos
}
}
///|
pub fn AuditReport::summary(self : AuditReport) -> String {
self.total_flags.to_string() +
" flags audited; errors=" +
self.errors.to_string() +
", warnings=" +
self.warnings.to_string() +
", infos=" +
self.infos.to_string()
}
///|
pub fn AuditReport::render(self : AuditReport) -> String {
let lines = [self.summary()]
for item in self.issues {
let severity = match item.severity {
Error => "error"
Warning => "warning"
Info => "info"
}
lines.push(
severity + " [" + item.key + "] " + item.code + ": " + item.message,
)
}
lines.join("\n")
}
///|
pub fn Provider::is_safe(self : Provider) -> Bool {
self.audit().is_clean()
}
///|
pub fn Provider::fingerprint(self : Provider) -> String {
let mut acc = 216613626
let keys = self.flag_keys()
keys.sort()
for key in keys {
let flag = self.get_flag_definition(key).unwrap()
let line = flag.key +
"=" +
flag.value.to_text() +
":" +
flag.value_kind() +
":" +
flag.enabled.to_string()
for char in line {
acc = (acc * 16777619 + char.to_int()) % 2147483647
}
}
acc.to_string()
}
///|
pub fn Provider::describe(self : Provider) -> String {
let lines = ["MoonFeatureGate provider " + self.fingerprint()]
let keys = self.flag_keys()
keys.sort()
for key in keys {
let flag = self.get_flag_definition(key).unwrap()
let mut rule = flag.value_kind()
if flag.is_rollout() {
rule = rule + ":rollout"
}
if flag.has_target() {
rule = rule + ":target"
}
if !flag.enabled {
rule = rule + ":disabled"
}
lines.push("- " + flag.key + " -> " + rule)
}
lines.join("\n")
}