v0.1.0-pre.055
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# file: kb-config/Cargo.toml
|
||||
# version: 2
|
||||
# version: 3
|
||||
|
||||
[package]
|
||||
name = "kb-config"
|
||||
@@ -9,6 +9,7 @@ license.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[dependencies]
|
||||
dotenvy.workspace = true
|
||||
kb-core = { path = "../kb-core" }
|
||||
jsonschema.workspace = true
|
||||
serde.workspace = true
|
||||
|
||||
@@ -66,3 +66,14 @@ Les structures exposées à Tauri utilisent `TS` avec un chemin `export_to` expl
|
||||
cargo test export_bindings -p kb-config
|
||||
cargo test -p kb-config settings::tests::
|
||||
```
|
||||
|
||||
## Résolution de l'environnement
|
||||
|
||||
`kb-config` est l'unique propriétaire du chargement des fichiers `.env` utilisés par la configuration. La priorité est déterministe :
|
||||
|
||||
1. variables déjà présentes dans l'environnement du processus ;
|
||||
2. fichier indiqué par `KB_ENV_FILE`, sinon `.env` à la racine du workspace ;
|
||||
3. fallback `${NOM:-valeur}` déclaré dans le JSON ;
|
||||
4. conservation de `${NOM}` lorsqu'une valeur requise reste absente, afin que les consommateurs optionnels puissent échouer seulement à l'utilisation.
|
||||
|
||||
Les crates métier ne chargent pas elles-mêmes de fichier `.env`.
|
||||
|
||||
98
kb-config/src/environment.rs
Normal file
98
kb-config/src/environment.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
// file: kb-config/src/environment.rs
|
||||
// version: 1
|
||||
|
||||
//! Environment-file loading and configuration placeholder resolution.
|
||||
|
||||
/// Result of loading one optional workspace environment file.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct EnvironmentLoadReport {
|
||||
/// Explicit or default environment file that was loaded.
|
||||
pub loaded_path: std::option::Option<std::path::PathBuf>,
|
||||
}
|
||||
|
||||
/// Loads the selected environment file without overriding process variables.
|
||||
///
|
||||
/// Resolution order is: existing process environment, selected `.env`, then
|
||||
/// fallback text declared with `${NAME:-fallback}` placeholders.
|
||||
pub fn load_workspace_environment(
|
||||
workspace_root: &std::path::Path,
|
||||
) -> kb_core::Result<EnvironmentLoadReport> {
|
||||
let explicit_path = std::env::var("KB_ENV_FILE").ok();
|
||||
let selected_path = match explicit_path {
|
||||
std::option::Option::Some(path) if !path.trim().is_empty() => {
|
||||
let candidate = std::path::PathBuf::from(path);
|
||||
if candidate.is_absolute() { candidate } else { workspace_root.join(candidate) }
|
||||
},
|
||||
_ => workspace_root.join(".env"),
|
||||
};
|
||||
if !selected_path.exists() {
|
||||
return std::result::Result::Ok(EnvironmentLoadReport { loaded_path: None });
|
||||
}
|
||||
match dotenvy::from_path(&selected_path) {
|
||||
std::result::Result::Ok(()) => {
|
||||
return std::result::Result::Ok(EnvironmentLoadReport {
|
||||
loaded_path: std::option::Option::Some(selected_path),
|
||||
});
|
||||
},
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"config_env_file_load_failed",
|
||||
format!("{}: {error}", selected_path.display()),
|
||||
));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolves `${NAME}` and `${NAME:-fallback}` placeholders in arbitrary text.
|
||||
/// Missing variables without fallbacks are preserved for lazy consumers.
|
||||
pub fn resolve_environment_placeholders(raw: &str) -> std::string::String {
|
||||
let mut output = std::string::String::with_capacity(raw.len());
|
||||
let bytes = raw.as_bytes();
|
||||
let mut index = 0_usize;
|
||||
while index < bytes.len() {
|
||||
if bytes[index] == b'$' && index + 1 < bytes.len() && bytes[index + 1] == b'{' {
|
||||
let start = index;
|
||||
let mut end = index + 2;
|
||||
while end < bytes.len() && bytes[end] != b'}' {
|
||||
end += 1;
|
||||
}
|
||||
if end < bytes.len() {
|
||||
let expression = &raw[index + 2..end];
|
||||
let (name, fallback) = match expression.split_once(":-") {
|
||||
std::option::Option::Some((name, fallback)) => {
|
||||
(name, std::option::Option::Some(fallback))
|
||||
},
|
||||
std::option::Option::None => (expression, std::option::Option::None),
|
||||
};
|
||||
let resolved =
|
||||
std::env::var(name).ok().or_else(|| fallback.map(|value| value.to_string()));
|
||||
match resolved {
|
||||
std::option::Option::Some(value) => output.push_str(&value),
|
||||
std::option::Option::None => output.push_str(&raw[start..=end]),
|
||||
}
|
||||
index = end + 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
output.push(bytes[index] as char);
|
||||
index += 1;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn fallback_is_used_when_variable_is_absent() {
|
||||
let resolved =
|
||||
super::resolve_environment_placeholders("${KB_CONFIG_TEST_MISSING:-fallback}");
|
||||
assert_eq!(resolved, "fallback");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unresolved_required_placeholder_is_preserved() {
|
||||
let resolved =
|
||||
super::resolve_environment_placeholders("prefix-${KB_CONFIG_TEST_MISSING}-suffix");
|
||||
assert_eq!(resolved, "prefix-${KB_CONFIG_TEST_MISSING}-suffix");
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,20 @@
|
||||
// file: kb-config/src/lib.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
//! Khadhroony Bot3 workspace configuration contract and loading helpers.
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
mod environment;
|
||||
mod settings;
|
||||
|
||||
/// Exposes the environment loading report.
|
||||
pub use self::environment::EnvironmentLoadReport;
|
||||
/// Exposes workspace environment-file loading.
|
||||
pub use self::environment::load_workspace_environment;
|
||||
/// Exposes environment placeholder resolution.
|
||||
pub use self::environment::resolve_environment_placeholders;
|
||||
/// Exposes the account listener configuration type.
|
||||
pub use self::settings::AccountListenerConfig;
|
||||
/// Exposes the root application configuration type.
|
||||
@@ -60,6 +67,8 @@ pub use self::settings::config_json_schema_value;
|
||||
pub use self::settings::parse_config_json;
|
||||
/// Exposes the configuration loader from a filesystem path.
|
||||
pub use self::settings::read_config_json_file;
|
||||
/// Exposes configuration loading with workspace environment resolution.
|
||||
pub use self::settings::read_config_json_file_with_environment;
|
||||
/// Exposes the compact JSON serializer for configuration values.
|
||||
pub use self::settings::serialize_config_json;
|
||||
/// Exposes the pretty JSON serializer for configuration values.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-config/src/settings.rs
|
||||
// version: 14
|
||||
// version: 15
|
||||
|
||||
//! Typed configuration models shared by applications and workers.
|
||||
|
||||
@@ -525,6 +525,28 @@ pub fn read_config_json_file(path: &std::path::Path) -> kb_core::Result<AppConfi
|
||||
return parse_config_json(&raw_json);
|
||||
}
|
||||
|
||||
/// Loads the workspace environment, resolves placeholders and parses one configuration file.
|
||||
pub fn read_config_json_file_with_environment(
|
||||
path: &std::path::Path,
|
||||
workspace_root: &std::path::Path,
|
||||
) -> kb_core::Result<AppConfig> {
|
||||
match crate::load_workspace_environment(workspace_root) {
|
||||
std::result::Result::Ok(_) => (),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
let raw_json = match std::fs::read_to_string(path) {
|
||||
std::result::Result::Ok(content) => content,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"config_file_read_failed",
|
||||
error.to_string(),
|
||||
));
|
||||
},
|
||||
};
|
||||
let resolved = crate::resolve_environment_placeholders(&raw_json);
|
||||
return parse_config_json(&resolved);
|
||||
}
|
||||
|
||||
/// Serializes a configuration value to compact JSON.
|
||||
pub fn serialize_config_json(config: &AppConfig) -> kb_core::Result<std::string::String> {
|
||||
match validate_config(config) {
|
||||
|
||||
Reference in New Issue
Block a user