Compare commits
18
Commits
v1.0.2
...
v1.0.4-preview
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4972318aff
|
||
|
|
f029efe315
|
||
|
|
dcf2efbf98
|
||
|
|
2e7d2f98fd
|
||
|
|
469e699074
|
||
|
|
d87ece69da
|
||
|
|
f73ba4b1d6
|
||
|
|
d7845349e8
|
||
|
|
7bea78f194
|
||
|
|
87c1a1c09c
|
||
|
|
d781f3f97b
|
||
|
|
a4f27ce091
|
||
|
|
61b016775e
|
||
|
|
439ccd60d4
|
||
|
|
1c99387d6f
|
||
|
|
bf32d82ae7
|
||
|
|
34f61bba5f
|
||
|
|
d351a8b6be
|
@@ -0,0 +1,9 @@
|
||||
[registry]
|
||||
default = "gitea"
|
||||
|
||||
[registries.gitea]
|
||||
index = "sparse+https://gitea.creative-dragonslayer.de/api/packages/Rust-Crates/cargo/" # Sparse index
|
||||
# index = "https://gitea.creative-dragonslayer.de/Rust-Crates/_cargo-index.git" # Git
|
||||
|
||||
[net]
|
||||
git-fetch-with-cli = true
|
||||
Generated
+1
@@ -3,6 +3,7 @@
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
|
||||
@@ -31,8 +31,10 @@ Config/
|
||||
├── LICENSE # GPL-3.0 Lizenztext
|
||||
├── README.md # Projektdokumentation & Nutzungsbeispiele
|
||||
├── AGENTS.md # Entwickler- und Agenten-Richtlinien
|
||||
└── src/
|
||||
└── lib.rs # Hauptimplementierung (load, store, modify, get_config, Pfadauflösung, Tests)
|
||||
├── src/
|
||||
│ └── lib.rs # Hauptimplementierung (load, store, modify, get_config, Pfadauflösung)
|
||||
└── tests/
|
||||
└── integration_tests.rs # Vollständige Integrationstests
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Generated
+18
-2
@@ -10,11 +10,12 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "config-ctdra"
|
||||
version = "1.0.2"
|
||||
version = "1.0.4"
|
||||
dependencies = [
|
||||
"confy",
|
||||
"libc",
|
||||
"program-ctdra",
|
||||
"serde",
|
||||
"sudo-ctdra",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -93,6 +94,12 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "program-ctdra"
|
||||
version = "1.0.0"
|
||||
source = "sparse+https://gitea.creative-dragonslayer.de/api/packages/Rust-Crates/cargo/"
|
||||
checksum = "54f20af67f90bf6d10697dd77bb4beb41dafad7fe0fb46aacfc81ad45a8e48bd"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.47"
|
||||
@@ -141,6 +148,15 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sudo-ctdra"
|
||||
version = "1.0.0"
|
||||
source = "sparse+https://gitea.creative-dragonslayer.de/api/packages/Rust-Crates/cargo/"
|
||||
checksum = "2c2746122bbe36f821de70db3ca4ad24edfb77118fc6a6bc7693082b50d23e5f"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.4"
|
||||
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "config-ctdra"
|
||||
version = "1.0.2"
|
||||
version = "1.0.4"
|
||||
edition = "2024"
|
||||
authors = ['DragonSlayer_14']
|
||||
readme = "README.md"
|
||||
@@ -10,8 +10,9 @@ description = "Einfache, threadsichere Konfigurationsverwaltung für Rust mit au
|
||||
|
||||
[dependencies]
|
||||
confy = "2.0.0"
|
||||
program-ctdra = { version = "1.0.0", registry = "gitea" }
|
||||
serde = { version = "1.0.229", features = ["derive"] }
|
||||
libc = "1.0.0-alpha.4"
|
||||
sudo-ctdra = { version = "1.0.0", registry = "gitea" }
|
||||
|
||||
[profile.release]
|
||||
debug = "none"
|
||||
|
||||
@@ -208,8 +208,8 @@ If you develop a new program, and you want it to be of the greatest possible use
|
||||
|
||||
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found.
|
||||
|
||||
DockerUpdater
|
||||
Copyright (C) 2026 Linuxapps
|
||||
config
|
||||
Copyright (C) 2026 Rust-Crates
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
|
||||
@@ -221,7 +221,7 @@ Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:
|
||||
|
||||
DockerUpdater Copyright (C) 2026 Linuxapps
|
||||
config Copyright (C) 2026 Rust-Crates
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
- **Einheitliche Pfadauflösung**:
|
||||
- **Root-Modus** (Linux/Unix UID 0): Konfigurationsdateien werden unter `/etc/<programmname>/<config_name>.toml` abgelegt.
|
||||
- **Benutzermodus**: Standardmäßiges Benutzerverzeichnis (z. B. `~/.config/<programmname>/<config_name>.toml` via `confy`).
|
||||
- **Individuell anpassbar**: Programmnamen (`set_program_name`), Dateinamen (`set_config_name`), Verzeichnisse (`set_custom_dir`) oder explizite Dateipfade (`set_custom_path`).
|
||||
- **Individuell anpassbar**: Dateinamen (`set_config_name`), Verzeichnisse (`set_custom_dir`) oder explizite Dateipfade (`set_custom_path`). Der Programmname wird automatisch über `program-ctdra` ermittelt.
|
||||
- **Globales Caching (`get_config::<T>()`)**: Einmaliges Laden und threadsicheres Zwischenspeichern statischer Referenzen pro Konfigurationstyp.
|
||||
- **Atomare Operationen**: `load`, `store`, `modify`, `modify_config` für konsistentes Laden, Bearbeiten und Speichern.
|
||||
|
||||
@@ -97,11 +97,9 @@ impl Default for Database {
|
||||
### 2. Globales Caching mit `get_config`
|
||||
|
||||
```rust
|
||||
use config_ctdra::{get_config, set_program_name};
|
||||
use config_ctdra::get_config;
|
||||
|
||||
fn main() {
|
||||
set_program_name("my-service");
|
||||
|
||||
let cfg = get_config::<AppConfig>();
|
||||
println!("Log-Level: {}", cfg.general.log_level);
|
||||
println!("DB URL: {}", cfg.database.url);
|
||||
|
||||
+8
-226
@@ -5,13 +5,13 @@
|
||||
//! - Einheitliche Pfadermittlung:
|
||||
//! - Bei Ausführung als Root (unter Linux/Unix): `/etc/<program_name>/<config_name>.toml`
|
||||
//! - Bei regulärem Benutzer: Standard-Benutzer-Konfigurationsverzeichnis (z. B. `~/.config/<program_name>/<config_name>.toml`)
|
||||
//! - Anpassbar über `set_program_name`, `set_config_name`, `set_custom_dir` und `set_custom_path`.
|
||||
//! - Anpassbar über `set_config_name`, `set_custom_dir` und `set_custom_path`.
|
||||
//! - Thread-sicheres globales Caching (`get_config::<T>()`).
|
||||
//! - Atomares Laden, Speichern und Modifizieren (`load`, `store`, `modify`, `modify_config`).
|
||||
//!
|
||||
//! # Beispiel
|
||||
//! ```rust
|
||||
//! use config_ctdra::{get_config, modify_config, set_program_name};
|
||||
//! use config_ctdra::{get_config, modify_config};
|
||||
//! use serde::{Deserialize, Serialize};
|
||||
//!
|
||||
//! #[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
|
||||
@@ -20,8 +20,6 @@
|
||||
//! pub enable_ssl: bool,
|
||||
//! }
|
||||
//!
|
||||
//! set_program_name("my-service");
|
||||
//!
|
||||
//! // Konfiguration abrufen (wird beim ersten Aufruf geladen und zwischengespeichert)
|
||||
//! let cfg = get_config::<MyConfig>();
|
||||
//! println!("Port: {}", cfg.server_port);
|
||||
@@ -34,11 +32,10 @@ use std::path::PathBuf;
|
||||
use std::sync::{OnceLock, RwLock};
|
||||
|
||||
pub use confy::ConfyError;
|
||||
use program_ctdra::try_program_name;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
|
||||
/// Optionaler benutzerdefinierter Programmname für die Konfigurationspfade.
|
||||
static PROGRAM_NAME: OnceLock<RwLock<Option<String>>> = OnceLock::new();
|
||||
use sudo_ctdra::is_run_as_root;
|
||||
|
||||
/// Optionaler benutzerdefinierter Konfigurationsdateiname (Standard: "config").
|
||||
static CONFIG_NAME: OnceLock<RwLock<Option<String>>> = OnceLock::new();
|
||||
@@ -56,50 +53,13 @@ static GLOBAL_CONFIGS: OnceLock<RwLock<HashMap<TypeId, &'static (dyn Any + Send
|
||||
/// Standardname für Konfigurationsdateien.
|
||||
const DEFAULT_CONFIG_NAME: &str = "config";
|
||||
|
||||
/// Prüft, ob das Programm mit Root-/Administrator-Rechten ausgeführt wird.
|
||||
/// Ermittelt den Programmnamen über `program-ctdra`.
|
||||
///
|
||||
/// # Returns
|
||||
/// # Panics
|
||||
///
|
||||
/// * `true` - Das Programm läuft mit erhöhten Rechten (z. B. UID 0 unter Linux/Unix)
|
||||
/// * `false` - Das Programm läuft mit normalen Benutzerrechten
|
||||
pub fn is_run_as_root() -> bool {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
std::process::Command::new("net")
|
||||
.args(["session"])
|
||||
.output()
|
||||
.map(|output| output.status.success())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
unsafe { libc::geteuid() == 0 }
|
||||
}
|
||||
}
|
||||
|
||||
/// Setzt den Programmnamen für die Pfadermittlung explizit.
|
||||
pub fn set_program_name(name: impl Into<String>) {
|
||||
let lock = PROGRAM_NAME.get_or_init(|| RwLock::new(None));
|
||||
if let Ok(mut guard) = lock.write() {
|
||||
*guard = Some(name.into());
|
||||
}
|
||||
}
|
||||
|
||||
/// Ermittelt den konfigurierten Programmnamen (aus `set_program_name` oder `std::env::current_exe`).
|
||||
/// Löst eine Panik aus, wenn der Programmname nicht ermittelt werden kann.
|
||||
pub fn get_program_name() -> String {
|
||||
if let Some(lock) = PROGRAM_NAME.get() {
|
||||
if let Ok(guard) = lock.read() {
|
||||
if let Some(name) = guard.as_ref() {
|
||||
return name.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
env::current_exe()
|
||||
.ok()
|
||||
.and_then(|p| p.file_stem().map(|s| s.to_string_lossy().to_string()))
|
||||
.filter(|s| !s.is_empty())
|
||||
.unwrap_or_else(|| "app".to_string())
|
||||
try_program_name().expect("Programmname konnte nicht ermittelt werden")
|
||||
}
|
||||
|
||||
/// Setzt den Namen der Konfigurationsdatei (ohne Dateiendung `.toml`).
|
||||
@@ -323,181 +283,3 @@ where
|
||||
guard.insert(TypeId::of::<T>(), boxed);
|
||||
boxed
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::sync::Mutex;
|
||||
|
||||
static TEST_LOCK: Mutex<()> = Mutex::new(());
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
struct DummyAppConfig {
|
||||
general: DummyGeneral,
|
||||
}
|
||||
|
||||
impl Default for DummyAppConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
general: DummyGeneral::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
struct DummyGeneral {
|
||||
log_level: String,
|
||||
apps_dir: String,
|
||||
}
|
||||
|
||||
impl Default for DummyGeneral {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
log_level: "info".to_string(),
|
||||
apps_dir: "/var/apps".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
struct CustomServerConfig {
|
||||
host: String,
|
||||
port: u16,
|
||||
}
|
||||
|
||||
impl Default for CustomServerConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 8080,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_config_loading() {
|
||||
let _guard = TEST_LOCK.lock().unwrap();
|
||||
let cfg = DummyAppConfig::default();
|
||||
assert_eq!(cfg.general.log_level, "info");
|
||||
assert_eq!(cfg.general.apps_dir, "/var/apps");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_program_and_config_name() {
|
||||
let _guard = TEST_LOCK.lock().unwrap();
|
||||
set_program_name("test-app");
|
||||
assert_eq!(get_program_name(), "test-app");
|
||||
|
||||
set_config_name("settings");
|
||||
assert_eq!(get_config_name(), "settings");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_custom_path_and_dir() {
|
||||
let _guard = TEST_LOCK.lock().unwrap();
|
||||
clear_custom_path();
|
||||
clear_custom_dir();
|
||||
set_config_name("config");
|
||||
|
||||
let temp_dir = env::temp_dir().join("test-config-crate-dir");
|
||||
let _ = fs::create_dir_all(&temp_dir);
|
||||
|
||||
set_custom_dir(&temp_dir);
|
||||
assert_eq!(get_custom_dir(), Some(temp_dir.clone()));
|
||||
|
||||
let expected_path = temp_dir.join(format!("{}.toml", get_config_name()));
|
||||
assert_eq!(get_config_path(), expected_path);
|
||||
|
||||
let explicit_file = temp_dir.join("explicit.toml");
|
||||
set_custom_path(&explicit_file);
|
||||
assert_eq!(get_custom_path(), Some(explicit_file.clone()));
|
||||
assert_eq!(get_config_path(), explicit_file);
|
||||
|
||||
clear_custom_path();
|
||||
clear_custom_dir();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_store_and_load_with_custom_path() {
|
||||
let _guard = TEST_LOCK.lock().unwrap();
|
||||
let temp_file = env::temp_dir().join("test_store_load.toml");
|
||||
let _ = fs::remove_file(&temp_file);
|
||||
|
||||
set_custom_path(&temp_file);
|
||||
|
||||
let initial_cfg = CustomServerConfig {
|
||||
host: "0.0.0.0".to_string(),
|
||||
port: 9000,
|
||||
};
|
||||
|
||||
let store_res = store(&initial_cfg);
|
||||
assert!(store_res.is_ok());
|
||||
|
||||
let loaded_cfg: CustomServerConfig = load().expect("Failed to load stored config");
|
||||
assert_eq!(loaded_cfg, initial_cfg);
|
||||
|
||||
// Test modify
|
||||
let mod_res = modify::<CustomServerConfig, _>(|c| {
|
||||
c.port = 9090;
|
||||
});
|
||||
assert!(mod_res.is_ok());
|
||||
|
||||
let reloaded_cfg: CustomServerConfig = load().expect("Failed to reload modified config");
|
||||
assert_eq!(reloaded_cfg.port, 9090);
|
||||
|
||||
let _ = fs::remove_file(&temp_file);
|
||||
clear_custom_path();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_config_types_global() {
|
||||
let _guard = TEST_LOCK.lock().unwrap();
|
||||
let file_a = env::temp_dir().join("test_global_type_a.toml");
|
||||
let _ = fs::remove_file(&file_a);
|
||||
|
||||
set_custom_path(&file_a);
|
||||
|
||||
let cfg_app: &'static DummyAppConfig = get_config();
|
||||
let cfg_server: &'static CustomServerConfig = get_config();
|
||||
|
||||
assert_eq!(cfg_app.general.apps_dir, "/var/apps");
|
||||
assert_eq!(cfg_server.port, 8080);
|
||||
assert_eq!(cfg_server.host, "127.0.0.1");
|
||||
|
||||
let _ = fs::remove_file(&file_a);
|
||||
clear_custom_path();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_modify_config_convenience() {
|
||||
let _guard = TEST_LOCK.lock().unwrap();
|
||||
let temp_file = env::temp_dir().join("test_modify_convenience.toml");
|
||||
let _ = fs::remove_file(&temp_file);
|
||||
|
||||
set_custom_path(&temp_file);
|
||||
|
||||
let initial = CustomServerConfig {
|
||||
host: "localhost".to_string(),
|
||||
port: 3000,
|
||||
};
|
||||
save_config(initial);
|
||||
|
||||
modify_config::<CustomServerConfig, _>(|c| {
|
||||
c.port = 4000;
|
||||
});
|
||||
|
||||
let loaded = load_config::<CustomServerConfig>();
|
||||
assert_eq!(loaded.port, 4000);
|
||||
assert_eq!(loaded.host, "localhost");
|
||||
|
||||
let _ = fs::remove_file(&temp_file);
|
||||
clear_custom_path();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_run_as_root() {
|
||||
let _ = is_run_as_root();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,404 @@
|
||||
use config_ctdra::{
|
||||
clear_custom_dir, clear_custom_path, get_config, get_config_name, get_config_path,
|
||||
get_custom_dir, get_custom_path, get_program_name, load, load_config, modify, modify_config,
|
||||
save_config, set_config_name, set_custom_dir, set_custom_path, store, ConfyError,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
|
||||
static TEST_LOCK: Mutex<()> = Mutex::new(());
|
||||
|
||||
fn lock_test() -> std::sync::MutexGuard<'static, ()> {
|
||||
TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner())
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
struct DummyAppConfig {
|
||||
general: DummyGeneral,
|
||||
}
|
||||
|
||||
impl Default for DummyAppConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
general: DummyGeneral::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
struct DummyGeneral {
|
||||
log_level: String,
|
||||
apps_dir: String,
|
||||
}
|
||||
|
||||
impl Default for DummyGeneral {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
log_level: "info".to_string(),
|
||||
apps_dir: "/var/apps".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
struct CustomServerConfig {
|
||||
host: String,
|
||||
port: u16,
|
||||
}
|
||||
|
||||
impl Default for CustomServerConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 8080,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
struct DatabaseConfig {
|
||||
url: String,
|
||||
max_connections: u32,
|
||||
}
|
||||
|
||||
impl Default for DatabaseConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
url: "postgres://localhost/db".to_string(),
|
||||
max_connections: 10,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn cleanup_temp_file(path: &PathBuf) {
|
||||
let _ = fs::remove_file(path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_program_name_resolution() {
|
||||
let _guard = lock_test();
|
||||
let name = get_program_name();
|
||||
assert!(!name.is_empty(), "Programmname darf nicht leer sein");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_name_getter_and_setter() {
|
||||
let _guard = lock_test();
|
||||
set_config_name("custom_app_config");
|
||||
assert_eq!(get_config_name(), "custom_app_config");
|
||||
|
||||
set_config_name("config");
|
||||
assert_eq!(get_config_name(), "config");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_custom_dir_and_path_management() {
|
||||
let _guard = lock_test();
|
||||
clear_custom_path();
|
||||
clear_custom_dir();
|
||||
set_config_name("app");
|
||||
|
||||
let temp_dir = env::temp_dir().join("test_custom_dir_crate");
|
||||
let _ = fs::create_dir_all(&temp_dir);
|
||||
|
||||
// 1. Benutzerdefiniertes Verzeichnis
|
||||
set_custom_dir(&temp_dir);
|
||||
assert_eq!(get_custom_dir(), Some(temp_dir.clone()));
|
||||
assert_eq!(get_config_path(), temp_dir.join("app.toml"));
|
||||
|
||||
// 2. Benutzerdefiniertes Verzeichnis mit .toml im Config-Namen
|
||||
set_config_name("app.toml");
|
||||
assert_eq!(get_config_path(), temp_dir.join("app.toml"));
|
||||
set_config_name("app");
|
||||
|
||||
// 3. Expliziter benutzerdefinierter Pfad hat Vorrang vor Verzeichnis
|
||||
let explicit_path = temp_dir.join("explicit_settings.toml");
|
||||
set_custom_path(&explicit_path);
|
||||
assert_eq!(get_custom_path(), Some(explicit_path.clone()));
|
||||
assert_eq!(get_config_path(), explicit_path);
|
||||
|
||||
// 4. Zurücksetzen
|
||||
clear_custom_path();
|
||||
assert_eq!(get_custom_path(), None);
|
||||
assert_eq!(get_config_path(), temp_dir.join("app.toml"));
|
||||
|
||||
clear_custom_dir();
|
||||
assert_eq!(get_custom_dir(), None);
|
||||
set_config_name("config");
|
||||
let _ = fs::remove_dir_all(&temp_dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_config_path_resolution() {
|
||||
let _guard = lock_test();
|
||||
clear_custom_path();
|
||||
clear_custom_dir();
|
||||
set_config_name("config");
|
||||
|
||||
let path = get_config_path();
|
||||
let prog_name = get_program_name();
|
||||
let path_str = path.to_string_lossy();
|
||||
|
||||
assert!(
|
||||
path_str.contains(&prog_name),
|
||||
"Pfad {:?} sollte Programmnamen {} enthalten",
|
||||
path,
|
||||
prog_name
|
||||
);
|
||||
assert!(
|
||||
path_str.ends_with("config.toml"),
|
||||
"Pfad {:?} sollte auf config.toml enden",
|
||||
path
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_config_loading() {
|
||||
let _guard = lock_test();
|
||||
let cfg = DummyAppConfig::default();
|
||||
assert_eq!(cfg.general.log_level, "info");
|
||||
assert_eq!(cfg.general.apps_dir, "/var/apps");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_store_and_load_flow() {
|
||||
let _guard = lock_test();
|
||||
let temp_file = env::temp_dir().join("test_store_and_load_flow.toml");
|
||||
cleanup_temp_file(&temp_file);
|
||||
set_custom_path(&temp_file);
|
||||
|
||||
let initial = CustomServerConfig {
|
||||
host: "192.168.1.100".to_string(),
|
||||
port: 9090,
|
||||
};
|
||||
|
||||
// Speichern
|
||||
let store_result = store(&initial);
|
||||
assert!(store_result.is_ok(), "Store sollte erfolgreich sein");
|
||||
assert!(temp_file.exists(), "Konfigurationsdatei sollte existieren");
|
||||
|
||||
// Laden
|
||||
let loaded: CustomServerConfig = load().expect("Load sollte erfolgreich sein");
|
||||
assert_eq!(loaded, initial);
|
||||
|
||||
cleanup_temp_file(&temp_file);
|
||||
clear_custom_path();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_config_fallback_and_invalid_toml() {
|
||||
let _guard = lock_test();
|
||||
let corrupt_file = env::temp_dir().join("corrupt_config_test.toml");
|
||||
cleanup_temp_file(&corrupt_file);
|
||||
fs::write(&corrupt_file, "INVALID_TOML_CONTENT = [[[[[").unwrap();
|
||||
set_custom_path(&corrupt_file);
|
||||
|
||||
// load::<T>() sollte bei ungültigem TOML fehlschlagen
|
||||
let res: Result<CustomServerConfig, ConfyError> = load();
|
||||
assert!(res.is_err(), "Laden von korruptem TOML sollte mit ConfyError fehlschlagen");
|
||||
|
||||
// load_config::<T>() fällt im Fehlerfall auf Default zurück
|
||||
let loaded_default: CustomServerConfig = load_config();
|
||||
assert_eq!(loaded_default, CustomServerConfig::default());
|
||||
|
||||
cleanup_temp_file(&corrupt_file);
|
||||
clear_custom_path();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_save_config_and_load_config() {
|
||||
let _guard = lock_test();
|
||||
let temp_file = env::temp_dir().join("test_save_config_convenience.toml");
|
||||
cleanup_temp_file(&temp_file);
|
||||
set_custom_path(&temp_file);
|
||||
|
||||
let server_cfg = CustomServerConfig {
|
||||
host: "0.0.0.0".to_string(),
|
||||
port: 443,
|
||||
};
|
||||
|
||||
save_config(server_cfg.clone());
|
||||
assert!(temp_file.exists());
|
||||
|
||||
let reloaded: CustomServerConfig = load_config();
|
||||
assert_eq!(reloaded, server_cfg);
|
||||
|
||||
cleanup_temp_file(&temp_file);
|
||||
clear_custom_path();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_modify_function() {
|
||||
let _guard = lock_test();
|
||||
let temp_file = env::temp_dir().join("test_modify_function.toml");
|
||||
cleanup_temp_file(&temp_file);
|
||||
set_custom_path(&temp_file);
|
||||
|
||||
let initial = CustomServerConfig {
|
||||
host: "10.0.0.1".to_string(),
|
||||
port: 80,
|
||||
};
|
||||
store(&initial).expect("Store initial config failed");
|
||||
|
||||
let modified_result = modify::<CustomServerConfig, _>(|cfg| {
|
||||
cfg.port = 8081;
|
||||
cfg.host = "10.0.0.2".to_string();
|
||||
});
|
||||
|
||||
assert!(modified_result.is_ok());
|
||||
let modified = modified_result.unwrap();
|
||||
assert_eq!(modified.port, 8081);
|
||||
assert_eq!(modified.host, "10.0.0.2");
|
||||
|
||||
let loaded: CustomServerConfig = load().expect("Reload failed");
|
||||
assert_eq!(loaded.port, 8081);
|
||||
assert_eq!(loaded.host, "10.0.0.2");
|
||||
|
||||
cleanup_temp_file(&temp_file);
|
||||
clear_custom_path();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_modify_config_convenience() {
|
||||
let _guard = lock_test();
|
||||
let temp_file = env::temp_dir().join("test_modify_config_convenience.toml");
|
||||
cleanup_temp_file(&temp_file);
|
||||
set_custom_path(&temp_file);
|
||||
|
||||
let initial = DatabaseConfig {
|
||||
url: "sqlite://memory".to_string(),
|
||||
max_connections: 5,
|
||||
};
|
||||
save_config(initial);
|
||||
|
||||
modify_config::<DatabaseConfig, _>(|cfg| {
|
||||
cfg.max_connections = 25;
|
||||
});
|
||||
|
||||
let loaded: DatabaseConfig = load_config();
|
||||
assert_eq!(loaded.max_connections, 25);
|
||||
assert_eq!(loaded.url, "sqlite://memory");
|
||||
|
||||
cleanup_temp_file(&temp_file);
|
||||
clear_custom_path();
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
struct GlobalAppConfig {
|
||||
service_name: String,
|
||||
}
|
||||
|
||||
impl Default for GlobalAppConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
service_name: "global_service".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
struct GlobalServerConfig {
|
||||
host: String,
|
||||
port: u16,
|
||||
}
|
||||
|
||||
impl Default for GlobalServerConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 8080,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
struct ConcurrentTestConfig {
|
||||
worker_id: u32,
|
||||
}
|
||||
|
||||
impl Default for ConcurrentTestConfig {
|
||||
fn default() -> Self {
|
||||
Self { worker_id: 1 }
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_config_singleton_cache() {
|
||||
let _guard = lock_test();
|
||||
let temp_file = env::temp_dir().join("test_get_config_singleton.toml");
|
||||
cleanup_temp_file(&temp_file);
|
||||
set_custom_path(&temp_file);
|
||||
|
||||
let initial = DatabaseConfig {
|
||||
url: "postgres://prod-db:5432/main".to_string(),
|
||||
max_connections: 50,
|
||||
};
|
||||
save_config(initial.clone());
|
||||
|
||||
// Erster Aufruf: lädt und speichert im Cache
|
||||
let ref1: &'static DatabaseConfig = get_config();
|
||||
assert_eq!(ref1.url, "postgres://prod-db:5432/main");
|
||||
assert_eq!(ref1.max_connections, 50);
|
||||
|
||||
// Zweiter Aufruf: liefert dieselbe statische Referenz
|
||||
let ref2: &'static DatabaseConfig = get_config();
|
||||
assert!(std::ptr::eq(ref1, ref2), "get_config muss dieselbe Referenz zurückgeben");
|
||||
|
||||
cleanup_temp_file(&temp_file);
|
||||
clear_custom_path();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_config_types_in_global_cache() {
|
||||
let _guard = lock_test();
|
||||
let temp_file = env::temp_dir().join("test_multiple_types_cache.toml");
|
||||
cleanup_temp_file(&temp_file);
|
||||
set_custom_path(&temp_file);
|
||||
|
||||
let app_cfg: &'static GlobalAppConfig = get_config();
|
||||
let srv_cfg: &'static GlobalServerConfig = get_config();
|
||||
|
||||
assert_eq!(app_cfg.service_name, "global_service");
|
||||
assert_eq!(srv_cfg.port, 8080);
|
||||
assert_eq!(srv_cfg.host, "127.0.0.1");
|
||||
|
||||
cleanup_temp_file(&temp_file);
|
||||
clear_custom_path();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_concurrent_access() {
|
||||
let _guard = lock_test();
|
||||
let temp_file = Arc::new(env::temp_dir().join("test_concurrent_access.toml"));
|
||||
cleanup_temp_file(&temp_file);
|
||||
set_custom_path(&*temp_file);
|
||||
|
||||
let initial = ConcurrentTestConfig { worker_id: 42 };
|
||||
save_config(initial);
|
||||
|
||||
let mut handles = Vec::new();
|
||||
for i in 0..10 {
|
||||
let handle = thread::spawn(move || {
|
||||
let cfg: &'static ConcurrentTestConfig = get_config();
|
||||
assert_eq!(cfg.worker_id, 42);
|
||||
let _ = load_config::<ConcurrentTestConfig>();
|
||||
if i % 2 == 0 {
|
||||
modify_config::<ConcurrentTestConfig, _>(|c| {
|
||||
c.worker_id = 1000 + i;
|
||||
});
|
||||
}
|
||||
});
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
for handle in handles {
|
||||
handle.join().unwrap();
|
||||
}
|
||||
|
||||
cleanup_temp_file(&temp_file);
|
||||
clear_custom_path();
|
||||
}
|
||||
Reference in New Issue
Block a user