springtime_web_axum/
config.rs

1//! Framework configuration is based on injecting an [WebConfigProvider], which can later be used to
2//! retrieve [WebConfig].
3//!
4//! By default, the config is created with opinionated default values, which can then be overwritten
5//! by values from `springtime.json` file under the `web` key.
6
7use config::{Config, File};
8use rustc_hash::FxHashMap;
9use serde::Deserialize;
10use springtime::config::CONFIG_FILE;
11use springtime::future::{BoxFuture, FutureExt};
12use springtime_di::component_registry::conditional::unregistered_component;
13use springtime_di::instance_provider::ErrorPtr;
14use springtime_di::{component_alias, injectable, Component};
15use std::sync::Arc;
16
17/// Name of the default server present in the default [WebConfig].
18pub const DEFAULT_SERVER_NAME: &str = "default";
19
20/// Server configuration.
21#[non_exhaustive]
22#[derive(Clone, Debug, Deserialize)]
23#[serde(default)]
24pub struct ServerConfig {
25    /// Address on which to listen.
26    pub listen_address: String,
27}
28
29impl Default for ServerConfig {
30    fn default() -> Self {
31        Self {
32            listen_address: "0.0.0.0:80".to_string(),
33        }
34    }
35}
36
37/// Framework configuration which can be provided by an [WebConfigProvider].
38#[non_exhaustive]
39#[derive(Clone, Debug, Deserialize)]
40#[serde(default)]
41pub struct WebConfig {
42    /// Map from server name to their config. Typically, only one server with one address will be
43    /// present (see: [DEFAULT_SERVER_NAME], but in case multiple servers are desired, they should
44    /// be specified here.
45    pub servers: FxHashMap<String, ServerConfig>,
46}
47
48impl Default for WebConfig {
49    fn default() -> Self {
50        Self {
51            servers: [(DEFAULT_SERVER_NAME.to_string(), Default::default())]
52                .into_iter()
53                .collect(),
54        }
55    }
56}
57
58impl WebConfig {
59    fn init_from_config() -> Result<Self, ErrorPtr> {
60        Config::builder()
61            .add_source(File::with_name(CONFIG_FILE).required(false))
62            .build()
63            .and_then(|config| config.try_deserialize::<WebConfigWrapper>())
64            .map(|config| config.web)
65            .map_err(|error| Arc::new(error) as ErrorPtr)
66    }
67}
68
69/// Provider for [WebConfig]. The primary instance of the provider will be used to retrieve web
70/// configuration.
71#[injectable]
72pub trait WebConfigProvider {
73    /// Provide current config.
74    fn config(&self) -> BoxFuture<'_, Result<&WebConfig, ErrorPtr>>;
75}
76
77#[derive(Component)]
78#[component(priority = -128, condition = "unregistered_component::<dyn WebConfigProvider + Send + Sync>", constructor = "DefaultWebConfigProvider::new")]
79struct DefaultWebConfigProvider {
80    // cached init result
81    #[component(ignore)]
82    config: Result<WebConfig, ErrorPtr>,
83}
84
85#[component_alias]
86impl WebConfigProvider for DefaultWebConfigProvider {
87    fn config(&self) -> BoxFuture<'_, Result<&WebConfig, ErrorPtr>> {
88        async {
89            match &self.config {
90                Ok(config) => Ok(config),
91                Err(error) => Err(error.clone()),
92            }
93        }
94        .boxed()
95    }
96}
97
98impl DefaultWebConfigProvider {
99    fn new() -> BoxFuture<'static, Result<Self, ErrorPtr>> {
100        async {
101            Ok(Self {
102                config: WebConfig::init_from_config(),
103            })
104        }
105        .boxed()
106    }
107}
108
109#[derive(Deserialize, Default)]
110#[serde(default)]
111struct WebConfigWrapper {
112    web: WebConfig,
113}