use core::fmt;
use serde::{Deserialize, Serialize};
#[cfg(doc)]
use super::{Request, Response};
#[cfg(doc)]
use crate::config::AppConfig;
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct DogSectionToml(String);
impl DogSectionToml {
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<String> for DogSectionToml {
fn from(toml: String) -> Self {
Self(toml)
}
}
impl fmt::Debug for DogSectionToml {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "DogSectionToml(<{} bytes>)", self.0.len())
}
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct EnvValue(String);
impl EnvValue {
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<String> for EnvValue {
fn from(value: String) -> Self {
Self(value)
}
}
impl fmt::Debug for EnvValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "EnvValue(<{} bytes>)", self.0.len())
}
}
#[cfg(test)]
mod tests {
use super::super::{Request, Response};
use super::*;
use std::collections::BTreeMap;
#[test]
fn env_value_debug_does_not_leak() {
let request = Request::SetSheepEnv {
name: "web".to_string(),
key: "DATABASE_URL".to_string(),
value: Some("postgres://user:hunter2@localhost/app".to_string().into()),
};
let debug = format!("{request:?}");
assert!(!debug.contains("hunter2"), "{debug}");
assert!(debug.contains("EnvValue(<37 bytes>)"), "{debug}");
let json = serde_json::to_string(&request).unwrap();
assert!(
json.contains(r#""value":"postgres://user:hunter2@localhost/app""#),
"{json}"
);
assert_eq!(serde_json::from_str::<Request>(&json).unwrap(), request);
}
#[test]
fn put_secrets_round_trips_and_hides_its_values() {
let request = Request::PutSecrets {
namespace: "vercel".into(),
environment: "production".into(),
entries: BTreeMap::from([(
"API_KEY".to_string(),
EnvValue::from("sk_live".to_string()),
)]),
};
let encoded = serde_json::to_string(&request).unwrap();
assert_eq!(serde_json::from_str::<Request>(&encoded).unwrap(), request);
assert_eq!(
format!("{request:?}"),
"PutSecrets { namespace: \"vercel\", environment: \"production\", \
entries: {\"API_KEY\": EnvValue(<7 bytes>)} }"
);
}
#[test]
fn set_sheep_env_batch_debug_does_not_leak() {
let request = Request::SetSheepEnvBatch {
name: "web".to_string(),
entries: BTreeMap::from([(
"DB_PASSWORD".to_string(),
EnvValue::from("hunter2".to_string()),
)]),
force: false,
dry_run: true,
};
assert_eq!(
format!("{request:?}"),
"SetSheepEnvBatch { name: \"web\", entries: {\"DB_PASSWORD\": EnvValue(<7 bytes>)}, \
force: false, dry_run: true }"
);
}
#[test]
fn dog_section_toml_debug_does_not_leak() {
let toml: DogSectionToml =
"webhook_url = \"https://discord.com/api/webhooks/1/super-secret-token\"\n"
.to_string()
.into();
assert_eq!(format!("{toml:?}"), "DogSectionToml(<70 bytes>)");
let response = Response::DogSection { toml };
assert_eq!(
format!("{response:?}"),
"DogSection { toml: DogSectionToml(<70 bytes>) }"
);
}
}