[feat](trx-config): warn about deprecated configuration keys
Several keys quietly stopped doing what they look like they do, and nothing said so: [remote] and the flat per-rig sections are ignored outright once [[remotes]] / [[rigs]] exist, [frontends.rigctl].port and --rigctl-port have been dead since rig_ports replaced them, [frontends.audio].rig_ports is superseded by rig_urls, and default_rig_id was renamed to default_rig_name. Warn once at load, naming the replacement. Defaults are indistinguishable from explicit values after deserialization, so the loader now records which key paths the file actually set and the checks work off that — no warning for a setting the user never wrote. The single-rig flat layout is not deprecated: it is the documented simple form, and only draws a warning when [[rigs]] is silently shadowing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SyX26FCpMQxiBoC7r5K1A7 Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -20,6 +20,7 @@ use tokio::task::JoinHandle;
|
|||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
|
|
||||||
use trx_app::{init_logging, normalize_name};
|
use trx_app::{init_logging, normalize_name};
|
||||||
|
use trx_config::ConfigFile;
|
||||||
use trx_core::audio::AudioStreamInfo;
|
use trx_core::audio::AudioStreamInfo;
|
||||||
|
|
||||||
use trx_core::decode::DecodedMessage;
|
use trx_core::decode::DecodedMessage;
|
||||||
@@ -80,7 +81,7 @@ struct Cli {
|
|||||||
/// rigctl frontend listen address
|
/// rigctl frontend listen address
|
||||||
#[arg(long = "rigctl-listen")]
|
#[arg(long = "rigctl-listen")]
|
||||||
rigctl_listen: Option<IpAddr>,
|
rigctl_listen: Option<IpAddr>,
|
||||||
/// rigctl frontend listen port
|
/// Deprecated: ignored, use [frontends.rigctl].rig_ports
|
||||||
#[arg(long = "rigctl-port")]
|
#[arg(long = "rigctl-port")]
|
||||||
rigctl_port: Option<u16>,
|
rigctl_port: Option<u16>,
|
||||||
/// JSON TCP frontend listen address
|
/// JSON TCP frontend listen address
|
||||||
@@ -127,6 +128,7 @@ fn check_config(loaded: &trx_config::ConfigLoad<ClientConfig>) -> DynResult<()>
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut warnings: Vec<String> = loaded.unknown_keys.iter().map(|k| k.to_string()).collect();
|
let mut warnings: Vec<String> = loaded.unknown_keys.iter().map(|k| k.to_string()).collect();
|
||||||
|
warnings.extend(ClientConfig::deprecations(&loaded.present_keys));
|
||||||
|
|
||||||
let mut cfg = loaded.config.clone();
|
let mut cfg = loaded.config.clone();
|
||||||
let mut errors = Vec::new();
|
let mut errors = Vec::new();
|
||||||
@@ -223,6 +225,13 @@ async fn async_init() -> DynResult<AppState> {
|
|||||||
info!("Loaded configuration from {}", path.display());
|
info!("Loaded configuration from {}", path.display());
|
||||||
}
|
}
|
||||||
loaded.report_unknown_keys(cli.strict_config)?;
|
loaded.report_unknown_keys(cli.strict_config)?;
|
||||||
|
loaded.report_deprecations();
|
||||||
|
if cli.rigctl_port.is_some() {
|
||||||
|
tracing::warn!(
|
||||||
|
"--rigctl-port is ignored; give each rig its own listener via \
|
||||||
|
[frontends.rigctl].rig_ports"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let mut cfg = loaded.config;
|
let mut cfg = loaded.config;
|
||||||
// Secrets configured as *_file are read before validation, so everything
|
// Secrets configured as *_file are read before validation, so everything
|
||||||
|
|||||||
@@ -935,6 +935,51 @@ impl ConfigFile for ClientConfig {
|
|||||||
"trx-client"
|
"trx-client"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Keys that no longer do what they look like they do.
|
||||||
|
fn deprecations(present: &std::collections::BTreeSet<String>) -> Vec<String> {
|
||||||
|
let mut warnings = Vec::new();
|
||||||
|
|
||||||
|
if present.contains("remote") {
|
||||||
|
if present.contains("remotes") {
|
||||||
|
warnings.push(
|
||||||
|
"[remote] is ignored because [[remotes]] is set; delete it".to_string(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
warnings.push(
|
||||||
|
"[remote] is superseded by [[remotes]], which supports several rigs; \
|
||||||
|
it still works but will be removed in a future release"
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if present.contains("frontends.rigctl.port") {
|
||||||
|
warnings.push(
|
||||||
|
"[frontends.rigctl].port is ignored; give each rig its own listener via \
|
||||||
|
[frontends.rigctl].rig_ports"
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if present.contains("frontends.audio.rig_ports") {
|
||||||
|
warnings.push(
|
||||||
|
"[frontends.audio].rig_ports is superseded by [frontends.audio].rig_urls, \
|
||||||
|
which can also point at a different host"
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if present.contains("frontends.http.default_rig_id") {
|
||||||
|
warnings.push(
|
||||||
|
"[frontends.http].default_rig_id has been renamed to default_rig_name; \
|
||||||
|
the old name still works but will be removed in a future release"
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
warnings
|
||||||
|
}
|
||||||
|
|
||||||
/// Include one `[[remotes]]` entry so unknown keys nested inside a remote
|
/// Include one `[[remotes]]` entry so unknown keys nested inside a remote
|
||||||
/// entry still get a suggestion.
|
/// entry still get a suggestion.
|
||||||
fn reference_value() -> toml::Value {
|
fn reference_value() -> toml::Value {
|
||||||
@@ -955,6 +1000,7 @@ impl ConfigFile for ClientConfig {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::ConfigFile;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_default_config() {
|
fn test_default_config() {
|
||||||
@@ -1404,6 +1450,51 @@ url = "remote.example.com:4530"
|
|||||||
.contains("poll_interval_ms must be > 0"));
|
.contains("poll_interval_ms must be > 0"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Deprecation warnings ---
|
||||||
|
|
||||||
|
fn present(paths: &[&str]) -> std::collections::BTreeSet<String> {
|
||||||
|
paths.iter().map(|p| p.to_string()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deprecation_flags_shadowed_remote_section() {
|
||||||
|
let warnings = ClientConfig::deprecations(&present(&["remote", "remotes"]));
|
||||||
|
assert!(
|
||||||
|
warnings.iter().any(|w| w.contains("[remote] is ignored")),
|
||||||
|
"unexpected warnings: {warnings:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deprecation_nudges_lone_remote_section() {
|
||||||
|
let warnings = ClientConfig::deprecations(&present(&["remote"]));
|
||||||
|
assert_eq!(warnings.len(), 1);
|
||||||
|
assert!(warnings[0].contains("superseded by [[remotes]]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deprecation_flags_ignored_rigctl_port() {
|
||||||
|
let warnings = ClientConfig::deprecations(&present(&["frontends.rigctl.port"]));
|
||||||
|
assert!(warnings.iter().any(|w| w.contains("rig_ports")));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deprecation_flags_renamed_default_rig_id() {
|
||||||
|
let warnings = ClientConfig::deprecations(&present(&["frontends.http.default_rig_id"]));
|
||||||
|
assert!(warnings.iter().any(|w| w.contains("default_rig_name")));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_no_deprecations_for_a_current_config() {
|
||||||
|
let warnings = ClientConfig::deprecations(&present(&[
|
||||||
|
"remotes",
|
||||||
|
"frontends.http.port",
|
||||||
|
"frontends.rigctl.rig_ports",
|
||||||
|
"frontends.audio.rig_urls",
|
||||||
|
]));
|
||||||
|
assert!(warnings.is_empty(), "unexpected warnings: {warnings:?}");
|
||||||
|
}
|
||||||
|
|
||||||
// --- Secret indirection ---
|
// --- Secret indirection ---
|
||||||
|
|
||||||
fn secret_file(content: &str) -> tempfile::NamedTempFile {
|
fn secret_file(content: &str) -> tempfile::NamedTempFile {
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ use serde::Serialize;
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::unknown::{describe, UnknownKey};
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
|
use crate::unknown::{describe, flatten_paths, UnknownKey};
|
||||||
|
|
||||||
/// Every section key that may appear at the root of a combined config file.
|
/// Every section key that may appear at the root of a combined config file.
|
||||||
pub const SECTION_KEYS: &[&str] = &["trx-server", "trx-client"];
|
pub const SECTION_KEYS: &[&str] = &["trx-server", "trx-client"];
|
||||||
@@ -37,6 +39,18 @@ pub struct ConfigLoad<T> {
|
|||||||
pub path: Option<PathBuf>,
|
pub path: Option<PathBuf>,
|
||||||
/// Keys present in the file that no config field claimed.
|
/// Keys present in the file that no config field claimed.
|
||||||
pub unknown_keys: Vec<UnknownKey>,
|
pub unknown_keys: Vec<UnknownKey>,
|
||||||
|
/// Every key path the file actually set. Defaults are indistinguishable
|
||||||
|
/// from explicit values once deserialized, so deprecation checks need this.
|
||||||
|
pub present_keys: BTreeSet<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ConfigFile> ConfigLoad<T> {
|
||||||
|
/// Log a warning for every deprecated key the file sets.
|
||||||
|
pub fn report_deprecations(&self) {
|
||||||
|
for message in T::deprecations(&self.present_keys) {
|
||||||
|
tracing::warn!("{}", message);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> ConfigLoad<T> {
|
impl<T> ConfigLoad<T> {
|
||||||
@@ -97,10 +111,12 @@ fn select_section(table: &toml::Table, key: &str) -> Option<toml::Value> {
|
|||||||
/// Returns `Ok(Some((cfg, unknown_keys)))` when the section is present and
|
/// Returns `Ok(Some((cfg, unknown_keys)))` when the section is present and
|
||||||
/// parses cleanly, `Ok(None)` when the section is absent, or `Err` on I/O /
|
/// parses cleanly, `Ok(None)` when the section is absent, or `Err` on I/O /
|
||||||
/// parse failure.
|
/// parse failure.
|
||||||
|
type LoadedSection<T> = (T, Vec<UnknownKey>, BTreeSet<String>);
|
||||||
|
|
||||||
fn load_section_from_file<T: ConfigFile>(
|
fn load_section_from_file<T: ConfigFile>(
|
||||||
path: &Path,
|
path: &Path,
|
||||||
key: &str,
|
key: &str,
|
||||||
) -> Result<Option<(T, Vec<UnknownKey>)>, ConfigError> {
|
) -> Result<Option<LoadedSection<T>>, ConfigError> {
|
||||||
let content = std::fs::read_to_string(path)
|
let content = std::fs::read_to_string(path)
|
||||||
.map_err(|e| ConfigError::ReadError(path.to_path_buf(), e.to_string()))?;
|
.map_err(|e| ConfigError::ReadError(path.to_path_buf(), e.to_string()))?;
|
||||||
|
|
||||||
@@ -116,13 +132,19 @@ fn load_section_from_file<T: ConfigFile>(
|
|||||||
crate::secrets::expand_env_vars(&mut section)
|
crate::secrets::expand_env_vars(&mut section)
|
||||||
.map_err(|e| ConfigError::ParseError(path.to_path_buf(), e))?;
|
.map_err(|e| ConfigError::ParseError(path.to_path_buf(), e))?;
|
||||||
|
|
||||||
|
let present_keys = flatten_paths(§ion);
|
||||||
|
|
||||||
// Deserialize straight from the TOML value so serde applies every default,
|
// Deserialize straight from the TOML value so serde applies every default,
|
||||||
// recording any key no field claimed.
|
// recording any key no field claimed.
|
||||||
let mut ignored: Vec<String> = Vec::new();
|
let mut ignored: Vec<String> = Vec::new();
|
||||||
let cfg: T = serde_ignored::deserialize(section, |path| ignored.push(path.to_string()))
|
let cfg: T = serde_ignored::deserialize(section, |path| ignored.push(path.to_string()))
|
||||||
.map_err(|e| ConfigError::ParseError(path.to_path_buf(), e.to_string()))?;
|
.map_err(|e| ConfigError::ParseError(path.to_path_buf(), e.to_string()))?;
|
||||||
|
|
||||||
Ok(Some((cfg, describe(&ignored, &T::reference_value()))))
|
Ok(Some((
|
||||||
|
cfg,
|
||||||
|
describe(&ignored, &T::reference_value()),
|
||||||
|
present_keys,
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait for loading configuration from a `trx-rs.toml` section.
|
/// Trait for loading configuration from a `trx-rs.toml` section.
|
||||||
@@ -130,6 +152,12 @@ pub trait ConfigFile: Sized + Default + DeserializeOwned + Serialize {
|
|||||||
/// Section key in `trx-rs.toml` (e.g. `"trx-server"` or `"trx-client"`).
|
/// Section key in `trx-rs.toml` (e.g. `"trx-server"` or `"trx-client"`).
|
||||||
fn section_key() -> &'static str;
|
fn section_key() -> &'static str;
|
||||||
|
|
||||||
|
/// Warnings for deprecated keys the file sets, given every key path present
|
||||||
|
/// in it. Defaults to none.
|
||||||
|
fn deprecations(_present_keys: &BTreeSet<String>) -> Vec<String> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
|
||||||
/// A TOML rendering of a populated config, used to suggest corrections for
|
/// A TOML rendering of a populated config, used to suggest corrections for
|
||||||
/// unknown keys. Implementations should fill in list-valued sections such
|
/// unknown keys. Implementations should fill in list-valued sections such
|
||||||
/// as `[[rigs]]` so keys nested inside them can be suggested too.
|
/// as `[[rigs]]` so keys nested inside them can be suggested too.
|
||||||
@@ -145,8 +173,8 @@ pub trait ConfigFile: Sized + Default + DeserializeOwned + Serialize {
|
|||||||
/// cannot be read, is not valid TOML, or is sectioned for some other
|
/// cannot be read, is not valid TOML, or is sectioned for some other
|
||||||
/// component only.
|
/// component only.
|
||||||
fn load_from_file(path: &Path) -> Result<ConfigLoad<Self>, ConfigError> {
|
fn load_from_file(path: &Path) -> Result<ConfigLoad<Self>, ConfigError> {
|
||||||
let (config, unknown_keys) = load_section_from_file::<Self>(path, Self::section_key())?
|
let (config, unknown_keys, present_keys) =
|
||||||
.ok_or_else(|| {
|
load_section_from_file::<Self>(path, Self::section_key())?.ok_or_else(|| {
|
||||||
ConfigError::ParseError(
|
ConfigError::ParseError(
|
||||||
path.to_path_buf(),
|
path.to_path_buf(),
|
||||||
format!("missing [{}] section", Self::section_key()),
|
format!("missing [{}] section", Self::section_key()),
|
||||||
@@ -156,6 +184,7 @@ pub trait ConfigFile: Sized + Default + DeserializeOwned + Serialize {
|
|||||||
config,
|
config,
|
||||||
path: Some(path.to_path_buf()),
|
path: Some(path.to_path_buf()),
|
||||||
unknown_keys,
|
unknown_keys,
|
||||||
|
present_keys,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,13 +195,14 @@ pub trait ConfigFile: Sized + Default + DeserializeOwned + Serialize {
|
|||||||
fn load_from_default_paths() -> Result<ConfigLoad<Self>, ConfigError> {
|
fn load_from_default_paths() -> Result<ConfigLoad<Self>, ConfigError> {
|
||||||
for path in config_search_paths() {
|
for path in config_search_paths() {
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
if let Some((config, unknown_keys)) =
|
if let Some((config, unknown_keys, present_keys)) =
|
||||||
load_section_from_file::<Self>(&path, Self::section_key())?
|
load_section_from_file::<Self>(&path, Self::section_key())?
|
||||||
{
|
{
|
||||||
return Ok(ConfigLoad {
|
return Ok(ConfigLoad {
|
||||||
config,
|
config,
|
||||||
path: Some(path),
|
path: Some(path),
|
||||||
unknown_keys,
|
unknown_keys,
|
||||||
|
present_keys,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,6 +211,7 @@ pub trait ConfigFile: Sized + Default + DeserializeOwned + Serialize {
|
|||||||
config: Self::default(),
|
config: Self::default(),
|
||||||
path: None,
|
path: None,
|
||||||
unknown_keys: Vec::new(),
|
unknown_keys: Vec::new(),
|
||||||
|
present_keys: BTreeSet::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1092,6 +1092,33 @@ impl ConfigFile for ServerConfig {
|
|||||||
"trx-server"
|
"trx-server"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The flat per-rig sections are silently ignored once `[[rigs]]` exists,
|
||||||
|
/// which is worth saying out loud.
|
||||||
|
fn deprecations(present: &std::collections::BTreeSet<String>) -> Vec<String> {
|
||||||
|
if !present.contains("rigs") {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
[
|
||||||
|
"rig",
|
||||||
|
"behavior",
|
||||||
|
"audio",
|
||||||
|
"sdr",
|
||||||
|
"pskreporter",
|
||||||
|
"aprsfi",
|
||||||
|
"decode_logs",
|
||||||
|
"decoders",
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.filter(|section| present.contains(**section))
|
||||||
|
.map(|section| {
|
||||||
|
format!(
|
||||||
|
"[{section}] is ignored because [[rigs]] is set; move it to the \
|
||||||
|
matching [rigs.{section}] entry"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
/// Include one `[[rigs]]` entry so unknown keys nested inside a rig entry
|
/// Include one `[[rigs]]` entry so unknown keys nested inside a rig entry
|
||||||
/// still get a suggestion.
|
/// still get a suggestion.
|
||||||
fn reference_value() -> toml::Value {
|
fn reference_value() -> toml::Value {
|
||||||
@@ -1106,6 +1133,7 @@ impl ConfigFile for ServerConfig {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::ConfigFile;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_default_config() {
|
fn test_default_config() {
|
||||||
@@ -1751,6 +1779,28 @@ port = 4531
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Deprecation warnings ---
|
||||||
|
|
||||||
|
fn present(paths: &[&str]) -> std::collections::BTreeSet<String> {
|
||||||
|
paths.iter().map(|p| p.to_string()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deprecation_flags_flat_sections_shadowed_by_rigs() {
|
||||||
|
let warnings = ServerConfig::deprecations(&present(&["rigs", "rig", "audio"]));
|
||||||
|
assert_eq!(warnings.len(), 2, "unexpected warnings: {warnings:?}");
|
||||||
|
assert!(warnings.iter().any(|w| w.contains("[rig] is ignored")));
|
||||||
|
assert!(warnings.iter().any(|w| w.contains("[audio] is ignored")));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_no_deprecation_for_single_rig_layout() {
|
||||||
|
// The flat layout is the documented simple form; only warn when it is
|
||||||
|
// being silently ignored.
|
||||||
|
let warnings = ServerConfig::deprecations(&present(&["rig", "audio", "listen"]));
|
||||||
|
assert!(warnings.is_empty(), "unexpected warnings: {warnings:?}");
|
||||||
|
}
|
||||||
|
|
||||||
// --- Secret indirection ---
|
// --- Secret indirection ---
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ use trx_core::audio::AudioStreamInfo;
|
|||||||
|
|
||||||
use trx_app::{init_logging, normalize_name};
|
use trx_app::{init_logging, normalize_name};
|
||||||
use trx_config::shared::BoundSocket;
|
use trx_config::shared::BoundSocket;
|
||||||
|
use trx_config::ConfigFile;
|
||||||
use trx_backend::{register_builtin_backends_on, RegistrationContext, RigAccess};
|
use trx_backend::{register_builtin_backends_on, RegistrationContext, RigAccess};
|
||||||
use trx_core::rig::controller::{AdaptivePolling, ExponentialBackoff};
|
use trx_core::rig::controller::{AdaptivePolling, ExponentialBackoff};
|
||||||
use trx_core::rig::request::RigRequest;
|
use trx_core::rig::request::RigRequest;
|
||||||
@@ -940,8 +941,10 @@ fn check_config(cli: &Cli, loaded: &trx_config::ConfigLoad<ServerConfig>) -> Dyn
|
|||||||
None => println!("(no config file found; checking built-in defaults)"),
|
None => println!("(no config file found; checking built-in defaults)"),
|
||||||
}
|
}
|
||||||
|
|
||||||
for key in &loaded.unknown_keys {
|
let mut warnings: Vec<String> = loaded.unknown_keys.iter().map(|k| k.to_string()).collect();
|
||||||
println!(" warning: {}", key);
|
warnings.extend(ServerConfig::deprecations(&loaded.present_keys));
|
||||||
|
for warning in &warnings {
|
||||||
|
println!(" warning: {}", warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut cfg = loaded.config.clone();
|
let mut cfg = loaded.config.clone();
|
||||||
@@ -966,17 +969,12 @@ fn check_config(cli: &Cli, loaded: &trx_config::ConfigLoad<ServerConfig>) -> Dyn
|
|||||||
rigs.len(),
|
rigs.len(),
|
||||||
rigs.iter().map(|r| r.id.as_str()).collect::<Vec<_>>().join(", ")
|
rigs.iter().map(|r| r.id.as_str()).collect::<Vec<_>>().join(", ")
|
||||||
);
|
);
|
||||||
if !loaded.unknown_keys.is_empty() {
|
if !warnings.is_empty() {
|
||||||
println!(" {} warning(s)", loaded.unknown_keys.len());
|
println!(" {} warning(s)", warnings.len());
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(format!(
|
Err(format!("{} error(s), {} warning(s)", errors.len(), warnings.len()).into())
|
||||||
"{} error(s), {} warning(s)",
|
|
||||||
errors.len(),
|
|
||||||
loaded.unknown_keys.len()
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1010,6 +1008,7 @@ async fn main() -> DynResult<()> {
|
|||||||
info!("Loaded configuration from {}", path.display());
|
info!("Loaded configuration from {}", path.display());
|
||||||
}
|
}
|
||||||
loaded.report_unknown_keys(cli.strict_config)?;
|
loaded.report_unknown_keys(cli.strict_config)?;
|
||||||
|
loaded.report_deprecations();
|
||||||
|
|
||||||
let mut cfg = loaded.config;
|
let mut cfg = loaded.config;
|
||||||
// Secrets configured as *_file are read before validation, so everything
|
// Secrets configured as *_file are read before validation, so everything
|
||||||
|
|||||||
Reference in New Issue
Block a user