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