1use std::path::PathBuf;
2
3use anyhow::{Context, Result};
4use serde::{Deserialize, Serialize};
5
6use crate::{
7 connect_builder::{IsUnset, SetApiToken, SetBaseurl, State},
8 ConnectBuilder,
9};
10
11#[derive(Debug, Deserialize, Default, Serialize, Clone)]
12pub struct Config {
13 #[serde(default)]
14 api_token: Option<String>,
15 #[serde(default)]
16 url: Option<String>,
17
18 #[serde(flatten)]
19 profiles: std::collections::HashMap<String, Credentials>,
20}
21
22#[derive(Debug, Deserialize, Default, Serialize, Clone)]
23pub struct Credentials {
24 pub api_token: String,
25 #[serde(default)]
26 pub url: Option<String>,
27}
28
29impl Config {
30 pub fn path() -> Result<PathBuf> {
31 let config_dir = dirs::config_dir()
32 .ok_or_else(|| anyhow::anyhow!("Could not get user config directory"))?
33 .join("edgee");
34 if !config_dir.exists() {
35 std::fs::create_dir_all(&config_dir).context("Could not create Edgee config dir")?;
36 }
37
38 Ok(config_dir.join("credentials.toml"))
39 }
40
41 pub fn load() -> Result<Self> {
42 if let Ok(api_token) = std::env::var("EDGEE_API_TOKEN") {
45 return Ok(Self {
46 api_token: Some(api_token),
47 url: Some(
48 std::env::var("EDGEE_API_URL").unwrap_or("https://api.edgee.app".to_string()),
49 ),
50 ..Default::default()
51 });
52 };
53
54 let creds_path = Self::path()?;
55 if !creds_path.exists() {
56 return Ok(Self::default());
57 }
58
59 let content =
60 std::fs::read_to_string(creds_path).context("Could not read credentials file")?;
61 toml::from_str(&content).context("Could not load credentials file")
62 }
63
64 pub fn save(&self) -> Result<()> {
65 use std::io::Write;
66
67 let content =
68 toml::to_string_pretty(self).context("Could not serialize credentials data")?;
69
70 let creds_path = Self::path()?;
71
72 let mut file = {
73 use std::fs::OpenOptions;
74
75 let mut options = OpenOptions::new();
76 options.write(true).create(true).truncate(true);
77
78 #[cfg(unix)]
79 {
80 use std::os::unix::fs::OpenOptionsExt;
81
82 options.mode(0o0600);
85 }
86
87 options.open(creds_path)?
88 };
89
90 file.write_all(content.as_bytes())
91 .context("Could not write credentials data")
92 }
93
94 pub fn get(&self, profile: &Option<String>) -> Option<Credentials> {
95 match profile {
96 Some(profile) => self.profiles.get(profile).cloned(),
97 None => match (self.api_token.clone(), self.url.clone()) {
98 (Some(api_token), Some(url)) => Some(Credentials {
99 api_token,
100 url: Some(url),
101 }),
102 (Some(api_token), _) => Some(Credentials {
103 api_token,
104 url: Some("https://api.edgee.app".to_string()),
105 }),
106 _ => None,
107 },
108 }
109 }
110
111 pub fn set(&mut self, profile: Option<String>, creds: Credentials) {
112 match profile {
113 Some(profile) => {
114 self.profiles.insert(profile, creds);
115 }
116 None => {
117 self.api_token = Some(creds.api_token);
118 self.url = creds.url;
119 }
120 }
121 }
122}
123
124impl Credentials {
125 pub fn check_api_token(&self) -> Result<()> {
126 Ok(())
128 }
129}
130
131impl<S: State> ConnectBuilder<S> {
132 pub fn credentials(self, creds: &Credentials) -> ConnectBuilder<SetApiToken<SetBaseurl<S>>>
133 where
134 S::ApiToken: IsUnset,
135 S::Baseurl: IsUnset,
136 {
137 let api_token = creds.api_token.clone();
138 let url = creds.url.clone();
139 self.baseurl(url.unwrap_or("https://api.edgee.app".to_string()))
140 .api_token(api_token)
141 }
142}