Files
khadhroony-solana-project/crates/ksp-config-lib/USAGE.md

288 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- file: crates/ksp-config-lib/USAGE.md -->
<!-- version: 2 -->
# Utilisation de ksp-config-lib
## 1. Bootstrap et moteur documentaire
Config doit interpréter ses propres arguments de bootstrap avant toute lecture de document :
```rust
let args: std::vec::Vec<std::ffi::OsString> = std::env::args_os().collect();
let bootstrap = match ksp_config_lib::ConfigBootstrapOptions::from_args(args.as_slice()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let registry = match ksp_config_lib::ConfigFileRegistry::from_args(args.as_slice()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let engine = ksp_config_lib::ConfigDocumentEngine::new(bootstrap, registry);
```
Les arguments compris par Config sont :
```text
--cfgpath=/path/to/config
--schemapath=/path/to/schemas
--filemap=cfg.std.logging=my-logging.json
```
`cfgpath` et `schemapath` ne sont jamais lus depuis JSON, `.env` ou une variable KSP : cette règle évite un bootstrap récursif.
## 2. Charger et valider un document connu
Les consumers utilisent un `file_id` logique :
```rust
let file_id = match ksp_config_lib::ConfigFileId::new(ksp_config_lib::FILE_ID_STD_LOGGING) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let document = engine.load_validated_document(&file_id);
```
Le moteur résout le path physique via le registre, charge le schema associé, valide le schema lui-même, valide l'instance puis applique les invariants sémantiques KSP.
## 3. Environnement effectif
`ConfigEnvironment::load()` capture les variables process KSP/KSPB et lit `./.env` :
```rust
let environment = match ksp_config_lib::ConfigEnvironment::load() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
```
La priorité est :
```text
process environment > .env > fallback > missing
```
Une chaîne vide explicitement présente est une valeur définie ; elle ne provoque pas l'utilisation du fallback.
Exemples de placeholders :
```text
${KSP_LOGS_DIRECTORY}
${KSP_LOGS_DIRECTORY:-logs}
```
Pour conserver la sensibilité et la provenance, préférer les variantes détaillées :
```rust
let resolved = environment.resolve_text_detailed("${KSP_SECRET_EXAMPLE}");
```
`ResolvedConfigText` / `ResolvedConfigJson` séparent valeur réelle et valeur sûre. Une représentation `Debug` ne doit pas révéler le réel d'un secret.
## 4. Construire Logging depuis Config
Le chemin normal consiste à charger le profil Logging, résoudre l'environnement puis construire directement le contrat Logging public :
```rust
let resolved = match engine.load_resolved_logging_config(std::option::Option::None, &environment) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let settings = resolved.into_settings();
let initialized = ksp_logging_lib::initialize(&settings);
let mut logging_guard = match initialized {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
```
L'application/service possède `logging_guard`. Config ne conserve pas de singleton Logging.
Un `logs_directory` relatif est ancré sur le current working directory du processus. Un path absolu est conservé. Une valeur explicite invalide produit une erreur effective : elle ne retombe pas silencieusement sur le fallback du placeholder.
Les `files[].path` restent relatifs sous le root Logging, y compris après interpolation.
## 5. Profils et composites
Pour un document standard profilé :
```rust
let profile = engine.load_resolved_profile(&file_id, std::option::Option::None);
```
`None` utilise le `default_profile`; `Some("profile_id")` impose un profil explicite.
Un composite référence les documents par `file_id`, jamais par filename. `load_resolved_composite(...)` conserve chaque `ResolvedConfigProfile` composant et sa provenance plutôt que d'aplatir plusieurs domaines dans une map ambiguë.
## 6. Management de `std.logging.json`
Une application de management construit la façade à partir d'un moteur :
```rust
let management = ksp_config_lib::ConfigManagement::new(engine);
let document = match management.load_logging_document() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
```
Le type `LoggingConfigDocument` et ses sous-structures exposent des setters/mutators typés. Après modification, la sauvegarde :
```rust
let saved = management.save_logging_document(&document);
```
valide le candidat complet avant toute substitution du fichier. Un candidat invalide ne remplace pas la source existante.
`read_source(file_id)` reste disponible pour une UI de réparation : il peut lire le texte brut d'un document enregistré même lorsque son JSON ou son schema est invalide. Il n'ouvre pas un path arbitraire.
## 7. Management de `.env`
Les rapports ordinaires sont sûrs :
```rust
let report = management.environment_report();
```
Ils distinguent notamment valeur souhaitée `.env`, valeur effective, source et shadowing process sans exposer un secret réel.
L'accès au réel est volontairement explicite :
```rust
let effective = management.reveal_effective_environment_value("KSP_SECRET_EXAMPLE");
let persisted = management.reveal_dotenv_value("KSP_SECRET_EXAMPLE");
```
Une application doit contrôler l'autorisation de l'utilisateur avant ces appels et ne jamais journaliser les valeurs retournées.
Les mutations persistantes utilisent :
```rust
let changed = management.set_dotenv_value("KSP_LOGS_DIRECTORY", "logs");
let removed = management.remove_dotenv_value("KSP_LOGS_DIRECTORY");
```
Elles n'altèrent jamais l'environnement hérité du processus. Une valeur process peut donc masquer une modification `.env`; `ConfigEnvironmentChangeReport` distingue `source_changed`, `effective_changed`, `shadowed_by_process_environment` et `reload_required`.
Sur Unix, un nouveau `.env` est créé avec des permissions privées `0600`; les permissions existantes sont préservées lors des remplacements atomiques.
## 8. `.env.example`
`/.env.example` est l'inventaire versionné. `/.env` reste local et ignoré.
Toute nouvelle variable runtime concrète `KSP_*` / `KSPB_*` introduite dans le code ou les documents Config doit être ajoutée à `.env.example` avec un commentaire expliquant son usage. Les audits `ksp-config-lib/tests/ownership.rs` font échouer `cargo test` lorsqu'une clé concrète est oubliée.
## 9. Frontière Tauri
Une application Tauri doit appeler les APIs ci-dessus via ses commandes/DTO applicatifs. Elle ne lit ni JSON ni `.env` directement et ne résout jamais elle-même les placeholders.
Les valeurs `Secret` ne doivent pas être incluses par défaut dans les DTO publics. Une action UI explicitement autorisée peut appeler une méthode `reveal_*` et transporter le résultat par un DTO spécifique, sans log ni diagnostic contenant la valeur réelle.
## 10. Index de la surface publique
Ce guide reste volontairement indépendant des numéros de release. Les contrats publics sont regroupés ci-dessous par usage ; les constantes de noms/erreurs accompagnent les mêmes familles et ne constituent pas des workflows séparés.
| Famille publique | Contrats principaux | Exemple |
|------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------|
| Bootstrap | `ConfigBootstrapOptions`, `ARG_CFG_PATH`, `ARG_SCHEMA_PATH`, `DEFAULT_CFG_PATH`, `DEFAULT_SCHEMA_PATH` | §1 |
| Registre logique | `ConfigFileRegistry`, `ConfigFileId`, `ConfigFileDescriptor`, `ConfigFileKind`, `ARG_FILE_MAP`, constantes `FILE_ID_*` / `DEFAULT_*_FILENAME` | §12 |
| Documents | `ConfigDocumentEngine`, `ConfigJsonDocument` | §2 |
| Profils | `ResolvedConfigProfile`, `ConfigProfileSelectionSource`, `ConfigValueOrigin` | §5 |
| Composites | `ResolvedConfigComposite`, `ResolvedCompositeComponent` | §5 |
| Environnement | `ConfigEnvironment`, `ConfigEnvironmentSource`, `ConfigEnvironmentValue`, `DEFAULT_DOTENV_PATH`, `DEFAULT_DOTENV_EXAMPLE_PATH` | §3, §78 |
| Sensibilité/provenance | `ConfigSensitivity`, `ConfigValueProvenance`, `ResolvedConfigText`, `ResolvedConfigJson`, `REDACTED_CONFIG_VALUE` | §3 |
| Logging effectif | `ResolvedLoggingConfig` | §4 |
| Management | `ConfigManagement`, `ConfigManagedSource`, `ConfigDocumentChangeReport`, `ConfigEnvironmentReport`, `ConfigEnvironmentChangeReport` | §67 |
| Source Logging typée | `LoggingConfigDocument`, `LoggingProfileConfig`, `LoggingConsoleConfig`, `LoggingFileConfig`, `LoggingOutputFilterConfig`, `LoggingTargetFilterConfig` | §6 et exemple ci-dessous |
| Erreurs Config | constantes `ERROR_CODE_*` réexportées par la crate | exemple ci-dessous |
### 10.1 Modifier une configuration Logging typée
Les getters permettent d'inspecter la source ; les setters et vues `*_mut()` permettent de construire un candidat avant validation/persistence :
```rust
let mut document = match management.load_logging_document() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
document.set_logs_directory("${KSP_LOGS_DIRECTORY:-logs}");
if let std::option::Option::Some(profile) = document.profiles_mut().first_mut() {
profile.set_default_filter("debug");
profile.console_mut().set_enabled(true);
profile.console_mut().filter_mut().set_level("info");
profile.console_mut().filter_mut().domains_mut().push("config".to_owned());
}
let saved = match management.save_logging_document(&document) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if saved.source_changed() && saved.reload_required() {
// The application decides when/how to reload the affected runtime consumer.
}
```
La construction depuis zéro utilise les constructeurs publics `LoggingConfigDocument::new`, `LoggingProfileConfig::new`, `LoggingConsoleConfig::new`, `LoggingFileConfig::new`, `LoggingOutputFilterConfig::new` et `LoggingTargetFilterConfig::new`. Les mêmes contraintes schema/sémantiques sont appliquées au moment de `save_logging_document()`.
### 10.2 Inspecter un source enregistré sans contourner Config
```rust
let source = match management.read_source(&file_id) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let logical_id = source.file_id();
let managed_path = source.path();
let raw_content = source.content();
```
Cette API est notamment destinée à une UI de réparation lorsque le document n'est plus validable. Elle n'autorise pas la lecture d'un chemin arbitraire.
### 10.3 Exploiter les rapports `.env`
```rust
let reports = match management.environment_report() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
for report in reports {
let name = report.variable_name();
let sensitivity = report.sensitivity();
let desired = report.desired_safe_value();
let effective = report.effective_safe_value();
let source = report.effective_source();
let shadowed = report.shadowed_by_process_environment();
let _ = (name, sensitivity, desired, effective, source, shadowed);
}
```
Après une mutation, `ConfigEnvironmentChangeReport` expose `source_changed()`, `effective_changed()`, `shadowed_by_process_environment()` et `reload_required()`.
### 10.4 Distinguer un code d'erreur Config
Les codes publics permettent à une UI/service de brancher sa logique sans parser le texte du message :
```rust
let loaded = engine.load_validated_document(&file_id);
if let std::result::Result::Err(error) = loaded {
if error.code() == ksp_config_lib::ERROR_CODE_SCHEMA_VALIDATION_FAILED {
// Present a schema-specific diagnostic path to the caller.
}
return std::result::Result::Err(error);
}
```
Le message/context d'erreur reste destiné au diagnostic ; l'identité machine-readable passe par `ErrorCode`.