use std::{
sync::{
OnceLock,
atomic::{AtomicBool, Ordering},
},
time::Duration,
};
static IS_CN_DONE: OnceLock<bool> = OnceLock::new();
static IS_CN_PROBING: AtomicBool = AtomicBool::new(false);
pub async fn is_cn() -> bool {
let user_region = std::env::var("LONGBRIDGE_REGION")
.ok()
.or_else(|| std::env::var("LONGPORT_REGION").ok());
if let Some(region) = user_region {
return region.eq_ignore_ascii_case("CN");
}
if let Some(&cached) = IS_CN_DONE.get() {
return cached;
}
if IS_CN_PROBING
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
let result = reqwest::Client::new()
.get("https://geotest.lbkrs.com")
.timeout(Duration::from_secs(5))
.send()
.await
.is_ok_and(|resp| resp.status().is_success());
let _ = IS_CN_DONE.set(result);
result
} else {
IS_CN_DONE.get().copied().unwrap_or(false)
}
}
pub const DC_REGION_HEADER: &str = "x-dc-region";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DcRegion {
Ap,
Us,
}
impl DcRegion {
pub fn from_credential(credential: &str) -> Self {
let credential = credential.strip_prefix("Bearer ").unwrap_or(credential);
if credential.starts_with("us_") {
DcRegion::Us
} else {
DcRegion::Ap
}
}
pub fn from_credentials(credentials: &[&str]) -> Self {
if credentials
.iter()
.any(|c| DcRegion::from_credential(c) == DcRegion::Us)
{
DcRegion::Us
} else {
DcRegion::Ap
}
}
pub fn as_str(self) -> &'static str {
match self {
DcRegion::Us => "us",
DcRegion::Ap => "ap",
}
}
pub fn allows(self, required: DcRegion) -> bool {
self == required
}
pub fn strip_region_prefix(credential: &str) -> &str {
credential.strip_prefix("Bearer ").unwrap_or(credential)
}
}
impl std::fmt::Display for DcRegion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
DcRegion::Us => "US",
DcRegion::Ap => "AP",
})
}
}
#[cfg(test)]
mod dc_region_tests {
use super::*;
#[test]
fn from_credential_detects_region() {
assert_eq!(DcRegion::from_credential("us_abc"), DcRegion::Us);
assert_eq!(DcRegion::from_credential("ap_abc"), DcRegion::Ap);
assert_eq!(DcRegion::from_credential("abc"), DcRegion::Ap);
assert_eq!(DcRegion::from_credential(""), DcRegion::Ap);
assert_eq!(DcRegion::from_credential("Bearer us_x"), DcRegion::Us);
assert_eq!(DcRegion::from_credential("Bearer ap_x"), DcRegion::Ap);
}
#[test]
fn from_credentials_is_us_if_any_is_us() {
assert_eq!(
DcRegion::from_credentials(&["ap_key", "us_secret", "ap_token"]),
DcRegion::Us
);
assert_eq!(
DcRegion::from_credentials(&["ap_key", "ap_secret", "ap_token"]),
DcRegion::Ap
);
assert_eq!(DcRegion::from_credentials(&[]), DcRegion::Ap);
}
#[test]
fn as_str_matches_header_value() {
assert_eq!(DcRegion::Us.as_str(), "us");
assert_eq!(DcRegion::Ap.as_str(), "ap");
}
#[test]
fn allows_matches_same_region() {
assert!(DcRegion::Ap.allows(DcRegion::Ap));
assert!(DcRegion::Us.allows(DcRegion::Us));
assert!(!DcRegion::Us.allows(DcRegion::Ap));
assert!(!DcRegion::Ap.allows(DcRegion::Us));
}
#[test]
fn display_is_uppercase() {
assert_eq!(DcRegion::Us.to_string(), "US");
assert_eq!(DcRegion::Ap.to_string(), "AP");
assert_eq!(DcRegion::Us.as_str(), "us");
assert_eq!(DcRegion::Ap.as_str(), "ap");
}
#[test]
fn strip_region_prefix_only_removes_bearer() {
assert_eq!(DcRegion::strip_region_prefix("us_m_eyJabc"), "us_m_eyJabc");
assert_eq!(DcRegion::strip_region_prefix("hk_m_eyJabc"), "hk_m_eyJabc");
assert_eq!(
DcRegion::strip_region_prefix("Bearer us_m_eyJabc"),
"us_m_eyJabc"
);
assert_eq!(DcRegion::strip_region_prefix("Bearer eyJabc"), "eyJabc");
assert_eq!(DcRegion::strip_region_prefix("eyJabc"), "eyJabc");
}
}