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 _: () = {
65 let mut i = 0;
66 while i < PolicyKey::ALL.len() {
67 assert!(PolicyKey::ALL[i] as usize == i);
68 i += 1;
69 }
70};
71
72impl fmt::Display for PolicyKey {
73 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74 f.write_str(self.as_str())
75 }
76}
77
78impl FromStr for PolicyKey {
79 type Err = String;
80
81 fn from_str(s: &str) -> Result<Self, Self::Err> {
82 let wanted = s.trim();
83 PolicyKey::ALL
84 .into_iter()
85 .find(|key| key.as_str() == wanted)
86 .ok_or_else(|| {
87 let valid: Vec<&str> = PolicyKey::ALL.iter().map(|k| k.as_str()).collect();
88 format!(
89 "unknown policy '{wanted}' (expected one of {})",
90 valid.join(", ")
91 )
92 })
93 }
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
99#[serde(rename_all = "lowercase")]
100pub enum PolicyMode {
101 #[default]
102 Off,
103 Warn,
104 Enforce,
105}
106
107impl PolicyMode {
108 pub const ALL: [PolicyMode; 3] = [PolicyMode::Off, PolicyMode::Warn, PolicyMode::Enforce];
109
110 pub fn as_str(self) -> &'static str {
111 match self {
112 PolicyMode::Off => "off",
113 PolicyMode::Warn => "warn",
114 PolicyMode::Enforce => "enforce",
115 }
116 }
117}
118
119impl fmt::Display for PolicyMode {
120 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121 f.write_str(self.as_str())
122 }
123}
124
125impl FromStr for PolicyMode {
126 type Err = String;
127
128 fn from_str(s: &str) -> Result<Self, Self::Err> {
129 let wanted = s.trim().to_ascii_lowercase();
130 PolicyMode::ALL
131 .into_iter()
132 .find(|mode| mode.as_str() == wanted)
133 .ok_or_else(|| {
134 format!(
135 "unknown policy mode '{}' (expected off, warn or enforce)",
136 s.trim()
137 )
138 })
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn mode_and_key_round_trip_str() {
148 for key in PolicyKey::ALL {
149 assert_eq!(key.to_string().parse::<PolicyKey>(), Ok(key));
150 assert_eq!(
151 serde_json::to_value(key).expect("serialize"),
152 serde_json::json!(key.as_str())
153 );
154 }
155 for mode in PolicyMode::ALL {
156 assert_eq!(mode.to_string().parse::<PolicyMode>(), Ok(mode));
157 }
158 assert_eq!(" Enforce ".parse::<PolicyMode>(), Ok(PolicyMode::Enforce));
159 assert_eq!(PolicyKey::TenantBinding.env_suffix(), "TENANT_BINDING");
160 }
161
162 #[test]
163 fn unknown_names_list_the_valid_ones() {
164 let err = "tenant-binding".parse::<PolicyKey>().unwrap_err();
165 assert!(
166 err.contains("tenant_binding") && err.contains("strict_filters"),
167 "{err}"
168 );
169 let err = "loud".parse::<PolicyMode>().unwrap_err();
170 assert!(err.contains("enforce"), "{err}");
171 }
172}