1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/doc_assets/settings.svg"))]
5use std::{path::PathBuf, process::Command};
8
9use base64::{Engine as _, engine::general_purpose::STANDARD};
10use thiserror::Error;
11use url::Url;
12
13pub mod ssl;
15
16#[cfg(feature = "config-observability")]
18pub mod observability;
19
20#[cfg(feature = "config-observability")]
22pub mod tracing;
23
24#[derive(Debug, Error)]
26pub enum ConfigError {
27 #[error("The config parameter {0} have an incorrect value `{1}`")]
29 WrongValue(String, String),
30 #[error("The path `{0}` provided don't match the pattern `{1}`")]
32 WrongPathPattern(String, glob::PatternError),
33 #[error("The path `{0}` provided is not correct")]
35 WrongPath(PathBuf),
36 #[error("The file `{0}` can't be read `{1}`")]
38 IoFile(String, std::io::Error),
39 #[cfg(feature = "config-openssl")]
40 #[error("Openssl error `{0}`")]
42 OpenSsl(#[from] openssl::error::ErrorStack),
43}
44
45pub 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
62pub 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
96pub 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}