Skip to main content

hey_sdk/
config.rs

1use std::env;
2use std::path::{Path, PathBuf};
3
4use serde::{Deserialize, Serialize};
5
6use crate::error::Error;
7
8/// Where HEY is when nothing says otherwise.
9pub const DEFAULT_BASE_URL: &str = "https://app.hey.com";
10/// The OAuth client HEY registers for its own SDKs and CLI.
11pub const DEFAULT_OAUTH_CLIENT_ID: &str = "khMWSVDVSq78oyKA3KtxmYRv";
12
13/// What a client needs to know before it can talk to HEY.
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(default)]
16pub struct Config {
17    /// Where HEY is: [`DEFAULT_BASE_URL`], or a HEY running on this machine.
18    pub base_url: String,
19    /// The OAuth client to sign in as.
20    pub oauth_client_id: String,
21    /// Where the response cache keeps its files.
22    pub cache_dir: PathBuf,
23    /// Whether JSON reads are cached on disk between runs.
24    pub cache_enabled: bool,
25}
26
27impl Default for Config {
28    fn default() -> Config {
29        Config {
30            base_url: DEFAULT_BASE_URL.to_string(),
31            oauth_client_id: DEFAULT_OAUTH_CLIENT_ID.to_string(),
32            cache_dir: default_cache_dir(),
33            cache_enabled: false,
34        }
35    }
36}
37
38impl Config {
39    /// Reads a JSON config file over the defaults. A missing file answers the defaults.
40    pub fn load(path: &Path) -> Result<Config, Error> {
41        match std::fs::read(path) {
42            Ok(bytes) => serde_json::from_slice(&bytes)
43                .map_err(|error| Error::usage(format!("{}: {error}", path.display()))),
44            Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(Config::default()),
45            Err(error) => Err(Error::usage(format!("{}: {error}", path.display()))),
46        }
47    }
48
49    /// Lets `HEY_BASE_URL`, `HEY_OAUTH_CLIENT_ID`, `HEY_CACHE_DIR` and `HEY_CACHE_ENABLED`
50    /// override whatever is set. An empty variable counts as unset.
51    #[must_use]
52    pub fn with_env(mut self) -> Config {
53        if let Some(value) = env_value("HEY_BASE_URL") {
54            self.base_url = value;
55        }
56        if let Some(value) = env_value("HEY_OAUTH_CLIENT_ID") {
57            self.oauth_client_id = value;
58        }
59        if let Some(value) = env_value("HEY_CACHE_DIR") {
60            self.cache_dir = PathBuf::from(value);
61        }
62        if let Some(value) = env_value("HEY_CACHE_ENABLED") {
63            self.cache_enabled = value.eq_ignore_ascii_case("true") || value == "1";
64        }
65        self
66    }
67
68    /// Points the client at another HEY, such as one running on this machine.
69    #[must_use]
70    pub fn with_base_url(mut self, base_url: impl Into<String>) -> Config {
71        self.base_url = base_url.into();
72        self
73    }
74
75    /// Turns the on-disk response cache on or off.
76    #[must_use]
77    pub fn with_cache_enabled(mut self, enabled: bool) -> Config {
78        self.cache_enabled = enabled;
79        self
80    }
81
82    /// The base URL without a trailing slash: the key credentials are stored under.
83    pub fn origin(&self) -> &str {
84        self.base_url.trim_end_matches('/')
85    }
86}
87
88fn env_value(name: &str) -> Option<String> {
89    env::var(name).ok().filter(|value| !value.is_empty())
90}
91
92/// Where the response cache goes when nothing says otherwise: `$XDG_CACHE_HOME/hey`,
93/// falling back to `~/.cache/hey`.
94pub fn default_cache_dir() -> PathBuf {
95    xdg_dir("XDG_CACHE_HOME", ".cache").join("hey")
96}
97
98/// Where credentials and settings go when nothing says otherwise: `$XDG_CONFIG_HOME/hey`,
99/// falling back to `~/.config/hey`. Shared with hey-cli.
100pub fn default_config_dir() -> PathBuf {
101    xdg_dir("XDG_CONFIG_HOME", ".config").join("hey")
102}
103
104fn xdg_dir(variable: &str, fallback: &str) -> PathBuf {
105    match env_value(variable) {
106        Some(value) => PathBuf::from(value),
107        None => env::home_dir()
108            .unwrap_or_else(|| PathBuf::from("."))
109            .join(fallback),
110    }
111}