Skip to content

Commit f777173

Browse files
authored
Support default values for recording vars (#3031)
1 parent c2a45d4 commit f777173

File tree

2 files changed

+138
-56
lines changed

2 files changed

+138
-56
lines changed

sdk/core/azure_core_test/src/recording.rs

Lines changed: 137 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ pub struct Recording {
5757
recording_file: String,
5858
recording_assets_file: Option<String>,
5959
id: Option<RecordingId>,
60-
variables: RwLock<HashMap<String, Value>>,
60+
variables: RwLock<HashMap<String, String>>,
6161
rand: OnceLock<Mutex<ChaCha20Rng>>,
6262
}
6363

@@ -325,24 +325,24 @@ impl Recording {
325325
let key = key.as_ref();
326326
if self.test_mode == TestMode::Playback {
327327
let variables = self.variables.read().map_err(read_lock_error).ok()?;
328-
return variables.get(key).map(Into::into);
328+
return variables.get(key).cloned();
329329
}
330330

331-
let value = self.env(key);
331+
// Get the environment variable or, if unset (None), the optional VarOptions::default_value.
332+
let options = options.unwrap_or_default();
333+
let (value, sanitized) = options.apply(self.env(key));
334+
332335
if self.test_mode == TestMode::Live {
333336
return value;
334337
}
335338

336-
match value {
337-
None => None,
338-
Some(v) if v.is_empty() => None,
339-
Some(v) => {
340-
let v = Some(v);
341-
let mut variables = self.variables.write().map_err(write_lock_error).ok()?;
342-
variables.insert(key.into(), Value::from(v.as_ref(), options));
343-
v
344-
}
339+
// Do not record unset (None) environment variables.
340+
if let Some(sanitized) = sanitized {
341+
let mut variables = self.variables.write().map_err(write_lock_error).ok()?;
342+
variables.insert(key.into(), sanitized);
345343
}
344+
345+
value
346346
}
347347
}
348348

@@ -403,7 +403,7 @@ impl Recording {
403403
env::var_os(self.service_directory.clone() + "_" + key.as_ref())
404404
.or_else(|| env::var_os(key.as_ref()))
405405
.or_else(|| env::var_os(String::from(AZURE_PREFIX) + key.as_ref()))
406-
.and_then(|v| v.into_string().ok())
406+
.and_then(|value| value.into_string().ok())
407407
}
408408

409409
fn rng(&self) -> &Mutex<ChaCha20Rng> {
@@ -416,9 +416,8 @@ impl Recording {
416416
.read()
417417
.map_err(read_lock_error)
418418
.unwrap_or_else(|err| panic!("{err}"));
419-
let seed: String = variables
419+
let seed = variables
420420
.get(RANDOM_SEED_NAME)
421-
.map(Into::into)
422421
.unwrap_or_else(|| panic!("random seed variable not set"));
423422
let seed = base64::decode(seed)
424423
.unwrap_or_else(|err| panic!("failed to decode random seed: {err}"));
@@ -438,7 +437,7 @@ impl Recording {
438437
.write()
439438
.map_err(write_lock_error)
440439
.unwrap_or_else(|err| panic!("{err}"));
441-
variables.insert(RANDOM_SEED_NAME.to_string(), Value::from(Some(seed), None));
440+
variables.insert(RANDOM_SEED_NAME.to_string(), seed);
442441

443442
rng.into()
444443
}
@@ -478,7 +477,7 @@ impl Recording {
478477
TestMode::Playback => {
479478
let result = client.playback_start(payload.try_into()?, None).await?;
480479
let mut variables = self.variables.write().map_err(write_lock_error)?;
481-
variables.extend(result.variables.into_iter().map(|(k, v)| (k, v.into())));
480+
variables.extend(result.variables.into_iter());
482481

483482
result.recording_id
484483
}
@@ -516,7 +515,9 @@ impl Recording {
516515
let variables = self.variables.read().map_err(read_lock_error)?;
517516
VariablePayload {
518517
variables: HashMap::from_iter(
519-
variables.iter().map(|(k, v)| (k.clone(), v.into())),
518+
variables
519+
.iter()
520+
.map(|(k, value)| (k.clone(), value.clone())),
520521
),
521522
}
522523
};
@@ -586,6 +587,9 @@ impl Drop for SkipGuard<'_> {
586587
/// Options for getting variables from a [`Recording`].
587588
#[derive(Clone, Debug)]
588589
pub struct VarOptions {
590+
/// The value to return if not already recorded.
591+
pub default_value: Option<Cow<'static, str>>,
592+
589593
/// Whether to sanitize the variable value with [`VarOptions::sanitize_value`].
590594
pub sanitize: bool,
591595

@@ -595,59 +599,137 @@ pub struct VarOptions {
595599
pub sanitize_value: Cow<'static, str>,
596600
}
597601

602+
impl VarOptions {
603+
/// Returns a tuple of the `value` or [`VarOptions::default_value`], and the sanitized value.
604+
///
605+
/// The `value` is only replaced with the `VarOptions::default_value` if `None`. This is returned as the first tuple field.
606+
///
607+
/// The [`VarOptions::sanitize_value`] is only `Some` if [`VarOptions::sanitize`] is `true`. This is returned as the second tuple field.
608+
fn apply<S: Into<String>>(self, value: Option<S>) -> (Option<String>, Option<String>) {
609+
let value = value.map_or_else(
610+
|| self.default_value.as_deref().map(ToString::to_string),
611+
|value| Some(value.into()),
612+
);
613+
let sanitized = match value.as_deref() {
614+
None => None,
615+
Some(_) if self.sanitize => Some(self.sanitize_value.to_string()),
616+
Some(v) => Some(v.to_string()),
617+
};
618+
(value, sanitized)
619+
}
620+
}
621+
598622
impl Default for VarOptions {
599623
fn default() -> Self {
600624
Self {
625+
default_value: None,
601626
sanitize: false,
602627
sanitize_value: Cow::Borrowed(crate::DEFAULT_SANITIZED_VALUE),
603628
}
604629
}
605630
}
606631

607-
#[derive(Debug)]
608-
struct Value {
609-
value: String,
610-
sanitized: Option<Cow<'static, str>>,
611-
}
632+
#[test]
633+
fn test_var_options_apply() {
634+
let (value, ..) = VarOptions::default().apply(None::<String>);
635+
assert_eq!(value, None);
612636

613-
impl Value {
614-
fn from<S>(value: Option<S>, options: Option<VarOptions>) -> Self
615-
where
616-
S: Into<String>,
617-
{
618-
Self {
619-
value: value.map_or_else(String::new, Into::into),
620-
sanitized: match options {
621-
Some(options) if options.sanitize => Some(options.sanitize_value.clone()),
622-
_ => None,
623-
},
624-
}
637+
let (value, ..) = VarOptions::default().apply(Some("".to_string()));
638+
assert_eq!(value, Some(String::new()));
639+
640+
let (value, ..) = VarOptions::default().apply(Some("test".to_string()));
641+
assert_eq!(value, Some("test".into()));
642+
643+
let (value, ..) = VarOptions {
644+
default_value: None,
645+
..Default::default()
625646
}
626-
}
647+
.apply(None::<String>);
648+
assert_eq!(value, None);
627649

628-
impl From<&str> for Value {
629-
fn from(value: &str) -> Self {
630-
Self {
631-
value: value.into(),
632-
sanitized: None,
633-
}
650+
let (value, ..) = VarOptions {
651+
default_value: Some("".into()),
652+
..Default::default()
634653
}
635-
}
654+
.apply(None::<String>);
655+
assert_eq!(value, Some("".into()));
636656

637-
impl From<String> for Value {
638-
fn from(value: String) -> Self {
639-
Self {
640-
value,
641-
sanitized: None,
642-
}
657+
let (value, ..) = VarOptions {
658+
default_value: Some("test".into()),
659+
..Default::default()
660+
}
661+
.apply(None::<String>);
662+
assert_eq!(value, Some("test".into()));
663+
664+
let (value, ..) = VarOptions {
665+
default_value: Some("default".into()),
666+
..Default::default()
667+
}
668+
.apply(Some("".to_string()));
669+
assert_eq!(value, Some("".into()));
670+
671+
let (value, ..) = VarOptions {
672+
default_value: Some("default".into()),
673+
..Default::default()
643674
}
675+
.apply(Some("test".to_string()));
676+
assert_eq!(value, Some("test".into()));
644677
}
645678

646-
impl From<&Value> for String {
647-
fn from(value: &Value) -> Self {
648-
value
649-
.sanitized
650-
.as_ref()
651-
.map_or_else(|| value.value.clone(), |v| v.to_string())
679+
#[test]
680+
fn test_var_options_apply_sanitized() {
681+
let (value, sanitized) = VarOptions::default().apply(None::<String>);
682+
assert_eq!(value, None);
683+
assert_eq!(sanitized, None);
684+
685+
let (value, sanitized) = VarOptions {
686+
sanitize: true,
687+
..Default::default()
688+
}
689+
.apply(None::<String>);
690+
assert_eq!(value, None);
691+
assert_eq!(sanitized, None);
692+
693+
let (value, sanitized) = VarOptions {
694+
sanitize: true,
695+
..Default::default()
696+
}
697+
.apply(Some("".to_string()));
698+
assert_eq!(value, Some("".to_string()));
699+
assert_eq!(sanitized, Some("Sanitized".into()));
700+
701+
let (value, sanitized) = VarOptions {
702+
sanitize: true,
703+
..Default::default()
704+
}
705+
.apply(Some("test".to_string()));
706+
assert_eq!(value, Some("test".to_string()));
707+
assert_eq!(sanitized, Some("Sanitized".into()));
708+
709+
let (value, sanitized) = VarOptions {
710+
sanitize: true,
711+
sanitize_value: "*****".into(),
712+
..Default::default()
713+
}
714+
.apply(None::<String>);
715+
assert_eq!(value, None);
716+
assert_eq!(sanitized, None);
717+
718+
let (value, sanitized) = VarOptions {
719+
sanitize: true,
720+
sanitize_value: "*****".into(),
721+
..Default::default()
722+
}
723+
.apply(Some("".to_string()));
724+
assert_eq!(value, Some("".to_string()));
725+
assert_eq!(sanitized, Some("*****".into()));
726+
727+
let (value, sanitized) = VarOptions {
728+
sanitize: true,
729+
sanitize_value: "*****".into(),
730+
..Default::default()
652731
}
732+
.apply(Some("test".to_string()));
733+
assert_eq!(value, Some("test".to_string()));
734+
assert_eq!(sanitized, Some("*****".into()));
653735
}

sdk/keyvault/assets.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"AssetsRepo": "Azure/azure-sdk-assets",
33
"AssetsRepoPrefixPath": "rust",
44
"TagPrefix": "rust/keyvault",
5-
"Tag": "rust/keyvault_456b9ceaa6"
5+
"Tag": "rust/keyvault_d5ac6e5fcc"
66
}

0 commit comments

Comments
 (0)