1use serde::{Deserialize, Serialize};
5use std::fmt;
6use std::str::FromStr;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
9#[serde(rename_all = "snake_case")]
10pub enum PolicyKey {
11 TenantBinding,
12 Graphql,
13 StorageScope,
14 Egress,
15 WasmLimits,
16 ServiceAccounts,
17 Audience,
18 RedisScope,
19 StrictFilters,
20 Outbox,
21 DbRoles,
22 SchemaGuard,
23}
24
25impl PolicyKey {
26 pub const ALL: [PolicyKey; 12] = [
27 PolicyKey::TenantBinding,
28 PolicyKey::Graphql,
29 PolicyKey::StorageScope,
30 PolicyKey::Egress,
31 PolicyKey::WasmLimits,
32 PolicyKey::ServiceAccounts,
33 PolicyKey::Audience,
34 PolicyKey::RedisScope,
35 PolicyKey::StrictFilters,
36 PolicyKey::Outbox,
37 PolicyKey::DbRoles,
38 PolicyKey::SchemaGuard,
39 ];
40
41 pub fn as_str(self) -> &'static str {
42 match self {
43 PolicyKey::TenantBinding => "tenant_binding",
44 PolicyKey::Graphql => "graphql",
45 PolicyKey::StorageScope => "storage_scope",
46 PolicyKey::Egress => "egress",
47 PolicyKey::WasmLimits => "wasm_limits",
48 PolicyKey::ServiceAccounts => "service_accounts",
49 PolicyKey::Audience => "audience",
50 PolicyKey::RedisScope => "redis_scope",
51 PolicyKey::StrictFilters => "strict_filters",
52 PolicyKey::Outbox => "outbox",
53 PolicyKey::DbRoles => "db_roles",
54 PolicyKey::SchemaGuard => "schema_guard",
55 }
56 }
57
58 pub fn env_suffix(self) -> String {
60 self.as_str().to_ascii_uppercase()
61 }
62}
63
64const _: () = {
67 let mut i = 0;
68 while i < PolicyKey::ALL.len() {
69 assert!(PolicyKey::ALL[i] as usize == i);
70 i += 1;
71 }
72};
73
74impl fmt::Display for PolicyKey {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 f.write_str(self.as_str())
77 }
78}
79
80impl FromStr for PolicyKey {
81 type Err = String;
82
83 fn from_str(s: &str) -> Result<Self, Self::Err> {
84 let wanted = s.trim();
85 PolicyKey::ALL
86 .into_iter()
87 .find(|key| key.as_str() == wanted)
88 .ok_or_else(|| {
89 let valid: Vec<&str> = PolicyKey::ALL.iter().map(|k| k.as_str()).collect();
90 format!(
91 "unknown policy '{wanted}' (expected one of {})",
92 valid.join(", ")
93 )
94 })
95 }
96}
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
101#[serde(rename_all = "lowercase")]
102pub enum PolicyMode {
103 #[default]
104 Off,
105 Warn,
106 Enforce,
107}
108
109impl PolicyMode {
110 pub const ALL: [PolicyMode; 3] = [PolicyMode::Off, PolicyMode::Warn, PolicyMode::Enforce];
111
112 pub fn as_str(self) -> &'static str {
113 match self {
114 PolicyMode::Off => "off",
115 PolicyMode::Warn => "warn",
116 PolicyMode::Enforce => "enforce",
117 }
118 }
119}
120
121impl fmt::Display for PolicyMode {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 f.write_str(self.as_str())
124 }
125}
126
127impl FromStr for PolicyMode {
128 type Err = String;
129
130 fn from_str(s: &str) -> Result<Self, Self::Err> {
131 let wanted = s.trim().to_ascii_lowercase();
132 PolicyMode::ALL
133 .into_iter()
134 .find(|mode| mode.as_str() == wanted)
135 .ok_or_else(|| {
136 format!(
137 "unknown policy mode '{}' (expected off, warn or enforce)",
138 s.trim()
139 )
140 })
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn mode_and_key_round_trip_str() {
150 for key in PolicyKey::ALL {
151 assert_eq!(key.to_string().parse::<PolicyKey>(), Ok(key));
152 assert_eq!(
153 serde_json::to_value(key).expect("serialize"),
154 serde_json::json!(key.as_str())
155 );
156 }
157 for mode in PolicyMode::ALL {
158 assert_eq!(mode.to_string().parse::<PolicyMode>(), Ok(mode));
159 }
160 assert_eq!(" Enforce ".parse::<PolicyMode>(), Ok(PolicyMode::Enforce));
161 assert_eq!(PolicyKey::TenantBinding.env_suffix(), "TENANT_BINDING");
162 }
163
164 #[test]
165 fn unknown_names_list_the_valid_ones() {
166 let err = "tenant-binding".parse::<PolicyKey>().unwrap_err();
167 assert!(
168 err.contains("tenant_binding") && err.contains("strict_filters"),
169 "{err}"
170 );
171 let err = "loud".parse::<PolicyMode>().unwrap_err();
172 assert!(err.contains("enforce"), "{err}");
173 }
174}