use crate::auth::identity_toolkit::IdentityToolkitEndpoints;
pub const EMULATOR_HOST_ENV_VAR: &str = "FIREBASE_AUTH_EMULATOR_HOST";
#[derive(Debug, Clone)]
pub enum ClientMode {
Live,
Emulator {
host: String,
},
}
impl ClientMode {
pub fn resolve(explicit_emulator_host: Option<String>) -> Self {
if let Some(host) = explicit_emulator_host {
return ClientMode::Emulator { host };
}
if let Ok(host) = std::env::var(EMULATOR_HOST_ENV_VAR) {
if !host.trim().is_empty() {
return ClientMode::Emulator { host };
}
}
ClientMode::Live
}
pub fn endpoints(&self, project_id: &str) -> IdentityToolkitEndpoints {
match self {
ClientMode::Live => IdentityToolkitEndpoints::live(project_id),
ClientMode::Emulator { host } => IdentityToolkitEndpoints::emulator(host, project_id),
}
}
pub fn requires_bearer_token(&self) -> bool {
matches!(self, ClientMode::Live)
}
pub fn emulator_api_key(&self) -> Option<&'static str> {
match self {
ClientMode::Live => None,
ClientMode::Emulator { .. } => Some("fake-api-key"),
}
}
pub fn emulator_bearer_token(&self) -> Option<&'static str> {
match self {
ClientMode::Live => None,
ClientMode::Emulator { .. } => Some("owner"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;
static ENV_VAR_TEST_LOCK: Mutex<()> = Mutex::new(());
fn with_emulator_env_var<T>(value: Option<&str>, f: impl FnOnce() -> T) -> T {
let _guard = ENV_VAR_TEST_LOCK.lock().unwrap();
let previous = std::env::var(EMULATOR_HOST_ENV_VAR).ok();
match value {
Some(v) => std::env::set_var(EMULATOR_HOST_ENV_VAR, v),
None => std::env::remove_var(EMULATOR_HOST_ENV_VAR),
}
let result = f();
match previous {
Some(v) => std::env::set_var(EMULATOR_HOST_ENV_VAR, v),
None => std::env::remove_var(EMULATOR_HOST_ENV_VAR),
}
result
}
#[test]
fn explicit_host_wins_over_env_var() {
with_emulator_env_var(Some("env-host:9099"), || {
let mode = ClientMode::resolve(Some("explicit-host:9099".to_string()));
assert!(matches!(mode, ClientMode::Emulator { host } if host == "explicit-host:9099"));
});
}
#[test]
fn env_var_is_used_when_no_explicit_host_given() {
with_emulator_env_var(Some("env-host:9099"), || {
let mode = ClientMode::resolve(None);
assert!(matches!(mode, ClientMode::Emulator { host } if host == "env-host:9099"));
});
}
#[test]
fn whitespace_only_env_var_is_treated_as_unset() {
with_emulator_env_var(Some(" "), || {
let mode = ClientMode::resolve(None);
assert!(matches!(mode, ClientMode::Live));
});
}
#[test]
fn defaults_to_live_with_nothing_set() {
with_emulator_env_var(None, || {
let mode = ClientMode::resolve(None);
assert!(matches!(mode, ClientMode::Live));
});
}
#[test]
fn requires_bearer_token_only_in_live_mode() {
assert!(ClientMode::Live.requires_bearer_token());
assert!(!ClientMode::Emulator {
host: "localhost:9099".to_string()
}
.requires_bearer_token());
}
#[test]
fn emulator_bearer_token_is_owner_only_in_emulator_mode() {
assert_eq!(ClientMode::Live.emulator_bearer_token(), None);
assert_eq!(
ClientMode::Emulator {
host: "localhost:9099".to_string()
}
.emulator_bearer_token(),
Some("owner")
);
}
#[test]
fn emulator_api_key_is_present_only_in_emulator_mode() {
assert_eq!(ClientMode::Live.emulator_api_key(), None);
assert!(ClientMode::Emulator {
host: "localhost:9099".to_string()
}
.emulator_api_key()
.is_some());
}
}