use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "type", content = "config", rename_all = "snake_case")]
pub enum PubsubCredentials {
#[default]
ApplicationDefault,
ServiceAccountJsonFile {
path: String,
},
ServiceAccountJsonInline {
json: String,
},
Anonymous,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct PubsubConnection {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub project_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub endpoint: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub emulator_host: Option<String>,
#[serde(default)]
pub credentials: PubsubCredentials,
}
impl PubsubConnection {
pub fn effective_emulator_host(&self) -> Option<String> {
self.emulator_host
.clone()
.or_else(|| std::env::var("PUBSUB_EMULATOR_HOST").ok())
.filter(|h| !h.trim().is_empty())
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn credentials_default_is_adc() {
assert_eq!(
PubsubCredentials::default(),
PubsubCredentials::ApplicationDefault
);
}
#[test]
fn credentials_serde_application_default() {
let v = serde_json::to_value(PubsubCredentials::ApplicationDefault).unwrap();
assert_eq!(v, json!({"type": "application_default"}));
let back: PubsubCredentials = serde_json::from_value(v).unwrap();
assert_eq!(back, PubsubCredentials::ApplicationDefault);
}
#[test]
fn credentials_serde_service_account_file_and_inline() {
let file = PubsubCredentials::ServiceAccountJsonFile {
path: "/run/secrets/sa.json".into(),
};
let v = serde_json::to_value(&file).unwrap();
assert_eq!(
v,
json!({"type": "service_account_json_file", "config": {"path": "/run/secrets/sa.json"}})
);
assert_eq!(
serde_json::from_value::<PubsubCredentials>(v).unwrap(),
file
);
let inline = PubsubCredentials::ServiceAccountJsonInline {
json: "{\"client_email\":\"x@y\"}".into(),
};
let v = serde_json::to_value(&inline).unwrap();
assert_eq!(v["type"], "service_account_json_inline");
assert_eq!(
serde_json::from_value::<PubsubCredentials>(v).unwrap(),
inline
);
}
#[test]
fn credentials_serde_anonymous() {
let v = serde_json::to_value(PubsubCredentials::Anonymous).unwrap();
assert_eq!(v, json!({"type": "anonymous"}));
assert_eq!(
serde_json::from_value::<PubsubCredentials>(v).unwrap(),
PubsubCredentials::Anonymous
);
}
#[test]
fn connection_flatten_shape_parses() {
let yaml = r#"
project_id: my-proj
emulator_host: "localhost:8085"
credentials: { type: anonymous }
"#;
let c: PubsubConnection = serde_yaml::from_str(yaml).unwrap();
assert_eq!(c.project_id.as_deref(), Some("my-proj"));
assert_eq!(c.emulator_host.as_deref(), Some("localhost:8085"));
assert_eq!(c.credentials, PubsubCredentials::Anonymous);
}
#[test]
fn connection_defaults() {
let c = PubsubConnection::default();
assert!(c.project_id.is_none());
assert!(c.endpoint.is_none());
assert!(c.emulator_host.is_none());
assert_eq!(c.credentials, PubsubCredentials::ApplicationDefault);
}
#[test]
fn effective_emulator_host_prefers_explicit() {
let c = PubsubConnection {
emulator_host: Some("localhost:8085".into()),
..Default::default()
};
assert_eq!(
c.effective_emulator_host().as_deref(),
Some("localhost:8085")
);
let c = PubsubConnection {
emulator_host: Some(" ".into()),
..Default::default()
};
let got = c.effective_emulator_host();
assert!(got.as_deref() != Some(" "));
}
}