alpaca-core 0.23.8

Shared primitives for the alpaca-rust workspace
Documentation
use std::env::VarError;

use url::Url;

use crate::{Credentials, Error, validate};

pub const DEFAULT_API_KEY_ENV: &str = "APCA_API_KEY_ID";
pub const DEFAULT_SECRET_KEY_ENV: &str = "APCA_API_SECRET_KEY";

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct BaseUrl(String);

impl BaseUrl {
    pub fn new(value: impl AsRef<str>) -> Result<Self, Error> {
        let value = validate::non_empty_string("base_url", value.as_ref().to_owned())?;
        let parsed = Url::parse(&value).map_err(|error| {
            Error::InvalidConfiguration(format!("base_url must be a valid absolute URL: {error}"))
        })?;

        if parsed.scheme().is_empty() || parsed.host_str().is_none() {
            return Err(Error::InvalidConfiguration(
                "base_url must be a valid absolute URL".to_owned(),
            ));
        }

        Ok(Self(value.trim_end_matches('/').to_owned()))
    }

    #[must_use]
    pub fn as_str(&self) -> &str {
        &self.0
    }

    #[must_use]
    pub fn join_path(&self, path: &str) -> String {
        format!("{}/{}", self.0, path.trim_start_matches('/'))
    }
}

pub fn credentials_from_env() -> Result<Option<Credentials>, Error> {
    credentials_from_env_names(DEFAULT_API_KEY_ENV, DEFAULT_SECRET_KEY_ENV)
}

pub fn credentials_from_env_names(
    api_key_var: &str,
    secret_key_var: &str,
) -> Result<Option<Credentials>, Error> {
    validate::valid_env_name("api_key_var", api_key_var)?;
    validate::valid_env_name("secret_key_var", secret_key_var)?;

    let api_key = read_var(api_key_var)?;
    let secret_key = read_var(secret_key_var)?;

    match (api_key, secret_key) {
        (Some(api_key), Some(secret_key)) => Credentials::new(api_key, secret_key).map(Some),
        (None, None) => Ok(None),
        _ => Err(Error::InvalidConfiguration(format!(
            "{api_key_var} and {secret_key_var} must be paired"
        ))),
    }
}

pub fn base_url_from_env_name(name: &str) -> Result<Option<BaseUrl>, Error> {
    validate::valid_env_name("base_url_var", name)?;

    match read_var(name)? {
        Some(value) => BaseUrl::new(value).map(Some),
        None => Ok(None),
    }
}

fn read_var(name: &str) -> Result<Option<String>, Error> {
    match std::env::var(name) {
        Ok(value) => Ok(Some(value)),
        Err(VarError::NotPresent) => Ok(None),
        Err(VarError::NotUnicode(_)) => Err(Error::InvalidConfiguration(format!(
            "{name} must contain valid unicode"
        ))),
    }
}