Skip to main content

alpaca_core/
env.rs

1use std::env::VarError;
2
3use url::Url;
4
5use crate::{Credentials, Error, validate};
6
7pub const DEFAULT_API_KEY_ENV: &str = "APCA_API_KEY_ID";
8pub const DEFAULT_SECRET_KEY_ENV: &str = "APCA_API_SECRET_KEY";
9
10#[derive(Clone, Debug, PartialEq, Eq, Hash)]
11pub struct BaseUrl(String);
12
13impl BaseUrl {
14    pub fn new(value: impl AsRef<str>) -> Result<Self, Error> {
15        let value = validate::non_empty_string("base_url", value.as_ref().to_owned())?;
16        let parsed = Url::parse(&value).map_err(|error| {
17            Error::InvalidConfiguration(format!("base_url must be a valid absolute URL: {error}"))
18        })?;
19
20        if parsed.scheme().is_empty() || parsed.host_str().is_none() {
21            return Err(Error::InvalidConfiguration(
22                "base_url must be a valid absolute URL".to_owned(),
23            ));
24        }
25
26        Ok(Self(value.trim_end_matches('/').to_owned()))
27    }
28
29    #[must_use]
30    pub fn as_str(&self) -> &str {
31        &self.0
32    }
33
34    #[must_use]
35    pub fn join_path(&self, path: &str) -> String {
36        format!("{}/{}", self.0, path.trim_start_matches('/'))
37    }
38}
39
40pub fn credentials_from_env() -> Result<Option<Credentials>, Error> {
41    credentials_from_env_names(DEFAULT_API_KEY_ENV, DEFAULT_SECRET_KEY_ENV)
42}
43
44pub fn credentials_from_env_names(
45    api_key_var: &str,
46    secret_key_var: &str,
47) -> Result<Option<Credentials>, Error> {
48    validate::valid_env_name("api_key_var", api_key_var)?;
49    validate::valid_env_name("secret_key_var", secret_key_var)?;
50
51    let api_key = read_var(api_key_var)?;
52    let secret_key = read_var(secret_key_var)?;
53
54    match (api_key, secret_key) {
55        (Some(api_key), Some(secret_key)) => Credentials::new(api_key, secret_key).map(Some),
56        (None, None) => Ok(None),
57        _ => Err(Error::InvalidConfiguration(format!(
58            "{api_key_var} and {secret_key_var} must be paired"
59        ))),
60    }
61}
62
63pub fn base_url_from_env_name(name: &str) -> Result<Option<BaseUrl>, Error> {
64    validate::valid_env_name("base_url_var", name)?;
65
66    match read_var(name)? {
67        Some(value) => BaseUrl::new(value).map(Some),
68        None => Ok(None),
69    }
70}
71
72fn read_var(name: &str) -> Result<Option<String>, Error> {
73    match std::env::var(name) {
74        Ok(value) => Ok(Some(value)),
75        Err(VarError::NotPresent) => Ok(None),
76        Err(VarError::NotUnicode(_)) => Err(Error::InvalidConfiguration(format!(
77            "{name} must contain valid unicode"
78        ))),
79    }
80}