openzeppelin_relayer/models/
plain_or_env_value.rs1use serde::{Deserialize, Serialize};
15use thiserror::Error;
16use validator::ValidationError;
17use zeroize::Zeroizing;
18
19use super::SecretString;
20
21#[derive(Error, Debug)]
22pub enum PlainOrEnvValueError {
23 #[error("Missing env var: {0}")]
24 MissingEnvVar(String),
25}
26
27#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
28#[serde(tag = "type", rename_all = "lowercase")]
29pub enum PlainOrEnvValue {
30 Env { value: String },
31 Plain { value: SecretString },
32}
33
34impl PlainOrEnvValue {
35 pub fn get_value(&self) -> Result<SecretString, PlainOrEnvValueError> {
36 match self {
37 PlainOrEnvValue::Env { value } => {
38 let value = Zeroizing::new(std::env::var(value).map_err(|_| {
39 PlainOrEnvValueError::MissingEnvVar(format!(
40 "Environment variable {} not found",
41 value
42 ))
43 })?);
44 Ok(SecretString::new(&value))
45 }
46 PlainOrEnvValue::Plain { value } => Ok(value.clone()),
47 }
48 }
49 pub fn is_empty(&self) -> bool {
50 let value = self.get_value();
51
52 match value {
53 Ok(v) => v.is_empty(),
54 Err(_) => true,
55 }
56 }
57}
58
59pub fn validate_plain_or_env_value(plain_or_env: &PlainOrEnvValue) -> Result<(), ValidationError> {
60 let value = plain_or_env.get_value().map_err(|e| {
61 let mut err = ValidationError::new("plain_or_env_value_error");
62 err.message = Some(format!("plain_or_env_value_error: {}", e).into());
63 err
64 })?;
65
66 match value.is_empty() {
67 true => Err(ValidationError::new(
68 "plain_or_env_value_error: value cannot be empty",
69 )),
70 false => Ok(()),
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77 use std::{env, sync::Mutex};
78 use validator::Validate;
79
80 static ENV_MUTEX: Mutex<()> = Mutex::new(());
81
82 #[derive(Validate)]
83 struct TestStruct {
84 #[validate(custom(function = "validate_plain_or_env_value"))]
85 value: PlainOrEnvValue,
86 }
87
88 #[test]
89 fn test_plain_value_get_value() {
90 let plain = PlainOrEnvValue::Plain {
91 value: SecretString::new("test-secret"),
92 };
93
94 let result = plain.get_value().unwrap();
95 result.as_str(|s| {
96 assert_eq!(s, "test-secret");
97 });
98 }
99
100 #[test]
101 fn test_env_value_get_value_when_env_exists() {
102 let _guard = ENV_MUTEX
103 .lock()
104 .unwrap_or_else(|poisoned| poisoned.into_inner());
105
106 env::set_var("TEST_ENV_VAR", "env-secret-value");
107
108 let env_value = PlainOrEnvValue::Env {
109 value: "TEST_ENV_VAR".to_string(),
110 };
111
112 let result = env_value.get_value().unwrap();
113 result.as_str(|s| {
114 assert_eq!(s, "env-secret-value");
115 });
116
117 env::remove_var("TEST_ENV_VAR");
118 }
119
120 #[test]
121 fn test_env_value_get_value_when_env_missing() {
122 let _guard = ENV_MUTEX
123 .lock()
124 .unwrap_or_else(|poisoned| poisoned.into_inner());
125
126 env::remove_var("NONEXISTENT_VAR");
127
128 let env_value = PlainOrEnvValue::Env {
129 value: "NONEXISTENT_VAR".to_string(),
130 };
131
132 let result = env_value.get_value();
133 assert!(result.is_err());
134
135 match result {
136 Err(PlainOrEnvValueError::MissingEnvVar(msg)) => {
137 assert!(msg.contains("NONEXISTENT_VAR"));
138 }
139 _ => panic!("Expected MissingEnvVar error"),
140 }
141 }
142
143 #[test]
144 fn test_is_empty_with_plain_empty_value() {
145 let plain = PlainOrEnvValue::Plain {
146 value: SecretString::new(""),
147 };
148
149 assert!(plain.is_empty());
150 }
151
152 #[test]
153 fn test_is_empty_with_plain_non_empty_value() {
154 let plain = PlainOrEnvValue::Plain {
155 value: SecretString::new("non-empty"),
156 };
157
158 assert!(!plain.is_empty());
159 }
160
161 #[test]
162 fn test_is_empty_with_env_missing_var() {
163 let _guard = ENV_MUTEX
164 .lock()
165 .unwrap_or_else(|poisoned| poisoned.into_inner());
166
167 env::remove_var("NONEXISTENT_VAR");
168
169 let env_value = PlainOrEnvValue::Env {
170 value: "NONEXISTENT_VAR".to_string(),
171 };
172
173 assert!(env_value.is_empty());
174 }
175
176 #[test]
177 fn test_is_empty_with_env_empty_var() {
178 let _guard = ENV_MUTEX
179 .lock()
180 .unwrap_or_else(|poisoned| poisoned.into_inner());
181
182 env::set_var("EMPTY_ENV_VAR", "");
183
184 let env_value = PlainOrEnvValue::Env {
185 value: "EMPTY_ENV_VAR".to_string(),
186 };
187
188 assert!(env_value.is_empty());
189
190 env::remove_var("EMPTY_ENV_VAR");
191 }
192
193 #[test]
194 fn test_is_empty_with_env_non_empty_var() {
195 let _guard = ENV_MUTEX
196 .lock()
197 .unwrap_or_else(|poisoned| poisoned.into_inner());
198
199 env::set_var("TEST_ENV_VAR", "some-value");
200
201 let env_value = PlainOrEnvValue::Env {
202 value: "TEST_ENV_VAR".to_string(),
203 };
204
205 assert!(!env_value.is_empty());
206
207 env::remove_var("TEST_ENV_VAR");
208 }
209
210 #[test]
211 fn test_validator_with_plain_empty_value() {
212 let test_struct = TestStruct {
213 value: PlainOrEnvValue::Plain {
214 value: SecretString::new(""),
215 },
216 };
217
218 let result = test_struct.validate();
219 assert!(result.is_err());
220 }
221
222 #[test]
223 fn test_validator_with_plain_non_empty_value() {
224 let test_struct = TestStruct {
225 value: PlainOrEnvValue::Plain {
226 value: SecretString::new("non-empty"),
227 },
228 };
229
230 let result = test_struct.validate();
231 assert!(result.is_ok());
232 }
233
234 #[test]
235 fn test_validator_with_env_missing_var() {
236 let _guard = ENV_MUTEX
237 .lock()
238 .unwrap_or_else(|poisoned| poisoned.into_inner());
239
240 env::remove_var("NONEXISTENT_VAR");
241
242 let test_struct = TestStruct {
243 value: PlainOrEnvValue::Env {
244 value: "NONEXISTENT_VAR".to_string(),
245 },
246 };
247
248 let result = test_struct.validate();
249 assert!(result.is_err());
250 }
251
252 #[test]
253 fn test_validator_with_env_empty_var() {
254 let _guard = ENV_MUTEX
255 .lock()
256 .unwrap_or_else(|poisoned| poisoned.into_inner());
257
258 env::set_var("EMPTY_ENV_VAR", "");
259
260 let test_struct = TestStruct {
261 value: PlainOrEnvValue::Env {
262 value: "EMPTY_ENV_VAR".to_string(),
263 },
264 };
265
266 let result = test_struct.validate();
267 assert!(result.is_err());
268
269 env::remove_var("EMPTY_ENV_VAR");
270 }
271
272 #[test]
273 fn test_validator_with_env_non_empty_var() {
274 let _guard = ENV_MUTEX
275 .lock()
276 .unwrap_or_else(|poisoned| poisoned.into_inner());
277
278 env::set_var("TEST_ENV_VAR", "some-value");
279
280 let test_struct = TestStruct {
281 value: PlainOrEnvValue::Env {
282 value: "TEST_ENV_VAR".to_string(),
283 },
284 };
285
286 let result = test_struct.validate();
287 assert!(result.is_ok());
288
289 env::remove_var("TEST_ENV_VAR");
290 }
291
292 #[test]
293 fn test_serialize_plain_value() {
294 let plain = PlainOrEnvValue::Plain {
295 value: SecretString::new("test-secret"),
296 };
297
298 let serialized = serde_json::to_string(&plain).unwrap();
299
300 assert!(serialized.contains(r#""type":"plain"#));
301 assert!(serialized.contains(r#""value":"REDACTED"#));
302 }
303
304 #[test]
305 fn test_serialize_env_value() {
306 let env_value = PlainOrEnvValue::Env {
307 value: "TEST_ENV_VAR".to_string(),
308 };
309
310 let serialized = serde_json::to_string(&env_value).unwrap();
311
312 assert!(serialized.contains(r#""type":"env"#));
313 assert!(serialized.contains(r#""value":"TEST_ENV_VAR"#));
314 }
315
316 #[test]
317 fn test_deserialize_plain_value() {
318 let json = r#"{"type":"plain","value":"test-secret"}"#;
319
320 let deserialized: PlainOrEnvValue = serde_json::from_str(json).unwrap();
321
322 match &deserialized {
323 PlainOrEnvValue::Plain { value } => {
324 value.as_str(|s| {
325 assert_eq!(s, "test-secret");
326 });
327 }
328 _ => panic!("Expected Plain variant"),
329 }
330 }
331
332 #[test]
333 fn test_deserialize_env_value() {
334 let json = r#"{"type":"env","value":"TEST_ENV_VAR"}"#;
335
336 let deserialized: PlainOrEnvValue = serde_json::from_str(json).unwrap();
337
338 match &deserialized {
339 PlainOrEnvValue::Env { value } => {
340 assert_eq!(value, "TEST_ENV_VAR");
341 }
342 _ => panic!("Expected Env variant"),
343 }
344 }
345
346 #[test]
347 fn test_error_messages() {
348 let error = PlainOrEnvValueError::MissingEnvVar("TEST_VAR".to_string());
349 let message = format!("{}", error);
350 assert_eq!(message, "Missing env var: TEST_VAR");
351 }
352
353 #[test]
354 fn test_validation_error_messages() {
355 let test_struct = TestStruct {
356 value: PlainOrEnvValue::Plain {
357 value: SecretString::new(""),
358 },
359 };
360
361 let result = test_struct.validate();
362 assert!(result.is_err());
363
364 if let Err(errors) = result {
365 let field_errors = errors.field_errors();
366 assert!(field_errors.contains_key("value"));
367
368 let error_msgs = &field_errors["value"];
369 assert!(!error_msgs.is_empty());
370
371 let has_empty_message = error_msgs
372 .iter()
373 .any(|e| e.code == "plain_or_env_value_error: value cannot be empty");
374
375 assert!(
376 has_empty_message,
377 "Validation error should mention empty value"
378 );
379 }
380 }
381}