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