///|
/// Compatibility classification for configuration deployments. It provides a
/// reviewable answer to the common question: can this provider replace the
/// previous one without making existing flag lookups disappear?
pub(all) enum CompatibilityLevel {
Identical
Additive
Changed
Breaking
} derive(Debug, Eq)
///|
pub(all) struct CompatibilityReport {
level : CompatibilityLevel
diff : ProviderDiff
message : String
} derive(Debug)
///|
pub fn compare_providers(
previous : Provider,
next : Provider,
) -> CompatibilityReport {
let diff = diff_providers(previous, next)
let level = if diff.added.length() == 0 &&
diff.removed.length() == 0 &&
diff.changed.length() == 0 {
Identical
} else if diff.removed.length() > 0 {
Breaking
} else if diff.changed.length() > 0 {
Changed
} else {
Additive
}
let message = match level {
Identical => "provider has no configuration changes"
Additive => "provider only adds new flag keys"
Changed =>
"provider changes existing flag definitions; replay tests required"
Breaking =>
"provider removes existing flag keys; compatibility review required"
}
{ level, diff, message }
}
///|
pub fn CompatibilityReport::is_safe(self : CompatibilityReport) -> Bool {
self.level is (Identical | Additive)
}
///|
pub fn CompatibilityReport::requires_replay(self : CompatibilityReport) -> Bool {
self.level is (Changed | Breaking)
}
///|
pub fn CompatibilityReport::summary(self : CompatibilityReport) -> String {
let level = match self.level {
Identical => "identical"
Additive => "additive"
Changed => "changed"
Breaking => "breaking"
}
level + ": " + self.message + " (" + self.diff.summary() + ")"
}
///|
pub fn Provider::same_configuration(self : Provider, other : Provider) -> Bool {
compare_providers(self, other).level is Identical
}
///|
pub fn Provider::has_only_additions(self : Provider, other : Provider) -> Bool {
compare_providers(self, other).level is Additive
}
///|
pub fn Provider::overlay_if_safe(
self : Provider,
overlay : Provider,
) -> Provider? {
let report = compare_providers(self, overlay)
if report.level is (Identical | Additive) {
Some(self.merge(overlay))
} else {
None
}
}
///|
pub(all) struct ProviderInventory {
total : Int
bool_flags : Int
string_flags : Int
int_flags : Int
double_flags : Int
static_flags : Int
rollout_flags : Int
targeted_flags : Int
enabled_flags : Int
disabled_flags : Int
} derive(Debug, Eq)
///|
pub fn Provider::inventory(self : Provider) -> ProviderInventory {
let stats = self.stats()
{
total: stats.total,
bool_flags: stats.bool_count,
string_flags: stats.string_count,
int_flags: stats.int_count,
double_flags: stats.double_count,
static_flags: stats.total - stats.rollout - stats.targeted,
rollout_flags: stats.rollout,
targeted_flags: stats.targeted,
enabled_flags: stats.enabled,
disabled_flags: stats.disabled,
}
}
///|
pub fn ProviderInventory::summary(self : ProviderInventory) -> String {
"flags=" +
self.total.to_string() +
", typed=(bool=" +
self.bool_flags.to_string() +
", string=" +
self.string_flags.to_string() +
", int=" +
self.int_flags.to_string() +
", double=" +
self.double_flags.to_string() +
"), rules=(static=" +
self.static_flags.to_string() +
", rollout=" +
self.rollout_flags.to_string() +
", target=" +
self.targeted_flags.to_string() +
")"
}
///|
pub fn Provider::rollout_keys(self : Provider) -> Array[String] {
let keys = []
for key, flag in self.flags {
if flag.is_rollout() {
keys.push(key)
}
}
keys.sort()
keys
}
///|
pub fn Provider::targeted_keys(self : Provider) -> Array[String] {
let keys = []
for key, flag in self.flags {
if flag.has_target() {
keys.push(key)
}
}
keys.sort()
keys
}
///|
pub fn Provider::static_keys(self : Provider) -> Array[String] {
let keys = []
for key, flag in self.flags {
if !flag.is_rollout() && !flag.has_target() {
keys.push(key)
}
}
keys.sort()
keys
}
///|
pub fn ProviderInventory::is_balanced(self : ProviderInventory) -> Bool {
self.total ==
self.bool_flags + self.string_flags + self.int_flags + self.double_flags
}