1
0
mirror of https://github.com/actix/actix-extras.git synced 2025-02-02 10:59:03 +01:00
2022-09-11 21:55:40 +01:00

812 lines
27 KiB
Rust

//! Easily manage Actix Web's settings from a TOML file and environment variables.
//!
//! To get started add a [`Settings::parse_toml("./Server.toml")`](Settings::parse_toml) call to the
//! top of your main function. This will create a template file with descriptions of all the
//! configurable settings. You can change or remove anything in that file and it will be picked up
//! the next time you run your application.
//!
//! Overriding parts of the file can be done from values using [`Settings::override_field`] or from
//! the environment using [`Settings::override_field_with_env_var`].
//!
//! # Examples
//!
//! See examples folder on GitHub for complete example.
//!
//! ```ignore
//! # use actix_web::{
//! # get,
//! # middleware::{Compress, Condition, Logger},
//! # web, App, HttpServer,
//! # };
//! use actix_settings::{ApplySettings as _, Mode, Settings};
//!
//! #[actix_web::main]
//! async fn main() -> std::io::Result<()> {
//! let mut settings = Settings::parse_toml("./Server.toml")
//! .expect("Failed to parse `Settings` from Server.toml");
//!
//! // If the environment variable `$APPLICATION__HOSTS` is set,
//! // have its value override the `settings.actix.hosts` setting:
//! Settings::override_field_with_env_var(&mut settings.actix.hosts, "APPLICATION__HOSTS")?;
//!
//! init_logger(&settings);
//!
//! HttpServer::new({
//! // clone settings into each worker thread
//! let settings = settings.clone();
//!
//! move || {
//! App::new()
//! // Include this `.wrap()` call for compression settings to take effect
//! .wrap(Condition::new(
//! settings.actix.enable_compression,
//! Compress::default(),
//! ))
//!
//! // add request logger
//! .wrap(Logger::default())
//!
//! // make `Settings` available to handlers
//! .app_data(web::Data::new(settings.clone()))
//!
//! // add request handlers as normal
//! .service(index)
//! }
//! })
//! // apply the `Settings` to Actix Web's `HttpServer`
//! .apply_settings(&settings)
//! .run()
//! .await
//! }
//! ```
#![forbid(unsafe_code)]
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible, missing_docs, missing_debug_implementations)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
use std::{
env, fmt,
fs::File,
io::{Read as _, Write as _},
path::Path,
time::Duration,
};
use actix_http::{Request, Response};
use actix_service::IntoServiceFactory;
use actix_web::{
body::MessageBody,
dev::{AppConfig, ServiceFactory},
http::KeepAlive as ActixKeepAlive,
Error as WebError, HttpServer,
};
use serde::{de, Deserialize};
#[macro_use]
mod error;
mod parse;
mod settings;
pub use self::{
error::Error,
parse::Parse,
settings::{
ActixSettings, Address, Backlog, KeepAlive, MaxConnectionRate, MaxConnections, Mode,
NumWorkers, Timeout, Tls,
},
};
/// Convenience type alias for `Result<T, AtError>`.
type AsResult<T> = std::result::Result<T, Error>;
/// Wrapper for server and application-specific settings.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)]
#[serde(bound = "A: Deserialize<'de>")]
pub struct BasicSettings<A> {
/// Actix Web server settings.
pub actix: ActixSettings,
/// Application-specific settings.
pub application: A,
}
/// Convenience type alias for [`BasicSettings`] with no defined application-specific settings.
pub type Settings = BasicSettings<NoSettings>;
/// Marker type representing no defined application-specific settings.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)]
#[non_exhaustive]
pub struct NoSettings {/* NOTE: turning this into a unit struct will cause deserialization failures. */}
impl<A> BasicSettings<A>
where
A: de::DeserializeOwned,
{
// NOTE **DO NOT** mess with the ordering of the tables in the default template.
// Especially the `[application]` table needs to be last in order
// for some tests to keep working.
/// Default settings file contents.
pub(crate) const DEFAULT_TOML_TEMPLATE: &'static str = include_str!("./defaults.toml");
/// Parse an instance of `Self` from a TOML file located at `filepath`.
///
/// If the file doesn't exist, it is generated from the default TOML template, after which the
/// newly generated file is read in and parsed.
pub fn parse_toml<P>(filepath: P) -> AsResult<Self>
where
P: AsRef<Path>,
{
let filepath = filepath.as_ref();
if !filepath.exists() {
Self::write_toml_file(filepath)?;
}
let mut f = File::open(filepath)?;
let len_guess = f.metadata().map(|md| md.len()).unwrap_or(128);
let mut contents = String::with_capacity(len_guess as usize);
f.read_to_string(&mut contents)?;
Ok(toml::from_str::<Self>(&contents)?)
}
/// Parse an instance of `Self` straight from the default TOML template.
// TODO: make infallible
// TODO: consider "template" rename
pub fn from_default_template() -> AsResult<Self> {
Self::from_template(Self::DEFAULT_TOML_TEMPLATE)
}
/// Parse an instance of `Self` straight from the default TOML template.
// TODO: consider "template" rename
pub fn from_template(template: &str) -> AsResult<Self> {
Ok(toml::from_str(template)?)
}
/// Writes the default TOML template to a new file, located at `filepath`.
///
/// # Errors
/// Returns a [`FileExists`](crate::AtError::FileExists) error if a file already exists at that
/// location.
pub fn write_toml_file<P>(filepath: P) -> AsResult<()>
where
P: AsRef<Path>,
{
let filepath = filepath.as_ref();
if filepath.exists() {
return Err(Error::FileExists(filepath.to_path_buf()));
}
let mut file = File::create(filepath)?;
file.write_all(Self::DEFAULT_TOML_TEMPLATE.trim().as_bytes())?;
file.flush()?;
Ok(())
}
/// Attempts to parse `value` and override the referenced `field`.
///
/// # Examples
/// ```
/// use actix_settings::{Settings, Mode};
///
/// # fn inner() -> Result<(), actix_settings::Error> {
/// let mut settings = Settings::from_default_template()?;
/// assert_eq!(settings.actix.mode, Mode::Development);
///
/// Settings::override_field(&mut settings.actix.mode, "production")?;
/// assert_eq!(settings.actix.mode, Mode::Production);
/// # Ok(()) }
/// ```
pub fn override_field<F, V>(field: &mut F, value: V) -> AsResult<()>
where
F: Parse,
V: AsRef<str>,
{
*field = F::parse(value.as_ref())?;
Ok(())
}
/// Attempts to read an environment variable, parse it, and override the referenced `field`.
///
/// # Examples
/// ```
/// use actix_settings::{Settings, Mode};
///
/// std::env::set_var("OVERRIDE__MODE", "production");
///
/// # fn inner() -> Result<(), actix_settings::Error> {
/// let mut settings = Settings::from_default_template()?;
/// assert_eq!(settings.actix.mode, Mode::Development);
///
/// Settings::override_field_with_env_var(&mut settings.actix.mode, "OVERRIDE__MODE")?;
/// assert_eq!(settings.actix.mode, Mode::Production);
/// # Ok(()) }
/// ```
pub fn override_field_with_env_var<F, N>(field: &mut F, var_name: N) -> AsResult<()>
where
F: Parse,
N: AsRef<str>,
{
match env::var(var_name.as_ref()) {
Err(env::VarError::NotPresent) => Ok((/*NOP*/)),
Err(var_error) => Err(Error::from(var_error)),
Ok(value) => Self::override_field(field, value),
}
}
}
/// Extension trait for applying parsed settings to the server object.
pub trait ApplySettings {
/// Apply a [`BasicSettings`] value to `self`.
///
/// [`BasicSettings`]: ./struct.BasicSettings.html
#[must_use]
fn apply_settings<A>(self, settings: &BasicSettings<A>) -> Self
where
A: de::DeserializeOwned;
}
impl<F, I, S, B> ApplySettings for HttpServer<F, I, S, B>
where
F: Fn() -> I + Send + Clone + 'static,
I: IntoServiceFactory<S, Request>,
S: ServiceFactory<Request, Config = AppConfig> + 'static,
S::Error: Into<WebError> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
S::Future: 'static,
B: MessageBody + 'static,
{
fn apply_settings<A>(mut self, settings: &BasicSettings<A>) -> Self
where
A: de::DeserializeOwned,
{
if settings.actix.tls.enabled {
// for Address { host, port } in &settings.actix.hosts {
// self = self.bind(format!("{}:{}", host, port))
// .unwrap(/*TODO*/);
// }
todo!("[ApplySettings] TLS support has not been implemented yet.");
} else {
for Address { host, port } in &settings.actix.hosts {
self = self.bind(format!("{}:{}", host, port))
.unwrap(/*TODO*/);
}
}
self = match settings.actix.num_workers {
NumWorkers::Default => self,
NumWorkers::Manual(n) => self.workers(n),
};
self = match settings.actix.backlog {
Backlog::Default => self,
Backlog::Manual(n) => self.backlog(n as u32),
};
self = match settings.actix.max_connections {
MaxConnections::Default => self,
MaxConnections::Manual(n) => self.max_connections(n),
};
self = match settings.actix.max_connection_rate {
MaxConnectionRate::Default => self,
MaxConnectionRate::Manual(n) => self.max_connection_rate(n),
};
self = match settings.actix.keep_alive {
KeepAlive::Default => self,
KeepAlive::Disabled => self.keep_alive(ActixKeepAlive::Disabled),
KeepAlive::Os => self.keep_alive(ActixKeepAlive::Os),
KeepAlive::Seconds(n) => self.keep_alive(Duration::from_secs(n as u64)),
};
self = match settings.actix.client_timeout {
Timeout::Default => self,
Timeout::Milliseconds(n) => {
self.client_request_timeout(Duration::from_millis(n as u64))
}
Timeout::Seconds(n) => self.client_request_timeout(Duration::from_secs(n as u64)),
};
self = match settings.actix.client_shutdown {
Timeout::Default => self,
Timeout::Milliseconds(n) => {
self.client_disconnect_timeout(Duration::from_millis(n as u64))
}
Timeout::Seconds(n) => self.client_disconnect_timeout(Duration::from_secs(n as u64)),
};
self = match settings.actix.shutdown_timeout {
Timeout::Default => self,
Timeout::Milliseconds(_) => self.shutdown_timeout(1),
Timeout::Seconds(n) => self.shutdown_timeout(n as u64),
};
self
}
}
#[cfg(test)]
mod tests {
use actix_web::App;
use super::*;
#[test]
fn apply_settings() {
let settings = Settings::parse_toml("Server.toml").unwrap();
let _ = HttpServer::new(App::new).apply_settings(&settings);
}
#[test]
fn override_field_hosts() {
let mut settings = Settings::from_default_template().unwrap();
assert_eq!(
settings.actix.hosts,
vec![Address {
host: "0.0.0.0".into(),
port: 9000
},]
);
Settings::override_field(
&mut settings.actix.hosts,
r#"[
["0.0.0.0", 1234],
["localhost", 2345]
]"#,
)
.unwrap();
assert_eq!(
settings.actix.hosts,
vec![
Address {
host: "0.0.0.0".into(),
port: 1234
},
Address {
host: "localhost".into(),
port: 2345
},
]
);
}
#[test]
fn override_field_with_env_var_hosts() {
let mut settings = Settings::from_default_template().unwrap();
assert_eq!(
settings.actix.hosts,
vec![Address {
host: "0.0.0.0".into(),
port: 9000
},]
);
std::env::set_var(
"OVERRIDE__HOSTS",
r#"[
["0.0.0.0", 1234],
["localhost", 2345]
]"#,
);
Settings::override_field_with_env_var(&mut settings.actix.hosts, "OVERRIDE__HOSTS")
.unwrap();
assert_eq!(
settings.actix.hosts,
vec![
Address {
host: "0.0.0.0".into(),
port: 1234
},
Address {
host: "localhost".into(),
port: 2345
},
]
);
}
#[test]
fn override_field_mode() {
let mut settings = Settings::from_default_template().unwrap();
assert_eq!(settings.actix.mode, Mode::Development);
Settings::override_field(&mut settings.actix.mode, "production").unwrap();
assert_eq!(settings.actix.mode, Mode::Production);
}
#[test]
fn override_field_with_env_var_mode() {
let mut settings = Settings::from_default_template().unwrap();
assert_eq!(settings.actix.mode, Mode::Development);
std::env::set_var("OVERRIDE__MODE", "production");
Settings::override_field_with_env_var(&mut settings.actix.mode, "OVERRIDE__MODE").unwrap();
assert_eq!(settings.actix.mode, Mode::Production);
}
#[test]
fn override_field_enable_compression() {
let mut settings = Settings::from_default_template().unwrap();
assert!(settings.actix.enable_compression);
Settings::override_field(&mut settings.actix.enable_compression, "false").unwrap();
assert!(!settings.actix.enable_compression);
}
#[test]
fn override_field_with_env_var_enable_compression() {
let mut settings = Settings::from_default_template().unwrap();
assert!(settings.actix.enable_compression);
std::env::set_var("OVERRIDE__ENABLE_COMPRESSION", "false");
Settings::override_field_with_env_var(
&mut settings.actix.enable_compression,
"OVERRIDE__ENABLE_COMPRESSION",
)
.unwrap();
assert!(!settings.actix.enable_compression);
}
#[test]
fn override_field_enable_log() {
let mut settings = Settings::from_default_template().unwrap();
assert!(settings.actix.enable_log);
Settings::override_field(&mut settings.actix.enable_log, "false").unwrap();
assert!(!settings.actix.enable_log);
}
#[test]
fn override_field_with_env_var_enable_log() {
let mut settings = Settings::from_default_template().unwrap();
assert!(settings.actix.enable_log);
std::env::set_var("OVERRIDE__ENABLE_LOG", "false");
Settings::override_field_with_env_var(
&mut settings.actix.enable_log,
"OVERRIDE__ENABLE_LOG",
)
.unwrap();
assert!(!settings.actix.enable_log);
}
#[test]
fn override_field_num_workers() {
let mut settings = Settings::from_default_template().unwrap();
assert_eq!(settings.actix.num_workers, NumWorkers::Default);
Settings::override_field(&mut settings.actix.num_workers, "42").unwrap();
assert_eq!(settings.actix.num_workers, NumWorkers::Manual(42));
}
#[test]
fn override_field_with_env_var_num_workers() {
let mut settings = Settings::from_default_template().unwrap();
assert_eq!(settings.actix.num_workers, NumWorkers::Default);
std::env::set_var("OVERRIDE__NUM_WORKERS", "42");
Settings::override_field_with_env_var(
&mut settings.actix.num_workers,
"OVERRIDE__NUM_WORKERS",
)
.unwrap();
assert_eq!(settings.actix.num_workers, NumWorkers::Manual(42));
}
#[test]
fn override_field_backlog() {
let mut settings = Settings::from_default_template().unwrap();
assert_eq!(settings.actix.backlog, Backlog::Default);
Settings::override_field(&mut settings.actix.backlog, "42").unwrap();
assert_eq!(settings.actix.backlog, Backlog::Manual(42));
}
#[test]
fn override_field_with_env_var_backlog() {
let mut settings = Settings::from_default_template().unwrap();
assert_eq!(settings.actix.backlog, Backlog::Default);
std::env::set_var("OVERRIDE__BACKLOG", "42");
Settings::override_field_with_env_var(&mut settings.actix.backlog, "OVERRIDE__BACKLOG")
.unwrap();
assert_eq!(settings.actix.backlog, Backlog::Manual(42));
}
#[test]
fn override_field_max_connections() {
let mut settings = Settings::from_default_template().unwrap();
assert_eq!(settings.actix.max_connections, MaxConnections::Default);
Settings::override_field(&mut settings.actix.max_connections, "42").unwrap();
assert_eq!(settings.actix.max_connections, MaxConnections::Manual(42));
}
#[test]
fn override_field_with_env_var_max_connections() {
let mut settings = Settings::from_default_template().unwrap();
assert_eq!(settings.actix.max_connections, MaxConnections::Default);
std::env::set_var("OVERRIDE__MAX_CONNECTIONS", "42");
Settings::override_field_with_env_var(
&mut settings.actix.max_connections,
"OVERRIDE__MAX_CONNECTIONS",
)
.unwrap();
assert_eq!(settings.actix.max_connections, MaxConnections::Manual(42));
}
#[test]
fn override_field_max_connection_rate() {
let mut settings = Settings::from_default_template().unwrap();
assert_eq!(
settings.actix.max_connection_rate,
MaxConnectionRate::Default
);
Settings::override_field(&mut settings.actix.max_connection_rate, "42").unwrap();
assert_eq!(
settings.actix.max_connection_rate,
MaxConnectionRate::Manual(42)
);
}
#[test]
fn override_field_with_env_var_max_connection_rate() {
let mut settings = Settings::from_default_template().unwrap();
assert_eq!(
settings.actix.max_connection_rate,
MaxConnectionRate::Default
);
std::env::set_var("OVERRIDE__MAX_CONNECTION_RATE", "42");
Settings::override_field_with_env_var(
&mut settings.actix.max_connection_rate,
"OVERRIDE__MAX_CONNECTION_RATE",
)
.unwrap();
assert_eq!(
settings.actix.max_connection_rate,
MaxConnectionRate::Manual(42)
);
}
#[test]
fn override_field_keep_alive() {
let mut settings = Settings::from_default_template().unwrap();
assert_eq!(settings.actix.keep_alive, KeepAlive::Default);
Settings::override_field(&mut settings.actix.keep_alive, "42 seconds").unwrap();
assert_eq!(settings.actix.keep_alive, KeepAlive::Seconds(42));
}
#[test]
fn override_field_with_env_var_keep_alive() {
let mut settings = Settings::from_default_template().unwrap();
assert_eq!(settings.actix.keep_alive, KeepAlive::Default);
std::env::set_var("OVERRIDE__KEEP_ALIVE", "42 seconds");
Settings::override_field_with_env_var(
&mut settings.actix.keep_alive,
"OVERRIDE__KEEP_ALIVE",
)
.unwrap();
assert_eq!(settings.actix.keep_alive, KeepAlive::Seconds(42));
}
#[test]
fn override_field_client_timeout() {
let mut settings = Settings::from_default_template().unwrap();
assert_eq!(settings.actix.client_timeout, Timeout::Default);
Settings::override_field(&mut settings.actix.client_timeout, "42 seconds").unwrap();
assert_eq!(settings.actix.client_timeout, Timeout::Seconds(42));
}
#[test]
fn override_field_with_env_var_client_timeout() {
let mut settings = Settings::from_default_template().unwrap();
assert_eq!(settings.actix.client_timeout, Timeout::Default);
std::env::set_var("OVERRIDE__CLIENT_TIMEOUT", "42 seconds");
Settings::override_field_with_env_var(
&mut settings.actix.client_timeout,
"OVERRIDE__CLIENT_TIMEOUT",
)
.unwrap();
assert_eq!(settings.actix.client_timeout, Timeout::Seconds(42));
}
#[test]
fn override_field_client_shutdown() {
let mut settings = Settings::from_default_template().unwrap();
assert_eq!(settings.actix.client_shutdown, Timeout::Default);
Settings::override_field(&mut settings.actix.client_shutdown, "42 seconds").unwrap();
assert_eq!(settings.actix.client_shutdown, Timeout::Seconds(42));
}
#[test]
fn override_field_with_env_var_client_shutdown() {
let mut settings = Settings::from_default_template().unwrap();
assert_eq!(settings.actix.client_shutdown, Timeout::Default);
std::env::set_var("OVERRIDE__CLIENT_SHUTDOWN", "42 seconds");
Settings::override_field_with_env_var(
&mut settings.actix.client_shutdown,
"OVERRIDE__CLIENT_SHUTDOWN",
)
.unwrap();
assert_eq!(settings.actix.client_shutdown, Timeout::Seconds(42));
}
#[test]
fn override_field_shutdown_timeout() {
let mut settings = Settings::from_default_template().unwrap();
assert_eq!(settings.actix.shutdown_timeout, Timeout::Default);
Settings::override_field(&mut settings.actix.shutdown_timeout, "42 seconds").unwrap();
assert_eq!(settings.actix.shutdown_timeout, Timeout::Seconds(42));
}
#[test]
fn override_field_with_env_var_shutdown_timeout() {
let mut settings = Settings::from_default_template().unwrap();
assert_eq!(settings.actix.shutdown_timeout, Timeout::Default);
std::env::set_var("OVERRIDE__SHUTDOWN_TIMEOUT", "42 seconds");
Settings::override_field_with_env_var(
&mut settings.actix.shutdown_timeout,
"OVERRIDE__SHUTDOWN_TIMEOUT",
)
.unwrap();
assert_eq!(settings.actix.shutdown_timeout, Timeout::Seconds(42));
}
#[test]
fn override_field_tls_enabled() {
let mut settings = Settings::from_default_template().unwrap();
assert!(!settings.actix.tls.enabled);
Settings::override_field(&mut settings.actix.tls.enabled, "true").unwrap();
assert!(settings.actix.tls.enabled);
}
#[test]
fn override_field_with_env_var_tls_enabled() {
let mut settings = Settings::from_default_template().unwrap();
assert!(!settings.actix.tls.enabled);
std::env::set_var("OVERRIDE__TLS_ENABLED", "true");
Settings::override_field_with_env_var(
&mut settings.actix.tls.enabled,
"OVERRIDE__TLS_ENABLED",
)
.unwrap();
assert!(settings.actix.tls.enabled);
}
#[test]
fn override_field_tls_certificate() {
let mut settings = Settings::from_default_template().unwrap();
assert_eq!(
settings.actix.tls.certificate,
Path::new("path/to/cert/cert.pem")
);
Settings::override_field(
&mut settings.actix.tls.certificate,
"/overridden/path/to/cert/cert.pem",
)
.unwrap();
assert_eq!(
settings.actix.tls.certificate,
Path::new("/overridden/path/to/cert/cert.pem")
);
}
#[test]
fn override_field_with_env_var_tls_certificate() {
let mut settings = Settings::from_default_template().unwrap();
assert_eq!(
settings.actix.tls.certificate,
Path::new("path/to/cert/cert.pem")
);
std::env::set_var(
"OVERRIDE__TLS_CERTIFICATE",
"/overridden/path/to/cert/cert.pem",
);
Settings::override_field_with_env_var(
&mut settings.actix.tls.certificate,
"OVERRIDE__TLS_CERTIFICATE",
)
.unwrap();
assert_eq!(
settings.actix.tls.certificate,
Path::new("/overridden/path/to/cert/cert.pem")
);
}
#[test]
fn override_field_tls_private_key() {
let mut settings = Settings::from_default_template().unwrap();
assert_eq!(
settings.actix.tls.private_key,
Path::new("path/to/cert/key.pem")
);
Settings::override_field(
&mut settings.actix.tls.private_key,
"/overridden/path/to/cert/key.pem",
)
.unwrap();
assert_eq!(
settings.actix.tls.private_key,
Path::new("/overridden/path/to/cert/key.pem")
);
}
#[test]
fn override_field_with_env_var_tls_private_key() {
let mut settings = Settings::from_default_template().unwrap();
assert_eq!(
settings.actix.tls.private_key,
Path::new("path/to/cert/key.pem")
);
std::env::set_var(
"OVERRIDE__TLS_PRIVATE_KEY",
"/overridden/path/to/cert/key.pem",
);
Settings::override_field_with_env_var(
&mut settings.actix.tls.private_key,
"OVERRIDE__TLS_PRIVATE_KEY",
)
.unwrap();
assert_eq!(
settings.actix.tls.private_key,
Path::new("/overridden/path/to/cert/key.pem")
);
}
#[test]
fn override_extended_field_with_custom_type() {
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
struct NestedSetting {
foo: String,
bar: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "kebab-case")]
struct AppSettings {
example_name: String,
nested_field: NestedSetting,
}
type CustomSettings = BasicSettings<AppSettings>;
let mut settings = CustomSettings::from_template(
&(CustomSettings::DEFAULT_TOML_TEMPLATE.to_string()
// NOTE: Add these entries to the `[application]` table:
+ "\nexample-name = \"example value\""
+ "\nnested-field = { foo = \"foo\", bar = false }"),
)
.unwrap();
assert_eq!(
settings.application,
AppSettings {
example_name: "example value".into(),
nested_field: NestedSetting {
foo: "foo".into(),
bar: false,
},
}
);
CustomSettings::override_field(
&mut settings.application.example_name,
"/overridden/path/to/cert/key.pem",
)
.unwrap();
assert_eq!(
settings.application,
AppSettings {
example_name: "/overridden/path/to/cert/key.pem".into(),
nested_field: NestedSetting {
foo: "foo".into(),
bar: false,
},
}
);
}
}