use crate::config::{load_chipzen_config, resolve_token, resolve_url, ChipzenConfig};
use crate::error::Error;
use crate::retry::RetryPolicy;
pub const ENV_NAMES: &[&str] = &["prod", "staging", "local"];
pub const CHIPZEN_ENV_VAR: &str = "CHIPZEN_ENV";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EnvName {
Prod,
Staging,
Local,
}
impl EnvName {
pub fn parse(s: &str) -> Option<Self> {
match s {
"prod" => Some(EnvName::Prod),
"staging" => Some(EnvName::Staging),
"local" => Some(EnvName::Local),
_ => None,
}
}
pub fn as_str(&self) -> &'static str {
match self {
EnvName::Prod => "prod",
EnvName::Staging => "staging",
EnvName::Local => "local",
}
}
fn lobby_url(&self, bot_id: &str) -> String {
match self {
EnvName::Prod => format!("wss://chipzen.ai/ws/external/bot/{bot_id}"),
EnvName::Staging => format!("wss://staging.chipzen.ai/ws/external/bot/{bot_id}"),
EnvName::Local => format!("ws://localhost:8001/ws/external/bot/{bot_id}"),
}
}
}
#[derive(Debug, Clone)]
pub struct ConnectionConfig {
pub url: String,
pub token: Option<String>,
pub retry_policy: RetryPolicy,
pub env: Option<EnvName>,
pub config: Option<ChipzenConfig>,
}
fn resolve_env_name(explicit: Option<EnvName>, env_var: Option<&str>) -> Result<EnvName, Error> {
if let Some(env) = explicit {
return Ok(env);
}
if let Some(val) = env_var.filter(|s| !s.is_empty()) {
return EnvName::parse(val).ok_or_else(|| {
Error::Protocol(format!(
"{CHIPZEN_ENV_VAR}={val:?} is not a recognized environment. Valid values: {}.",
ENV_NAMES.join(", ")
))
});
}
Ok(EnvName::Prod)
}
pub fn connect_to_chipzen(
bot_id: &str,
env: Option<EnvName>,
retry_policy: Option<RetryPolicy>,
config: Option<ChipzenConfig>,
) -> Result<ConnectionConfig, Error> {
if bot_id.is_empty() {
return Err(Error::Protocol(
"connect_to_chipzen() requires a non-empty bot_id. Pass the external-API \
bot UUID issued by the Chipzen platform."
.to_string(),
));
}
let env_var = std::env::var(CHIPZEN_ENV_VAR).ok();
let resolved_env = resolve_env_name(env, env_var.as_deref())?;
let env_derived_url = resolved_env.lobby_url(bot_id);
let config = match config {
Some(c) => Some(c),
None => load_chipzen_config(None)?,
};
let (url, env_for_return) = match resolve_url(None, config.as_ref()) {
Some(config_url) => (config_url, None),
None => (env_derived_url, Some(resolved_env)),
};
let token = resolve_token(None, config.as_ref());
let policy = retry_policy.unwrap_or_default();
Ok(ConnectionConfig {
url,
token,
retry_policy: policy,
env: env_for_return,
config,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn env_name_parse_round_trips() {
for name in ENV_NAMES {
assert_eq!(EnvName::parse(name).unwrap().as_str(), *name);
}
assert!(EnvName::parse("prd").is_none());
}
#[test]
fn lobby_url_templates_match_python() {
assert_eq!(
EnvName::Prod.lobby_url("b"),
"wss://chipzen.ai/ws/external/bot/b"
);
assert_eq!(
EnvName::Staging.lobby_url("b"),
"wss://staging.chipzen.ai/ws/external/bot/b"
);
assert_eq!(
EnvName::Local.lobby_url("b"),
"ws://localhost:8001/ws/external/bot/b"
);
}
#[test]
fn resolve_env_explicit_wins() {
let env = resolve_env_name(Some(EnvName::Local), Some("staging")).unwrap();
assert_eq!(env, EnvName::Local);
}
#[test]
fn resolve_env_uses_env_var_then_default() {
assert_eq!(
resolve_env_name(None, Some("staging")).unwrap(),
EnvName::Staging
);
assert_eq!(resolve_env_name(None, Some("")).unwrap(), EnvName::Prod);
assert_eq!(resolve_env_name(None, None).unwrap(), EnvName::Prod);
}
#[test]
fn resolve_env_rejects_unknown_env_var() {
let err = resolve_env_name(None, Some("prd")).unwrap_err();
assert!(format!("{err}").contains("not a recognized environment"));
}
#[test]
fn connect_requires_bot_id() {
let err = connect_to_chipzen("", Some(EnvName::Prod), None, None).unwrap_err();
assert!(format!("{err}").contains("non-empty bot_id"));
}
#[test]
fn connect_builds_env_derived_url() {
let conn = connect_to_chipzen(
"abc",
Some(EnvName::Staging),
None,
Some(ChipzenConfig::default()),
)
.unwrap();
assert_eq!(conn.url, "wss://staging.chipzen.ai/ws/external/bot/abc");
assert_eq!(conn.env, Some(EnvName::Staging));
}
#[test]
fn connect_config_url_overrides_env_derived() {
let cfg = ChipzenConfig {
url: Some("wss://verbatim/url".into()),
token: Some("cz_extbot_t".into()),
..Default::default()
};
let conn = connect_to_chipzen("abc", Some(EnvName::Prod), None, Some(cfg)).unwrap();
assert_eq!(conn.url, "wss://verbatim/url");
assert_eq!(conn.env, None);
assert_eq!(conn.token.as_deref(), Some("cz_extbot_t"));
}
}