Skip to main content

prosa_utils/
config.rs

1//! Module for ProSA configuration object
2//!
3//! <svg width="40" height="40">
4#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/doc_assets/settings.svg"))]
5//! </svg>
6
7use std::{path::PathBuf, process::Command};
8
9use base64::{Engine as _, engine::general_purpose::STANDARD};
10use thiserror::Error;
11use url::Url;
12
13// Feature openssl or rusttls,...
14pub mod ssl;
15
16// Feature opentelemetry
17#[cfg(feature = "config-observability")]
18pub mod observability;
19
20// Feature tracing
21#[cfg(feature = "config-observability")]
22pub mod tracing;
23
24/// Error define for configuration object
25#[derive(Debug, Error)]
26pub enum ConfigError {
27    /// Error that indicate a wrong path format in filesystem
28    #[error("The config parameter {0} have an incorrect value `{1}`")]
29    WrongValue(String, String),
30    /// Error that indicate a wrong path format pattern in filesystem
31    #[error("The path `{0}` provided don't match the pattern `{1}`")]
32    WrongPathPattern(String, glob::PatternError),
33    /// Error that indicate a wrong path format in filesystem
34    #[error("The path `{0}` provided is not correct")]
35    WrongPath(PathBuf),
36    /// Error on a file read
37    #[error("The file `{0}` can't be read `{1}`")]
38    IoFile(String, std::io::Error),
39    #[cfg(feature = "config-openssl")]
40    /// SSL error
41    #[error("Openssl error `{0}`")]
42    OpenSsl(#[from] openssl::error::ErrorStack),
43}
44
45/// Method to try get the country name from the OS
46pub fn os_country() -> Option<String> {
47    if let Some(lang) = option_env!("LANG") {
48        let language = if let Some(pos) = lang.find('.') {
49            &lang[..pos]
50        } else {
51            lang
52        };
53
54        if let Some(pos) = language.find('_') {
55            return Some(String::from(&language[pos + 1..]));
56        }
57    }
58
59    None
60}
61
62/// Method to try get the hostname from the OS
63pub fn hostname() -> Option<String> {
64    #[cfg(target_family = "unix")]
65    if let Some(host) = option_env!("HOSTNAME").map(str::trim)
66        && !host.is_empty()
67        && !host.contains('\n')
68    {
69        return Some(String::from(host));
70    }
71
72    #[cfg(target_family = "unix")]
73    return Command::new("hostname")
74        .arg("-s")
75        .output()
76        .ok()
77        .and_then(|h| {
78            str::from_utf8(h.stdout.trim_ascii())
79                .ok()
80                .filter(|h| !h.is_empty() && !h.contains('\n'))
81                .map(|h| h.to_string())
82        });
83
84    #[cfg(target_family = "windows")]
85    return Command::new("hostname").output().ok().and_then(|h| {
86        str::from_utf8(h.stdout.trim_ascii())
87            .ok()
88            .filter(|h| !h.is_empty() && !h.contains('\n'))
89            .map(|h| h.to_string())
90    });
91
92    #[cfg(all(not(target_family = "unix"), not(target_family = "windows")))]
93    return None;
94}
95
96/// Method to get authentication value out of URL username/password
97///
98/// - If user password is provided, it return *Basic* authentication with base64 encoded username:password
99/// - If only password is provided, it return *Bearer* authentication with the password as token
100///
101/// ```
102/// use url::Url;
103/// use prosa_utils::config::url_authentication;
104///
105/// let basic_auth_target = Url::parse("http://user:pass@localhost:8080").unwrap();
106/// assert_eq!(Some(String::from("Basic dXNlcjpwYXNz")), url_authentication(&basic_auth_target));
107///
108/// let bearer_auth_target = Url::parse("http://:token@localhost:8080").unwrap();
109/// assert_eq!(Some(String::from("Bearer token")), url_authentication(&bearer_auth_target));
110/// ```
111pub fn url_authentication(url: &Url) -> Option<String> {
112    if let Some(password) = url.password() {
113        if url.username().is_empty() {
114            Some(format!("Bearer {password}"))
115        } else {
116            Some(format!(
117                "Basic {}",
118                STANDARD.encode(format!("{}:{}", url.username(), password))
119            ))
120        }
121    } else {
122        None
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    #[test]
131    fn test_os_country() {
132        let country = os_country();
133        if let Some(cn) = country {
134            assert_eq!(2, cn.len());
135        }
136    }
137
138    #[test]
139    fn test_hostname() {
140        let host = hostname();
141        if let Some(hn) = host {
142            assert!(!hn.is_empty());
143        }
144    }
145
146    #[test]
147    fn test_url_authentication_basic() {
148        let basic_auth_target = Url::parse("http://user:pass@localhost:8080").unwrap();
149        assert_eq!(
150            Some(String::from("Basic dXNlcjpwYXNz")),
151            url_authentication(&basic_auth_target)
152        );
153    }
154
155    #[test]
156    fn test_url_authentication_bearer() {
157        let bearer_auth_target = Url::parse("http://:token@localhost:8080").unwrap();
158        assert_eq!(
159            Some(String::from("Bearer token")),
160            url_authentication(&bearer_auth_target)
161        );
162    }
163}