architect_api/
config.rs

1//! Common configuration for Architect installs.
2
3use crate::symbology::MarketdataVenue;
4use anyhow::{anyhow, Context, Result};
5use serde::{Deserialize, Serialize};
6use std::{
7    collections::BTreeMap,
8    path::{Path, PathBuf},
9};
10use url::Url;
11
12#[derive(Debug, Default, Clone, Serialize, Deserialize)]
13pub struct Config {
14    /// TLS client identity to present to upstream Architect services;
15    /// should point to the Architect license certificate file.
16    pub license: Option<PathBuf>,
17    /// TLS client identity key; if not specified, the corresponding
18    /// private key will be looked for at the same path but with .key
19    /// extension.
20    pub license_key: Option<PathBuf>,
21    /// JWT authority configuration
22    #[serde(default)]
23    pub jwt: JwtAuthorityConfig,
24    /// If set, use a non-default secrets store path.
25    #[serde(default)]
26    pub secrets: Option<PathBuf>,
27    #[serde(default)]
28    pub userdb: Option<Url>,
29    #[serde(default)]
30    pub symbology: Option<Url>,
31    #[serde(default)]
32    pub marketdata: BTreeMap<MarketdataVenue, Url>,
33    /// Connect to the specified Architect core
34    #[serde(default)]
35    pub core: Option<Url>,
36}
37
38#[derive(Debug, Default, Clone, Serialize, Deserialize)]
39pub struct JwtAuthorityConfig {
40    /// JWT audience to use when minting JWTs; if not set,
41    /// default to broad fallback `*.architect.co`.
42    #[serde(default)]
43    pub aud: Option<String>,
44}
45
46impl Config {
47    pub fn license(&self) -> Result<Option<PathBuf>> {
48        canonicalize(&self.license)
49    }
50
51    pub fn license_key(&self) -> Result<Option<PathBuf>> {
52        canonicalize(&self.license_key)
53    }
54
55    pub fn secrets(&self) -> Result<Option<PathBuf>> {
56        canonicalize(&self.secrets)
57    }
58}
59
60fn canonicalize(path: &Option<impl AsRef<Path>>) -> Result<Option<PathBuf>> {
61    if let Some(path) = path {
62        let expanded = expand_tilde(path).ok_or_else(|| {
63            anyhow!("while expanding tilde: {}", path.as_ref().display())
64        })?;
65        let canonicalized = match std::fs::canonicalize(&expanded)
66            .with_context(|| format!("while resolving path: {}", expanded.display()))
67        {
68            Ok(canon) => canon,
69            Err(_e) => {
70                // path doesn't exist, so it can't be a symlink/hardlink
71                // this isn't the only error case though
72                expanded
73            }
74        };
75        Ok(Some(canonicalized))
76    } else {
77        Ok(None)
78    }
79}
80
81fn expand_tilde<P: AsRef<Path>>(path_user_input: P) -> Option<PathBuf> {
82    let p = path_user_input.as_ref();
83    if !p.starts_with("~") {
84        return Some(p.to_path_buf());
85    }
86    if p == Path::new("~") {
87        return dirs::home_dir();
88    }
89    dirs::home_dir().map(|mut h| {
90        if h == Path::new("/") {
91            // Corner case: `h` root directory;
92            // don't prepend extra `/`, just drop the tilde.
93            p.strip_prefix("~").unwrap().to_path_buf()
94        } else {
95            h.push(p.strip_prefix("~/").unwrap());
96            h
97        }
98    })
99}