dr_core/
config.rs

1//! Configuration loading and handling with environment variables
2use crate::error::Error;
3use crate::secrets::{Mask, Secret};
4use serde::{Deserialize, Serialize};
5use std::env;
6use std::fs;
7use std::path::Path;
8use std::{collections::HashMap, fmt::Display};
9use url::Url;
10
11/// Default environment variable names
12pub enum Defaults {
13    Config,
14    Profile,
15    Username,
16    Host,
17    Port,
18    Pwd,
19    Token,
20}
21
22impl Display for Defaults {
23    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24        let envar = match self {
25            Defaults::Config => "DR_CONFIG",
26            Defaults::Profile => "DR_PROFILE",
27            Defaults::Username => "DR_USER",
28            Defaults::Host => "DR_HOST",
29            Defaults::Port => "DR_PORT",
30            Defaults::Pwd => "DR_PWD",
31            Defaults::Token => "DR_TOKEN",
32        };
33        write!(f, "{}", envar)
34    }
35}
36
37impl From<Defaults> for String {
38    fn from(value: Defaults) -> Self {
39        format!("{}", value)
40    }
41}
42
43impl From<&Defaults> for String {
44    fn from(value: &Defaults) -> Self {
45        format!("{}", value)
46    }
47}
48
49/// Loads a config RON file at the designated path
50pub fn load(path: &Path) -> Result<HashMap<String, Profile>, Error> {
51    fs::read_to_string(path).map_err(Error::from).and_then(|s| {
52        ron::from_str(&s)
53            .map_err(|e| e.to_string())
54            .map_err(Error::from)
55    })
56}
57
58/// Loads the conf file at DR_CONFIG environment variable
59pub fn default() -> Result<HashMap<String, Profile>, Error> {
60    let def = env::var(Defaults::Config.to_string()).map_err(Error::from)?;
61    let path = Path::new(&def);
62    load(path)
63}
64
65/// Credentials profile for connecting to Dremio
66#[derive(Debug, Deserialize, Serialize, Clone)]
67pub struct Profile {
68    pub username: String,
69    pub secret: Secret<String>,
70    pub host: Url,
71}
72
73impl Profile {
74    /// Attempts to load the default profile from the default config RON file
75    pub fn try_default() -> Result<Self, Error> {
76        let dr_config = env::var(Defaults::Config.to_string())?;
77        let path = Path::new(&dr_config);
78        let name = env::var(Defaults::Profile.to_string())?;
79        Profile::load_with(path, &name)
80    }
81
82    /// Loads a given profile from the designated file path
83    pub fn load_with(path: &Path, name: &str) -> Result<Self, Error> {
84        let conf = load(path)?;
85        let profile = conf
86            .get(name)
87            .ok_or(format!("Profile {} is not in {:?}", name, path))?;
88        Ok(profile.to_owned())
89    }
90
91    /// Creates a new Profile
92    pub fn new(username: &str, secret: Secret<String>, host: Url) -> Self {
93        Profile {
94            username: String::from(username),
95            secret,
96            host,
97        }
98    }
99
100    /// Creates a new Profile from the DR environment variables
101    pub fn from_envars(
102        username: Option<&str>,
103        token: Option<Secret<String>>,
104        pwd: Option<Secret<String>>,
105        host: Option<Url>,
106        port: Option<u16>,
107    ) -> Result<Self, Error> {
108        let username = match username {
109            Some(val) => String::from(val),
110            None => env::var(Defaults::Username.to_string())?,
111        };
112        let secret = match (token, pwd) {
113            (Some(inner), None) => match inner {
114                Secret::Token(mask) => Secret::Token(mask),
115                Secret::Pwd(mask) => Secret::Token(mask),
116            },
117            (None, Some(inner)) => match inner {
118                Secret::Token(mask) => Secret::Pwd(mask),
119                Secret::Pwd(mask) => Secret::Pwd(mask),
120            },
121            (Some(t), Some(_)) => match t {
122                Secret::Token(mask) => Secret::Token(mask),
123                Secret::Pwd(mask) => Secret::Token(mask),
124            },
125            (None, None) => match env::var(Defaults::Token.to_string()) {
126                Ok(token_val) => Secret::Token(Mask::from(token_val)),
127                Err(_) => Secret::Pwd(Mask::from(env::var(Defaults::Pwd.to_string())?)),
128            },
129        };
130        let mut host = match host {
131            Some(val) => val,
132            None => Url::parse(&env::var(Defaults::Host.to_string())?)?,
133        };
134        let port = match port {
135            Some(p) => Some(p),
136            None => env::var(Defaults::Port.to_string())
137                .ok()
138                .and_then(|s| s.parse::<u16>().ok()),
139        };
140        if let Some(p) = port {
141            host.set_port(Some(p)).expect("Should have set the port");
142        }
143        Ok(Profile::new(&username, secret, host))
144    }
145}