use std::env::var_os;
use std::ffi::OsString;
use url::Url;
use crate::api::API_BASE_URL;
use crate::data::DATA_BASE_URL;
use crate::data::DATA_STREAM_BASE_URL;
use crate::Error;
const ENV_API_BASE_URL: &str = "APCA_API_BASE_URL";
const ENV_API_STREAM_URL: &str = "APCA_API_STREAM_URL";
const ENV_KEY_ID: &str = "APCA_API_KEY_ID";
const ENV_SECRET: &str = "APCA_API_SECRET_KEY";
fn make_api_stream_url(base_url: Url) -> Result<Url, Error> {
let mut url = base_url;
url.set_scheme("wss").map_err(|()| {
Error::Str(format!("unable to change URL scheme for {url}: invalid URL?").into())
})?;
url.set_path("stream");
Ok(url)
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub struct ApiInfo {
pub api_base_url: Url,
pub api_stream_url: Url,
pub data_base_url: Url,
pub data_stream_base_url: Url,
pub key_id: String,
pub secret: String,
}
impl ApiInfo {
pub fn from_parts(
api_base_url: impl AsRef<str>,
key_id: impl ToString,
secret: impl ToString,
) -> Result<Self, Error> {
let api_base_url = Url::parse(api_base_url.as_ref())?;
let api_stream_url = make_api_stream_url(api_base_url.clone())?;
Ok(Self {
api_base_url,
api_stream_url,
data_base_url: Url::parse(DATA_BASE_URL).unwrap(),
data_stream_base_url: Url::parse(DATA_STREAM_BASE_URL).unwrap(),
key_id: key_id.to_string(),
secret: secret.to_string(),
})
}
#[allow(unused_qualifications)]
pub fn from_env() -> Result<Self, Error> {
let api_base_url = var_os(ENV_API_BASE_URL)
.unwrap_or_else(|| OsString::from(API_BASE_URL))
.into_string()
.map_err(|_| {
Error::Str(format!("{ENV_API_BASE_URL} environment variable is not a valid string").into())
})?;
let api_base_url = Url::parse(&api_base_url)?;
let api_stream_url = var_os(ENV_API_STREAM_URL)
.map(Result::<_, Error>::Ok)
.unwrap_or_else(|| {
let url = make_api_stream_url(api_base_url.clone())?;
Ok(OsString::from(url.as_str()))
})?
.into_string()
.map_err(|_| {
Error::Str(
format!("{ENV_API_STREAM_URL} environment variable is not a valid string").into(),
)
})?;
let api_stream_url = Url::parse(&api_stream_url)?;
let key_id = var_os(ENV_KEY_ID)
.ok_or_else(|| Error::Str(format!("{ENV_KEY_ID} environment variable not found").into()))?
.into_string()
.map_err(|_| {
Error::Str(format!("{ENV_KEY_ID} environment variable is not a valid string").into())
})?;
let secret = var_os(ENV_SECRET)
.ok_or_else(|| Error::Str(format!("{ENV_SECRET} environment variable not found").into()))?
.into_string()
.map_err(|_| {
Error::Str(format!("{ENV_SECRET} environment variable is not a valid string").into())
})?;
Ok(Self {
api_base_url,
api_stream_url,
data_base_url: Url::parse(DATA_BASE_URL).unwrap(),
data_stream_base_url: Url::parse(DATA_STREAM_BASE_URL).unwrap(),
key_id,
secret,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_parts() {
let api_base_url = "https://paper-api.alpaca.markets/";
let key_id = "XXXXXXXXXXXXXXXXXXXX";
let secret = "YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY";
let api_info = ApiInfo::from_parts(api_base_url, key_id, secret).unwrap();
assert_eq!(api_info.api_base_url.as_str(), api_base_url);
assert_eq!(api_info.key_id, key_id);
assert_eq!(api_info.secret, secret);
}
}