use std::fmt;
use crate::error::SecretString;
pub const DEFAULT_BASE_URL: &str = "https://financialmodelingprep.com";
pub const DEFAULT_PATH_PREFIX: &str = "stable";
pub const FMP_API_KEY_ENV: &str = "FMP_API_KEY";
pub fn fmp_api_key_from_env() -> Option<String> {
fmp_api_key_from_value(std::env::var(FMP_API_KEY_ENV).ok().as_deref())
}
fn fmp_api_key_from_value(value: Option<&str>) -> Option<String> {
let trimmed = value?.trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed.to_owned())
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum RedirectPolicy {
None,
#[default]
SameOrigin,
}
#[derive(Clone, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum Authentication {
#[default]
None,
FmpHeader(SecretString),
FmpQuery(SecretString),
Bearer(SecretString),
CustomHeader {
name: String,
prefix: Option<String>,
secret: SecretString,
},
CustomQuery {
name: String,
secret: SecretString,
},
}
impl Authentication {
pub fn fmp_header(api_key: impl Into<String>) -> Self {
Self::FmpHeader(SecretString::new(api_key))
}
pub fn fmp_header_from_env() -> Option<Self> {
fmp_api_key_from_env().map(Self::fmp_header)
}
pub fn fmp_query(api_key: impl Into<String>) -> Self {
Self::FmpQuery(SecretString::new(api_key))
}
pub fn bearer(token: impl Into<String>) -> Self {
Self::Bearer(SecretString::new(token))
}
pub fn custom_header(
name: impl Into<String>,
prefix: Option<String>,
secret: impl Into<String>,
) -> Self {
Self::CustomHeader {
name: name.into(),
prefix,
secret: SecretString::new(secret),
}
}
pub fn custom_query(name: impl Into<String>, secret: impl Into<String>) -> Self {
Self::CustomQuery {
name: name.into(),
secret: SecretString::new(secret),
}
}
}
impl fmt::Debug for Authentication {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None => formatter.write_str("None"),
Self::FmpHeader(_) => formatter.write_str("FmpHeader([REDACTED])"),
Self::FmpQuery(_) => formatter.write_str("FmpQuery([REDACTED])"),
Self::Bearer(_) => formatter.write_str("Bearer([REDACTED])"),
Self::CustomHeader { name, prefix, .. } => formatter
.debug_struct("CustomHeader")
.field("name", name)
.field("has_prefix", &prefix.is_some())
.field("secret", &"[REDACTED]")
.finish(),
Self::CustomQuery { name, .. } => formatter
.debug_struct("CustomQuery")
.field("name", name)
.field("secret", &"[REDACTED]")
.finish(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn env_key_is_absent_when_unset_empty_or_blank() {
assert_eq!(fmp_api_key_from_value(None), None);
assert_eq!(fmp_api_key_from_value(Some("")), None);
assert_eq!(fmp_api_key_from_value(Some(" \t\n")), None);
}
#[test]
fn env_key_is_trimmed_when_set() {
assert_eq!(
fmp_api_key_from_value(Some("shell-secret")).as_deref(),
Some("shell-secret")
);
assert_eq!(
fmp_api_key_from_value(Some(" shell-secret\n")).as_deref(),
Some("shell-secret")
);
}
#[test]
fn env_helpers_agree_with_the_process_environment() {
let expected = fmp_api_key_from_value(std::env::var(FMP_API_KEY_ENV).ok().as_deref());
assert_eq!(fmp_api_key_from_env(), expected);
assert_eq!(
Authentication::fmp_header_from_env(),
expected.map(Authentication::fmp_header)
);
}
}