Skip to main content

htsget_config/config/
parser.rs

1//! Parse config for a file and environment variables.
2//!
3
4use crate::config::Config;
5use figment::Figment;
6use figment::providers::{Env, Format, Serialized, Toml};
7use serde::Deserialize;
8use std::fmt::Debug;
9use std::io;
10use std::path::Path;
11use tracing::info;
12
13const ENVIRONMENT_VARIABLE_PREFIX: &str = "HTSGET_";
14
15/// A struct to represent a string or a path, used for parsing and deserializing config.
16#[derive(Debug)]
17pub enum Parser<'a> {
18  String(&'a str),
19  Path(&'a Path),
20}
21
22impl Parser<'_> {
23  /// Deserialize a string or path into a config value using Figment.
24  pub fn deserialize_config_into<T>(&self) -> io::Result<T>
25  where
26    for<'de> T: Deserialize<'de> + Debug,
27  {
28    let config = Figment::from(Serialized::defaults(Config::default()))
29      .merge(match self {
30        Parser::String(string) => Toml::string(string),
31        Parser::Path(path) => Toml::file(path),
32      })
33      .merge(
34        Env::prefixed(ENVIRONMENT_VARIABLE_PREFIX)
35          .filter(|k| k != "config")
36          .map(|k| {
37            // This has to list all possible nested values to resolve issues with ambiguity when
38            // deserializing. E.g. see https://github.com/SergioBenitez/Figment/issues/12
39            k.as_str()
40              .to_lowercase()
41              .replace("ticket_server_", "ticket_server.")
42              .replace("data_server_", "data_server.")
43              .replace("cors_", "cors.")
44              .replace("tls_", "tls.")
45              .replace("http_", "http.")
46              .replace("auth_", "auth.")
47              .into()
48          }),
49      )
50      .extract()
51      .map_err(|err| io::Error::other(format!("failed to parse config: {err}")))?;
52
53    info!(config = ?config, "config created");
54
55    Ok(config)
56  }
57}
58
59/// Read a deserializable config struct from a TOML file.
60pub fn from_path<T>(path: &Path) -> io::Result<T>
61where
62  for<'a> T: Deserialize<'a> + Debug,
63{
64  Parser::Path(path).deserialize_config_into()
65}
66
67/// Read a deserializable config struct from a str.
68pub fn from_str<T>(str: &str) -> io::Result<T>
69where
70  for<'a> T: Deserialize<'a> + Debug,
71{
72  Parser::String(str).deserialize_config_into()
73}