Compare commits
25
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dcf2efbf98
|
||
|
|
2e7d2f98fd
|
||
|
|
469e699074
|
||
|
|
d87ece69da
|
||
|
|
f73ba4b1d6
|
||
|
|
d7845349e8
|
||
|
|
7bea78f194
|
||
|
|
87c1a1c09c
|
||
|
|
d781f3f97b
|
||
|
|
a4f27ce091
|
||
|
|
61b016775e
|
||
|
|
439ccd60d4
|
||
|
|
1c99387d6f
|
||
|
|
bf32d82ae7
|
||
|
|
34f61bba5f
|
||
|
|
d351a8b6be
|
||
|
|
a18eb13cbf
|
||
|
|
dd7c2d2390
|
||
|
|
107b18b461
|
||
|
|
506bf90cce
|
||
|
|
6d784b074b
|
||
|
|
8e1401439a
|
||
|
|
e9b47d7eb0
|
||
|
|
d70b1eb725
|
||
|
|
661acf9781
|
@@ -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
|
||||||
@@ -58,12 +58,13 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITEA_URL: ${{ gitea.server_url || github.server_url }}
|
GITEA_URL: ${{ gitea.server_url || github.server_url }}
|
||||||
REPO: ${{ gitea.repository || github.repository }}
|
REPO: ${{ gitea.repository || github.repository }}
|
||||||
|
REPO_NAME: ${{ gitea.repository_name || github.event.repository.name }}
|
||||||
TOKEN: ${{ secrets.PACKAGE_TOKEN || secrets.RELEASE_TOKEN || secrets.PUBLISH_TOKEN || secrets.API_TOKEN || secrets.PAT_TOKEN || secrets.CUSTOM_TOKEN || secrets.GITEA_TOKEN || secrets.GITHUB_TOKEN || github.token }}
|
TOKEN: ${{ secrets.PACKAGE_TOKEN || secrets.RELEASE_TOKEN || secrets.PUBLISH_TOKEN || secrets.API_TOKEN || secrets.PAT_TOKEN || secrets.CUSTOM_TOKEN || secrets.GITEA_TOKEN || secrets.GITHUB_TOKEN || github.token }}
|
||||||
run: |
|
run: |
|
||||||
VERSION="$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n1)"
|
VERSION="$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n1)"
|
||||||
TAG_NAME="v${VERSION}"
|
TAG_NAME="v${VERSION}"
|
||||||
RELEASE_TITLE="Release ${TAG_NAME}"
|
RELEASE_TITLE="Release ${TAG_NAME}"
|
||||||
RELEASE_NOTES="Automatisches Release für config Crate ${VERSION}."
|
RELEASE_NOTES="Automatisches Release für ${REPO_NAME} Crate ${VERSION}."
|
||||||
|
|
||||||
echo "Erstelle oder hole Release für Tag ${TAG_NAME} in ${REPO}..."
|
echo "Erstelle oder hole Release für Tag ${TAG_NAME} in ${REPO}..."
|
||||||
|
|
||||||
|
|||||||
@@ -35,12 +35,13 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITEA_URL: ${{ gitea.server_url || github.server_url }}
|
GITEA_URL: ${{ gitea.server_url || github.server_url }}
|
||||||
REPO: ${{ gitea.repository || github.repository }}
|
REPO: ${{ gitea.repository || github.repository }}
|
||||||
|
REPO_NAME: ${{ gitea.repository_name || github.event.repository.name }}
|
||||||
TOKEN: ${{ secrets.PACKAGE_TOKEN || secrets.RELEASE_TOKEN || secrets.PUBLISH_TOKEN || secrets.API_TOKEN || secrets.PAT_TOKEN || secrets.CUSTOM_TOKEN || secrets.GITEA_TOKEN || secrets.GITHUB_TOKEN || github.token }}
|
TOKEN: ${{ secrets.PACKAGE_TOKEN || secrets.RELEASE_TOKEN || secrets.PUBLISH_TOKEN || secrets.API_TOKEN || secrets.PAT_TOKEN || secrets.CUSTOM_TOKEN || secrets.GITEA_TOKEN || secrets.GITHUB_TOKEN || github.token }}
|
||||||
run: |
|
run: |
|
||||||
VERSION="$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n1)"
|
VERSION="$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n1)"
|
||||||
TAG_NAME="v${VERSION}-preview"
|
TAG_NAME="v${VERSION}-preview"
|
||||||
RELEASE_TITLE="Preview Release ${TAG_NAME}"
|
RELEASE_TITLE="Preview Release ${TAG_NAME}"
|
||||||
RELEASE_NOTES="Automatisches Preview-Release für config Crate ${VERSION} (Branch: Testing)."
|
RELEASE_NOTES="Automatisches Preview-Release für ${REPO_NAME} Crate ${VERSION} (Branch: Testing)."
|
||||||
|
|
||||||
echo "Erstelle oder hole Preview-Release für Tag ${TAG_NAME} in ${REPO}..."
|
echo "Erstelle oder hole Preview-Release für Tag ${TAG_NAME} in ${REPO}..."
|
||||||
|
|
||||||
|
|||||||
Generated
+1
@@ -3,6 +3,7 @@
|
|||||||
<component name="NewModuleRootManager">
|
<component name="NewModuleRootManager">
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
# AGENTS.md — Entwickler- und Agenten-Dokumentation für Config
|
# AGENTS.md — Entwickler- und Agenten-Dokumentation für Config (config-ctdra)
|
||||||
|
|
||||||
Diese Datei dient als Leitfaden und Kontextdokumentation für autonome Agenten und Entwickler, die an der Codebasis der Crate `config` arbeiten.
|
Diese Datei dient als Leitfaden und Kontextdokumentation für autonome Agenten und Entwickler, die an der Codebasis der Crate `config-ctdra` arbeiten.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Projektübersicht
|
## 1. Projektübersicht
|
||||||
|
|
||||||
`config` ist eine leichtgewichtige, threadsichere Rust-Crate (Edition 2024) zur Konfigurationsverwaltung. Sie ermöglicht das Speichern, Laden und Modifizieren beliebiger Konfigurationsstrukturen mit automatischer Pfadermittlung für Root- und regulären Benutzermodus.
|
`config-ctdra` ist eine leichtgewichtige, threadsichere Rust-Crate (Edition 2024) zur Konfigurationsverwaltung. Sie ermöglicht das Speichern, Laden und Modifizieren beliebiger Konfigurationsstrukturen mit automatischer Pfadermittlung für Root- und regulären Benutzermodus.
|
||||||
|
|
||||||
### Wichtige Rahmenbedingungen & Prinzipien
|
### Wichtige Rahmenbedingungen & Prinzipien
|
||||||
|
|
||||||
@@ -31,8 +31,10 @@ Config/
|
|||||||
├── LICENSE # GPL-3.0 Lizenztext
|
├── LICENSE # GPL-3.0 Lizenztext
|
||||||
├── README.md # Projektdokumentation & Nutzungsbeispiele
|
├── README.md # Projektdokumentation & Nutzungsbeispiele
|
||||||
├── AGENTS.md # Entwickler- und Agenten-Richtlinien
|
├── AGENTS.md # Entwickler- und Agenten-Richtlinien
|
||||||
└── src/
|
├── src/
|
||||||
└── lib.rs # Hauptimplementierung (load, store, modify, get_config, Pfadauflösung, Tests)
|
│ └── lib.rs # Hauptimplementierung (load, store, modify, get_config, Pfadauflösung)
|
||||||
|
└── tests/
|
||||||
|
└── integration_tests.rs # Vollständige Integrationstests
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
Generated
+19
-3
@@ -9,12 +9,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "config"
|
name = "config-ctdra"
|
||||||
version = "1.0.0"
|
version = "1.0.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"confy",
|
"confy",
|
||||||
"libc",
|
"program-ctdra",
|
||||||
"serde",
|
"serde",
|
||||||
|
"sudo-ctdra",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -93,6 +94,12 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"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]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.47"
|
version = "1.0.47"
|
||||||
@@ -141,6 +148,15 @@ dependencies = [
|
|||||||
"serde_core",
|
"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]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "3.0.4"
|
version = "3.0.4"
|
||||||
|
|||||||
+4
-3
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "config"
|
name = "config-ctdra"
|
||||||
version = "1.0.0"
|
version = "1.0.3"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
authors = ['DragonSlayer_14']
|
authors = ['DragonSlayer_14']
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@@ -10,8 +10,9 @@ description = "Einfache, threadsichere Konfigurationsverwaltung für Rust mit au
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
confy = "2.0.0"
|
confy = "2.0.0"
|
||||||
|
program-ctdra = { version = "1.0.0", registry = "gitea" }
|
||||||
serde = { version = "1.0.229", features = ["derive"] }
|
serde = { version = "1.0.229", features = ["derive"] }
|
||||||
libc = "1.0.0-alpha.4"
|
sudo-ctdra = { version = "1.0.0", registry = "gitea" }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = "none"
|
debug = "none"
|
||||||
|
|||||||
@@ -201,3 +201,32 @@ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY C
|
|||||||
If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee.
|
If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee.
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an “about box”.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school, if any, to sign a “copyright disclaimer” for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read <https://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Config (`config`)
|
# Config (`config-ctdra`)
|
||||||
|
|
||||||
`config` ist eine leichtgewichtige, threadsichere Konfigurationsverwaltungs-Crate für Rust mit automatischer Pfadermittlung für Root- und regulären Benutzermodus sowie Unterstützung für beliebige benutzerdefinierte Datenstrukturen.
|
`config-ctdra` ist eine leichtgewichtige, threadsichere Konfigurationsverwaltungs-Crate für Rust mit automatischer Pfadermittlung für Root- und regulären Benutzermodus sowie Unterstützung für beliebige benutzerdefinierte Datenstrukturen.
|
||||||
|
|
||||||
## Merkmale
|
## Merkmale
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
- **Einheitliche Pfadauflösung**:
|
- **Einheitliche Pfadauflösung**:
|
||||||
- **Root-Modus** (Linux/Unix UID 0): Konfigurationsdateien werden unter `/etc/<programmname>/<config_name>.toml` abgelegt.
|
- **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`).
|
- **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.
|
- **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.
|
- **Atomare Operationen**: `load`, `store`, `modify`, `modify_config` für konsistentes Laden, Bearbeiten und Speichern.
|
||||||
|
|
||||||
@@ -29,16 +29,16 @@ Füge anschließend die Abhängigkeit in deiner `Cargo.toml` ein:
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
config = { version = "1.0.0", registry = "gitea" }
|
config-ctdra = { version = "1.0.1", registry = "gitea" }
|
||||||
```
|
```
|
||||||
|
|
||||||
Alternativ als Git- oder Pfad-Abhängigkeit:
|
Alternativ als Git- oder Pfad-Abhängigkeit:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
config = { git = "https://gitea.creative-dragonslayer.de/Rust-Crates/config.git", branch = "main" }
|
config-ctdra = { git = "https://gitea.creative-dragonslayer.de/Rust-Crates/config.git", branch = "main" }
|
||||||
# oder lokal:
|
# oder lokal:
|
||||||
# config = { path = "../Config" }
|
# config-ctdra = { path = "../Config" }
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -97,11 +97,9 @@ impl Default for Database {
|
|||||||
### 2. Globales Caching mit `get_config`
|
### 2. Globales Caching mit `get_config`
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use config::{get_config, set_program_name};
|
use config_ctdra::get_config;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
set_program_name("my-service");
|
|
||||||
|
|
||||||
let cfg = get_config::<AppConfig>();
|
let cfg = get_config::<AppConfig>();
|
||||||
println!("Log-Level: {}", cfg.general.log_level);
|
println!("Log-Level: {}", cfg.general.log_level);
|
||||||
println!("DB URL: {}", cfg.database.url);
|
println!("DB URL: {}", cfg.database.url);
|
||||||
@@ -111,7 +109,7 @@ fn main() {
|
|||||||
### 3. Konfiguration modifizieren
|
### 3. Konfiguration modifizieren
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use config::modify_config;
|
use config_ctdra::modify_config;
|
||||||
|
|
||||||
fn update_settings() {
|
fn update_settings() {
|
||||||
modify_config::<AppConfig, _>(|cfg| {
|
modify_config::<AppConfig, _>(|cfg| {
|
||||||
@@ -123,9 +121,9 @@ fn update_settings() {
|
|||||||
### 4. Manuelles Laden und Speichern
|
### 4. Manuelles Laden und Speichern
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use config::{load, store};
|
use config_ctdra::{load, store};
|
||||||
|
|
||||||
fn custom_workflow() -> Result<(), config::ConfyError> {
|
fn custom_workflow() -> Result<(), config_ctdra::ConfyError> {
|
||||||
let mut cfg: AppConfig = load()?;
|
let mut cfg: AppConfig = load()?;
|
||||||
cfg.general.log_level = "debug".to_string();
|
cfg.general.log_level = "debug".to_string();
|
||||||
store(&cfg)?;
|
store(&cfg)?;
|
||||||
|
|||||||
+8
-226
@@ -5,13 +5,13 @@
|
|||||||
//! - Einheitliche Pfadermittlung:
|
//! - Einheitliche Pfadermittlung:
|
||||||
//! - Bei Ausführung als Root (unter Linux/Unix): `/etc/<program_name>/<config_name>.toml`
|
//! - 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`)
|
//! - 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>()`).
|
//! - Thread-sicheres globales Caching (`get_config::<T>()`).
|
||||||
//! - Atomares Laden, Speichern und Modifizieren (`load`, `store`, `modify`, `modify_config`).
|
//! - Atomares Laden, Speichern und Modifizieren (`load`, `store`, `modify`, `modify_config`).
|
||||||
//!
|
//!
|
||||||
//! # Beispiel
|
//! # Beispiel
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use config::{get_config, modify_config, set_program_name};
|
//! use config_ctdra::{get_config, modify_config};
|
||||||
//! use serde::{Deserialize, Serialize};
|
//! use serde::{Deserialize, Serialize};
|
||||||
//!
|
//!
|
||||||
//! #[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
|
//! #[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
|
||||||
@@ -20,8 +20,6 @@
|
|||||||
//! pub enable_ssl: bool,
|
//! pub enable_ssl: bool,
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! set_program_name("my-service");
|
|
||||||
//!
|
|
||||||
//! // Konfiguration abrufen (wird beim ersten Aufruf geladen und zwischengespeichert)
|
//! // Konfiguration abrufen (wird beim ersten Aufruf geladen und zwischengespeichert)
|
||||||
//! let cfg = get_config::<MyConfig>();
|
//! let cfg = get_config::<MyConfig>();
|
||||||
//! println!("Port: {}", cfg.server_port);
|
//! println!("Port: {}", cfg.server_port);
|
||||||
@@ -34,11 +32,10 @@ use std::path::PathBuf;
|
|||||||
use std::sync::{OnceLock, RwLock};
|
use std::sync::{OnceLock, RwLock};
|
||||||
|
|
||||||
pub use confy::ConfyError;
|
pub use confy::ConfyError;
|
||||||
|
use program_ctdra::try_program_name;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use sudo_ctdra::is_run_as_root;
|
||||||
/// Optionaler benutzerdefinierter Programmname für die Konfigurationspfade.
|
|
||||||
static PROGRAM_NAME: OnceLock<RwLock<Option<String>>> = OnceLock::new();
|
|
||||||
|
|
||||||
/// Optionaler benutzerdefinierter Konfigurationsdateiname (Standard: "config").
|
/// Optionaler benutzerdefinierter Konfigurationsdateiname (Standard: "config").
|
||||||
static CONFIG_NAME: OnceLock<RwLock<Option<String>>> = OnceLock::new();
|
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.
|
/// Standardname für Konfigurationsdateien.
|
||||||
const DEFAULT_CONFIG_NAME: &str = "config";
|
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)
|
/// Löst eine Panik aus, wenn der Programmname nicht ermittelt werden kann.
|
||||||
/// * `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`).
|
|
||||||
pub fn get_program_name() -> String {
|
pub fn get_program_name() -> String {
|
||||||
if let Some(lock) = PROGRAM_NAME.get() {
|
try_program_name().expect("Programmname konnte nicht ermittelt werden")
|
||||||
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())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Setzt den Namen der Konfigurationsdatei (ohne Dateiendung `.toml`).
|
/// Setzt den Namen der Konfigurationsdatei (ohne Dateiendung `.toml`).
|
||||||
@@ -323,181 +283,3 @@ where
|
|||||||
guard.insert(TypeId::of::<T>(), boxed);
|
guard.insert(TypeId::of::<T>(), boxed);
|
||||||
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