///|
/// Deployment policy used by preflight checks. It lets a service enforce
/// conventions before activating a provider without changing evaluation rules.
pub(all) struct ProviderPolicy {
require_non_empty_keys : Bool
allow_disabled_flags : Bool
allow_zero_rollout : Bool
require_rollout_key_prefix : String?
max_rollout_percentage : Int
} derive(Debug, Eq)
///|
pub fn default_policy() -> ProviderPolicy {
{
require_non_empty_keys: true,
allow_disabled_flags: true,
allow_zero_rollout: true,
require_rollout_key_prefix: None,
max_rollout_percentage: 10000,
}
}
///|
pub(all) struct PolicyViolation {
key : String
code : String
message : String
} derive(Debug, Eq)
///|
pub(all) struct PolicyReport {
provider_fingerprint : String
violations : Array[PolicyViolation]
} derive(Debug)
///|
fn violates_policy(
policy : ProviderPolicy,
flag : FlagDefinition,
violations : Array[PolicyViolation],
) -> Unit {
if policy.require_non_empty_keys && flag.key == "" {
violations.push({
key: flag.key,
code: "empty_key",
message: "flag keys must not be empty",
})
}
if !policy.allow_disabled_flags && !flag.enabled {
violations.push({
key: flag.key,
code: "disabled_not_allowed",
message: "disabled flags are not allowed by deployment policy",
})
}
match flag.rollout_percentage {
Some(percentage) => {
if percentage == 0 && !policy.allow_zero_rollout {
violations.push({
key: flag.key,
code: "zero_rollout_not_allowed",
message: "zero percent rollout is not allowed",
})
}
if percentage > policy.max_rollout_percentage {
violations.push({
key: flag.key,
code: "rollout_limit",
message: "rollout percentage exceeds deployment policy",
})
}
match policy.require_rollout_key_prefix {
Some(prefix) if !flag.key.has_prefix(prefix) =>
violations.push({
key: flag.key,
code: "rollout_key_prefix",
message: "rollout flag key does not use the required prefix",
})
_ => ()
}
}
None => ()
}
}
///|
pub fn Provider::check_policy(
self : Provider,
policy : ProviderPolicy,
) -> PolicyReport {
let violations = []
for _, flag in self.flags {
violates_policy(policy, flag, violations)
}
{ provider_fingerprint: self.fingerprint(), violations }
}
///|
pub fn PolicyReport::is_compliant(self : PolicyReport) -> Bool {
self.violations.length() == 0
}
///|
pub fn PolicyReport::violation_count(self : PolicyReport) -> Int {
self.violations.length()
}
///|
pub fn PolicyReport::summary(self : PolicyReport) -> String {
"provider=" +
self.provider_fingerprint +
", violations=" +
self.violations.length().to_string()
}
///|
pub fn PolicyReport::render(self : PolicyReport) -> String {
let lines = [self.summary()]
for violation in self.violations {
lines.push(
"[" + violation.key + "] " + violation.code + ": " + violation.message,
)
}
lines.join("\n")
}
///|
pub fn Provider::activate_if_compliant(
self : Provider,
policy : ProviderPolicy,
) -> Provider? {
if self.check_policy(policy).is_compliant() {
Some(self)
} else {
None
}
}
///|
pub fn ProviderPolicy::production() -> ProviderPolicy {
{
require_non_empty_keys: true,
allow_disabled_flags: false,
allow_zero_rollout: false,
require_rollout_key_prefix: Some("release."),
max_rollout_percentage: 10000,
}
}
///|
pub fn ProviderPolicy::for_tests() -> ProviderPolicy {
{
require_non_empty_keys: true,
allow_disabled_flags: true,
allow_zero_rollout: true,
require_rollout_key_prefix: None,
max_rollout_percentage: 10000,
}
}