mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
Fix: Environment variables do not overwrite Config.toml options (#2433)
* Fix environment variable parsing * Fix failing tests dfgdfgfd dsf * Add new test
This commit is contained in:
parent
642504f2ba
commit
d528cbd828
4 changed files with 172 additions and 67 deletions
|
@ -10,7 +10,7 @@ readme = "../README.md"
|
||||||
rust-version.workspace = true
|
rust-version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
config = { version = "0.14", default-features = false, features = ["toml"] }
|
config = { version = "0.14", default-features = false, features = ["toml", "convert-case"] }
|
||||||
regex = "1.7.0"
|
regex = "1.7.0"
|
||||||
serde = { version = "1.0.151", features = ["derive"] }
|
serde = { version = "1.0.151", features = ["derive"] }
|
||||||
thiserror = "1.0.38"
|
thiserror = "1.0.38"
|
||||||
|
@ -19,3 +19,4 @@ typed-builder = "0.18"
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { version = "1", features = ["rt", "macros"] }
|
tokio = { version = "1", features = ["rt", "macros"] }
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
|
temp-env = { version = "0.3.6", features = ["async_closure"] }
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
|
|
||||||
use crate::errors::LeptosConfigError;
|
use crate::errors::LeptosConfigError;
|
||||||
use config::{Config, File, FileFormat};
|
use config::{Case, Config, File, FileFormat};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::{
|
use std::{
|
||||||
convert::TryFrom, env::VarError, fs, net::SocketAddr, path::Path,
|
convert::TryFrom, env::VarError, fs, net::SocketAddr, path::Path,
|
||||||
|
@ -300,7 +300,9 @@ impl TryFrom<String> for ReloadWSProtocol {
|
||||||
|
|
||||||
/// Loads [LeptosOptions] from a Cargo.toml text content with layered overrides.
|
/// Loads [LeptosOptions] from a Cargo.toml text content with layered overrides.
|
||||||
/// If an env var is specified, like `LEPTOS_ENV`, it will override a setting in the file.
|
/// If an env var is specified, like `LEPTOS_ENV`, it will override a setting in the file.
|
||||||
pub fn get_config_from_str(text: &str) -> Result<ConfFile, LeptosConfigError> {
|
pub fn get_config_from_str(
|
||||||
|
text: &str,
|
||||||
|
) -> Result<LeptosOptions, LeptosConfigError> {
|
||||||
let re: Regex = Regex::new(r"(?m)^\[package.metadata.leptos\]").unwrap();
|
let re: Regex = Regex::new(r"(?m)^\[package.metadata.leptos\]").unwrap();
|
||||||
let re_workspace: Regex =
|
let re_workspace: Regex =
|
||||||
Regex::new(r"(?m)^\[\[workspace.metadata.leptos\]\]").unwrap();
|
Regex::new(r"(?m)^\[\[workspace.metadata.leptos\]\]").unwrap();
|
||||||
|
@ -324,14 +326,18 @@ pub fn get_config_from_str(text: &str) -> Result<ConfFile, LeptosConfigError> {
|
||||||
// so that serde error messages have right line number
|
// so that serde error messages have right line number
|
||||||
let newlines = text[..start].matches('\n').count();
|
let newlines = text[..start].matches('\n').count();
|
||||||
let input = "\n".repeat(newlines) + &text[start..];
|
let input = "\n".repeat(newlines) + &text[start..];
|
||||||
let toml = input.replace(metadata_name, "[leptos-options]");
|
// so the settings will be interpreted as root level settings
|
||||||
|
let toml = input.replace(metadata_name, "");
|
||||||
let settings = Config::builder()
|
let settings = Config::builder()
|
||||||
// Read the "default" configuration file
|
// Read the "default" configuration file
|
||||||
.add_source(File::from_str(&toml, FileFormat::Toml))
|
.add_source(File::from_str(&toml, FileFormat::Toml))
|
||||||
// Layer on the environment-specific values.
|
// Layer on the environment-specific values.
|
||||||
// Add in settings from environment variables (with a prefix of LEPTOS and '_' as separator)
|
// Add in settings from environment variables (with a prefix of LEPTOS)
|
||||||
// E.g. `LEPTOS_RELOAD_PORT=5001 would set `LeptosOptions.reload_port`
|
// E.g. `LEPTOS_RELOAD_PORT=5001 would set `LeptosOptions.reload_port`
|
||||||
.add_source(config::Environment::with_prefix("LEPTOS").separator("_"))
|
.add_source(
|
||||||
|
config::Environment::with_prefix("LEPTOS")
|
||||||
|
.convert_case(Case::Kebab),
|
||||||
|
)
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
settings
|
settings
|
||||||
|
@ -361,7 +367,8 @@ pub async fn get_config_from_file<P: AsRef<Path>>(
|
||||||
) -> Result<ConfFile, LeptosConfigError> {
|
) -> Result<ConfFile, LeptosConfigError> {
|
||||||
let text = fs::read_to_string(path)
|
let text = fs::read_to_string(path)
|
||||||
.map_err(|_| LeptosConfigError::ConfigNotFound)?;
|
.map_err(|_| LeptosConfigError::ConfigNotFound)?;
|
||||||
get_config_from_str(&text)
|
let leptos_options = get_config_from_str(&text)?;
|
||||||
|
Ok(ConfFile { leptos_options })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads [LeptosOptions] from environment variables or rely on the defaults
|
/// Loads [LeptosOptions] from environment variables or rely on the defaults
|
||||||
|
|
|
@ -30,44 +30,53 @@ fn ws_from_str_test() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn env_w_default_test() {
|
fn env_w_default_test() {
|
||||||
std::env::set_var("LEPTOS_CONFIG_ENV_TEST", "custom");
|
_ = temp_env::with_var("LEPTOS_CONFIG_ENV_TEST", Some("custom"), || {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
env_w_default("LEPTOS_CONFIG_ENV_TEST", "default").unwrap(),
|
env_w_default("LEPTOS_CONFIG_ENV_TEST", "default").unwrap(),
|
||||||
String::from("custom")
|
String::from("custom")
|
||||||
);
|
);
|
||||||
std::env::remove_var("LEPTOS_CONFIG_ENV_TEST");
|
});
|
||||||
assert_eq!(
|
|
||||||
env_w_default("LEPTOS_CONFIG_ENV_TEST", "default").unwrap(),
|
_ = temp_env::with_var_unset("LEPTOS_CONFIG_ENV_TEST", || {
|
||||||
String::from("default")
|
assert_eq!(
|
||||||
);
|
env_w_default("LEPTOS_CONFIG_ENV_TEST", "default").unwrap(),
|
||||||
|
String::from("default")
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn env_wo_default_test() {
|
fn env_wo_default_test() {
|
||||||
std::env::set_var("LEPTOS_CONFIG_ENV_TEST", "custom");
|
_ = temp_env::with_var("LEPTOS_CONFIG_ENV_TEST", Some("custom"), || {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
env_wo_default("LEPTOS_CONFIG_ENV_TEST").unwrap(),
|
env_wo_default("LEPTOS_CONFIG_ENV_TEST").unwrap(),
|
||||||
Some(String::from("custom"))
|
Some(String::from("custom"))
|
||||||
);
|
);
|
||||||
std::env::remove_var("LEPTOS_CONFIG_ENV_TEST");
|
});
|
||||||
assert_eq!(env_wo_default("LEPTOS_CONFIG_ENV_TEST").unwrap(), None);
|
|
||||||
|
_ = temp_env::with_var_unset("LEPTOS_CONFIG_ENV_TEST", || {
|
||||||
|
assert_eq!(env_wo_default("LEPTOS_CONFIG_ENV_TEST").unwrap(), None);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn try_from_env_test() {
|
fn try_from_env_test() {
|
||||||
// Test config values from environment variables
|
// Test config values from environment variables
|
||||||
std::env::set_var("LEPTOS_OUTPUT_NAME", "app_test");
|
let config = temp_env::with_vars(
|
||||||
std::env::set_var("LEPTOS_SITE_ROOT", "my_target/site");
|
[
|
||||||
std::env::set_var("LEPTOS_SITE_PKG_DIR", "my_pkg");
|
("LEPTOS_OUTPUT_NAME", Some("app_test")),
|
||||||
std::env::set_var("LEPTOS_SITE_ADDR", "0.0.0.0:80");
|
("LEPTOS_SITE_ROOT", Some("my_target/site")),
|
||||||
std::env::set_var("LEPTOS_RELOAD_PORT", "8080");
|
("LEPTOS_SITE_PKG_DIR", Some("my_pkg")),
|
||||||
std::env::set_var("LEPTOS_RELOAD_EXTERNAL_PORT", "8080");
|
("LEPTOS_SITE_ADDR", Some("0.0.0.0:80")),
|
||||||
std::env::set_var("LEPTOS_ENV", "PROD");
|
("LEPTOS_RELOAD_PORT", Some("8080")),
|
||||||
std::env::set_var("LEPTOS_RELOAD_WS_PROTOCOL", "WSS");
|
("LEPTOS_RELOAD_EXTERNAL_PORT", Some("8080")),
|
||||||
|
("LEPTOS_ENV", Some("PROD")),
|
||||||
|
("LEPTOS_RELOAD_WS_PROTOCOL", Some("WSS")),
|
||||||
|
],
|
||||||
|
|| LeptosOptions::try_from_env().unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
let config = LeptosOptions::try_from_env().unwrap();
|
|
||||||
assert_eq!(config.output_name, "app_test");
|
assert_eq!(config.output_name, "app_test");
|
||||||
|
|
||||||
assert_eq!(config.site_root, "my_target/site");
|
assert_eq!(config.site_root, "my_target/site");
|
||||||
assert_eq!(config.site_pkg_dir, "my_pkg");
|
assert_eq!(config.site_pkg_dir, "my_pkg");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
@ -23,13 +23,7 @@ env = "PROD"
|
||||||
|
|
||||||
const CARGO_TOML_CONTENT_ERR: &str = r#"\
|
const CARGO_TOML_CONTENT_ERR: &str = r#"\
|
||||||
[package.metadata.leptos]
|
[package.metadata.leptos]
|
||||||
_output-name = "app-test"
|
- invalid toml -
|
||||||
_site-root = "my_target/site"
|
|
||||||
_site-pkg-dir = "my_pkg"
|
|
||||||
_site-addr = "0.0.0.0:80"
|
|
||||||
_reload-port = "8080"
|
|
||||||
_reload-external-port = "8080"
|
|
||||||
_env = "PROD"
|
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -43,10 +37,23 @@ async fn get_configuration_from_file_ok() {
|
||||||
let path: &Path = cargo_tmp.as_ref();
|
let path: &Path = cargo_tmp.as_ref();
|
||||||
let path_s = path.to_string_lossy().to_string();
|
let path_s = path.to_string_lossy().to_string();
|
||||||
|
|
||||||
let config = get_configuration(Some(&path_s))
|
let config = temp_env::async_with_vars(
|
||||||
.await
|
[
|
||||||
.unwrap()
|
("LEPTOS_OUTPUT_NAME", None::<&str>),
|
||||||
.leptos_options;
|
("LEPTOS_SITE_ROOT", None::<&str>),
|
||||||
|
("LEPTOS_SITE_PKG_DIR", None::<&str>),
|
||||||
|
("LEPTOS_SITE_ADDR", None::<&str>),
|
||||||
|
("LEPTOS_RELOAD_PORT", None::<&str>),
|
||||||
|
("LEPTOS_RELOAD_EXTERNAL_PORT", None::<&str>),
|
||||||
|
],
|
||||||
|
async {
|
||||||
|
get_configuration(Some(&path_s))
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.leptos_options
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
assert_eq!(config.output_name, "app-test");
|
assert_eq!(config.output_name, "app-test");
|
||||||
assert_eq!(config.site_root, "my_target/site");
|
assert_eq!(config.site_root, "my_target/site");
|
||||||
|
@ -91,10 +98,23 @@ async fn get_config_from_file_ok() {
|
||||||
write!(output, "{CARGO_TOML_CONTENT_OK}").unwrap();
|
write!(output, "{CARGO_TOML_CONTENT_OK}").unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let config = get_config_from_file(&cargo_tmp)
|
let config = temp_env::async_with_vars(
|
||||||
.await
|
[
|
||||||
.unwrap()
|
("LEPTOS_OUTPUT_NAME", None::<&str>),
|
||||||
.leptos_options;
|
("LEPTOS_SITE_ROOT", None::<&str>),
|
||||||
|
("LEPTOS_SITE_PKG_DIR", None::<&str>),
|
||||||
|
("LEPTOS_SITE_ADDR", None::<&str>),
|
||||||
|
("LEPTOS_RELOAD_PORT", None::<&str>),
|
||||||
|
("LEPTOS_RELOAD_EXTERNAL_PORT", None::<&str>),
|
||||||
|
],
|
||||||
|
async {
|
||||||
|
get_config_from_file(&cargo_tmp)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.leptos_options
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
assert_eq!(config.output_name, "app-test");
|
assert_eq!(config.output_name, "app-test");
|
||||||
assert_eq!(config.site_root, "my_target/site");
|
assert_eq!(config.site_root, "my_target/site");
|
||||||
|
@ -129,9 +149,18 @@ async fn get_config_from_file_empty() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn get_config_from_str_content() {
|
fn get_config_from_str_content() {
|
||||||
let config = get_config_from_str(CARGO_TOML_CONTENT_OK)
|
let config = temp_env::with_vars_unset(
|
||||||
.unwrap()
|
[
|
||||||
.leptos_options;
|
"LEPTOS_OUTPUT_NAME",
|
||||||
|
"LEPTOS_SITE_ROOT",
|
||||||
|
"LEPTOS_SITE_PKG_DIR",
|
||||||
|
"LEPTOS_SITE_ADDR",
|
||||||
|
"LEPTOS_RELOAD_PORT",
|
||||||
|
"LEPTOS_RELOAD_EXTERNAL_PORT",
|
||||||
|
],
|
||||||
|
|| get_config_from_str(CARGO_TOML_CONTENT_OK).unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(config.output_name, "app-test");
|
assert_eq!(config.output_name, "app-test");
|
||||||
assert_eq!(config.site_root, "my_target/site");
|
assert_eq!(config.site_root, "my_target/site");
|
||||||
assert_eq!(config.site_pkg_dir, "my_pkg");
|
assert_eq!(config.site_pkg_dir, "my_pkg");
|
||||||
|
@ -146,16 +175,20 @@ fn get_config_from_str_content() {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn get_config_from_env() {
|
async fn get_config_from_env() {
|
||||||
// Test config values from environment variables
|
// Test config values from environment variables
|
||||||
std::env::set_var("LEPTOS_OUTPUT_NAME", "app-test");
|
let config = temp_env::async_with_vars(
|
||||||
std::env::set_var("LEPTOS_SITE_ROOT", "my_target/site");
|
[
|
||||||
std::env::set_var("LEPTOS_SITE_PKG_DIR", "my_pkg");
|
("LEPTOS_OUTPUT_NAME", Some("app-test")),
|
||||||
std::env::set_var("LEPTOS_SITE_ADDR", "0.0.0.0:80");
|
("LEPTOS_SITE_ROOT", Some("my_target/site")),
|
||||||
std::env::set_var("LEPTOS_RELOAD_PORT", "8080");
|
("LEPTOS_SITE_PKG_DIR", Some("my_pkg")),
|
||||||
std::env::set_var("LEPTOS_RELOAD_EXTERNAL_PORT", "8080");
|
("LEPTOS_SITE_ADDR", Some("0.0.0.0:80")),
|
||||||
|
("LEPTOS_RELOAD_PORT", Some("8080")),
|
||||||
|
("LEPTOS_RELOAD_EXTERNAL_PORT", Some("8080")),
|
||||||
|
],
|
||||||
|
async { get_configuration(None).await.unwrap().leptos_options },
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
let config = get_configuration(None).await.unwrap().leptos_options;
|
|
||||||
assert_eq!(config.output_name, "app-test");
|
assert_eq!(config.output_name, "app-test");
|
||||||
|
|
||||||
assert_eq!(config.site_root, "my_target/site");
|
assert_eq!(config.site_root, "my_target/site");
|
||||||
assert_eq!(config.site_pkg_dir, "my_pkg");
|
assert_eq!(config.site_pkg_dir, "my_pkg");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -166,13 +199,19 @@ async fn get_config_from_env() {
|
||||||
assert_eq!(config.reload_external_port, Some(8080));
|
assert_eq!(config.reload_external_port, Some(8080));
|
||||||
|
|
||||||
// Test default config values
|
// Test default config values
|
||||||
std::env::remove_var("LEPTOS_SITE_ROOT");
|
let config = temp_env::async_with_vars(
|
||||||
std::env::remove_var("LEPTOS_SITE_PKG_DIR");
|
[
|
||||||
std::env::remove_var("LEPTOS_SITE_ADDR");
|
("LEPTOS_OUTPUT_NAME", None::<&str>),
|
||||||
std::env::remove_var("LEPTOS_RELOAD_PORT");
|
("LEPTOS_SITE_ROOT", None::<&str>),
|
||||||
std::env::set_var("LEPTOS_RELOAD_EXTERNAL_PORT", "443");
|
("LEPTOS_SITE_PKG_DIR", None::<&str>),
|
||||||
|
("LEPTOS_SITE_ADDR", None::<&str>),
|
||||||
|
("LEPTOS_RELOAD_PORT", None::<&str>),
|
||||||
|
("LEPTOS_RELOAD_EXTERNAL_PORT", None::<&str>),
|
||||||
|
],
|
||||||
|
async { get_configuration(None).await.unwrap().leptos_options },
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
let config = get_configuration(None).await.unwrap().leptos_options;
|
|
||||||
assert_eq!(config.site_root, "target/site");
|
assert_eq!(config.site_root, "target/site");
|
||||||
assert_eq!(config.site_pkg_dir, "pkg");
|
assert_eq!(config.site_pkg_dir, "pkg");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -180,7 +219,7 @@ async fn get_config_from_env() {
|
||||||
SocketAddr::from_str("127.0.0.1:3000").unwrap()
|
SocketAddr::from_str("127.0.0.1:3000").unwrap()
|
||||||
);
|
);
|
||||||
assert_eq!(config.reload_port, 3001);
|
assert_eq!(config.reload_port, 3001);
|
||||||
assert_eq!(config.reload_external_port, Some(443));
|
assert_eq!(config.reload_external_port, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -197,3 +236,52 @@ fn leptos_options_builder_default() {
|
||||||
assert_eq!(conf.reload_port, 3001);
|
assert_eq!(conf.reload_port, 3001);
|
||||||
assert_eq!(conf.reload_external_port, None);
|
assert_eq!(conf.reload_external_port, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn environment_variable_override() {
|
||||||
|
// first check without variables set
|
||||||
|
let config = temp_env::with_vars_unset(
|
||||||
|
[
|
||||||
|
"LEPTOS_OUTPUT_NAME",
|
||||||
|
"LEPTOS_SITE_ROOT",
|
||||||
|
"LEPTOS_SITE_PKG_DIR",
|
||||||
|
"LEPTOS_SITE_ADDR",
|
||||||
|
"LEPTOS_RELOAD_PORT",
|
||||||
|
"LEPTOS_RELOAD_EXTERNAL_PORT",
|
||||||
|
],
|
||||||
|
|| get_config_from_str(CARGO_TOML_CONTENT_OK).unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(config.output_name, "app-test");
|
||||||
|
assert_eq!(config.site_root, "my_target/site");
|
||||||
|
assert_eq!(config.site_pkg_dir, "my_pkg");
|
||||||
|
assert_eq!(
|
||||||
|
config.site_addr,
|
||||||
|
SocketAddr::from_str("0.0.0.0:80").unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(config.reload_port, 8080);
|
||||||
|
assert_eq!(config.reload_external_port, Some(8080));
|
||||||
|
|
||||||
|
// check the override
|
||||||
|
let config = temp_env::with_vars(
|
||||||
|
[
|
||||||
|
("LEPTOS_OUTPUT_NAME", Some("app-test2")),
|
||||||
|
("LEPTOS_SITE_ROOT", Some("my_target/site2")),
|
||||||
|
("LEPTOS_SITE_PKG_DIR", Some("my_pkg2")),
|
||||||
|
("LEPTOS_SITE_ADDR", Some("0.0.0.0:82")),
|
||||||
|
("LEPTOS_RELOAD_PORT", Some("8082")),
|
||||||
|
("LEPTOS_RELOAD_EXTERNAL_PORT", Some("8082")),
|
||||||
|
],
|
||||||
|
|| get_config_from_str(CARGO_TOML_CONTENT_OK).unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(config.output_name, "app-test2");
|
||||||
|
assert_eq!(config.site_root, "my_target/site2");
|
||||||
|
assert_eq!(config.site_pkg_dir, "my_pkg2");
|
||||||
|
assert_eq!(
|
||||||
|
config.site_addr,
|
||||||
|
SocketAddr::from_str("0.0.0.0:82").unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(config.reload_port, 8082);
|
||||||
|
assert_eq!(config.reload_external_port, Some(8082));
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue