service_skeleton/
config.rs

1use secrecy::SecretString;
2use std::{
3	collections::HashMap,
4	fmt::{Debug, Display},
5};
6
7use crate::Error;
8
9pub trait Service {
10	fn from_env_vars(
11		prefix: &str,
12		vars: impl Iterator<Item = (String, String)>,
13	) -> Result<Self, Error>
14	where
15		Self: Sized;
16}
17
18impl Service for () {
19	fn from_env_vars(
20		_prefix: &str,
21		_vars: impl Iterator<Item = (String, String)>,
22	) -> Result<Self, Error> {
23		Ok(())
24	}
25}
26
27pub fn determine_value<RT: Debug + Sync + Send, E: Display>(
28	var: &str,
29	parser: impl Fn(&str) -> Result<RT, E>,
30	env_value: Option<&str>,
31	default: Option<&'static str>,
32) -> Result<RT, Error> {
33	let value_to_parse: &str = match (env_value, default) {
34		(None, Some(default_value)) => Ok(default_value),
35		(Some(value), _) => Ok(value),
36		(None, None) => Err(Error::no_config_value(var)),
37	}?;
38
39	parser(value_to_parse).map_err(|e| Error::config_value_parse(var, e))
40}
41
42pub fn determine_optional_value<RT: Debug + Sync + Send, E: Display>(
43	var: &str,
44	parser: impl Fn(&str) -> Result<RT, E>,
45	env_value: Option<&str>,
46	default: Option<&'static str>,
47) -> Result<Option<RT>, Error> {
48	let value_to_parse: &str = match (env_value, default) {
49		(None, Some(default_value)) => Ok::<&str, Error>(default_value),
50		(Some(value), _) => Ok::<&str, Error>(value),
51		(None, None) => return Ok(None),
52	}?;
53
54	parser(value_to_parse)
55		.map_err(|e| Error::config_value_parse(var, e))
56		.map(|v| Some(v))
57}
58
59pub fn fetch_encrypted_field<H1: ::std::hash::BuildHasher, H2: ::std::hash::BuildHasher>(
60	var_map: &HashMap<String, String, H1>,
61	key_map: &mut HashMap<Key, SecretString, H2>,
62	value_field_var: &str,
63	key_spec: &Key,
64) -> Result<Option<String>, Error> {
65	let Some(value) = var_map.get(value_field_var) else {
66		return Ok(None);
67	};
68
69	let key = if let Some(k) = key_map.get(key_spec) {
70		k
71	} else {
72		let k = match key_spec {
73			Key::File(ref file_env) => {
74				let Some(key_file) = var_map.get(file_env) else {
75					return Err(Error::no_config_value(file_env));
76				};
77				std::fs::read_to_string(key_file)
78					.map_err(|e| Error::key_read(key_file, e))?
79					.trim_end()
80					.to_string()
81			}
82		};
83		key_map.insert(key_spec.clone(), k.into());
84		// How much I wish there was an Entry method that allowed fallible closures...
85		#[allow(clippy::unwrap_used)]
86		key_map.get(key_spec).unwrap()
87	};
88
89	Ok(Some(sscrypt::decrypt(value, value_field_var, key)?))
90}
91
92#[derive(Clone, Debug, PartialEq, Hash, Eq)]
93#[non_exhaustive]
94// This enum is not meant to be used directly; it is an implementation detail that must be made
95// public because it is used in derived code
96#[doc(hidden)]
97pub enum Key {
98	File(String),
99}