///|
/// Named policy catalog for deployments with multiple data release channels.
pub(all) struct PolicyEntry {
name : String
version : String
policy : RedactionPolicy
owner : String
description : String
enabled : Bool
checksum : String
} derive(Debug, Eq)
///|
pub(all) struct PolicyCatalog {
mut entries : Map[String, PolicyEntry]
mut default_name : String
} derive(Debug)
///|
pub fn PolicyCatalog::new() -> PolicyCatalog {
{ entries: Map([]), default_name: "" }
}
///|
pub fn PolicyCatalog::with_defaults() -> PolicyCatalog {
let catalog = PolicyCatalog::new()
catalog.add(PolicyEntry::{
name: "default",
version: "1",
policy: RedactionPolicy::default(),
owner: "platform",
description: "balanced clinical text policy",
enabled: true,
checksum: "",
})
catalog.add(PolicyEntry::{
name: "strict",
version: "1",
policy: RedactionPolicy::strict(),
owner: "platform",
description: "preserve-length strict release policy",
enabled: true,
checksum: "",
})
catalog.add(PolicyEntry::{
name: "review",
version: "1",
policy: RedactionPolicy::review(),
owner: "platform",
description: "review-only exploration policy",
enabled: true,
checksum: "",
})
catalog.default_name = "default"
catalog
}
///|
pub fn policy_entry_checksum(entry : PolicyEntry) -> String {
stable_hash(
entry.name + ":" + entry.version + ":" + policy_summary(entry.policy),
)
}
///|
pub fn policy_entry_normalized(entry : PolicyEntry) -> PolicyEntry {
{ ..entry, checksum: policy_entry_checksum(entry) }
}
///|
pub fn PolicyCatalog::add(self : PolicyCatalog, entry : PolicyEntry) -> Unit {
let normalized = policy_entry_normalized(entry)
self.entries[normalized.name] = normalized
if self.default_name.is_empty() {
self.default_name = normalized.name
}
}
///|
pub fn PolicyCatalog::remove(self : PolicyCatalog, name : String) -> Bool {
if self.entries.contains(name) {
self.entries.remove(name)
if self.default_name == name {
self.default_name = ""
}
true
} else {
false
}
}
///|
pub fn PolicyCatalog::get(self : PolicyCatalog, name : String) -> PolicyEntry? {
self.entries.get(name)
}
///|
pub fn PolicyCatalog::default_entry(self : PolicyCatalog) -> PolicyEntry? {
self.entries.get(self.default_name)
}
///|
pub fn PolicyCatalog::set_default(self : PolicyCatalog, name : String) -> Bool {
match self.entries.get(name) {
Some(entry) if entry.enabled => {
self.default_name = name
true
}
_ => false
}
}
///|
pub fn PolicyCatalog::names(self : PolicyCatalog) -> Array[String] {
let names = self.entries.keys().to_array()
names.sort()
names
}
///|
pub fn PolicyCatalog::enabled(self : PolicyCatalog) -> Array[PolicyEntry] {
self.entries.values().filter(fn(entry) { entry.enabled }).to_array()
}
///|
pub fn PolicyCatalog::disable(self : PolicyCatalog, name : String) -> Bool {
match self.entries.get(name) {
Some(entry) => {
self.entries[name] = { ..entry, enabled: false }
if self.default_name == name {
self.default_name = ""
}
true
}
None => false
}
}
///|
pub fn PolicyCatalog::enable(self : PolicyCatalog, name : String) -> Bool {
match self.entries.get(name) {
Some(entry) => {
self.entries[name] = { ..entry, enabled: true }
true
}
None => false
}
}
///|
pub fn PolicyCatalog::policy_for(
self : PolicyCatalog,
name : String,
) -> RedactionPolicy? {
self.get(name).map(fn(entry) { entry.policy })
}
///|
pub fn PolicyCatalog::default_policy(self : PolicyCatalog) -> RedactionPolicy {
match self.default_entry() {
Some(entry) => entry.policy
None => RedactionPolicy::default()
}
}
///|
pub fn PolicyCatalog::size(self : PolicyCatalog) -> Int {
self.entries.length()
}
///|
pub fn PolicyCatalog::checksum(self : PolicyCatalog) -> String {
let lines = []
for name, entry in self.entries {
lines.push(name + ":" + policy_entry_checksum(entry))
}
lines.sort()
stable_hash(lines.join("\n"))
}
///|
pub fn policy_catalog_summary(catalog : PolicyCatalog) -> String {
[
"policies=\{catalog.size()}",
"enabled=\{catalog.enabled().length()}",
"default=\{catalog.default_name}",
"checksum=\{catalog.checksum()}",
].join("\n")
}
///|
pub fn policy_catalog_validate(catalog : PolicyCatalog) -> Array[String] {
let issues = []
if catalog.default_name.is_empty() {
issues.push("default policy is not configured")
} else if catalog.default_entry() is None {
issues.push("default policy does not exist")
}
for name, entry in catalog.entries {
if name != entry.name {
issues.push("catalog key differs from policy name: \{name}")
}
if entry.version.trim().is_empty() {
issues.push("policy version is empty: \{name}")
}
if entry.enabled && policy_summary(entry.policy).is_empty() {
issues.push("enabled policy has empty summary: \{name}")
}
}
issues
}
///|
pub fn policy_catalog_is_valid(catalog : PolicyCatalog) -> Bool {
policy_catalog_validate(catalog).is_empty()
}
///|
pub fn policy_catalog_find_by_owner(
catalog : PolicyCatalog,
owner : String,
) -> Array[PolicyEntry] {
catalog.entries.values().filter(fn(entry) { entry.owner == owner }).to_array()
}
///|
pub fn policy_catalog_find_by_mode(
catalog : PolicyCatalog,
mode : ReplacementMode,
) -> Array[PolicyEntry] {
catalog.entries
.values()
.filter(fn(entry) { entry.policy.mode == mode })
.to_array()
}
///|
pub fn policy_catalog_for_locale(
catalog : PolicyCatalog,
locale : LocaleHint,
) -> Array[PolicyEntry] {
catalog.entries
.values()
.filter(fn(entry) {
entry.policy.locale == locale || entry.policy.locale == Mixed
})
.to_array()
}
///|
pub fn policy_catalog_json(catalog : PolicyCatalog) -> String {
let entries = catalog.entries
.values()
.to_array()
.map(fn(entry) {
"{" +
"\"name\":\{json_escape(entry.name)}," +
"\"version\":\{json_escape(entry.version)}," +
"\"owner\":\{json_escape(entry.owner)}," +
"\"description\":\{json_escape(entry.description)}," +
"\"enabled\":\{entry.enabled}," +
"\"checksum\":\{json_escape(entry.checksum)}" +
"}"
})
.join(",")
"{" +
"\"default\":\{json_escape(catalog.default_name)}," +
"\"checksum\":\{json_escape(catalog.checksum())}," +
"\"entries\":[" +
entries +
"]}"
}
///|
pub fn policy_catalog_markdown(catalog : PolicyCatalog) -> String {
let lines = [
"| Name | Version | Owner | Enabled | Mode | Checksum |", "| --- | --- | --- | --- | --- | --- |",
]
for name in catalog.names() {
match catalog.get(name) {
Some(entry) =>
lines.push(
"| \{entry.name} | \{entry.version} | \{entry.owner} | \{entry.enabled} | \{mode_name(entry.policy.mode)} | \{entry.checksum} |",
)
None => ()
}
}
lines.join("\n")
}
///|
pub fn policy_catalog_clone(catalog : PolicyCatalog) -> PolicyCatalog {
let result = PolicyCatalog::new()
result.default_name = catalog.default_name
for entry in catalog.entries.values() {
result.add(entry)
}
result
}
///|
pub fn policy_catalog_same(left : PolicyCatalog, right : PolicyCatalog) -> Bool {
left.checksum() == right.checksum() && left.default_name == right.default_name
}
///|
pub fn policy_catalog_merge(
left : PolicyCatalog,
right : PolicyCatalog,
) -> PolicyCatalog {
let result = policy_catalog_clone(left)
for entry in right.entries.values() {
result.add(entry)
}
if result.default_name.is_empty() {
result.default_name = right.default_name
}
result
}
///|
pub fn policy_catalog_apply(
catalog : PolicyCatalog,
name : String,
input : String,
) -> DeidResult raise DeidError {
let policy = match catalog.policy_for(name) {
Some(value) => value
None => catalog.default_policy()
}
redact_with_policy(input, policy)
}
///|
pub fn policy_catalog_apply_default(
catalog : PolicyCatalog,
input : String,
) -> DeidResult raise DeidError {
policy_catalog_apply(catalog, catalog.default_name, input)
}
///|
pub fn policy_catalog_mode_counts(catalog : PolicyCatalog) -> Map[String, Int] {
let counts : Map[String, Int] = Map([])
for entry in catalog.entries.values() {
let key = mode_name(entry.policy.mode)
counts[key] = counts.get_or_default(key, 0) + 1
}
counts
}
///|
pub fn policy_catalog_locale_counts(
catalog : PolicyCatalog,
) -> Map[String, Int] {
let counts : Map[String, Int] = Map([])
for entry in catalog.entries.values() {
let key = locale_name(entry.policy.locale)
counts[key] = counts.get_or_default(key, 0) + 1
}
counts
}