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 let creds_path = Self::path()?;
43 if !creds_path.exists() {
44 return Ok(Self::default());
45 }
46
47 let content =
48 std::fs::read_to_string(creds_path).context("Could not read credentials file")?;
49 toml::from_str(&content).context("Could not load credentials file")
50 }
51
52 pub fn save(&self) -> Result<()> {
53 use std::io::Write;
54
55 let content =
56 toml::to_string_pretty(self).context("Could not serialize credentials data")?;
57
58 let creds_path = Self::path()?;
59
60 let mut file = {
61 use std::fs::OpenOptions;
62
63 let mut options = OpenOptions::new();
64 options.write(true).create(true).truncate(true);
65
66 #[cfg(unix)]
67 {
68 use std::os::unix::fs::OpenOptionsExt;
69
70 options.mode(0o0600);
73 }
74
75 options.open(creds_path)?
76 };
77
78 file.write_all(content.as_bytes())
79 .context("Could not write credentials data")
80 }
81
82 pub fn get(&self, profile: &Option<String>) -> Option<Credentials> {
83 match profile {
84 Some(profile) => self.profiles.get(profile).cloned(),
85 None => match (self.api_token.clone(), self.url.clone()) {
86 (Some(api_token), Some(url)) => Some(Credentials {
87 api_token,
88 url: Some(url),
89 }),
90 (Some(api_token), _) => Some(Credentials {
91 api_token,
92 url: Some("https://api.edgee.app".to_string()),
93 }),
94 _ => None,
95 },
96 }
97 }
98
99 pub fn set(&mut self, profile: Option<String>, creds: Credentials) {
100 match profile {
101 Some(profile) => {
102 self.profiles.insert(profile, creds);
103 }
104 None => {
105 self.api_token = Some(creds.api_token);
106 self.url = creds.url;
107 }
108 }
109 }
110}
111
112impl Credentials {
113 pub fn check_api_token(&self) -> Result<()> {
114 Ok(())
116 }
117}
118
119impl<S: State> ConnectBuilder<S> {
120 pub fn credentials(self, creds: &Credentials) -> ConnectBuilder<SetApiToken<SetBaseurl<S>>>
121 where
122 S::ApiToken: IsUnset,
123 S::Baseurl: IsUnset,
124 {
125 let api_token = creds.api_token.clone();
126 let url = creds.url.clone();
127 self.baseurl(url.unwrap_or("https://api.edgee.app".to_string()))
128 .api_token(api_token)
129 }
130}