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