Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8c65096f4c | |||
| cb142b1cb8 | |||
| 08c8046262 | |||
| c0b131bf6f | |||
| 5bf9651038 | |||
| e91bf36e9e | |||
| 1125ade4c1 | |||
| 518cc9257f | |||
| 46afe10423 | |||
| 25edcc231e | |||
| 6094d33120 | |||
| dcd23abb92 | |||
| 36b98e0abc | |||
| 3c069347b7 | |||
| a0a5595186 | |||
| 6c1172efc3 | |||
| d5536b0778 | |||
| c6794af05b | |||
| bcda6db11f | |||
| 51b5114ae2 | |||
| c97a8739ab | |||
| c2ccef3849 | |||
| 58134c81d1 | |||
| 5b3967327a | |||
| 804b92b957 | |||
| e6772c109d | |||
| 7e84522787 | |||
| 37461ec248 | |||
| 4252a21140 | |||
| 7a889dd466 | |||
| d3cc7c93f0 | |||
| 561b6678ed | |||
| e500024c3a | |||
| 033800f897 | |||
| cf8d79567d | |||
| cb19742cf7 | |||
| c03a3eaf5d | |||
| f7acac3fc8 | |||
| 039b93cfb3 | |||
| c9a0f7fd37 | |||
| 704110d509 | |||
| fb32ed03f8 | |||
| 4439589f0d | |||
| 1e1bc0d421 | |||
| bd26d4f636 | |||
| 2a7f3e8f40 | |||
| 1d37f04d63 | |||
| 1ccf11bcc1 | |||
| d06284fca2 | |||
| fff69ead9c | |||
| c93a833bac | |||
| 05e0e96083 | |||
| b57f796187 | |||
| ecfb9500eb | |||
| e14ce1c36f | |||
| 83cb861e54 | |||
| 1d1bc6a4d6 | |||
| c4636ac8b9 | |||
| f15448ff6e | |||
| bec1f2ec08 | |||
| f625ee5979 | |||
| e68f073505 | |||
| bffb4f9a31 | |||
| 9059a2dc45 |
5
.pydevproject
Normal file
5
.pydevproject
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<?eclipse-pydev version="1.0"?><pydev_project>
|
||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
|
||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python interpreter</pydev_property>
|
||||
</pydev_project>
|
||||
20
CHANGELOG.md
20
CHANGELOG.md
@@ -1,10 +1,28 @@
|
||||
<!-- file: CHANGELOG.md -->
|
||||
<!-- version: 5 -->
|
||||
<!-- version: 9 -->
|
||||
|
||||
# Changelog KSP
|
||||
|
||||
## 0.2.5 — Wallet foundation — 2026-08-20
|
||||
|
||||
`0.2.5` publie `ksp-wallet-lib` et le format natif interopérable `.kspwallet` V1. La release stabilise les capacités cryptographiquement indépendantes VIEW/OWNER, le masquage de la Pubkey/alias/notes à l’état verrouillé, Argon2id v19 avec paramètres sérialisés et profil de création `65 536 KiB / 3 / 1`, XChaCha20-Poly1305, une autorité Ed25519 OWNER distincte de la keypair Solana, les compartiments `owner_control`/`metadata`/`secret`, la création et l’ouverture mémoire/fichier, la persistence no-clobber, la signature Solana OWNER sans getter secret, l’administration alias/notes, les rotations OWNER/VIEW, la révocation forte VIEW avec rekey metadata, la détection de handles stale et les adapters Solana CLI JSON/Base58 pour inspection/import/export. Le wire V1 est documenté indépendamment de Rust dans `docs/formats/KSPWALLET_V1.md` et couvert par des vecteurs publics ainsi que des canaris adversariaux et une reproduction externe. La release conserve Wallet indépendant de Config/Transport/ExecutionPolicy/Store/Tauri et expose la Pubkey via `ksp-core-lib` tandis que `solana-keypair` reste encapsulée dans Wallet.
|
||||
|
||||
La clôture technique `pre.010-fix.001` à `fix.003` met la dépendance directe KSP à `ed25519-dalek 3.0.0`, accepte la génération `2.2.0` transitive encore imposée par `solana-keypair 3.1.2`, normalise les sources Rust du workspace et introduit `python3 scripts/audit_rust_workspace_rules.py` comme gate structurel complémentaire à rustfmt/Clippy. Le checkpoint final `pre.010-fix.003` est validé avec audit Python clean, `cargo check`, Clippy, tests Wallet et workspace complet verts. Le prompt `prompts/011-V0_2_6_START_PROMPT.md` ouvre ensuite `0.2.6 — Wallet Desk` par audit/sizing avant implémentation.
|
||||
|
||||
## 0.2.4 — HTTP Blocks + Economics + compliance HTTP finale — 2026-08-18
|
||||
|
||||
`0.2.4` achève la surface HTTP Solana standard de `ksp-onchain-transport-lib` : les 10 wrappers Blocks et 5 wrappers Economics portent la couverture typée à **52/52 méthodes HTTP courantes**, tandis que les **14/14 méthodes historiques Deprecated/runtime Removed** restent conservées pour compliance. La release stabilise notamment `getBlock` moderne avec sa forme bare encoding legacy dépréciée, les quatre variantes `transactionDetails`, les versions transaction numériques génériques, `numRewardPartitions` (SIMD-0118), `commissionBps` (SIMD-0291), les overloads de ranges/performance, `getBlockProduction`, les valeurs Economics fournies par le runtime et `getInflationReward` positionnel sans plafond d'adresses inventé. Le réaudit final confirme l'égalité exacte entre l'inventaire HTTP officiel courant et le registre KSP, applique `KSP-TRANSPORT-007` aux **52/52 wrappers**, et ne matérialise aucune extension spéculative issue de la watchlist SIMD. Les canaries de frontières, le workspace complet et les deux smokes Devnet — Transport pur couvrant Accounts/Tokens/Cluster/Transactions/Blocks/Economics et composition Config -> Transport transitoire — sont validés avant publication. Le prompt `prompts/010-V0_2_5_START_PROMPT.md`, enrichi par `pre.009-fix.001`, ouvre `0.2.5 — Wallet foundation` avec threat model, interopérabilité `.kspwallet` et capacités indépendantes VIEW/OWNER.
|
||||
|
||||
Ce changelog résume uniquement les releases KSP considérées comme stables, dans l'ordre chronologique décroissant. Les détails de chaque livraison restent dans `deltas/`.
|
||||
|
||||
## 0.2.3 — HTTP Transactions — 2026-08-18
|
||||
|
||||
`0.2.3` complète la famille HTTP Transactions de `ksp-onchain-transport-lib` avec 11 wrappers typés, portant la surface HTTP typée courante à 37 méthodes : 4 foundation, 22 Accounts/Tokens/Cluster et 11 Transactions. La release stabilise la classification exacte `8 Read / RetrySafe`, `2 WriteSubmission / NeverAfterDispatch` (`requestAirdrop`, `sendTransaction`) et `1 Simulation / RetrySafe` (`simulateTransaction`), avec preuves end-to-end qu'une write submission n'est jamais resoumise après dispatch ambigu mais peut retenter lorsqu'un échec est prouvé `NotDispatched`. `getTransaction` couvre la config moderne complète et la forme bare encoding legacy dépréciée ; les variantes de transaction, metadata, version et `transactionIndex` restent préservées losslessly. `simulateTransaction` couvre sa config complète, ses invariants déterministes et son résultat riche sans introduire de client RPC Solana haut niveau ni de décodage transactionnel dans Transport. La release formalise `KSP-TRANSPORT-007` et réaudite rétroactivement les 37 wrappers courants sans remédiation fonctionnelle. Les canaries conservent 52 méthodes HTTP courantes, 14 historiques Deprecated/Removed et 15 Blocks/Economics réservées à `0.2.4`. Les graphes Cargo et les deux smokes Devnet — Transport pur read-only et composition Config -> Transport transitoire — ont été validés avant publication. Le prompt `prompts/009-V0_2_4_START_PROMPT.md` ouvre `0.2.4 — HTTP Blocks + Economics + compliance HTTP finale`.
|
||||
|
||||
## 0.2.2 — HTTP Accounts + Tokens + Cluster — 2026-08-18
|
||||
|
||||
`0.2.2` complète la surface HTTP typée de `ksp-onchain-transport-lib` avec 22 wrappers supplémentaires : 5 Accounts, 5 Tokens et 12 Cluster, portant la couverture typée totale à 26 méthodes avec les quatre canaris foundation de `0.2.1`. La release stabilise les DTOs/configs wire communs, préserve les formes Account encodées/`jsonParsed`, les `null` et champs optionnels, les selectors Token exclusifs, les contextes RPC, les structures Cluster/leader/vote et les limites locales auditées (`getMultipleAccounts <= 100`, `getProgramAccounts <= 4` filtres, `memcmp` raw <= 128 octets, `getSlotLeaders` entre 1 et 5000). Les 22 descriptors restent `Read / RetrySafe` et passent exclusivement par le flux central `descriptor -> execute_standard_rpc -> pool/admission -> executor HTTP`. La release n'ajoute aucune dépendance externe, ne crée aucun client HTTP parallèle et conserve le firewall Transport -> Config/Store/Program. Elle ajoute un smoke Devnet pur à Transport avec settings programmatiques couvrant Accounts/Tokens/Cluster, conserve séparément le smoke historique Config -> Transport comme exception transitoire, et valide les deux explicitement avant publication. Les canaries figent toujours 52 méthodes HTTP courantes, 14 historiques Deprecated/Removed et la partition typed `4 / 22 / 11 / 15`. Le prompt `prompts/008-V0_2_3_START_PROMPT.md` ouvre `0.2.3 — HTTP Transactions`.
|
||||
|
||||
## 0.2.1 — HTTP Solana foundation — 2026-08-17
|
||||
|
||||
`0.2.1` stabilise `ksp-onchain-transport-lib` comme foundation HTTP JSON-RPC Solana provider-neutral : settings publics, endpoints/pool/rôles, priorités et fairness, RPS/burst/concurrence/cooldown, deadline commune, retry/backoff, classification no-resend après dispatch ambigu, snapshots sûrs et exécution HTTP réelle via `reqwest`/rustls. La release fige un registre audité de 52 méthodes HTTP courantes et 14 méthodes historiques Deprecated/Removed, avec quatre wrappers typés canari (`getBalance`, `getGenesisHash`, `getHealth`, `getVersion`) et une partition explicite des 48 méthodes restantes sur `0.2.2`–`0.2.4`. Elle ajoute `std.transport`, son schema/exemple et l'adapter `ksp-config-lib -> ksp-onchain-transport-lib`, sans dépendance inverse, ainsi que la redaction des URLs/provider credentials, la neutralisation des URLs contenues dans les `reqwest::Error`, un sink Logging Transport dédié à `info`, des fixtures HTTP déterministes, des canaries de complétude et un smoke Devnet opt-in validant la composition Config -> Transport. Le smoke cross-crates reste temporairement hébergé dans Config et doit migrer vers une future surface d'intégration/orchestration ; ce placement n'est pas un modèle pour les futurs smokes `Config + autre crate`. Le prompt `prompts/007-V0_2_2_START_PROMPT.md` ouvre `0.2.2 — HTTP Accounts + Tokens + Cluster`.
|
||||
|
||||
14
Cargo.toml
14
Cargo.toml
@@ -1,12 +1,12 @@
|
||||
# file: Cargo.toml
|
||||
# version: 109
|
||||
# version: 163
|
||||
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = ["crates/ksp-app-config-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib", "crates/ksp-onchain-transport-lib"]
|
||||
members = ["crates/ksp-app-config-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-wallet-lib"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.2.1"
|
||||
version = "0.2.5"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
||||
@@ -14,21 +14,29 @@ authors = ["SinuS von SifriduS <sinus@sasedev.net>"]
|
||||
publish = false
|
||||
|
||||
[workspace.dependencies]
|
||||
argon2 = { version = "^0.5", default-features = false }
|
||||
chacha20poly1305 = { version = "^0.11", default-features = false }
|
||||
ed25519-dalek = { version = "^3.0", default-features = false }
|
||||
getrandom = { version = "^0.4", default-features = false }
|
||||
base64 = { version = "^0.23" }
|
||||
fs2 = { version = "^0.4" }
|
||||
serde = { version = "^1.0" }
|
||||
serde_json = { version = "^1.0" }
|
||||
jsonschema = { version = "^0.49", default-features = false }
|
||||
reqwest = { version = "^0.13", default-features = false }
|
||||
solana-keypair = { version = "^3.1", default-features = false }
|
||||
solana-pubkey = { version = "^4.3", default-features = false }
|
||||
tracing = { version = "^0.1", default-features = false }
|
||||
tracing-subscriber = { version = "^0.3", default-features = false }
|
||||
tracing-appender = { version = "^0.2", default-features = false }
|
||||
tokio = { version = "^1.53", default-features = false }
|
||||
tempfile = { version = "^3.27" }
|
||||
chrono = { version = "^0.4", default-features = false }
|
||||
tauri = { version = "^2.11" }
|
||||
tauri-build = { version = "^2.6" }
|
||||
tauri-plugin-tracing = { version = "^0.3" }
|
||||
ts-rs = { version = "^12.0" }
|
||||
zeroize = { version = "^1.9" }
|
||||
|
||||
[workspace.lints.rust]
|
||||
missing_docs = "warn"
|
||||
|
||||
14
ROADMAP.md
14
ROADMAP.md
@@ -1,5 +1,5 @@
|
||||
<!-- file: ROADMAP.md -->
|
||||
<!-- version: 34 -->
|
||||
<!-- version: 55 -->
|
||||
|
||||
# Roadmap KSP
|
||||
|
||||
@@ -46,16 +46,16 @@ Le roadmap décrit les objectifs à atteindre et les grandes étapes prévues. U
|
||||
### Releases fonctionnelles décidées/pressenties
|
||||
|
||||
- [X] `0.2.1` — **HTTP transport foundation réduite par le gate `pre.001`** : crate/settings/JSON-RPC/registry 52 current + 14 deprecated historiques, pool/rôles/limites/retry, Config adapter, documentation et 4 méthodes typées canari (`getBalance`, `getGenesisHash`, `getHealth`, `getVersion`) publiés stables.
|
||||
- [ ] `0.2.2` — Compléter HTTP Accounts + Tokens + Cluster : 5 méthodes Accounts restantes + 5 Tokens + 12 Cluster restantes, soit 22 méthodes.
|
||||
- [ ] `0.2.3` — Compléter les 11 méthodes HTTP Transactions, y compris write/submission technique avec politique no-resend ambigu.
|
||||
- [ ] `0.2.4` — Compléter les 10 méthodes HTTP Blocks + 5 Economics et exécuter la compliance finale de toute la surface HTTP 52 current + 14 deprecated historiques.
|
||||
- [ ] `0.2.5` — Introduire `ksp-wallet-lib`, le format `.kspwallet`, la gestion sûre des secrets et une architecture d'import/export extensible ; exclure `WalletPolicy`.
|
||||
- [ ] `0.2.6` — Introduire `ksp-app-wallet-desk` utilisant Config composite + Wallet + transport HTTP, notamment pour afficher l'identité et le solde d'un wallet.
|
||||
- [X] `0.2.2` — HTTP Accounts + Tokens + Cluster : 22 wrappers typés (5 Accounts + 5 Tokens + 12 Cluster), canaries de complétude 52+14, smoke Devnet Transport pur et smoke historique Config -> Transport validés, documentation durable et prompt `0.2.3` publiés stables.
|
||||
- [X] `0.2.3` — HTTP Transactions stable : 11/11 wrappers typés publiés, classification `8 Read / 2 WriteSubmission / 1 Simulation`, no-resend ambigu prouvé pour les write submissions, `KSP-TRANSPORT-007` réaudité conforme sur les 37 wrappers HTTP courants, graphes Cargo et deux smokes Devnet validés ; `0.2.4` reprend les 15 Blocks/Economics restants.
|
||||
- [X] `0.2.4` — HTTP Blocks + Economics stable : 15/15 wrappers `V0_2_4` publiés, surface typed complète à 52/52 méthodes courantes, 14/14 historiques conservées, réaudit SIMD/inventaire final et `KSP-TRANSPORT-007` global validés ; deux smokes Devnet passés avant publication.
|
||||
- [X] `0.2.5` — Wallet foundation stable : `.kspwallet` V1, VIEW/OWNER indépendants, Argon2id/XChaCha20-Poly1305, autorité Ed25519 OWNER, persistence no-clobber, signature, administration/rotations/révocation VIEW forte, import/export Solana CLI JSON + Base58, canaris adversariaux, interop externe et documentation durable publiés. La clôture `pre.010-fix.001`–`fix.003` ajoute `ed25519-dalek 3.0.0` direct, normalise le Rust workspace et installe l’audit structurel Python complémentaire à rustfmt/Clippy. `Pubkey` reste via `ksp-core-lib`, la keypair reste encapsulée dans Wallet et Config/Transport/ExecutionPolicy/Store/Tauri restent hors Wallet.
|
||||
- [ ] `0.2.6` — Introduire `ksp-app-wallet-desk` utilisant Config composite + Wallet + transport HTTP, notamment pour afficher l'identité et le solde d'un wallet ; le prompt de démarrage est préparé par `0.2.5-pre.010`.
|
||||
- [ ] `0.2.7` — Étendre `ksp-onchain-transport-lib` au WebSocket Solana standard complet ; permettre plusieurs sessions sur une même URL sans imposer encore un pool automatique complexe.
|
||||
- [ ] `0.2.8` — Ajouter Helius LaserStream WebSocket comme extension du moteur WebSocket standard, sans duplication de client.
|
||||
- [ ] `0.2.9` — Ajouter une première fondation Yellowstone gRPC standard/provider-neutral ; dimensionner la surface exacte à `pre.001` selon la documentation normative actuelle.
|
||||
- [ ] `0.2.10` — Introduire `ksp-offchain-transport-lib` avec un premier lecteur de prix, au minimum SOL/USD et SOL/EUR.
|
||||
- [ ] `0.2.11` — Introduire une petite application desk de visualisation des prix.
|
||||
- [ ] `0.2.11` — Introduire une petite application desk de visualisation/validation des prix offchain, puis intégrer cette capacité dans `ksp-app-wallet-desk` sans dupliquer la logique de récupération/normalisation possédée par le composant spécialisé.
|
||||
- [ ] `0.2.12` — Introduire la première surface de `ksp-interface-lib`, comprenant une API wire publique utilisable par les implémentations officielles et externes.
|
||||
- [ ] `0.2.13` — Introduire `ksp-program-api` comme premier contrat Program extensible, sans imposer encore `ksp-program-lib` complet.
|
||||
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
// file: crates/ksp-app-config-desk/src/app_state.rs
|
||||
// version: 6
|
||||
// version: 8
|
||||
|
||||
//! Shared backend state owned by the Tauri application.
|
||||
|
||||
struct LoggingRuntimeState {
|
||||
guard: ksp_logging_lib::LoggingGuard,
|
||||
active_profile_id: std::option::Option<String>,
|
||||
selection_source: String,
|
||||
generation: u32,
|
||||
fallback_active: bool,
|
||||
startup_diagnostic: std::option::Option<crate::CommandErrorDto>,
|
||||
}
|
||||
|
||||
/// Shared Config Desk application state managed by Tauri.
|
||||
pub(crate) struct AppState {
|
||||
config_management: ksp_config_lib::ConfigManagement,
|
||||
@@ -28,7 +19,7 @@ impl AppState {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let runtime_identity = crate::logging_runtime::launch_identity();
|
||||
let runtime_identity = crate::launch_identity();
|
||||
let runtime_identity = match runtime_identity {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
@@ -175,7 +166,7 @@ impl AppState {
|
||||
let dropped = runtime.guard.dropped_lines();
|
||||
let mut files = std::vec::Vec::<crate::LoggingRuntimeFileDto>::new();
|
||||
for metadata in runtime.guard.active_file_outputs() {
|
||||
files.push(crate::logging_runtime::project_file(&metadata));
|
||||
files.push(crate::project_logging_file(&metadata));
|
||||
}
|
||||
return std::result::Result::Ok(crate::LoggingRuntimeStatusDto {
|
||||
active_profile: runtime.active_profile_id.clone(),
|
||||
@@ -185,9 +176,9 @@ impl AppState {
|
||||
application_id: identity.application_id().to_owned(),
|
||||
launch_timestamp: identity.launch_timestamp().to_owned(),
|
||||
console_enabled,
|
||||
dropped_console_lines: crate::logging_runtime::count_to_u64(dropped.console()),
|
||||
dropped_file_lines: crate::logging_runtime::count_to_u64(dropped.file()),
|
||||
dropped_total_lines: crate::logging_runtime::count_to_u64(dropped.total()),
|
||||
dropped_console_lines: crate::count_to_u64(dropped.console()),
|
||||
dropped_file_lines: crate::count_to_u64(dropped.file()),
|
||||
dropped_total_lines: crate::count_to_u64(dropped.total()),
|
||||
files,
|
||||
});
|
||||
}
|
||||
@@ -206,3 +197,12 @@ impl AppState {
|
||||
.is_ok();
|
||||
}
|
||||
}
|
||||
|
||||
struct LoggingRuntimeState {
|
||||
guard: ksp_logging_lib::LoggingGuard,
|
||||
active_profile_id: std::option::Option<String>,
|
||||
selection_source: String,
|
||||
generation: u32,
|
||||
fallback_active: bool,
|
||||
startup_diagnostic: std::option::Option<crate::CommandErrorDto>,
|
||||
}
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
// file: crates/ksp-app-config-desk/src/bootstrap.rs
|
||||
// version: 3
|
||||
// version: 6
|
||||
|
||||
//! Config and Logging bootstrap for the desktop application.
|
||||
|
||||
/// Crate-internal `LoggingStartup` state shared across the owning crate.
|
||||
pub(crate) struct LoggingStartup {
|
||||
/// Stores the guard value for this state.
|
||||
pub(crate) guard: ksp_logging_lib::LoggingGuard,
|
||||
/// Stores the active profile id value for this state.
|
||||
pub(crate) active_profile_id: std::option::Option<String>,
|
||||
/// Stores the selection source value for this state.
|
||||
pub(crate) selection_source: String,
|
||||
/// Stores the fallback active value for this state.
|
||||
pub(crate) fallback_active: bool,
|
||||
/// Stores the startup diagnostic value for this state.
|
||||
pub(crate) startup_diagnostic: std::option::Option<crate::CommandErrorDto>,
|
||||
}
|
||||
|
||||
@@ -23,6 +29,7 @@ enum LoggingStartupPlan {
|
||||
},
|
||||
}
|
||||
|
||||
/// Executes the crate-internal config management operation for the owning module.
|
||||
pub(crate) fn config_management(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<ksp_config_lib::ConfigManagement> {
|
||||
let bootstrap = ksp_config_lib::ConfigBootstrapOptions::from_args(arguments);
|
||||
let bootstrap = match bootstrap {
|
||||
@@ -38,10 +45,11 @@ pub(crate) fn config_management(arguments: &[std::ffi::OsString]) -> ksp_core_li
|
||||
return std::result::Result::Ok(ksp_config_lib::ConfigManagement::new(engine));
|
||||
}
|
||||
|
||||
/// Executes the crate-internal initialize logging operation for the owning module.
|
||||
pub(crate) fn initialize_logging(
|
||||
management: &ksp_config_lib::ConfigManagement,
|
||||
runtime_identity: &ksp_logging_lib::LoggingRuntimeIdentity,
|
||||
) -> ksp_core_lib::Result<LoggingStartup> {
|
||||
) -> ksp_core_lib::Result<crate::LoggingStartup> {
|
||||
let plan = resolve_logging_startup(management);
|
||||
return match plan {
|
||||
LoggingStartupPlan::Managed { active_profile_id, settings } => {
|
||||
@@ -71,6 +79,15 @@ pub(crate) fn initialize_logging(
|
||||
};
|
||||
}
|
||||
|
||||
fn fallback_logging_settings() -> ksp_logging_lib::LoggingSettings {
|
||||
return ksp_logging_lib::LoggingSettings::new(
|
||||
ksp_logging_lib::LogFilterLevel::Info,
|
||||
ksp_logging_lib::SpanEvents::Off,
|
||||
std::option::Option::Some(ksp_logging_lib::ConsoleSettings::stderr()),
|
||||
std::vec::Vec::new(),
|
||||
);
|
||||
}
|
||||
|
||||
fn resolve_logging_startup(management: &ksp_config_lib::ConfigManagement) -> LoggingStartupPlan {
|
||||
let environment = ksp_config_lib::ConfigEnvironment::load();
|
||||
let environment = match environment {
|
||||
@@ -93,7 +110,7 @@ fn fallback_startup_plan(initial_error: ksp_core_lib::Error) -> LoggingStartupPl
|
||||
fn initialize_fallback_logging(
|
||||
initial_error: ksp_core_lib::Error,
|
||||
runtime_identity: &ksp_logging_lib::LoggingRuntimeIdentity,
|
||||
) -> ksp_core_lib::Result<LoggingStartup> {
|
||||
) -> ksp_core_lib::Result<crate::LoggingStartup> {
|
||||
let diagnostic = crate::CommandErrorDto::from_error(&initial_error);
|
||||
return initialize_planned_fallback_logging(initial_error, diagnostic, fallback_logging_settings(), runtime_identity);
|
||||
}
|
||||
@@ -103,7 +120,7 @@ fn initialize_planned_fallback_logging(
|
||||
diagnostic: crate::CommandErrorDto,
|
||||
settings: ksp_logging_lib::LoggingSettings,
|
||||
runtime_identity: &ksp_logging_lib::LoggingRuntimeIdentity,
|
||||
) -> ksp_core_lib::Result<LoggingStartup> {
|
||||
) -> ksp_core_lib::Result<crate::LoggingStartup> {
|
||||
let guard = ksp_logging_lib::initialize_with_identity(&settings, runtime_identity);
|
||||
let guard = match guard {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -132,15 +149,6 @@ fn initialize_planned_fallback_logging(
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn fallback_logging_settings() -> ksp_logging_lib::LoggingSettings {
|
||||
return ksp_logging_lib::LoggingSettings::new(
|
||||
ksp_logging_lib::LogFilterLevel::Info,
|
||||
ksp_logging_lib::SpanEvents::Off,
|
||||
std::option::Option::Some(ksp_logging_lib::ConsoleSettings::stderr()),
|
||||
std::vec::Vec::new(),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/bootstrap.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -1,8 +1,28 @@
|
||||
// file: crates/ksp-app-config-desk/src/constants.rs
|
||||
// version: 9
|
||||
// version: 10
|
||||
|
||||
//! Application-owned tracing targets and domains.
|
||||
|
||||
/// Structured domain used while bootstrapping Config and Logging.
|
||||
pub(crate) const TRACING_DOMAIN_BOOTSTRAP: &str = "config.bootstrap";
|
||||
/// Structured domain for Config document inventory, diagnostics and repair.
|
||||
pub(crate) const TRACING_DOMAIN_DOCUMENTS: &str = "config.documents";
|
||||
/// Structured domain for safe Config environment reports.
|
||||
pub(crate) const TRACING_DOMAIN_ENVIRONMENT: &str = "config.environment";
|
||||
/// Structured domain used by technical frontend events.
|
||||
pub(crate) const TRACING_DOMAIN_FRONTEND: &str = "frontend";
|
||||
/// Structured domain for typed Logging editor inspection.
|
||||
pub(crate) const TRACING_DOMAIN_LOGGING_EDITOR: &str = "config.logging_editor";
|
||||
/// Structured domain for active Logging runtime metadata and explicit profile application.
|
||||
pub(crate) const TRACING_DOMAIN_LOGGING_RUNTIME: &str = "config.logging_runtime";
|
||||
/// Structured domain used by controlled Logging test events.
|
||||
pub(crate) const TRACING_DOMAIN_LOGGING_TEST: &str = "config.logging_test";
|
||||
/// Structured domain for Config profile selection and provenance inspection.
|
||||
pub(crate) const TRACING_DOMAIN_PROFILES: &str = "config.profiles";
|
||||
/// Structured domain for explicit privileged Secret reveal operations.
|
||||
pub(crate) const TRACING_DOMAIN_SECRETS: &str = "config.secrets";
|
||||
/// Structured domain used by Tauri window lifecycle operations.
|
||||
pub(crate) const TRACING_DOMAIN_WINDOWS: &str = "desktop.window";
|
||||
/// Owning target for backend events emitted by Config Desk.
|
||||
pub(crate) const TRACING_TARGET: &str = "ksp-app-config-desk";
|
||||
/// Owning target for generic frontend events emitted through the KSP bridge.
|
||||
@@ -13,23 +33,3 @@ pub(crate) const TRACING_TARGET_FRONTEND_MAIN: &str = "ksp-app-config-desk.front
|
||||
pub(crate) const TRACING_TARGET_FRONTEND_SPLASH: &str = "ksp-app-config-desk.frontend.splash";
|
||||
/// Dedicated target used by the controlled Logging test panel.
|
||||
pub(crate) const TRACING_TARGET_LOGGING_TEST: &str = "ksp-app-config-desk.logging-test";
|
||||
/// Structured domain used while bootstrapping Config and Logging.
|
||||
pub(crate) const TRACING_DOMAIN_BOOTSTRAP: &str = "config.bootstrap";
|
||||
/// Structured domain used by technical frontend events.
|
||||
pub(crate) const TRACING_DOMAIN_FRONTEND: &str = "frontend";
|
||||
/// Structured domain used by Tauri window lifecycle operations.
|
||||
pub(crate) const TRACING_DOMAIN_WINDOWS: &str = "desktop.window";
|
||||
/// Structured domain for Config document inventory, diagnostics and repair.
|
||||
pub(crate) const TRACING_DOMAIN_DOCUMENTS: &str = "config.documents";
|
||||
/// Structured domain for Config profile selection and provenance inspection.
|
||||
pub(crate) const TRACING_DOMAIN_PROFILES: &str = "config.profiles";
|
||||
/// Structured domain for safe Config environment reports.
|
||||
pub(crate) const TRACING_DOMAIN_ENVIRONMENT: &str = "config.environment";
|
||||
/// Structured domain for explicit privileged Secret reveal operations.
|
||||
pub(crate) const TRACING_DOMAIN_SECRETS: &str = "config.secrets";
|
||||
/// Structured domain for typed Logging editor inspection.
|
||||
pub(crate) const TRACING_DOMAIN_LOGGING_EDITOR: &str = "config.logging_editor";
|
||||
/// Structured domain for active Logging runtime metadata and explicit profile application.
|
||||
pub(crate) const TRACING_DOMAIN_LOGGING_RUNTIME: &str = "config.logging_runtime";
|
||||
/// Structured domain used by controlled Logging test events.
|
||||
pub(crate) const TRACING_DOMAIN_LOGGING_TEST: &str = "config.logging_test";
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// file: crates/ksp-app-config-desk/src/documents.rs
|
||||
// version: 1
|
||||
// version: 3
|
||||
|
||||
//! Generic Config document inventory, diagnostics and validated repair services.
|
||||
|
||||
use ts_rs::TS; // rust-rules: derive-import
|
||||
use ts_rs::TS; // rust-rules: trait-import
|
||||
|
||||
const STATUS_VALID: &str = "valid";
|
||||
const STATUS_INVALID: &str = "invalid";
|
||||
const STAGE_VALID: &str = "valid";
|
||||
const STAGE_READ: &str = "read";
|
||||
const STAGE_EFFECTIVE: &str = "effective";
|
||||
const STAGE_JSON: &str = "json";
|
||||
const STAGE_OTHER: &str = "other";
|
||||
const STAGE_READ: &str = "read";
|
||||
const STAGE_SCHEMA: &str = "schema";
|
||||
const STAGE_SEMANTIC: &str = "semantic";
|
||||
const STAGE_EFFECTIVE: &str = "effective";
|
||||
const STAGE_OTHER: &str = "other";
|
||||
const STAGE_VALID: &str = "valid";
|
||||
const STATUS_INVALID: &str = "invalid";
|
||||
const STATUS_VALID: &str = "valid";
|
||||
|
||||
/// Safe inventory row for one Config document registered by `ksp-config-lib`.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
@@ -78,7 +78,7 @@ impl ConfigDocumentErrorDto {
|
||||
}
|
||||
|
||||
/// Lists all Config-kind documents from the Config registry and evaluates their current backend validation state.
|
||||
pub(crate) fn inventory(state: &crate::AppState) -> std::vec::Vec<ConfigDocumentSummaryDto> {
|
||||
pub(crate) fn document_inventory(state: &crate::AppState) -> std::vec::Vec<ConfigDocumentSummaryDto> {
|
||||
let management = state.config_management();
|
||||
let engine = management.engine();
|
||||
let mut documents = std::vec::Vec::new();
|
||||
@@ -98,7 +98,7 @@ pub(crate) fn inventory(state: &crate::AppState) -> std::vec::Vec<ConfigDocument
|
||||
}
|
||||
|
||||
/// Loads one Config document detail, including raw source when Config can read it.
|
||||
pub(crate) fn detail(state: &crate::AppState, file_id_text: &str) -> std::result::Result<ConfigDocumentDetailDto, ConfigDocumentErrorDto> {
|
||||
pub(crate) fn document_detail(state: &crate::AppState, file_id_text: &str) -> std::result::Result<ConfigDocumentDetailDto, ConfigDocumentErrorDto> {
|
||||
let file_id = parse_config_file_id(file_id_text);
|
||||
let file_id = match file_id {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -133,7 +133,7 @@ pub(crate) fn detail(state: &crate::AppState, file_id_text: &str) -> std::result
|
||||
}
|
||||
|
||||
/// Validates and atomically persists one raw Config source candidate, then reloads the document detail.
|
||||
pub(crate) fn save_source(
|
||||
pub(crate) fn save_document_source(
|
||||
state: &crate::AppState,
|
||||
file_id_text: &str,
|
||||
source: &str,
|
||||
@@ -148,7 +148,7 @@ pub(crate) fn save_source(
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(ConfigDocumentErrorDto::from_error(&error)),
|
||||
};
|
||||
let document = detail(state, file_id.as_str());
|
||||
let document = crate::document_detail(state, file_id.as_str());
|
||||
let document = match document {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// file: crates/ksp-app-config-desk/src/dto_common.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Common Tauri DTOs shared by Config Desk commands.
|
||||
|
||||
use ts_rs::TS; // rust-rules: derive-import
|
||||
use ts_rs::TS; // rust-rules: trait-import
|
||||
|
||||
/// Safe command error projection that never serializes arbitrary KSP error context or source values.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// file: crates/ksp-app-config-desk/src/environment.rs
|
||||
// version: 2
|
||||
// version: 4
|
||||
|
||||
//! Safe Config environment reports and `.env` management projections for Config Desk.
|
||||
|
||||
use ts_rs::TS; // rust-rules: derive-import
|
||||
use ts_rs::TS; // rust-rules: trait-import
|
||||
|
||||
/// Safe desired/effective environment view exposed to the ordinary Config Desk frontend.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
@@ -63,17 +63,17 @@ impl EnvironmentMutationOperation {
|
||||
}
|
||||
|
||||
/// Returns the safe environment report owned by Config.
|
||||
pub(crate) fn report(state: &crate::AppState) -> ksp_core_lib::Result<std::vec::Vec<ConfigEnvironmentReportDto>> {
|
||||
pub(crate) fn environment_report(state: &crate::AppState) -> ksp_core_lib::Result<std::vec::Vec<ConfigEnvironmentReportDto>> {
|
||||
return report_from_management(state.config_management());
|
||||
}
|
||||
|
||||
/// Creates or replaces one `.env` entry exclusively through ConfigManagement.
|
||||
pub(crate) fn set_value(state: &crate::AppState, variable_name: &str, value: &str) -> ksp_core_lib::Result<ConfigEnvironmentChangeDto> {
|
||||
pub(crate) fn set_environment_value(state: &crate::AppState, variable_name: &str, value: &str) -> ksp_core_lib::Result<ConfigEnvironmentChangeDto> {
|
||||
return set_value_from_management(state.config_management(), variable_name, value);
|
||||
}
|
||||
|
||||
/// Removes one `.env` entry exclusively through ConfigManagement.
|
||||
pub(crate) fn remove_value(state: &crate::AppState, variable_name: &str) -> ksp_core_lib::Result<ConfigEnvironmentChangeDto> {
|
||||
pub(crate) fn remove_environment_value(state: &crate::AppState, variable_name: &str) -> ksp_core_lib::Result<ConfigEnvironmentChangeDto> {
|
||||
return remove_value_from_management(state.config_management(), variable_name);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,44 +1,42 @@
|
||||
// file: crates/ksp-app-config-desk/src/errors.rs
|
||||
// version: 9
|
||||
// version: 10
|
||||
|
||||
//! Application-local error codes for the configuration desktop shell.
|
||||
|
||||
/// Tauri runtime assembly or execution failed.
|
||||
pub(crate) const ERROR_CODE_TAURI_RUNTIME_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "tauri_runtime_failed");
|
||||
/// Shared Config Desk application state is internally inconsistent.
|
||||
pub(crate) const ERROR_CODE_APP_STATE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "app_state_invalid");
|
||||
/// Shared Config Desk runtime state cannot be locked safely.
|
||||
pub(crate) const ERROR_CODE_APP_STATE_LOCK_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "app_state_lock_failed");
|
||||
/// A Documents command requested a registered file that is not a Config-kind document.
|
||||
pub(crate) const ERROR_CODE_DOCUMENT_KIND_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "document_kind_invalid");
|
||||
/// Frontend logging requested an unsupported level.
|
||||
pub(crate) const ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "frontend_log_level_invalid");
|
||||
/// Frontend logging requested a target outside the application whitelist.
|
||||
pub(crate) const ERROR_CODE_FRONTEND_LOG_TARGET_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "frontend_log_target_invalid");
|
||||
/// Config Desk could not install the managed Logging runtime or its safe fallback.
|
||||
pub(crate) const ERROR_CODE_LOGGING_BOOTSTRAP_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "logging_bootstrap_failed");
|
||||
/// Config Desk persisted a Logging candidate but could not restore the previous source after runtime application failed.
|
||||
pub(crate) const ERROR_CODE_LOGGING_SOURCE_ROLLBACK_FAILED: ksp_core_lib::ErrorCode =
|
||||
ksp_core_lib::ErrorCode::new("config_desk", "logging_source_rollback_failed");
|
||||
/// Shared Config Desk application state is internally inconsistent.
|
||||
pub(crate) const ERROR_CODE_APP_STATE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "app_state_invalid");
|
||||
/// Shared Config Desk runtime state cannot be locked safely.
|
||||
pub(crate) const ERROR_CODE_APP_STATE_LOCK_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "app_state_lock_failed");
|
||||
/// Frontend logging requested an unsupported level.
|
||||
pub(crate) const ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "frontend_log_level_invalid");
|
||||
/// Frontend logging requested a target outside the application whitelist.
|
||||
pub(crate) const ERROR_CODE_FRONTEND_LOG_TARGET_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "frontend_log_target_invalid");
|
||||
/// Controlled Logging test request contains an unsupported or invalid selector.
|
||||
pub(crate) const ERROR_CODE_LOGGING_TEST_REQUEST_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "logging_test_request_invalid");
|
||||
|
||||
/// A validated Config document does not expose the standard profile contract expected by the Profiles panel.
|
||||
pub(crate) const ERROR_CODE_PROFILE_CONTRACT_MISSING: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "profile_contract_missing");
|
||||
/// Config profile data could not be projected safely for the frontend.
|
||||
pub(crate) const ERROR_CODE_PROFILE_PROJECTION_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "profile_projection_failed");
|
||||
/// A KSP desk splash environment duration is malformed or exceeds its safety bound.
|
||||
pub(crate) const ERROR_CODE_SPLASH_SETTING_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "splash_setting_invalid");
|
||||
/// Splash readiness was invoked from a window other than the splash window.
|
||||
pub(crate) const ERROR_CODE_SPLASH_ORIGIN_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "splash_origin_invalid");
|
||||
/// A required Tauri window is missing from the configured application runtime.
|
||||
pub(crate) const ERROR_CODE_TAURI_WINDOW_MISSING: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "tauri_window_missing");
|
||||
/// A Tauri window show/focus/destroy/event operation failed.
|
||||
pub(crate) const ERROR_CODE_TAURI_WINDOW_OPERATION_FAILED: ksp_core_lib::ErrorCode =
|
||||
ksp_core_lib::ErrorCode::new("config_desk", "tauri_window_operation_failed");
|
||||
|
||||
/// A Documents command requested a registered file that is not a Config-kind document.
|
||||
pub(crate) const ERROR_CODE_DOCUMENT_KIND_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "document_kind_invalid");
|
||||
/// Privileged reveal was requested for a non-Secret KSP/KSPB namespace.
|
||||
pub(crate) const ERROR_CODE_SECRET_REVEAL_REQUIRES_SECRET: ksp_core_lib::ErrorCode =
|
||||
ksp_core_lib::ErrorCode::new("config_desk", "secret_reveal_requires_secret");
|
||||
/// Privileged reveal requested an unsupported source selector.
|
||||
pub(crate) const ERROR_CODE_SECRET_REVEAL_SOURCE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "secret_reveal_source_invalid");
|
||||
/// Splash readiness was invoked from a window other than the splash window.
|
||||
pub(crate) const ERROR_CODE_SPLASH_ORIGIN_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "splash_origin_invalid");
|
||||
/// A KSP desk splash environment duration is malformed or exceeds its safety bound.
|
||||
pub(crate) const ERROR_CODE_SPLASH_SETTING_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "splash_setting_invalid");
|
||||
/// Tauri runtime assembly or execution failed.
|
||||
pub(crate) const ERROR_CODE_TAURI_RUNTIME_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "tauri_runtime_failed");
|
||||
/// A required Tauri window is missing from the configured application runtime.
|
||||
pub(crate) const ERROR_CODE_TAURI_WINDOW_MISSING: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config_desk", "tauri_window_missing");
|
||||
/// A Tauri window show/focus/destroy/event operation failed.
|
||||
pub(crate) const ERROR_CODE_TAURI_WINDOW_OPERATION_FAILED: ksp_core_lib::ErrorCode =
|
||||
ksp_core_lib::ErrorCode::new("config_desk", "tauri_window_operation_failed");
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// file: crates/ksp-app-config-desk/src/frontend_logging.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! KSP-owned bridge for technical log events emitted by Config Desk frontend scripts.
|
||||
|
||||
use ts_rs::TS; // rust-rules: derive-import
|
||||
use ts_rs::TS; // rust-rules: trait-import
|
||||
|
||||
/// Log payload sent by Config Desk frontend scripts.
|
||||
#[derive(Clone, Debug, serde::Deserialize, TS)]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-config-desk/src/lib.rs
|
||||
// version: 14
|
||||
// version: 16
|
||||
|
||||
//! Tauri desktop application for managing and validating KSP configuration.
|
||||
|
||||
@@ -28,61 +28,173 @@ mod tw_splash;
|
||||
/// Runs the KSP configuration desktop application.
|
||||
pub use self::tauri::run;
|
||||
|
||||
/// Shared Config Desk application state managed by Tauri.
|
||||
pub(crate) use self::app_state::AppState;
|
||||
/// Crate-internal `LoggingStartup` state shared across the owning crate.
|
||||
pub(crate) use self::bootstrap::LoggingStartup;
|
||||
/// Executes the crate-internal config management operation for the owning module.
|
||||
pub(crate) use self::bootstrap::config_management;
|
||||
/// Executes the crate-internal initialize logging operation for the owning module.
|
||||
pub(crate) use self::bootstrap::initialize_logging;
|
||||
/// Structured domain used while bootstrapping Config and Logging.
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_BOOTSTRAP;
|
||||
/// Structured domain for Config document inventory, diagnostics and repair.
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_DOCUMENTS;
|
||||
/// Structured domain for safe Config environment reports.
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_ENVIRONMENT;
|
||||
/// Structured domain used by technical frontend events.
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_FRONTEND;
|
||||
/// Structured domain for typed Logging editor inspection.
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_LOGGING_EDITOR;
|
||||
/// Structured domain for active Logging runtime metadata and explicit profile application.
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_LOGGING_RUNTIME;
|
||||
/// Structured domain used by controlled Logging test events.
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_LOGGING_TEST;
|
||||
/// Structured domain for Config profile selection and provenance inspection.
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_PROFILES;
|
||||
/// Structured domain for explicit privileged Secret reveal operations.
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_SECRETS;
|
||||
/// Structured domain used by Tauri window lifecycle operations.
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_WINDOWS;
|
||||
/// Owning target for backend events emitted by Config Desk.
|
||||
pub(crate) use self::constants::TRACING_TARGET;
|
||||
/// Owning target for generic frontend events emitted through the KSP bridge.
|
||||
pub(crate) use self::constants::TRACING_TARGET_FRONTEND;
|
||||
/// Owning target for main-window frontend events.
|
||||
pub(crate) use self::constants::TRACING_TARGET_FRONTEND_MAIN;
|
||||
/// Owning target for splash-window frontend events.
|
||||
pub(crate) use self::constants::TRACING_TARGET_FRONTEND_SPLASH;
|
||||
/// Dedicated target used by the controlled Logging test panel.
|
||||
pub(crate) use self::constants::TRACING_TARGET_LOGGING_TEST;
|
||||
/// Detailed management view for one registered Config document.
|
||||
pub(crate) use self::documents::ConfigDocumentDetailDto;
|
||||
/// Safe document-specific command error with backend-owned diagnostic classification.
|
||||
pub(crate) use self::documents::ConfigDocumentErrorDto;
|
||||
/// Result of one validated raw-source repair attempt that reached persistence successfully.
|
||||
pub(crate) use self::documents::ConfigDocumentSaveResultDto;
|
||||
/// Safe inventory row for one Config document registered by `ksp-config-lib`.
|
||||
pub(crate) use self::documents::ConfigDocumentSummaryDto;
|
||||
/// Loads one Config document detail, including raw source when Config can read it.
|
||||
pub(crate) use self::documents::document_detail;
|
||||
/// Lists all Config-kind documents from the Config registry and evaluates their current backend validation state.
|
||||
pub(crate) use self::documents::document_inventory;
|
||||
/// Validates and atomically persists one raw Config source candidate, then reloads the document detail.
|
||||
pub(crate) use self::documents::save_document_source;
|
||||
/// Initial application/runtime snapshot exposed to the frontend.
|
||||
pub(crate) use self::dto_common::AppSnapshotDto;
|
||||
/// Safe command error projection that never serializes arbitrary KSP error context or source values.
|
||||
pub(crate) use self::dto_common::CommandErrorDto;
|
||||
/// Safe result metadata for one `.env` mutation.
|
||||
pub(crate) use self::environment::ConfigEnvironmentChangeDto;
|
||||
/// Safe desired/effective environment view exposed to the ordinary Config Desk frontend.
|
||||
pub(crate) use self::environment::ConfigEnvironmentReportDto;
|
||||
/// Returns the safe environment report owned by Config.
|
||||
pub(crate) use self::environment::environment_report;
|
||||
/// Removes one `.env` entry exclusively through ConfigManagement.
|
||||
pub(crate) use self::environment::remove_environment_value;
|
||||
/// Creates or replaces one `.env` entry exclusively through ConfigManagement.
|
||||
pub(crate) use self::environment::set_environment_value;
|
||||
/// Shared Config Desk application state is internally inconsistent.
|
||||
pub(crate) use self::errors::ERROR_CODE_APP_STATE_INVALID;
|
||||
/// Shared Config Desk runtime state cannot be locked safely.
|
||||
pub(crate) use self::errors::ERROR_CODE_APP_STATE_LOCK_FAILED;
|
||||
/// A Documents command requested a registered file that is not a Config-kind document.
|
||||
pub(crate) use self::errors::ERROR_CODE_DOCUMENT_KIND_INVALID;
|
||||
/// Frontend logging requested an unsupported level.
|
||||
pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID;
|
||||
/// Frontend logging requested a target outside the application whitelist.
|
||||
pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_TARGET_INVALID;
|
||||
/// Config Desk could not install the managed Logging runtime or its safe fallback.
|
||||
pub(crate) use self::errors::ERROR_CODE_LOGGING_BOOTSTRAP_FAILED;
|
||||
/// Config Desk persisted a Logging candidate but could not restore the previous source after runtime application failed.
|
||||
pub(crate) use self::errors::ERROR_CODE_LOGGING_SOURCE_ROLLBACK_FAILED;
|
||||
/// Controlled Logging test request contains an unsupported or invalid selector.
|
||||
pub(crate) use self::errors::ERROR_CODE_LOGGING_TEST_REQUEST_INVALID;
|
||||
/// A validated Config document does not expose the standard profile contract expected by the Profiles panel.
|
||||
pub(crate) use self::errors::ERROR_CODE_PROFILE_CONTRACT_MISSING;
|
||||
/// Config profile data could not be projected safely for the frontend.
|
||||
pub(crate) use self::errors::ERROR_CODE_PROFILE_PROJECTION_FAILED;
|
||||
/// Privileged reveal was requested for a non-Secret KSP/KSPB namespace.
|
||||
pub(crate) use self::errors::ERROR_CODE_SECRET_REVEAL_REQUIRES_SECRET;
|
||||
/// Privileged reveal requested an unsupported source selector.
|
||||
pub(crate) use self::errors::ERROR_CODE_SECRET_REVEAL_SOURCE_INVALID;
|
||||
/// Splash readiness was invoked from a window other than the splash window.
|
||||
pub(crate) use self::errors::ERROR_CODE_SPLASH_ORIGIN_INVALID;
|
||||
/// A KSP desk splash environment duration is malformed or exceeds its safety bound.
|
||||
pub(crate) use self::errors::ERROR_CODE_SPLASH_SETTING_INVALID;
|
||||
/// Tauri runtime assembly or execution failed.
|
||||
pub(crate) use self::errors::ERROR_CODE_TAURI_RUNTIME_FAILED;
|
||||
/// A required Tauri window is missing from the configured application runtime.
|
||||
pub(crate) use self::errors::ERROR_CODE_TAURI_WINDOW_MISSING;
|
||||
/// A Tauri window show/focus/destroy/event operation failed.
|
||||
pub(crate) use self::errors::ERROR_CODE_TAURI_WINDOW_OPERATION_FAILED;
|
||||
/// Log payload sent by Config Desk frontend scripts.
|
||||
pub(crate) use self::frontend_logging::FrontendLogPayloadDto;
|
||||
/// Emits one validated frontend event through the KSP Logging facade.
|
||||
pub(crate) use self::frontend_logging::emit_frontend_log_event;
|
||||
/// Console sink configuration exposed to the editor.
|
||||
pub(crate) use self::logging_editor::LoggingConsoleDto;
|
||||
/// Complete editable Logging candidate accepted from the frontend.
|
||||
pub(crate) use self::logging_editor::LoggingDocumentCandidateDto;
|
||||
/// Complete typed Logging document exposed to the editor.
|
||||
pub(crate) use self::logging_editor::LoggingDocumentDto;
|
||||
/// Result of one validated typed Logging persistence operation.
|
||||
pub(crate) use self::logging_editor::LoggingDocumentSaveResultDto;
|
||||
pub(crate) use self::logging_runtime::{LoggingRuntimeFileDto, LoggingRuntimeStatusDto};
|
||||
pub(crate) use self::logging_test::{LoggingTestRequestDto, LoggingTestResultDto};
|
||||
/// One persistent file sink exposed to the editor.
|
||||
pub(crate) use self::logging_editor::LoggingFileDto;
|
||||
/// One Logging sink selector/filter exposed to the editor.
|
||||
pub(crate) use self::logging_editor::LoggingOutputFilterDto;
|
||||
/// One typed Logging profile exposed to the editor.
|
||||
pub(crate) use self::logging_editor::LoggingProfileDto;
|
||||
/// One global Logging target override exposed to the editor.
|
||||
pub(crate) use self::logging_editor::LoggingTargetFilterDto;
|
||||
/// Loads the typed Logging source through ConfigManagement and projects it for the frontend.
|
||||
pub(crate) use self::logging_editor::logging_document;
|
||||
/// Validates, atomically persists and hot-reloads one typed Logging candidate through ConfigManagement and `ksp-logging-lib`.
|
||||
pub(crate) use self::logging_editor::save_logging_document;
|
||||
/// Metadata for one currently active persistent Logging file output.
|
||||
pub(crate) use self::logging_runtime::LoggingRuntimeFileDto;
|
||||
/// Safe observable state of the currently active KSP Logging runtime.
|
||||
pub(crate) use self::logging_runtime::LoggingRuntimeStatusDto;
|
||||
/// Applies one persisted Logging profile explicitly without changing `default_profile` or the source document.
|
||||
pub(crate) use self::logging_runtime::apply_logging_profile;
|
||||
/// Executes the crate-internal count to u64 operation for the owning module.
|
||||
pub(crate) use self::logging_runtime::count_to_u64;
|
||||
/// Creates the stable runtime identity for this Config Desk process launch.
|
||||
pub(crate) use self::logging_runtime::launch_identity;
|
||||
/// Returns safe metadata for the currently active Logging runtime.
|
||||
pub(crate) use self::logging_runtime::logging_runtime_status;
|
||||
/// Executes the crate-internal project logging file operation for the owning module.
|
||||
pub(crate) use self::logging_runtime::project_logging_file;
|
||||
/// Controlled request emitted through the backend KSP Logging facade.
|
||||
pub(crate) use self::logging_test::LoggingTestRequestDto;
|
||||
/// Safe result metadata for one controlled backend Logging test request.
|
||||
pub(crate) use self::logging_test::LoggingTestResultDto;
|
||||
/// Emits one controlled backend test request through `ksp-logging-lib` only.
|
||||
pub(crate) use self::logging_test::emit_logging_test;
|
||||
/// Safe detailed view of one resolved Config profile.
|
||||
pub(crate) use self::profiles::ConfigProfileDetailDto;
|
||||
/// One Config document that exposes the standard `default_profile` / `profiles` contract.
|
||||
pub(crate) use self::profiles::ConfigProfileDocumentDto;
|
||||
/// Resolves one default or explicitly selected profile into a safe inspection DTO.
|
||||
pub(crate) use self::profiles::profile_detail;
|
||||
/// Lists validated Config documents that expose standard profiles.
|
||||
pub(crate) use self::profiles::profile_inventory;
|
||||
/// Privileged reveal request kept separate from ordinary environment-report DTOs.
|
||||
pub(crate) use self::secrets::SecretRevealRequestDto;
|
||||
/// Privileged reveal response. This type deliberately does not derive `Clone` or `Debug`.
|
||||
pub(crate) use self::secrets::SecretRevealResponseDto;
|
||||
/// Reveals one real Secret value only after the dedicated Tauri command has been explicitly invoked.
|
||||
pub(crate) use self::secrets::reveal_secret;
|
||||
/// Command emitted by Rust to the splash frontend.
|
||||
pub(crate) use self::splash::SplashOrderDto;
|
||||
/// Runtime timings used by the common desk splash lifecycle.
|
||||
pub(crate) use self::splash::SplashSettings;
|
||||
pub(crate) use self::tw_main::show_and_focus as show_main_window;
|
||||
pub(crate) use self::tw_splash::frontend_ready as splash_frontend_ready_service;
|
||||
/// Resolves the required main window or returns a typed error.
|
||||
pub(crate) use self::tw_main::require_main_window;
|
||||
/// Shows main window.
|
||||
pub(crate) use self::tw_main::show_main_window;
|
||||
/// Resolves the required splash window or returns a typed error.
|
||||
pub(crate) use self::tw_splash::require_splash_window;
|
||||
/// Executes the crate-internal splash frontend ready service operation for the owning module.
|
||||
pub(crate) use self::tw_splash::splash_frontend_ready_service;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// file: crates/ksp-app-config-desk/src/logging_editor.rs
|
||||
// version: 4
|
||||
// version: 6
|
||||
|
||||
//! Typed Logging document projection and validated persistence for the Config Desk Logging editor.
|
||||
|
||||
use ts_rs::TS; // rust-rules: derive-import
|
||||
use ts_rs::TS; // rust-rules: trait-import
|
||||
|
||||
/// One Logging sink selector/filter exposed to the editor.
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, TS)]
|
||||
@@ -32,7 +32,7 @@ pub(crate) struct LoggingConsoleDto {
|
||||
/// Console format source text.
|
||||
pub(crate) format: String,
|
||||
/// Sink-local selectors.
|
||||
pub(crate) filter: LoggingOutputFilterDto,
|
||||
pub(crate) filter: crate::LoggingOutputFilterDto,
|
||||
}
|
||||
|
||||
/// One persistent file sink exposed to the editor.
|
||||
@@ -53,7 +53,7 @@ pub(crate) struct LoggingFileDto {
|
||||
/// Whether ANSI output is requested.
|
||||
pub(crate) ansi: bool,
|
||||
/// Sink-local selectors.
|
||||
pub(crate) filter: LoggingOutputFilterDto,
|
||||
pub(crate) filter: crate::LoggingOutputFilterDto,
|
||||
}
|
||||
|
||||
/// One global Logging target override exposed to the editor.
|
||||
@@ -79,11 +79,11 @@ pub(crate) struct LoggingProfileDto {
|
||||
/// Span lifecycle source text.
|
||||
pub(crate) span_events: String,
|
||||
/// Console sink configuration.
|
||||
pub(crate) console: LoggingConsoleDto,
|
||||
pub(crate) console: crate::LoggingConsoleDto,
|
||||
/// Persistent file sinks in source order.
|
||||
pub(crate) files: std::vec::Vec<LoggingFileDto>,
|
||||
pub(crate) files: std::vec::Vec<crate::LoggingFileDto>,
|
||||
/// Global target overrides in source order.
|
||||
pub(crate) target_filters: std::vec::Vec<LoggingTargetFilterDto>,
|
||||
pub(crate) target_filters: std::vec::Vec<crate::LoggingTargetFilterDto>,
|
||||
}
|
||||
|
||||
/// Complete typed Logging document exposed to the editor.
|
||||
@@ -102,7 +102,7 @@ pub(crate) struct LoggingDocumentDto {
|
||||
/// Autonomous default profile identifier.
|
||||
pub(crate) default_profile: String,
|
||||
/// Typed profiles in source order.
|
||||
pub(crate) profiles: std::vec::Vec<LoggingProfileDto>,
|
||||
pub(crate) profiles: std::vec::Vec<crate::LoggingProfileDto>,
|
||||
}
|
||||
|
||||
/// Complete editable Logging candidate accepted from the frontend.
|
||||
@@ -115,7 +115,7 @@ pub(crate) struct LoggingDocumentCandidateDto {
|
||||
/// Autonomous default profile identifier.
|
||||
pub(crate) default_profile: String,
|
||||
/// Typed profiles in source order.
|
||||
pub(crate) profiles: std::vec::Vec<LoggingProfileDto>,
|
||||
pub(crate) profiles: std::vec::Vec<crate::LoggingProfileDto>,
|
||||
}
|
||||
|
||||
/// Result of one validated typed Logging persistence operation.
|
||||
@@ -138,12 +138,12 @@ pub(crate) struct LoggingDocumentSaveResultDto {
|
||||
}
|
||||
|
||||
/// Loads the typed Logging source through ConfigManagement and projects it for the frontend.
|
||||
pub(crate) fn document(state: &crate::AppState) -> ksp_core_lib::Result<LoggingDocumentDto> {
|
||||
pub(crate) fn logging_document(state: &crate::AppState) -> ksp_core_lib::Result<LoggingDocumentDto> {
|
||||
return document_from_management(state.config_management());
|
||||
}
|
||||
|
||||
/// Validates, atomically persists and hot-reloads one typed Logging candidate through ConfigManagement and `ksp-logging-lib`.
|
||||
pub(crate) fn save(state: &crate::AppState, candidate: LoggingDocumentCandidateDto) -> ksp_core_lib::Result<LoggingDocumentSaveResultDto> {
|
||||
pub(crate) fn save_logging_document(state: &crate::AppState, candidate: LoggingDocumentCandidateDto) -> ksp_core_lib::Result<LoggingDocumentSaveResultDto> {
|
||||
let file_id = ksp_config_lib::ConfigFileId::new(ksp_config_lib::FILE_ID_STD_LOGGING);
|
||||
let file_id = match file_id {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -255,7 +255,7 @@ fn document_from_management(management: &ksp_config_lib::ConfigManagement) -> ks
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let mut profiles = std::vec::Vec::<LoggingProfileDto>::with_capacity(source.profiles().len());
|
||||
let mut profiles = std::vec::Vec::<crate::LoggingProfileDto>::with_capacity(source.profiles().len());
|
||||
let mut file_count = 0usize;
|
||||
let mut target_filter_count = 0usize;
|
||||
for profile in source.profiles() {
|
||||
@@ -291,7 +291,7 @@ fn config_candidate(candidate: LoggingDocumentCandidateDto) -> ksp_config_lib::L
|
||||
return ksp_config_lib::LoggingConfigDocument::new(candidate.logs_directory, candidate.default_profile, profiles);
|
||||
}
|
||||
|
||||
fn config_profile(profile: LoggingProfileDto) -> ksp_config_lib::LoggingProfileConfig {
|
||||
fn config_profile(profile: crate::LoggingProfileDto) -> ksp_config_lib::LoggingProfileConfig {
|
||||
let mut files = std::vec::Vec::<ksp_config_lib::LoggingFileConfig>::with_capacity(profile.files.len());
|
||||
for file in profile.files {
|
||||
let mut config =
|
||||
@@ -319,14 +319,14 @@ fn config_profile(profile: LoggingProfileDto) -> ksp_config_lib::LoggingProfileC
|
||||
);
|
||||
}
|
||||
|
||||
fn config_output_filter(filter: LoggingOutputFilterDto) -> ksp_config_lib::LoggingOutputFilterConfig {
|
||||
fn config_output_filter(filter: crate::LoggingOutputFilterDto) -> ksp_config_lib::LoggingOutputFilterConfig {
|
||||
return ksp_config_lib::LoggingOutputFilterConfig::new(filter.level, filter.targets, filter.domains);
|
||||
}
|
||||
|
||||
fn project_profile(profile: &ksp_config_lib::LoggingProfileConfig) -> LoggingProfileDto {
|
||||
let mut files = std::vec::Vec::<LoggingFileDto>::with_capacity(profile.files().len());
|
||||
fn project_profile(profile: &ksp_config_lib::LoggingProfileConfig) -> crate::LoggingProfileDto {
|
||||
let mut files = std::vec::Vec::<crate::LoggingFileDto>::with_capacity(profile.files().len());
|
||||
for file in profile.files() {
|
||||
files.push(LoggingFileDto {
|
||||
files.push(crate::LoggingFileDto {
|
||||
output_id: file.output_id().to_owned(),
|
||||
enabled: file.enabled(),
|
||||
path: file.path().to_owned(),
|
||||
@@ -336,15 +336,15 @@ fn project_profile(profile: &ksp_config_lib::LoggingProfileConfig) -> LoggingPro
|
||||
filter: project_output_filter(file.filter()),
|
||||
});
|
||||
}
|
||||
let mut target_filters = std::vec::Vec::<LoggingTargetFilterDto>::with_capacity(profile.target_filters().len());
|
||||
let mut target_filters = std::vec::Vec::<crate::LoggingTargetFilterDto>::with_capacity(profile.target_filters().len());
|
||||
for target_filter in profile.target_filters() {
|
||||
target_filters.push(LoggingTargetFilterDto { target_prefix: target_filter.target_prefix().to_owned(), level: target_filter.level().to_owned() });
|
||||
target_filters.push(crate::LoggingTargetFilterDto { target_prefix: target_filter.target_prefix().to_owned(), level: target_filter.level().to_owned() });
|
||||
}
|
||||
return LoggingProfileDto {
|
||||
return crate::LoggingProfileDto {
|
||||
profile_id: profile.profile_id().to_owned(),
|
||||
default_filter: profile.default_filter().to_owned(),
|
||||
span_events: profile.span_events().to_owned(),
|
||||
console: LoggingConsoleDto {
|
||||
console: crate::LoggingConsoleDto {
|
||||
enabled: profile.console().enabled(),
|
||||
output: profile.console().output().to_owned(),
|
||||
ansi: profile.console().ansi(),
|
||||
@@ -356,8 +356,8 @@ fn project_profile(profile: &ksp_config_lib::LoggingProfileConfig) -> LoggingPro
|
||||
};
|
||||
}
|
||||
|
||||
fn project_output_filter(filter: &ksp_config_lib::LoggingOutputFilterConfig) -> LoggingOutputFilterDto {
|
||||
return LoggingOutputFilterDto {
|
||||
fn project_output_filter(filter: &ksp_config_lib::LoggingOutputFilterConfig) -> crate::LoggingOutputFilterDto {
|
||||
return crate::LoggingOutputFilterDto {
|
||||
level: filter.level().to_owned(),
|
||||
targets: filter.targets().to_vec(),
|
||||
domains: filter.domains().to_vec(),
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// file: crates/ksp-app-config-desk/src/logging_runtime.rs
|
||||
// version: 2
|
||||
// version: 4
|
||||
|
||||
//! Runtime Logging metadata, launch identity and explicit profile application for Config Desk.
|
||||
|
||||
use ts_rs::TS; // rust-rules: derive-import
|
||||
use ts_rs::TS; // rust-rules: trait-import
|
||||
|
||||
/// Metadata for one currently active persistent Logging file output.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
@@ -56,12 +56,12 @@ pub(crate) fn launch_identity() -> ksp_core_lib::Result<ksp_logging_lib::Logging
|
||||
}
|
||||
|
||||
/// Returns safe metadata for the currently active Logging runtime.
|
||||
pub(crate) fn status(state: &crate::AppState) -> ksp_core_lib::Result<LoggingRuntimeStatusDto> {
|
||||
pub(crate) fn logging_runtime_status(state: &crate::AppState) -> ksp_core_lib::Result<LoggingRuntimeStatusDto> {
|
||||
return state.logging_runtime_status();
|
||||
}
|
||||
|
||||
/// Applies one persisted Logging profile explicitly without changing `default_profile` or the source document.
|
||||
pub(crate) fn apply_profile(state: &crate::AppState, profile_id: &str) -> ksp_core_lib::Result<LoggingRuntimeStatusDto> {
|
||||
pub(crate) fn apply_logging_profile(state: &crate::AppState, profile_id: &str) -> ksp_core_lib::Result<LoggingRuntimeStatusDto> {
|
||||
let environment = ksp_config_lib::ConfigEnvironment::load();
|
||||
let environment = match environment {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -88,7 +88,8 @@ pub(crate) fn apply_profile(state: &crate::AppState, profile_id: &str) -> ksp_co
|
||||
return state.logging_runtime_status();
|
||||
}
|
||||
|
||||
pub(crate) fn project_file(metadata: &ksp_logging_lib::RuntimeFileMetadata) -> LoggingRuntimeFileDto {
|
||||
/// Executes the crate-internal project logging file operation for the owning module.
|
||||
pub(crate) fn project_logging_file(metadata: &ksp_logging_lib::RuntimeFileMetadata) -> LoggingRuntimeFileDto {
|
||||
return LoggingRuntimeFileDto {
|
||||
output_id: metadata.output_id().to_owned(),
|
||||
directory: metadata.directory().display().to_string(),
|
||||
@@ -97,7 +98,7 @@ pub(crate) fn project_file(metadata: &ksp_logging_lib::RuntimeFileMetadata) -> L
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) const fn rotation_label(rotation: ksp_logging_lib::FileRotation) -> &'static str {
|
||||
const fn rotation_label(rotation: ksp_logging_lib::FileRotation) -> &'static str {
|
||||
return match rotation {
|
||||
ksp_logging_lib::FileRotation::Never => "never",
|
||||
ksp_logging_lib::FileRotation::Hourly => "hourly",
|
||||
@@ -105,6 +106,7 @@ pub(crate) const fn rotation_label(rotation: ksp_logging_lib::FileRotation) -> &
|
||||
};
|
||||
}
|
||||
|
||||
/// Executes the crate-internal count to u64 operation for the owning module.
|
||||
pub(crate) fn count_to_u64(value: usize) -> u64 {
|
||||
let converted = u64::try_from(value);
|
||||
return match converted {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// file: crates/ksp-app-config-desk/src/logging_test.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Controlled Logging test events for validating KSP runtime routing from Config Desk.
|
||||
|
||||
use ts_rs::TS; // rust-rules: derive-import
|
||||
use ts_rs::TS; // rust-rules: trait-import
|
||||
|
||||
/// Controlled request emitted through the backend KSP Logging facade.
|
||||
#[derive(Clone, Debug, serde::Deserialize, TS)]
|
||||
@@ -56,7 +56,7 @@ enum LoggingTestTarget {
|
||||
}
|
||||
|
||||
/// Emits one controlled backend test request through `ksp-logging-lib` only.
|
||||
pub(crate) fn emit(state: &crate::AppState, request: LoggingTestRequestDto) -> ksp_core_lib::Result<LoggingTestResultDto> {
|
||||
pub(crate) fn emit_logging_test(state: &crate::AppState, request: LoggingTestRequestDto) -> ksp_core_lib::Result<LoggingTestResultDto> {
|
||||
let level = parse_level(request.level.as_str());
|
||||
let level = match level {
|
||||
std::result::Result::Ok(value) => value,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// file: crates/ksp-app-config-desk/src/profiles.rs
|
||||
// version: 1
|
||||
// version: 4
|
||||
|
||||
//! Safe Config profile inspection and provenance projections for Config Desk.
|
||||
|
||||
use ts_rs::TS; // rust-rules: derive-import
|
||||
use ts_rs::TS; // rust-rules: trait-import
|
||||
|
||||
/// One Config document that exposes the standard `default_profile` / `profiles` contract.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
@@ -77,13 +77,22 @@ pub(crate) struct ConfigProfileDetailDto {
|
||||
pub(crate) environment_provenance: std::vec::Vec<ConfigProfileEnvironmentProvenanceDto>,
|
||||
}
|
||||
|
||||
struct ProfileContract {
|
||||
default_profile: String,
|
||||
profile_ids: std::vec::Vec<String>,
|
||||
}
|
||||
|
||||
/// Lists validated Config documents that expose standard profiles.
|
||||
pub(crate) fn inventory(state: &crate::AppState) -> ksp_core_lib::Result<std::vec::Vec<ConfigProfileDocumentDto>> {
|
||||
pub(crate) fn profile_inventory(state: &crate::AppState) -> ksp_core_lib::Result<std::vec::Vec<ConfigProfileDocumentDto>> {
|
||||
return inventory_from_management(state.config_management());
|
||||
}
|
||||
|
||||
/// Resolves one default or explicitly selected profile into a safe inspection DTO.
|
||||
pub(crate) fn detail(state: &crate::AppState, file_id: &str, requested_profile: std::option::Option<&str>) -> ksp_core_lib::Result<ConfigProfileDetailDto> {
|
||||
pub(crate) fn profile_detail(
|
||||
state: &crate::AppState,
|
||||
file_id: &str,
|
||||
requested_profile: std::option::Option<&str>,
|
||||
) -> ksp_core_lib::Result<ConfigProfileDetailDto> {
|
||||
return detail_from_management(state.config_management(), file_id, requested_profile);
|
||||
}
|
||||
|
||||
@@ -213,11 +222,6 @@ fn detail_from_management(
|
||||
});
|
||||
}
|
||||
|
||||
struct ProfileContract {
|
||||
default_profile: String,
|
||||
profile_ids: std::vec::Vec<String>,
|
||||
}
|
||||
|
||||
fn profile_contract(document: &ksp_config_lib::ConfigJsonDocument) -> ksp_core_lib::Result<std::option::Option<ProfileContract>> {
|
||||
let root = match document.value().as_object() {
|
||||
std::option::Option::Some(value) => value,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// file: crates/ksp-app-config-desk/src/secrets.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Privileged, explicitly requested Config Secret reveal boundary for Config Desk.
|
||||
|
||||
use ts_rs::TS; // rust-rules: derive-import
|
||||
use ts_rs::TS; // rust-rules: trait-import
|
||||
|
||||
/// Privileged reveal request kept separate from ordinary environment-report DTOs.
|
||||
#[derive(serde::Deserialize, TS)]
|
||||
@@ -56,7 +56,7 @@ impl SecretRevealSource {
|
||||
}
|
||||
|
||||
/// Reveals one real Secret value only after the dedicated Tauri command has been explicitly invoked.
|
||||
pub(crate) fn reveal(state: &crate::AppState, request: SecretRevealRequestDto) -> ksp_core_lib::Result<SecretRevealResponseDto> {
|
||||
pub(crate) fn reveal_secret(state: &crate::AppState, request: SecretRevealRequestDto) -> ksp_core_lib::Result<SecretRevealResponseDto> {
|
||||
return reveal_from_management(state.config_management(), request);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
// file: crates/ksp-app-config-desk/src/splash.rs
|
||||
// version: 3
|
||||
// version: 5
|
||||
|
||||
//! Common splash settings and frontend event contracts for Config Desk.
|
||||
|
||||
use ts_rs::TS; // rust-rules: derive-import
|
||||
use ts_rs::TS; // rust-rules: trait-import
|
||||
|
||||
const ENV_SPLASH_MINIMUM_MS: &str = "KSP_DESK_SPLASH_MINIMUM_MS";
|
||||
const ENV_SPLASH_FADE_MS: &str = "KSP_DESK_SPLASH_FADE_MS";
|
||||
const DEFAULT_SPLASH_MINIMUM_MS: u64 = 1200;
|
||||
const DEFAULT_SPLASH_FADE_MS: u32 = 300;
|
||||
const MAX_SPLASH_MINIMUM_MS: u64 = 60_000;
|
||||
const DEFAULT_SPLASH_MINIMUM_MS: u64 = 1200;
|
||||
const ENV_SPLASH_FADE_MS: &str = "KSP_DESK_SPLASH_FADE_MS";
|
||||
const ENV_SPLASH_MINIMUM_MS: &str = "KSP_DESK_SPLASH_MINIMUM_MS";
|
||||
const MAX_SPLASH_FADE_MS: u32 = 10_000;
|
||||
const MAX_SPLASH_MINIMUM_MS: u64 = 60_000;
|
||||
|
||||
/// Runtime timings used by the common desk splash lifecycle.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
@@ -94,14 +94,6 @@ impl SplashSettings {
|
||||
}
|
||||
}
|
||||
|
||||
const fn environment_source_code(source: ksp_config_lib::ConfigEnvironmentSource) -> &'static str {
|
||||
return match source {
|
||||
ksp_config_lib::ConfigEnvironmentSource::Process => "process",
|
||||
ksp_config_lib::ConfigEnvironmentSource::DotEnv => "dotenv",
|
||||
ksp_config_lib::ConfigEnvironmentSource::Fallback => "fallback",
|
||||
};
|
||||
}
|
||||
|
||||
/// Command emitted by Rust to the splash frontend.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -116,12 +108,21 @@ pub(crate) struct SplashOrderDto {
|
||||
}
|
||||
|
||||
impl SplashOrderDto {
|
||||
/// Creates a new `SplashOrderDto` value.
|
||||
#[must_use]
|
||||
pub(crate) fn new(action: &str, message: std::option::Option<&str>, duration_ms: std::option::Option<u32>) -> Self {
|
||||
return Self { action: action.to_owned(), message: message.map(std::string::ToString::to_string), duration_ms };
|
||||
}
|
||||
}
|
||||
|
||||
const fn environment_source_code(source: ksp_config_lib::ConfigEnvironmentSource) -> &'static str {
|
||||
return match source {
|
||||
ksp_config_lib::ConfigEnvironmentSource::Process => "process",
|
||||
ksp_config_lib::ConfigEnvironmentSource::DotEnv => "dotenv",
|
||||
ksp_config_lib::ConfigEnvironmentSource::Fallback => "fallback",
|
||||
};
|
||||
}
|
||||
|
||||
fn parse_u64_setting(variable_name: &str, value: &str, maximum: u64) -> ksp_core_lib::Result<u64> {
|
||||
let parsed = value.parse::<u64>();
|
||||
let parsed = match parsed {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-config-desk/src/tauri.rs
|
||||
// version: 15
|
||||
// version: 16
|
||||
|
||||
//! Tauri runtime assembly for the KSP configuration desktop application.
|
||||
|
||||
@@ -60,11 +60,11 @@ fn configure_commands(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tau
|
||||
|
||||
fn configure_setup(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
|
||||
return builder.setup(|app| {
|
||||
let splash = crate::tw_splash::require_window(app);
|
||||
let splash = crate::require_splash_window(app);
|
||||
if let std::result::Result::Err(error) = splash {
|
||||
return std::result::Result::Err(std::boxed::Box::new(error));
|
||||
}
|
||||
let main = crate::tw_main::require_window(app);
|
||||
let main = crate::require_main_window(app);
|
||||
if let std::result::Result::Err(error) = main {
|
||||
return std::result::Result::Err(std::boxed::Box::new(error));
|
||||
}
|
||||
@@ -83,7 +83,7 @@ fn get_app_snapshot(state: tauri::State<'_, crate::AppState>) -> std::result::Re
|
||||
|
||||
#[tauri::command]
|
||||
fn get_config_documents(state: tauri::State<'_, crate::AppState>) -> std::vec::Vec<crate::ConfigDocumentSummaryDto> {
|
||||
return crate::documents::inventory(&state);
|
||||
return crate::document_inventory(&state);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -91,7 +91,7 @@ fn get_config_document_detail(
|
||||
file_id: String,
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<crate::ConfigDocumentDetailDto, crate::ConfigDocumentErrorDto> {
|
||||
return crate::documents::detail(&state, file_id.as_str());
|
||||
return crate::document_detail(&state, file_id.as_str());
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -100,14 +100,14 @@ fn save_config_document_source(
|
||||
source: String,
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<crate::ConfigDocumentSaveResultDto, crate::ConfigDocumentErrorDto> {
|
||||
return crate::documents::save_source(&state, file_id.as_str(), source.as_str());
|
||||
return crate::save_document_source(&state, file_id.as_str(), source.as_str());
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn get_config_profile_documents(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<std::vec::Vec<crate::ConfigProfileDocumentDto>, crate::CommandErrorDto> {
|
||||
let result = crate::profiles::inventory(&state);
|
||||
let result = crate::profile_inventory(&state);
|
||||
return match result {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||
@@ -120,7 +120,7 @@ fn get_config_profile_detail(
|
||||
profile_id: std::option::Option<String>,
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<crate::ConfigProfileDetailDto, crate::CommandErrorDto> {
|
||||
let result = crate::profiles::detail(&state, file_id.as_str(), profile_id.as_deref());
|
||||
let result = crate::profile_detail(&state, file_id.as_str(), profile_id.as_deref());
|
||||
return match result {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||
@@ -131,7 +131,7 @@ fn get_config_profile_detail(
|
||||
fn get_environment_report(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<std::vec::Vec<crate::ConfigEnvironmentReportDto>, crate::CommandErrorDto> {
|
||||
let result = crate::environment::report(&state);
|
||||
let result = crate::environment_report(&state);
|
||||
return match result {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||
@@ -144,7 +144,7 @@ fn set_environment_value(
|
||||
value: String,
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<crate::ConfigEnvironmentChangeDto, crate::CommandErrorDto> {
|
||||
let result = crate::environment::set_value(&state, variable_name.as_str(), value.as_str());
|
||||
let result = crate::set_environment_value(&state, variable_name.as_str(), value.as_str());
|
||||
return match result {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||
@@ -156,7 +156,7 @@ fn remove_environment_value(
|
||||
variable_name: String,
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<crate::ConfigEnvironmentChangeDto, crate::CommandErrorDto> {
|
||||
let result = crate::environment::remove_value(&state, variable_name.as_str());
|
||||
let result = crate::remove_environment_value(&state, variable_name.as_str());
|
||||
return match result {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||
@@ -168,7 +168,7 @@ fn reveal_environment_value(
|
||||
request: crate::SecretRevealRequestDto,
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<crate::SecretRevealResponseDto, crate::CommandErrorDto> {
|
||||
let result = crate::secrets::reveal(&state, request);
|
||||
let result = crate::reveal_secret(&state, request);
|
||||
return match result {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||
@@ -177,7 +177,7 @@ fn reveal_environment_value(
|
||||
|
||||
#[tauri::command]
|
||||
fn get_logging_document(state: tauri::State<'_, crate::AppState>) -> std::result::Result<crate::LoggingDocumentDto, crate::CommandErrorDto> {
|
||||
let result = crate::logging_editor::document(&state);
|
||||
let result = crate::logging_document(&state);
|
||||
return match result {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||
@@ -186,7 +186,7 @@ fn get_logging_document(state: tauri::State<'_, crate::AppState>) -> std::result
|
||||
|
||||
#[tauri::command]
|
||||
fn get_logging_runtime_status(state: tauri::State<'_, crate::AppState>) -> std::result::Result<crate::LoggingRuntimeStatusDto, crate::CommandErrorDto> {
|
||||
let result = crate::logging_runtime::status(&state);
|
||||
let result = crate::logging_runtime_status(&state);
|
||||
return match result {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||
@@ -198,7 +198,7 @@ fn apply_logging_profile(
|
||||
profile_id: String,
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<crate::LoggingRuntimeStatusDto, crate::CommandErrorDto> {
|
||||
let result = crate::logging_runtime::apply_profile(&state, profile_id.as_str());
|
||||
let result = crate::apply_logging_profile(&state, profile_id.as_str());
|
||||
return match result {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||
@@ -210,7 +210,7 @@ fn save_logging_document(
|
||||
candidate: crate::LoggingDocumentCandidateDto,
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<crate::LoggingDocumentSaveResultDto, crate::CommandErrorDto> {
|
||||
let result = crate::logging_editor::save(&state, candidate);
|
||||
let result = crate::save_logging_document(&state, candidate);
|
||||
return match result {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||
@@ -222,7 +222,7 @@ fn emit_logging_test(
|
||||
request: crate::LoggingTestRequestDto,
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<crate::LoggingTestResultDto, crate::CommandErrorDto> {
|
||||
let result = crate::logging_test::emit(&state, request);
|
||||
let result = crate::emit_logging_test(&state, request);
|
||||
return match result {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
// file: crates/ksp-app-config-desk/src/tw_main.rs
|
||||
// version: 1
|
||||
// version: 4
|
||||
|
||||
//! Tauri-window helpers for the Config Desk main window.
|
||||
|
||||
use tauri::Manager; // rust-rules: trait-import
|
||||
|
||||
/// Crate-internal `WINDOW_LABEL_MAIN` constant.
|
||||
pub(crate) const WINDOW_LABEL_MAIN: &str = "main";
|
||||
|
||||
pub(crate) fn require_window(manager: &impl Manager<tauri::Wry>) -> ksp_core_lib::Result<tauri::WebviewWindow> {
|
||||
/// Resolves the required main window or returns a typed error.
|
||||
pub(crate) fn require_main_window(manager: &impl Manager<tauri::Wry>) -> ksp_core_lib::Result<tauri::WebviewWindow> {
|
||||
let window = manager.get_webview_window(WINDOW_LABEL_MAIN);
|
||||
return match window {
|
||||
std::option::Option::Some(value) => std::result::Result::Ok(value),
|
||||
@@ -18,8 +20,9 @@ pub(crate) fn require_window(manager: &impl Manager<tauri::Wry>) -> ksp_core_lib
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn show_and_focus(app: &tauri::AppHandle) -> ksp_core_lib::Result<()> {
|
||||
let window = require_window(app);
|
||||
/// Shows main window.
|
||||
pub(crate) fn show_main_window(app: &tauri::AppHandle) -> ksp_core_lib::Result<()> {
|
||||
let window = crate::require_main_window(app);
|
||||
let window = match window {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
// file: crates/ksp-app-config-desk/src/tw_splash.rs
|
||||
// version: 2
|
||||
// version: 5
|
||||
|
||||
//! Tauri-window lifecycle for the Config Desk splash window.
|
||||
|
||||
use tauri::Emitter; // rust-rules: trait-import
|
||||
use tauri::Manager; // rust-rules: trait-import
|
||||
|
||||
/// Crate-internal `WINDOW_LABEL_SPLASH` constant.
|
||||
pub(crate) const WINDOW_LABEL_SPLASH: &str = "splash";
|
||||
|
||||
const SPLASH_EVENT_NAME: &str = "ksp-splash-order";
|
||||
|
||||
pub(crate) fn require_window(manager: &impl Manager<tauri::Wry>) -> ksp_core_lib::Result<tauri::WebviewWindow> {
|
||||
/// Resolves the required splash window or returns a typed error.
|
||||
pub(crate) fn require_splash_window(manager: &impl Manager<tauri::Wry>) -> ksp_core_lib::Result<tauri::WebviewWindow> {
|
||||
let window = manager.get_webview_window(WINDOW_LABEL_SPLASH);
|
||||
return match window {
|
||||
std::option::Option::Some(value) => std::result::Result::Ok(value),
|
||||
@@ -20,7 +23,12 @@ pub(crate) fn require_window(manager: &impl Manager<tauri::Wry>) -> ksp_core_lib
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) async fn frontend_ready(app: tauri::AppHandle, invoking_window: tauri::WebviewWindow, state: &crate::AppState) -> ksp_core_lib::Result<()> {
|
||||
/// Executes the crate-internal splash frontend ready service operation for the owning module.
|
||||
pub(crate) async fn splash_frontend_ready_service(
|
||||
app: tauri::AppHandle,
|
||||
invoking_window: tauri::WebviewWindow,
|
||||
state: &crate::AppState,
|
||||
) -> ksp_core_lib::Result<()> {
|
||||
if invoking_window.label() != WINDOW_LABEL_SPLASH {
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_SPLASH_ORIGIN_INVALID, "Splash readiness may only originate from the splash window")
|
||||
|
||||
@@ -1,31 +1,29 @@
|
||||
// file: crates/ksp-app-config-desk/unit_tests/documents.rs
|
||||
// version: 1
|
||||
|
||||
use super::*;
|
||||
// version: 2
|
||||
|
||||
#[test]
|
||||
fn diagnostic_classifier_distinguishes_json_schema_semantic_and_effective_errors() {
|
||||
let json = ksp_core_lib::Error::new(ksp_config_lib::ERROR_CODE_JSON_SYNTAX_INVALID, "json");
|
||||
assert_eq!(classify_error(&json), STAGE_JSON);
|
||||
assert_eq!(super::classify_error(&json), super::STAGE_JSON);
|
||||
let schema = ksp_core_lib::Error::new(ksp_config_lib::ERROR_CODE_SCHEMA_VALIDATION_FAILED, "schema");
|
||||
assert_eq!(classify_error(&schema), STAGE_SCHEMA);
|
||||
assert_eq!(super::classify_error(&schema), super::STAGE_SCHEMA);
|
||||
let semantic = ksp_core_lib::Error::new(ksp_config_lib::ERROR_CODE_DOCUMENT_SEMANTIC_INVALID, "semantic");
|
||||
assert_eq!(classify_error(&semantic), STAGE_SEMANTIC);
|
||||
assert_eq!(super::classify_error(&semantic), super::STAGE_SEMANTIC);
|
||||
let effective = ksp_core_lib::Error::new(ksp_config_lib::ERROR_CODE_EFFECTIVE_CONFIG_INVALID, "effective");
|
||||
assert_eq!(classify_error(&effective), STAGE_EFFECTIVE);
|
||||
assert_eq!(super::classify_error(&effective), super::STAGE_EFFECTIVE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn schema_source_read_errors_are_classified_as_schema_failures() {
|
||||
let error = ksp_core_lib::Error::new(ksp_config_lib::ERROR_CODE_JSON_FILE_READ_FAILED, "missing schema").with_context("file_id", "schema.std.logging");
|
||||
assert_eq!(classify_error(&error), STAGE_SCHEMA);
|
||||
assert_eq!(super::classify_error(&error), super::STAGE_SCHEMA);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn document_error_projection_does_not_expose_arbitrary_error_context() {
|
||||
let error = ksp_core_lib::Error::new(ksp_config_lib::ERROR_CODE_JSON_SYNTAX_INVALID, "invalid source").with_context("detail", "sensitive-canary");
|
||||
let projection = ConfigDocumentErrorDto::from_error(&error);
|
||||
assert_eq!(projection.diagnostic_stage, STAGE_JSON);
|
||||
let projection = crate::ConfigDocumentErrorDto::from_error(&error);
|
||||
assert_eq!(projection.diagnostic_stage, super::STAGE_JSON);
|
||||
assert_eq!(projection.error.domain, "config");
|
||||
assert_eq!(projection.error.code, "json_syntax_invalid");
|
||||
assert_eq!(projection.error.message, "invalid source");
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// file: crates/ksp-app-config-desk/unit_tests/dto_common.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
#[test]
|
||||
fn command_error_projection_excludes_context_values() {
|
||||
let error = ksp_core_lib::Error::new(ksp_core_lib::ErrorCode::new("test", "failure"), "safe message").with_context("secret_canary", "must-not-be-exported");
|
||||
let dto = super::CommandErrorDto::from_error(&error);
|
||||
let dto = crate::CommandErrorDto::from_error(&error);
|
||||
assert_eq!(dto.domain, "test");
|
||||
assert_eq!(dto.code, "failure");
|
||||
assert_eq!(dto.message, "safe message");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-config-desk/unit_tests/environment.rs
|
||||
// version: 2
|
||||
// version: 4
|
||||
|
||||
#[test]
|
||||
fn namespace_projection_distinguishes_ksp_and_kspb_sensitivity_families() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-config-desk/unit_tests/logging_editor.rs
|
||||
// version: 4
|
||||
// version: 7
|
||||
|
||||
#[test]
|
||||
fn committed_logging_document_maps_complete_read_only_editor_contract() {
|
||||
@@ -66,7 +66,7 @@ fn editor_candidate_round_trips_through_typed_config_contract() {
|
||||
let document = super::document_from_management(&management);
|
||||
assert!(document.is_ok(), "Logging editor document should load: {document:?}");
|
||||
if let std::result::Result::Ok(document) = document {
|
||||
let candidate = super::LoggingDocumentCandidateDto {
|
||||
let candidate = crate::LoggingDocumentCandidateDto {
|
||||
logs_directory: document.logs_directory.clone(),
|
||||
default_profile: document.default_profile.clone(),
|
||||
profiles: document.profiles.clone(),
|
||||
@@ -89,42 +89,42 @@ fn editor_candidate_round_trips_through_typed_config_contract() {
|
||||
|
||||
#[test]
|
||||
fn profile_candidate_preserves_multi_file_and_target_filter_shape() {
|
||||
let profile = super::LoggingProfileDto {
|
||||
let profile = crate::LoggingProfileDto {
|
||||
profile_id: "clone_test".to_owned(),
|
||||
default_filter: "info".to_owned(),
|
||||
span_events: "full".to_owned(),
|
||||
console: super::LoggingConsoleDto {
|
||||
console: crate::LoggingConsoleDto {
|
||||
enabled: true,
|
||||
output: "stdout".to_owned(),
|
||||
ansi: false,
|
||||
format: "human".to_owned(),
|
||||
filter: super::LoggingOutputFilterDto { level: "info".to_owned(), targets: std::vec!["*".to_owned()], domains: std::vec!["*".to_owned()] },
|
||||
filter: crate::LoggingOutputFilterDto { level: "info".to_owned(), targets: std::vec!["*".to_owned()], domains: std::vec!["*".to_owned()] },
|
||||
},
|
||||
files: std::vec![
|
||||
super::LoggingFileDto {
|
||||
crate::LoggingFileDto {
|
||||
output_id: "file.one".to_owned(),
|
||||
enabled: true,
|
||||
path: "one.log".to_owned(),
|
||||
rotation: "daily".to_owned(),
|
||||
format: "human".to_owned(),
|
||||
ansi: false,
|
||||
filter: super::LoggingOutputFilterDto { level: "debug".to_owned(), targets: std::vec!["*".to_owned()], domains: std::vec!["*".to_owned()] },
|
||||
filter: crate::LoggingOutputFilterDto { level: "debug".to_owned(), targets: std::vec!["*".to_owned()], domains: std::vec!["*".to_owned()] },
|
||||
},
|
||||
super::LoggingFileDto {
|
||||
crate::LoggingFileDto {
|
||||
output_id: "file.two".to_owned(),
|
||||
enabled: false,
|
||||
path: "two.jsonl".to_owned(),
|
||||
rotation: "hourly".to_owned(),
|
||||
format: "json".to_owned(),
|
||||
ansi: false,
|
||||
filter: super::LoggingOutputFilterDto {
|
||||
filter: crate::LoggingOutputFilterDto {
|
||||
level: "error".to_owned(),
|
||||
targets: std::vec!["ksp-config-lib".to_owned()],
|
||||
domains: std::vec!["config".to_owned()],
|
||||
},
|
||||
},
|
||||
],
|
||||
target_filters: std::vec![super::LoggingTargetFilterDto { target_prefix: "ksp-app-config-desk".to_owned(), level: "trace".to_owned() }],
|
||||
target_filters: std::vec![crate::LoggingTargetFilterDto { target_prefix: "ksp-app-config-desk".to_owned(), level: "trace".to_owned() }],
|
||||
};
|
||||
let config = super::config_profile(profile);
|
||||
assert_eq!(config.profile_id(), "clone_test");
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// file: crates/ksp-app-config-desk/unit_tests/logging_runtime.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
#[test]
|
||||
fn launch_identity_is_safe_and_contains_application_and_process_identity() {
|
||||
let identity = super::launch_identity();
|
||||
let identity = crate::launch_identity();
|
||||
assert!(identity.is_ok());
|
||||
if let std::result::Result::Ok(identity) = identity {
|
||||
assert_eq!(identity.application_id(), crate::TRACING_TARGET);
|
||||
@@ -21,6 +21,6 @@ fn runtime_rotation_labels_are_stable() {
|
||||
|
||||
#[test]
|
||||
fn runtime_count_projection_is_non_lossy_for_normal_values() {
|
||||
assert_eq!(super::count_to_u64(0), 0);
|
||||
assert_eq!(super::count_to_u64(42), 42);
|
||||
assert_eq!(crate::count_to_u64(0), 0);
|
||||
assert_eq!(crate::count_to_u64(42), 42);
|
||||
}
|
||||
|
||||
@@ -1,25 +1,23 @@
|
||||
// file: crates/ksp-app-config-desk/unit_tests/logging_test.rs
|
||||
// version: 1
|
||||
|
||||
use super::{LoggingTestLevel, LoggingTestTarget};
|
||||
// version: 2
|
||||
|
||||
#[test]
|
||||
fn controlled_logging_test_levels_include_all_five_levels_and_batch_mode() {
|
||||
assert!(matches!(super::parse_level("trace"), std::result::Result::Ok(LoggingTestLevel::Trace)));
|
||||
assert!(matches!(super::parse_level("debug"), std::result::Result::Ok(LoggingTestLevel::Debug)));
|
||||
assert!(matches!(super::parse_level("info"), std::result::Result::Ok(LoggingTestLevel::Info)));
|
||||
assert!(matches!(super::parse_level("warn"), std::result::Result::Ok(LoggingTestLevel::Warn)));
|
||||
assert!(matches!(super::parse_level("error"), std::result::Result::Ok(LoggingTestLevel::Error)));
|
||||
assert!(matches!(super::parse_level("all"), std::result::Result::Ok(LoggingTestLevel::All)));
|
||||
assert!(matches!(super::parse_level("trace"), std::result::Result::Ok(super::LoggingTestLevel::Trace)));
|
||||
assert!(matches!(super::parse_level("debug"), std::result::Result::Ok(super::LoggingTestLevel::Debug)));
|
||||
assert!(matches!(super::parse_level("info"), std::result::Result::Ok(super::LoggingTestLevel::Info)));
|
||||
assert!(matches!(super::parse_level("warn"), std::result::Result::Ok(super::LoggingTestLevel::Warn)));
|
||||
assert!(matches!(super::parse_level("error"), std::result::Result::Ok(super::LoggingTestLevel::Error)));
|
||||
assert!(matches!(super::parse_level("all"), std::result::Result::Ok(super::LoggingTestLevel::All)));
|
||||
assert!(super::parse_level("fatal").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn controlled_logging_test_targets_are_static_ksp_targets() {
|
||||
assert!(matches!(super::parse_target("app"), std::result::Result::Ok(LoggingTestTarget::App)));
|
||||
assert!(matches!(super::parse_target("logging_test"), std::result::Result::Ok(LoggingTestTarget::Dedicated)));
|
||||
assert_eq!(super::target_value(LoggingTestTarget::App), crate::TRACING_TARGET);
|
||||
assert_eq!(super::target_value(LoggingTestTarget::Dedicated), crate::TRACING_TARGET_LOGGING_TEST);
|
||||
assert!(matches!(super::parse_target("app"), std::result::Result::Ok(super::LoggingTestTarget::App)));
|
||||
assert!(matches!(super::parse_target("logging_test"), std::result::Result::Ok(super::LoggingTestTarget::Dedicated)));
|
||||
assert_eq!(super::target_value(super::LoggingTestTarget::App), crate::TRACING_TARGET);
|
||||
assert_eq!(super::target_value(super::LoggingTestTarget::Dedicated), crate::TRACING_TARGET_LOGGING_TEST);
|
||||
assert!(super::parse_target("external").is_err());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-config-desk/unit_tests/profiles.rs
|
||||
// version: 3
|
||||
// version: 5
|
||||
|
||||
#[test]
|
||||
fn profile_inventory_exposes_registered_standard_profile_documents() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-config-desk/unit_tests/secrets.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
#[test]
|
||||
fn reveal_policy_accepts_only_secret_names_and_supported_sources() {
|
||||
@@ -21,7 +21,7 @@ fn reveal_policy_accepts_only_secret_names_and_supported_sources() {
|
||||
|
||||
#[test]
|
||||
fn reveal_response_type_does_not_require_debug_or_clone_contracts() {
|
||||
let response = super::SecretRevealResponseDto {
|
||||
let response = crate::SecretRevealResponseDto {
|
||||
variable_name: "KSP_SECRET_TEST_VALUE".to_owned(),
|
||||
source: "dotenv".to_owned(),
|
||||
value: std::option::Option::Some("secret-canary".to_owned()),
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
// file: crates/ksp-config-lib/src/bootstrap.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
/// Default root containing KSP runtime configuration documents.
|
||||
pub const DEFAULT_CFG_PATH: &str = "config";
|
||||
/// Default root containing KSP JSON schemas.
|
||||
pub const DEFAULT_SCHEMA_PATH: &str = "config/schemas";
|
||||
/// Bootstrap argument used to replace the configuration document root.
|
||||
pub const ARG_CFG_PATH: &str = "--cfgpath";
|
||||
/// Bootstrap argument used to replace the schema root.
|
||||
pub const ARG_SCHEMA_PATH: &str = "--schemapath";
|
||||
/// Default root containing KSP runtime configuration documents.
|
||||
pub const DEFAULT_CFG_PATH: &str = "config";
|
||||
/// Default root containing KSP JSON schemas.
|
||||
pub const DEFAULT_SCHEMA_PATH: &str = "config/schemas";
|
||||
|
||||
/// Non-recursive bootstrap options required before Config can resolve any managed document.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-config-lib/src/composite.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
/// One resolved document component selected by a composite profile.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
@@ -118,6 +118,7 @@ struct CompositeDocumentReferenceSource {
|
||||
profile_id: std::option::Option<String>,
|
||||
}
|
||||
|
||||
/// Validates composite document contract.
|
||||
pub(crate) fn validate_composite_document_contract(engine: &crate::ConfigDocumentEngine, document: &crate::ConfigJsonDocument) -> ksp_core_lib::Result<()> {
|
||||
if !document.file_id().as_str().starts_with("cfg.composite.") {
|
||||
return std::result::Result::Ok(());
|
||||
@@ -198,12 +199,8 @@ fn resolve_composite_document(
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let resolved = crate::profile::load_resolved_profile_with_source(
|
||||
engine,
|
||||
&file_id,
|
||||
reference.profile_id.as_deref(),
|
||||
crate::ConfigProfileSelectionSource::Composite,
|
||||
);
|
||||
let resolved =
|
||||
crate::load_resolved_profile_with_source(engine, &file_id, reference.profile_id.as_deref(), crate::ConfigProfileSelectionSource::Composite);
|
||||
let resolved = match resolved {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
@@ -262,8 +259,7 @@ fn validate_reference(
|
||||
"referenced file_id is not a Config document",
|
||||
));
|
||||
}
|
||||
let resolved =
|
||||
crate::profile::load_resolved_profile_with_source(engine, &file_id, reference.profile_id.as_deref(), crate::ConfigProfileSelectionSource::Composite);
|
||||
let resolved = crate::load_resolved_profile_with_source(engine, &file_id, reference.profile_id.as_deref(), crate::ConfigProfileSelectionSource::Composite);
|
||||
if let std::result::Result::Err(error) = resolved {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-config-lib/src/document.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
/// A Config-managed JSON document that has passed syntax, schema and current semantic validation.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
@@ -85,6 +85,7 @@ impl ConfigDocumentEngine {
|
||||
return self.validate_document(document, &schema_file_id);
|
||||
}
|
||||
|
||||
/// Validates source candidate.
|
||||
pub(crate) fn validate_source_candidate(&self, file_id: &crate::ConfigFileId, source: &str) -> ksp_core_lib::Result<ConfigJsonDocument> {
|
||||
let path = self.registry.resolve_path(&self.bootstrap, file_id);
|
||||
let path = match path {
|
||||
@@ -99,6 +100,7 @@ impl ConfigDocumentEngine {
|
||||
return self.validate_candidate(file_id, value);
|
||||
}
|
||||
|
||||
/// Validates candidate.
|
||||
pub(crate) fn validate_candidate(&self, file_id: &crate::ConfigFileId, value: serde_json::Value) -> ksp_core_lib::Result<ConfigJsonDocument> {
|
||||
let descriptor = self.registry.descriptor(file_id);
|
||||
let descriptor = match descriptor {
|
||||
@@ -256,11 +258,11 @@ fn validate_instance(document: &ConfigJsonDocument, schema: &ConfigJsonDocument)
|
||||
}
|
||||
|
||||
fn validate_document_semantics(engine: &ConfigDocumentEngine, document: &ConfigJsonDocument) -> ksp_core_lib::Result<()> {
|
||||
let profile_validation = crate::profile::validate_document_profile_contract(document);
|
||||
let profile_validation = crate::validate_document_profile_contract(document);
|
||||
if let std::result::Result::Err(error) = profile_validation {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let composite_validation = crate::composite::validate_composite_document_contract(engine, document);
|
||||
let composite_validation = crate::validate_composite_document_contract(engine, document);
|
||||
if let std::result::Result::Err(error) = composite_validation {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
// file: crates/ksp-config-lib/src/environment.rs
|
||||
// version: 5
|
||||
|
||||
/// Default local environment file read by Config from the process launch directory.
|
||||
pub const DEFAULT_DOTENV_PATH: &str = ".env";
|
||||
// version: 8
|
||||
|
||||
/// Versioned environment contract template expected at the repository/runtime root.
|
||||
pub const DEFAULT_DOTENV_EXAMPLE_PATH: &str = ".env.example";
|
||||
/// Default local environment file read by Config from the process launch directory.
|
||||
pub const DEFAULT_DOTENV_PATH: &str = ".env";
|
||||
|
||||
const LOGGING_DOMAIN: &str = "config.environment";
|
||||
|
||||
@@ -230,6 +229,7 @@ impl ConfigEnvironment {
|
||||
};
|
||||
}
|
||||
|
||||
/// Loads from dotenv path.
|
||||
pub(crate) fn load_from_dotenv_path(dotenv_path: &std::path::Path) -> ksp_core_lib::Result<Self> {
|
||||
let process = collect_process_environment(std::env::vars_os());
|
||||
let process = match process {
|
||||
@@ -244,57 +244,24 @@ impl ConfigEnvironment {
|
||||
return std::result::Result::Ok(Self { process, dotenv, dotenv_path: dotenv_path.to_path_buf() });
|
||||
}
|
||||
|
||||
/// Returns the current process values.
|
||||
pub(crate) const fn process_values(&self) -> &std::collections::BTreeMap<String, String> {
|
||||
return &self.process;
|
||||
}
|
||||
|
||||
/// Returns the current dotenv values.
|
||||
pub(crate) const fn dotenv_values(&self) -> &std::collections::BTreeMap<String, String> {
|
||||
return &self.dotenv;
|
||||
}
|
||||
|
||||
/// Builds `ConfigEnvironment` from maps.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn from_maps(process: std::collections::BTreeMap<String, String>, dotenv: std::collections::BTreeMap<String, String>) -> Self {
|
||||
return Self { process, dotenv, dotenv_path: std::path::PathBuf::from(DEFAULT_DOTENV_PATH) };
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_process_environment<I>(values: I) -> ksp_core_lib::Result<std::collections::BTreeMap<String, String>>
|
||||
where
|
||||
I: std::iter::IntoIterator<Item = (std::ffi::OsString, std::ffi::OsString)>,
|
||||
{
|
||||
let mut output = std::collections::BTreeMap::<String, String>::new();
|
||||
for (name, value) in values {
|
||||
let name = match name.to_str() {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => continue,
|
||||
};
|
||||
if !has_supported_namespace(name) {
|
||||
continue;
|
||||
}
|
||||
let validation = validate_supported_variable_name(name);
|
||||
if let std::result::Result::Err(error) = validation {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let value = value.into_string();
|
||||
let value = match value {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(invalid_environment_value_error(name)),
|
||||
};
|
||||
output.insert(name.to_owned(), value);
|
||||
}
|
||||
return std::result::Result::Ok(output);
|
||||
}
|
||||
|
||||
fn load_dotenv_file(path: &std::path::Path) -> ksp_core_lib::Result<std::collections::BTreeMap<String, String>> {
|
||||
let content = std::fs::read_to_string(path);
|
||||
let content = match content {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) if error.kind() == std::io::ErrorKind::NotFound => return std::result::Result::Ok(std::collections::BTreeMap::new()),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(dotenv_read_error(path, error)),
|
||||
};
|
||||
return parse_dotenv_content(path, content.as_str());
|
||||
}
|
||||
|
||||
/// Executes the crate-internal parse dotenv content operation for the owning module.
|
||||
pub(crate) fn parse_dotenv_content(path: &std::path::Path, content: &str) -> ksp_core_lib::Result<std::collections::BTreeMap<String, String>> {
|
||||
let mut output = std::collections::BTreeMap::<String, String>::new();
|
||||
for (line_index, raw_line) in content.lines().enumerate() {
|
||||
@@ -342,6 +309,61 @@ pub(crate) fn parse_dotenv_content(path: &std::path::Path, content: &str) -> ksp
|
||||
return std::result::Result::Ok(output);
|
||||
}
|
||||
|
||||
/// Validates supported variable name.
|
||||
pub(crate) fn validate_supported_variable_name(variable_name: &str) -> ksp_core_lib::Result<()> {
|
||||
if !has_supported_namespace(variable_name) {
|
||||
return std::result::Result::Err(invalid_variable_error(variable_name, "variable must use the KSP_ or KSPB_ namespace"));
|
||||
}
|
||||
let prefix_length = if variable_name.starts_with("KSPB_") { 5 } else { 4 };
|
||||
if variable_name.len() <= prefix_length {
|
||||
return std::result::Result::Err(invalid_variable_error(variable_name, "variable namespace must be followed by a name"));
|
||||
}
|
||||
for byte in variable_name.bytes() {
|
||||
let valid = byte.is_ascii_uppercase() || byte.is_ascii_digit() || byte == b'_';
|
||||
if !valid {
|
||||
return std::result::Result::Err(invalid_variable_error(variable_name, "variable names use uppercase ASCII letters, digits and underscores"));
|
||||
}
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn collect_process_environment<I>(values: I) -> ksp_core_lib::Result<std::collections::BTreeMap<String, String>>
|
||||
where
|
||||
I: std::iter::IntoIterator<Item = (std::ffi::OsString, std::ffi::OsString)>,
|
||||
{
|
||||
let mut output = std::collections::BTreeMap::<String, String>::new();
|
||||
for (name, value) in values {
|
||||
let name = match name.to_str() {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => continue,
|
||||
};
|
||||
if !has_supported_namespace(name) {
|
||||
continue;
|
||||
}
|
||||
let validation = validate_supported_variable_name(name);
|
||||
if let std::result::Result::Err(error) = validation {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let value = value.into_string();
|
||||
let value = match value {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(invalid_environment_value_error(name)),
|
||||
};
|
||||
output.insert(name.to_owned(), value);
|
||||
}
|
||||
return std::result::Result::Ok(output);
|
||||
}
|
||||
|
||||
fn load_dotenv_file(path: &std::path::Path) -> ksp_core_lib::Result<std::collections::BTreeMap<String, String>> {
|
||||
let content = std::fs::read_to_string(path);
|
||||
let content = match content {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) if error.kind() == std::io::ErrorKind::NotFound => return std::result::Result::Ok(std::collections::BTreeMap::new()),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(dotenv_read_error(path, error)),
|
||||
};
|
||||
return crate::parse_dotenv_content(path, content.as_str());
|
||||
}
|
||||
|
||||
fn parse_dotenv_value(path: &std::path::Path, line_number: usize, raw_value: &str) -> ksp_core_lib::Result<String> {
|
||||
if raw_value.starts_with('\'') && (raw_value.len() < 2 || !raw_value.ends_with('\'')) {
|
||||
return std::result::Result::Err(dotenv_syntax_error(path, line_number, "single-quoted value is not terminated"));
|
||||
@@ -516,23 +538,6 @@ fn escape_json_pointer_token(value: &str) -> String {
|
||||
return value.replace('~', "~0").replace('/', "~1");
|
||||
}
|
||||
|
||||
pub(crate) fn validate_supported_variable_name(variable_name: &str) -> ksp_core_lib::Result<()> {
|
||||
if !has_supported_namespace(variable_name) {
|
||||
return std::result::Result::Err(invalid_variable_error(variable_name, "variable must use the KSP_ or KSPB_ namespace"));
|
||||
}
|
||||
let prefix_length = if variable_name.starts_with("KSPB_") { 5 } else { 4 };
|
||||
if variable_name.len() <= prefix_length {
|
||||
return std::result::Result::Err(invalid_variable_error(variable_name, "variable namespace must be followed by a name"));
|
||||
}
|
||||
for byte in variable_name.bytes() {
|
||||
let valid = byte.is_ascii_uppercase() || byte.is_ascii_digit() || byte == b'_';
|
||||
if !valid {
|
||||
return std::result::Result::Err(invalid_variable_error(variable_name, "variable names use uppercase ASCII letters, digits and underscores"));
|
||||
}
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn has_supported_namespace(variable_name: &str) -> bool {
|
||||
return variable_name.starts_with("KSP_") || variable_name.starts_with("KSPB_");
|
||||
}
|
||||
|
||||
@@ -1,68 +1,47 @@
|
||||
// file: crates/ksp-config-lib/src/error.rs
|
||||
// version: 8
|
||||
// version: 9
|
||||
|
||||
/// Error code used when a Config bootstrap argument is missing its value.
|
||||
pub const ERROR_CODE_BOOTSTRAP_ARGUMENT_MISSING_VALUE: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "bootstrap_argument_missing_value");
|
||||
|
||||
/// Error code used when a Config bootstrap path is empty, inaccessible, or resolves to an existing non-directory path.
|
||||
pub const ERROR_CODE_BOOTSTRAP_INVALID_PATH: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "bootstrap_invalid_path");
|
||||
|
||||
/// Error code used when a logical Config file identifier is malformed.
|
||||
pub const ERROR_CODE_FILE_ID_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "file_id_invalid");
|
||||
|
||||
/// Error code used when a requested logical Config file identifier is not registered.
|
||||
pub const ERROR_CODE_FILE_ID_UNKNOWN: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "file_id_unknown");
|
||||
|
||||
/// Error code used when the same logical Config file identifier is registered more than once.
|
||||
pub const ERROR_CODE_FILE_ID_DUPLICATE: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "file_id_duplicate");
|
||||
|
||||
/// Error code used when a Config filename mapping or descriptor relation is invalid.
|
||||
pub const ERROR_CODE_FILE_MAPPING_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "file_mapping_invalid");
|
||||
|
||||
/// Error code used when a Config-managed JSON document or schema cannot be read from its resolved path.
|
||||
pub const ERROR_CODE_JSON_FILE_READ_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "json_file_read_failed");
|
||||
|
||||
/// Error code used when a Config-managed file contains invalid JSON syntax.
|
||||
pub const ERROR_CODE_JSON_SYNTAX_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "json_syntax_invalid");
|
||||
|
||||
/// Error code used when a JSON Schema document is itself invalid for the selected JSON Schema draft.
|
||||
pub const ERROR_CODE_SCHEMA_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "schema_invalid");
|
||||
|
||||
/// Error code used when a Config document does not satisfy its registered JSON Schema.
|
||||
pub const ERROR_CODE_SCHEMA_VALIDATION_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "schema_validation_failed");
|
||||
|
||||
/// Error code used when a schema-valid Config document violates KSP semantic invariants for its document type.
|
||||
pub const ERROR_CODE_DOCUMENT_SEMANTIC_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "document_semantic_invalid");
|
||||
|
||||
/// Error code used when an explicitly requested Config profile does not exist in a validated document.
|
||||
pub const ERROR_CODE_PROFILE_NOT_FOUND: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "profile_not_found");
|
||||
|
||||
/// Error code used when a composite document references an invalid, unknown, or unsupported Config document.
|
||||
pub const ERROR_CODE_COMPOSITE_REFERENCE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "composite_reference_invalid");
|
||||
|
||||
/// Error code used when a schema-valid Config document violates KSP semantic invariants for its document type.
|
||||
pub const ERROR_CODE_DOCUMENT_SEMANTIC_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "document_semantic_invalid");
|
||||
/// Error code used when the local `.env` file cannot be read for a reason other than absence.
|
||||
pub const ERROR_CODE_DOTENV_FILE_READ_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "dotenv_file_read_failed");
|
||||
|
||||
/// Error code used when the local `.env` file contains syntax Config cannot interpret safely.
|
||||
pub const ERROR_CODE_DOTENV_SYNTAX_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "dotenv_syntax_invalid");
|
||||
|
||||
/// Error code used when a Config environment variable name is malformed or outside the KSP/KSPB namespaces.
|
||||
pub const ERROR_CODE_ENVIRONMENT_VARIABLE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "environment_variable_invalid");
|
||||
|
||||
/// Error code used when a referenced Config environment variable is absent and has no fallback.
|
||||
pub const ERROR_CODE_ENVIRONMENT_VARIABLE_MISSING: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "environment_variable_missing");
|
||||
|
||||
/// Error code used when a supported process environment variable has a value that cannot become a JSON UTF-8 string.
|
||||
pub const ERROR_CODE_ENVIRONMENT_VALUE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "environment_value_invalid");
|
||||
|
||||
/// Error code used when a `${NAME}` / `${NAME:-fallback}` expression is malformed.
|
||||
pub const ERROR_CODE_ENVIRONMENT_PLACEHOLDER_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "environment_placeholder_invalid");
|
||||
|
||||
/// Error code used when an environment-resolved Config cannot be mapped safely to a runtime consumer contract.
|
||||
pub const ERROR_CODE_EFFECTIVE_CONFIG_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "effective_config_invalid");
|
||||
|
||||
/// Error code used when a `${NAME}` / `${NAME:-fallback}` expression is malformed.
|
||||
pub const ERROR_CODE_ENVIRONMENT_PLACEHOLDER_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "environment_placeholder_invalid");
|
||||
/// Error code used when a supported process environment variable has a value that cannot become a JSON UTF-8 string.
|
||||
pub const ERROR_CODE_ENVIRONMENT_VALUE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "environment_value_invalid");
|
||||
/// Error code used when a Config environment variable name is malformed or outside the KSP/KSPB namespaces.
|
||||
pub const ERROR_CODE_ENVIRONMENT_VARIABLE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "environment_variable_invalid");
|
||||
/// Error code used when a referenced Config environment variable is absent and has no fallback.
|
||||
pub const ERROR_CODE_ENVIRONMENT_VARIABLE_MISSING: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "environment_variable_missing");
|
||||
/// Error code used when the same logical Config file identifier is registered more than once.
|
||||
pub const ERROR_CODE_FILE_ID_DUPLICATE: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "file_id_duplicate");
|
||||
/// Error code used when a logical Config file identifier is malformed.
|
||||
pub const ERROR_CODE_FILE_ID_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "file_id_invalid");
|
||||
/// Error code used when a requested logical Config file identifier is not registered.
|
||||
pub const ERROR_CODE_FILE_ID_UNKNOWN: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "file_id_unknown");
|
||||
/// Error code used when a Config filename mapping or descriptor relation is invalid.
|
||||
pub const ERROR_CODE_FILE_MAPPING_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "file_mapping_invalid");
|
||||
/// Error code used when a Config-managed JSON document or schema cannot be read from its resolved path.
|
||||
pub const ERROR_CODE_JSON_FILE_READ_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "json_file_read_failed");
|
||||
/// Error code used when a Config-managed file contains invalid JSON syntax.
|
||||
pub const ERROR_CODE_JSON_SYNTAX_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "json_syntax_invalid");
|
||||
/// Error code used when an explicit Config management operation is unsupported or targets the wrong managed resource kind.
|
||||
pub const ERROR_CODE_MANAGEMENT_OPERATION_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "management_operation_invalid");
|
||||
|
||||
/// Error code used when an atomic managed Config or `.env` persistence operation fails before commit.
|
||||
pub const ERROR_CODE_PERSISTENCE_WRITE_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "persistence_write_failed");
|
||||
/// Error code used when an explicitly requested Config profile does not exist in a validated document.
|
||||
pub const ERROR_CODE_PROFILE_NOT_FOUND: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "profile_not_found");
|
||||
/// Error code used when a JSON Schema document is itself invalid for the selected JSON Schema draft.
|
||||
pub const ERROR_CODE_SCHEMA_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "schema_invalid");
|
||||
/// Error code used when a Config document does not satisfy its registered JSON Schema.
|
||||
pub const ERROR_CODE_SCHEMA_VALIDATION_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("config", "schema_validation_failed");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// file: crates/ksp-config-lib/src/lib.rs
|
||||
// version: 12
|
||||
// version: 14
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
#![forbid(unsafe_code)]
|
||||
@@ -25,8 +26,6 @@ mod registry;
|
||||
mod sensitivity;
|
||||
mod transport;
|
||||
|
||||
pub(crate) use self::constants::TRACING_TARGET;
|
||||
|
||||
/// Bootstrap argument used to replace the configuration document root.
|
||||
pub use self::bootstrap::ARG_CFG_PATH;
|
||||
/// Bootstrap argument used to replace the schema root.
|
||||
@@ -171,3 +170,22 @@ pub use self::sensitivity::ResolvedConfigJson;
|
||||
pub use self::sensitivity::ResolvedConfigText;
|
||||
/// Effective standard HTTP Transport configuration mapped to `ksp_onchain_transport_lib::HttpTransportSettings`.
|
||||
pub use self::transport::ResolvedTransportConfig;
|
||||
|
||||
/// Validates composite document contract.
|
||||
pub(crate) use self::composite::validate_composite_document_contract;
|
||||
/// Owning tracing target for events emitted by the Config crate.
|
||||
pub(crate) use self::constants::TRACING_TARGET;
|
||||
/// Executes the crate-internal parse dotenv content operation for the owning module.
|
||||
pub(crate) use self::environment::parse_dotenv_content;
|
||||
/// Validates supported variable name.
|
||||
pub(crate) use self::environment::validate_supported_variable_name;
|
||||
/// Executes the crate-internal atomic write operation for the owning module.
|
||||
pub(crate) use self::persistence::atomic_write;
|
||||
/// Executes the crate-internal atomic write private operation for the owning module.
|
||||
pub(crate) use self::persistence::atomic_write_private;
|
||||
/// Loads resolved profile with source.
|
||||
pub(crate) use self::profile::load_resolved_profile_with_source;
|
||||
/// Validates document profile contract.
|
||||
pub(crate) use self::profile::validate_document_profile_contract;
|
||||
/// Executes the crate-internal build registry operation for the owning module.
|
||||
pub(crate) use self::registry::build_registry;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-config-lib/src/management.rs
|
||||
// version: 4
|
||||
// version: 6
|
||||
|
||||
/// Raw source of one registered Config document read for explicit management/correction.
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
@@ -727,7 +727,7 @@ impl ConfigManagement {
|
||||
/// Authentication/authorization of the human user belongs to the calling application. Calling this method is the explicit Config boundary that opts into
|
||||
/// real-value access; the returned value must never be logged.
|
||||
pub fn reveal_effective_environment_value(&self, variable_name: &str) -> ksp_core_lib::Result<std::option::Option<String>> {
|
||||
let validation = crate::environment::validate_supported_variable_name(variable_name);
|
||||
let validation = crate::validate_supported_variable_name(variable_name);
|
||||
if let std::result::Result::Err(error) = validation {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
@@ -744,7 +744,7 @@ impl ConfigManagement {
|
||||
|
||||
/// Explicitly reveals the real persisted `.env` value for management display/editing.
|
||||
pub fn reveal_dotenv_value(&self, variable_name: &str) -> ksp_core_lib::Result<std::option::Option<String>> {
|
||||
let validation = crate::environment::validate_supported_variable_name(variable_name);
|
||||
let validation = crate::validate_supported_variable_name(variable_name);
|
||||
if let std::result::Result::Err(error) = validation {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
@@ -758,7 +758,7 @@ impl ConfigManagement {
|
||||
|
||||
/// Creates or updates one supported KSP/KSPB `.env` entry atomically without mutating the inherited process environment.
|
||||
pub fn set_dotenv_value(&self, variable_name: &str, value: &str) -> ksp_core_lib::Result<ConfigEnvironmentChangeReport> {
|
||||
let validation = crate::environment::validate_supported_variable_name(variable_name);
|
||||
let validation = crate::validate_supported_variable_name(variable_name);
|
||||
if let std::result::Result::Err(error) = validation {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
@@ -781,7 +781,7 @@ impl ConfigManagement {
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if candidate != content {
|
||||
let write = crate::persistence::atomic_write_private(self.dotenv_path.as_path(), candidate.as_bytes());
|
||||
let write = crate::atomic_write_private(self.dotenv_path.as_path(), candidate.as_bytes());
|
||||
match write {
|
||||
std::result::Result::Ok(()) => {},
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
@@ -797,7 +797,7 @@ impl ConfigManagement {
|
||||
|
||||
/// Removes one supported KSP/KSPB `.env` entry atomically without mutating the inherited process environment.
|
||||
pub fn remove_dotenv_value(&self, variable_name: &str) -> ksp_core_lib::Result<ConfigEnvironmentChangeReport> {
|
||||
let validation = crate::environment::validate_supported_variable_name(variable_name);
|
||||
let validation = crate::validate_supported_variable_name(variable_name);
|
||||
if let std::result::Result::Err(error) = validation {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
@@ -820,7 +820,7 @@ impl ConfigManagement {
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if candidate != content {
|
||||
let write = crate::persistence::atomic_write_private(self.dotenv_path.as_path(), candidate.as_bytes());
|
||||
let write = crate::atomic_write_private(self.dotenv_path.as_path(), candidate.as_bytes());
|
||||
match write {
|
||||
std::result::Result::Ok(()) => {},
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
@@ -838,6 +838,7 @@ impl ConfigManagement {
|
||||
return crate::ConfigEnvironment::load_from_dotenv_path(self.dotenv_path.as_path());
|
||||
}
|
||||
|
||||
/// Executes the crate-internal with dotenv path operation for `ConfigManagement`.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn with_dotenv_path(engine: crate::ConfigDocumentEngine, dotenv_path: std::path::PathBuf) -> Self {
|
||||
return Self { engine, dotenv_path };
|
||||
@@ -921,7 +922,7 @@ fn read_dotenv_source(path: &std::path::Path) -> ksp_core_lib::Result<String> {
|
||||
let content = std::fs::read_to_string(path);
|
||||
return match content {
|
||||
std::result::Result::Ok(value) => {
|
||||
let validation = crate::environment::parse_dotenv_content(path, value.as_str());
|
||||
let validation = crate::parse_dotenv_content(path, value.as_str());
|
||||
match validation {
|
||||
std::result::Result::Ok(_) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(error),
|
||||
@@ -937,7 +938,7 @@ fn read_dotenv_source(path: &std::path::Path) -> ksp_core_lib::Result<String> {
|
||||
}
|
||||
|
||||
fn update_dotenv_source(path: &std::path::Path, source: &str, variable_name: &str, replacement: std::option::Option<&str>) -> ksp_core_lib::Result<String> {
|
||||
let validation = crate::environment::parse_dotenv_content(path, source);
|
||||
let validation = crate::parse_dotenv_content(path, source);
|
||||
if let std::result::Result::Err(error) = validation {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
@@ -967,7 +968,7 @@ fn update_dotenv_source(path: &std::path::Path, source: &str, variable_name: &st
|
||||
if !candidate.is_empty() {
|
||||
candidate.push('\n');
|
||||
}
|
||||
let candidate_validation = crate::environment::parse_dotenv_content(path, candidate.as_str());
|
||||
let candidate_validation = crate::parse_dotenv_content(path, candidate.as_str());
|
||||
return match candidate_validation {
|
||||
std::result::Result::Ok(_) => std::result::Result::Ok(candidate),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(error),
|
||||
@@ -1030,7 +1031,7 @@ fn persist_document_source(file_id: &crate::ConfigFileId, path: &std::path::Path
|
||||
if existing.as_slice() == source {
|
||||
return std::result::Result::Ok(ConfigDocumentChangeReport { source_changed: false, reload_required: false });
|
||||
}
|
||||
let write = crate::persistence::atomic_write(path, source);
|
||||
let write = crate::atomic_write(path, source);
|
||||
if let std::result::Result::Err(error) = write {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
// file: crates/ksp-config-lib/src/persistence.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
static NEXT_TEMPORARY_FILE_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
|
||||
|
||||
/// Executes the crate-internal atomic write operation for the owning module.
|
||||
pub(crate) fn atomic_write(path: &std::path::Path, content: &[u8]) -> ksp_core_lib::Result<()> {
|
||||
return atomic_write_with_policy(path, content, false);
|
||||
}
|
||||
|
||||
/// Executes the crate-internal atomic write private operation for the owning module.
|
||||
pub(crate) fn atomic_write_private(path: &std::path::Path, content: &[u8]) -> ksp_core_lib::Result<()> {
|
||||
return atomic_write_with_policy(path, content, true);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-config-lib/src/profile.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
/// Origin of one top-level value in a resolved standard Config profile.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
@@ -135,6 +135,7 @@ impl crate::ConfigDocumentEngine {
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads resolved profile with source.
|
||||
pub(crate) fn load_resolved_profile_with_source(
|
||||
engine: &crate::ConfigDocumentEngine,
|
||||
file_id: &crate::ConfigFileId,
|
||||
@@ -153,6 +154,7 @@ pub(crate) fn load_resolved_profile_with_source(
|
||||
return resolve_document_profile(&document, requested_profile, source);
|
||||
}
|
||||
|
||||
/// Validates document profile contract.
|
||||
pub(crate) fn validate_document_profile_contract(document: &crate::ConfigJsonDocument) -> ksp_core_lib::Result<()> {
|
||||
let root = match document.value().as_object() {
|
||||
std::option::Option::Some(value) => value,
|
||||
|
||||
@@ -1,18 +1,10 @@
|
||||
// file: crates/ksp-config-lib/src/registry.rs
|
||||
// version: 5
|
||||
// version: 7
|
||||
|
||||
/// Bootstrap argument used to replace a known Config filename mapping.
|
||||
pub const ARG_FILE_MAP: &str = "--filemap";
|
||||
/// Logical file identifier for the standard Logging configuration document.
|
||||
pub const FILE_ID_STD_LOGGING: &str = "cfg.std.logging";
|
||||
/// Logical file identifier for the standard Logging JSON Schema document.
|
||||
pub const FILE_ID_SCHEMA_STD_LOGGING: &str = "schema.std.logging";
|
||||
/// Logical file identifier for the standard HTTP Transport configuration document.
|
||||
pub const FILE_ID_STD_TRANSPORT: &str = "cfg.std.transport";
|
||||
/// Logical file identifier for the standard HTTP Transport JSON Schema document.
|
||||
pub const FILE_ID_SCHEMA_STD_TRANSPORT: &str = "schema.std.transport";
|
||||
/// Logical file identifier for the generic composite JSON Schema document.
|
||||
pub const FILE_ID_SCHEMA_COMPOSITE: &str = "schema.composite";
|
||||
/// Default physical filename for the generic composite JSON Schema document.
|
||||
pub const DEFAULT_COMPOSITE_SCHEMA_FILENAME: &str = "composite.schema.json";
|
||||
/// Default physical filename for the standard Logging configuration document.
|
||||
pub const DEFAULT_STD_LOGGING_FILENAME: &str = "std.logging.json";
|
||||
/// Default physical filename for the standard Logging JSON Schema document.
|
||||
@@ -21,8 +13,16 @@ pub const DEFAULT_STD_LOGGING_SCHEMA_FILENAME: &str = "std.logging.schema.json";
|
||||
pub const DEFAULT_STD_TRANSPORT_FILENAME: &str = "std.transport.json";
|
||||
/// Default physical filename for the standard HTTP Transport JSON Schema document.
|
||||
pub const DEFAULT_STD_TRANSPORT_SCHEMA_FILENAME: &str = "std.transport.schema.json";
|
||||
/// Default physical filename for the generic composite JSON Schema document.
|
||||
pub const DEFAULT_COMPOSITE_SCHEMA_FILENAME: &str = "composite.schema.json";
|
||||
/// Logical file identifier for the generic composite JSON Schema document.
|
||||
pub const FILE_ID_SCHEMA_COMPOSITE: &str = "schema.composite";
|
||||
/// Logical file identifier for the standard Logging JSON Schema document.
|
||||
pub const FILE_ID_SCHEMA_STD_LOGGING: &str = "schema.std.logging";
|
||||
/// Logical file identifier for the standard HTTP Transport JSON Schema document.
|
||||
pub const FILE_ID_SCHEMA_STD_TRANSPORT: &str = "schema.std.transport";
|
||||
/// Logical file identifier for the standard Logging configuration document.
|
||||
pub const FILE_ID_STD_LOGGING: &str = "cfg.std.logging";
|
||||
/// Logical file identifier for the standard HTTP Transport configuration document.
|
||||
pub const FILE_ID_STD_TRANSPORT: &str = "cfg.std.transport";
|
||||
|
||||
/// Stable logical identifier for a Config-managed file.
|
||||
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||
@@ -89,6 +89,7 @@ impl ConfigFileDescriptor {
|
||||
return self.schema_file_id.as_ref();
|
||||
}
|
||||
|
||||
/// Creates a new `ConfigFileDescriptor` value.
|
||||
pub(crate) fn new(
|
||||
file_id: &'static str,
|
||||
kind: ConfigFileKind,
|
||||
@@ -165,7 +166,7 @@ impl ConfigFileRegistry {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return build_registry([logging, logging_schema, transport, transport_schema, composite_schema]);
|
||||
return crate::build_registry([logging, logging_schema, transport, transport_schema, composite_schema]);
|
||||
}
|
||||
|
||||
/// Creates the default registry and applies repeatable `--filemap=<file_id>=<filename>` overrides from raw process arguments.
|
||||
@@ -249,6 +250,7 @@ impl ConfigFileRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes the crate-internal build registry operation for the owning module.
|
||||
pub(crate) fn build_registry<const N: usize>(descriptors: [ConfigFileDescriptor; N]) -> ksp_core_lib::Result<ConfigFileRegistry> {
|
||||
let mut registry = ConfigFileRegistry { descriptors: std::collections::BTreeMap::new() };
|
||||
for descriptor in descriptors {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-config-lib/src/sensitivity.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
/// Replacement used for secret environment fragments in safe diagnostic representations.
|
||||
pub const REDACTED_CONFIG_VALUE: &str = "********";
|
||||
@@ -18,7 +18,7 @@ pub enum ConfigSensitivity {
|
||||
impl ConfigSensitivity {
|
||||
/// Classifies one supported KSP/KSPB environment variable by its namespace.
|
||||
pub fn from_variable_name(variable_name: &str) -> ksp_core_lib::Result<Self> {
|
||||
let validation = crate::environment::validate_supported_variable_name(variable_name);
|
||||
let validation = crate::validate_supported_variable_name(variable_name);
|
||||
if let std::result::Result::Err(error) = validation {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
@@ -103,6 +103,7 @@ pub struct ResolvedConfigText {
|
||||
}
|
||||
|
||||
impl ResolvedConfigText {
|
||||
/// Creates a new `ResolvedConfigText` value.
|
||||
pub(crate) fn new(value: String, safe_value: String, sensitivity: ConfigSensitivity, provenance: std::vec::Vec<ConfigValueProvenance>) -> Self {
|
||||
return Self { value, safe_value, sensitivity, provenance };
|
||||
}
|
||||
@@ -153,6 +154,7 @@ pub struct ResolvedConfigJson {
|
||||
}
|
||||
|
||||
impl ResolvedConfigJson {
|
||||
/// Creates a new `ResolvedConfigJson` value.
|
||||
pub(crate) fn new(
|
||||
value: serde_json::Value,
|
||||
safe_value: serde_json::Value,
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
// file: crates/ksp-config-lib/unit_tests/bootstrap.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
#[test]
|
||||
fn defaults_use_hardcoded_ksp_roots() {
|
||||
let result = super::ConfigBootstrapOptions::defaults();
|
||||
let result = crate::ConfigBootstrapOptions::defaults();
|
||||
assert!(result.is_ok(), "default bootstrap paths should be valid: {result:?}");
|
||||
if let std::result::Result::Ok(options) = result {
|
||||
assert_eq!(options.cfg_path(), std::path::Path::new(super::DEFAULT_CFG_PATH));
|
||||
assert_eq!(options.schema_path(), std::path::Path::new(super::DEFAULT_SCHEMA_PATH));
|
||||
assert_eq!(options.cfg_path(), std::path::Path::new(crate::DEFAULT_CFG_PATH));
|
||||
assert_eq!(options.schema_path(), std::path::Path::new(crate::DEFAULT_SCHEMA_PATH));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cfg_path_override_keeps_schema_default() {
|
||||
let defaults = super::ConfigBootstrapOptions::defaults();
|
||||
let defaults = crate::ConfigBootstrapOptions::defaults();
|
||||
assert!(defaults.is_ok(), "default bootstrap paths should be valid: {defaults:?}");
|
||||
if let std::result::Result::Ok(options) = defaults {
|
||||
let result = options.with_cfg_path("custom-config");
|
||||
assert!(result.is_ok(), "cfg path override should be valid: {result:?}");
|
||||
if let std::result::Result::Ok(options) = result {
|
||||
assert_eq!(options.cfg_path(), std::path::Path::new("custom-config"));
|
||||
assert_eq!(options.schema_path(), std::path::Path::new(super::DEFAULT_SCHEMA_PATH));
|
||||
assert_eq!(options.schema_path(), std::path::Path::new(crate::DEFAULT_SCHEMA_PATH));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn schema_path_override_keeps_cfg_default() {
|
||||
let defaults = super::ConfigBootstrapOptions::defaults();
|
||||
let defaults = crate::ConfigBootstrapOptions::defaults();
|
||||
assert!(defaults.is_ok(), "default bootstrap paths should be valid: {defaults:?}");
|
||||
if let std::result::Result::Ok(options) = defaults {
|
||||
let result = options.with_schema_path("custom-schemas");
|
||||
assert!(result.is_ok(), "schema path override should be valid: {result:?}");
|
||||
if let std::result::Result::Ok(options) = result {
|
||||
assert_eq!(options.cfg_path(), std::path::Path::new(super::DEFAULT_CFG_PATH));
|
||||
assert_eq!(options.cfg_path(), std::path::Path::new(crate::DEFAULT_CFG_PATH));
|
||||
assert_eq!(options.schema_path(), std::path::Path::new("custom-schemas"));
|
||||
}
|
||||
}
|
||||
@@ -42,21 +42,21 @@ fn schema_path_override_keeps_cfg_default() {
|
||||
#[test]
|
||||
fn cfg_cli_override_keeps_schema_default() {
|
||||
let args = [std::ffi::OsString::from("ksp-app"), std::ffi::OsString::from("--cfgpath=cli-config")];
|
||||
let result = super::ConfigBootstrapOptions::from_args(&args);
|
||||
let result = crate::ConfigBootstrapOptions::from_args(&args);
|
||||
assert!(result.is_ok(), "cfg CLI override should parse: {result:?}");
|
||||
if let std::result::Result::Ok(options) = result {
|
||||
assert_eq!(options.cfg_path(), std::path::Path::new("cli-config"));
|
||||
assert_eq!(options.schema_path(), std::path::Path::new(super::DEFAULT_SCHEMA_PATH));
|
||||
assert_eq!(options.schema_path(), std::path::Path::new(crate::DEFAULT_SCHEMA_PATH));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn schema_cli_override_keeps_cfg_default() {
|
||||
let args = [std::ffi::OsString::from("ksp-app"), std::ffi::OsString::from("--schemapath=cli-schemas")];
|
||||
let result = super::ConfigBootstrapOptions::from_args(&args);
|
||||
let result = crate::ConfigBootstrapOptions::from_args(&args);
|
||||
assert!(result.is_ok(), "schema CLI override should parse: {result:?}");
|
||||
if let std::result::Result::Ok(options) = result {
|
||||
assert_eq!(options.cfg_path(), std::path::Path::new(super::DEFAULT_CFG_PATH));
|
||||
assert_eq!(options.cfg_path(), std::path::Path::new(crate::DEFAULT_CFG_PATH));
|
||||
assert_eq!(options.schema_path(), std::path::Path::new("cli-schemas"));
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,7 @@ fn parser_accepts_inline_and_separate_forms_and_last_value_wins() {
|
||||
std::ffi::OsString::from("--schemapath"),
|
||||
std::ffi::OsString::from("second-schemas"),
|
||||
];
|
||||
let result = super::ConfigBootstrapOptions::from_args(&args);
|
||||
let result = crate::ConfigBootstrapOptions::from_args(&args);
|
||||
assert!(result.is_ok(), "bootstrap arguments should parse: {result:?}");
|
||||
if let std::result::Result::Ok(options) = result {
|
||||
assert_eq!(options.cfg_path(), std::path::Path::new("second-config"));
|
||||
@@ -84,7 +84,7 @@ fn parser_accepts_inline_and_separate_forms_and_last_value_wins() {
|
||||
#[test]
|
||||
fn parser_reports_missing_separate_value() {
|
||||
let args = [std::ffi::OsString::from("ksp-app"), std::ffi::OsString::from("--cfgpath")];
|
||||
let result = super::ConfigBootstrapOptions::from_args(&args);
|
||||
let result = crate::ConfigBootstrapOptions::from_args(&args);
|
||||
assert!(result.is_err(), "missing value must be rejected");
|
||||
if let std::result::Result::Err(error) = result {
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_BOOTSTRAP_ARGUMENT_MISSING_VALUE);
|
||||
@@ -94,7 +94,7 @@ fn parser_reports_missing_separate_value() {
|
||||
#[test]
|
||||
fn parser_reports_another_option_as_missing_separate_value() {
|
||||
let args = [std::ffi::OsString::from("ksp-app"), std::ffi::OsString::from("--cfgpath"), std::ffi::OsString::from("--other-option")];
|
||||
let result = super::ConfigBootstrapOptions::from_args(&args);
|
||||
let result = crate::ConfigBootstrapOptions::from_args(&args);
|
||||
assert!(result.is_err(), "another option must not become a bootstrap path value");
|
||||
if let std::result::Result::Err(error) = result {
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_BOOTSTRAP_ARGUMENT_MISSING_VALUE);
|
||||
@@ -104,7 +104,7 @@ fn parser_reports_another_option_as_missing_separate_value() {
|
||||
#[test]
|
||||
fn empty_inline_path_is_rejected() {
|
||||
let args = [std::ffi::OsString::from("ksp-app"), std::ffi::OsString::from("--schemapath=")];
|
||||
let result = super::ConfigBootstrapOptions::from_args(&args);
|
||||
let result = crate::ConfigBootstrapOptions::from_args(&args);
|
||||
assert!(result.is_err(), "empty path must be rejected");
|
||||
if let std::result::Result::Err(error) = result {
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_BOOTSTRAP_INVALID_PATH);
|
||||
@@ -113,7 +113,7 @@ fn empty_inline_path_is_rejected() {
|
||||
|
||||
#[test]
|
||||
fn explicit_programmatic_paths_do_not_depend_on_default_roots() {
|
||||
let result = super::ConfigBootstrapOptions::from_paths("programmatic-config", "programmatic-schemas");
|
||||
let result = crate::ConfigBootstrapOptions::from_paths("programmatic-config", "programmatic-schemas");
|
||||
assert!(result.is_ok(), "explicit programmatic paths should be valid: {result:?}");
|
||||
if let std::result::Result::Ok(options) = result {
|
||||
assert_eq!(options.cfg_path(), std::path::Path::new("programmatic-config"));
|
||||
@@ -126,7 +126,7 @@ fn existing_non_directory_path_is_rejected() {
|
||||
let fixture = unique_fixture_path("existing-file");
|
||||
let create = std::fs::write(fixture.as_path(), b"fixture");
|
||||
assert!(create.is_ok(), "fixture file should be creatable: {create:?}");
|
||||
let result = super::ConfigBootstrapOptions::from_paths(fixture.as_path(), "programmatic-schemas");
|
||||
let result = crate::ConfigBootstrapOptions::from_paths(fixture.as_path(), "programmatic-schemas");
|
||||
let remove = std::fs::remove_file(fixture.as_path());
|
||||
assert!(remove.is_ok(), "fixture file should be removable: {remove:?}");
|
||||
assert!(result.is_err(), "existing file must not be accepted as a bootstrap directory");
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// file: crates/ksp-config-lib/unit_tests/composite.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
const TEST_COMPOSITE_FILE_ID: &str = "cfg.composite.test";
|
||||
const TEST_COMPOSITE_FILENAME: &str = "examples/composite.example.json";
|
||||
const TEST_COMPOSITE_FILE_ID: &str = "cfg.composite.test";
|
||||
|
||||
#[test]
|
||||
fn fixture_composite_example_resolves_default_document_profile() {
|
||||
@@ -197,7 +197,7 @@ fn test_registry(composite_filename: &'static str) -> ksp_core_lib::Result<crate
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return crate::registry::build_registry([logging, logging_schema, composite_schema, composite]);
|
||||
return crate::build_registry([logging, logging_schema, composite_schema, composite]);
|
||||
}
|
||||
|
||||
fn prepare_fixture(fixture: &FixtureRoots, composite: &str) -> std::io::Result<()> {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-config-lib/unit_tests/document.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
#[test]
|
||||
fn committed_logging_document_passes_registered_schema_and_semantic_validation() {
|
||||
@@ -11,7 +11,7 @@ fn committed_logging_document_passes_registered_schema_and_semantic_validation()
|
||||
assert!(registry.is_ok(), "default registry should be valid: {registry:?}");
|
||||
assert!(file_id.is_ok(), "logging file_id should be valid: {file_id:?}");
|
||||
if let (std::result::Result::Ok(bootstrap), std::result::Result::Ok(registry), std::result::Result::Ok(file_id)) = (bootstrap, registry, file_id) {
|
||||
let engine = super::ConfigDocumentEngine::new(bootstrap, registry);
|
||||
let engine = crate::ConfigDocumentEngine::new(bootstrap, registry);
|
||||
let document = engine.load_validated_document(&file_id);
|
||||
assert!(document.is_ok(), "committed std.logging.json should validate: {document:?}");
|
||||
if let std::result::Result::Ok(document) = document {
|
||||
@@ -228,7 +228,7 @@ fn valid_logging_profile(profile_id: &str, output_id: &str) -> String {
|
||||
);
|
||||
}
|
||||
|
||||
fn load_fixture(fixture: &FixtureRoots) -> ksp_core_lib::Result<super::ConfigJsonDocument> {
|
||||
fn load_fixture(fixture: &FixtureRoots) -> ksp_core_lib::Result<crate::ConfigJsonDocument> {
|
||||
let bootstrap = crate::ConfigBootstrapOptions::from_paths(fixture.config.as_path(), fixture.schemas.as_path());
|
||||
let bootstrap = match bootstrap {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -244,7 +244,7 @@ fn load_fixture(fixture: &FixtureRoots) -> ksp_core_lib::Result<super::ConfigJso
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let engine = super::ConfigDocumentEngine::new(bootstrap, registry);
|
||||
let engine = crate::ConfigDocumentEngine::new(bootstrap, registry);
|
||||
return engine.load_validated_document(&file_id);
|
||||
}
|
||||
|
||||
@@ -278,7 +278,7 @@ fn valid_minimal_logging_schema() -> &'static str {
|
||||
}"#;
|
||||
}
|
||||
|
||||
fn assert_error_code(result: ksp_core_lib::Result<super::ConfigJsonDocument>, expected: ksp_core_lib::ErrorCode) {
|
||||
fn assert_error_code(result: ksp_core_lib::Result<crate::ConfigJsonDocument>, expected: ksp_core_lib::ErrorCode) {
|
||||
assert!(result.is_err(), "fixture should fail with {expected:?}: {result:?}");
|
||||
if let std::result::Result::Err(error) = result {
|
||||
assert_eq!(error.code(), expected);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-config-lib/unit_tests/environment.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
#[test]
|
||||
fn process_environment_wins_over_dotenv_and_fallback_even_when_empty() {
|
||||
@@ -7,7 +7,7 @@ fn process_environment_wins_over_dotenv_and_fallback_even_when_empty() {
|
||||
process.insert("KSP_LOGS_DIRECTORY".to_owned(), String::new());
|
||||
let mut dotenv = std::collections::BTreeMap::<String, String>::new();
|
||||
dotenv.insert("KSP_LOGS_DIRECTORY".to_owned(), "dotenv-logs".to_owned());
|
||||
let environment = super::ConfigEnvironment::from_maps(process, dotenv);
|
||||
let environment = crate::ConfigEnvironment::from_maps(process, dotenv);
|
||||
let resolved = environment.resolve_variable("KSP_LOGS_DIRECTORY", std::option::Option::Some("fallback-logs"));
|
||||
assert!(resolved.is_ok(), "process value should resolve");
|
||||
let resolved = match resolved {
|
||||
@@ -15,7 +15,7 @@ fn process_environment_wins_over_dotenv_and_fallback_even_when_empty() {
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
assert_eq!(resolved.value(), "");
|
||||
assert_eq!(resolved.source(), super::ConfigEnvironmentSource::Process);
|
||||
assert_eq!(resolved.source(), crate::ConfigEnvironmentSource::Process);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -23,7 +23,7 @@ fn dotenv_wins_over_fallback_when_process_value_is_absent() {
|
||||
let process = std::collections::BTreeMap::<String, String>::new();
|
||||
let mut dotenv = std::collections::BTreeMap::<String, String>::new();
|
||||
dotenv.insert("KSP_LOGS_DIRECTORY".to_owned(), "dotenv-logs".to_owned());
|
||||
let environment = super::ConfigEnvironment::from_maps(process, dotenv);
|
||||
let environment = crate::ConfigEnvironment::from_maps(process, dotenv);
|
||||
let resolved = environment.resolve_variable("KSP_LOGS_DIRECTORY", std::option::Option::Some("fallback-logs"));
|
||||
assert!(resolved.is_ok(), "dotenv value should resolve");
|
||||
let resolved = match resolved {
|
||||
@@ -31,25 +31,25 @@ fn dotenv_wins_over_fallback_when_process_value_is_absent() {
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
assert_eq!(resolved.value(), "dotenv-logs");
|
||||
assert_eq!(resolved.source(), super::ConfigEnvironmentSource::DotEnv);
|
||||
assert_eq!(resolved.source(), crate::ConfigEnvironmentSource::DotEnv);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_dotenv_value_is_defined_and_beats_fallback() {
|
||||
let mut dotenv = std::collections::BTreeMap::<String, String>::new();
|
||||
dotenv.insert("KSP_LOGS_DIRECTORY".to_owned(), String::new());
|
||||
let environment = super::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), dotenv);
|
||||
let environment = crate::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), dotenv);
|
||||
let resolved = environment.resolve_variable("KSP_LOGS_DIRECTORY", std::option::Option::Some("fallback-logs"));
|
||||
assert!(resolved.is_ok(), "empty dotenv value should resolve");
|
||||
if let std::result::Result::Ok(resolved) = resolved {
|
||||
assert_eq!(resolved.value(), "");
|
||||
assert_eq!(resolved.source(), super::ConfigEnvironmentSource::DotEnv);
|
||||
assert_eq!(resolved.source(), crate::ConfigEnvironmentSource::DotEnv);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fallback_is_used_only_when_external_sources_are_absent() {
|
||||
let environment = super::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
|
||||
let environment = crate::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
|
||||
let resolved = environment.resolve_variable("KSP_LOGS_DIRECTORY", std::option::Option::Some("fallback-logs"));
|
||||
assert!(resolved.is_ok(), "fallback should resolve");
|
||||
let resolved = match resolved {
|
||||
@@ -57,12 +57,12 @@ fn fallback_is_used_only_when_external_sources_are_absent() {
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
assert_eq!(resolved.value(), "fallback-logs");
|
||||
assert_eq!(resolved.source(), super::ConfigEnvironmentSource::Fallback);
|
||||
assert_eq!(resolved.source(), crate::ConfigEnvironmentSource::Fallback);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_variable_without_fallback_is_a_distinct_error() {
|
||||
let environment = super::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
|
||||
let environment = crate::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
|
||||
let resolved = environment.resolve_variable("KSP_LOGS_DIRECTORY", std::option::Option::None);
|
||||
let error = match resolved {
|
||||
std::result::Result::Ok(_) => return,
|
||||
@@ -78,7 +78,7 @@ fn ksp_and_kspb_namespaces_are_supported_but_external_names_are_rejected() {
|
||||
process.insert("KSP_LOGS_DIRECTORY".to_owned(), "logs".to_owned());
|
||||
let bot_variable = ["KSPB_", "TEST_KEY"].concat();
|
||||
process.insert(bot_variable.clone(), "hidden".to_owned());
|
||||
let environment = super::ConfigEnvironment::from_maps(process, std::collections::BTreeMap::new());
|
||||
let environment = crate::ConfigEnvironment::from_maps(process, std::collections::BTreeMap::new());
|
||||
assert!(environment.resolve_variable("KSP_LOGS_DIRECTORY", std::option::Option::None).is_ok());
|
||||
assert!(environment.resolve_variable(bot_variable.as_str(), std::option::Option::None).is_ok());
|
||||
let external = environment.resolve_variable("OTHER_NETWORK", std::option::Option::None);
|
||||
@@ -91,7 +91,7 @@ fn ksp_and_kspb_namespaces_are_supported_but_external_names_are_rejected() {
|
||||
|
||||
#[test]
|
||||
fn text_resolver_supports_multiple_placeholders_and_literal_fallbacks() {
|
||||
let environment = super::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
|
||||
let environment = crate::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
|
||||
let resolved = environment.resolve_text("logs=${KSP_LOGS_DIRECTORY:-logs};second=${KSP_LOGS_DIRECTORY:-other}");
|
||||
assert!(resolved.is_ok(), "multiple placeholders should resolve");
|
||||
if let std::result::Result::Ok(resolved) = resolved {
|
||||
@@ -101,7 +101,7 @@ fn text_resolver_supports_multiple_placeholders_and_literal_fallbacks() {
|
||||
|
||||
#[test]
|
||||
fn malformed_or_nested_placeholders_are_rejected() {
|
||||
let environment = super::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
|
||||
let environment = crate::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
|
||||
let unclosed = environment.resolve_text("${KSP_LOGS_DIRECTORY");
|
||||
let unclosed = match unclosed {
|
||||
std::result::Result::Ok(_) => return,
|
||||
@@ -120,7 +120,7 @@ fn malformed_or_nested_placeholders_are_rejected() {
|
||||
fn json_resolver_walks_objects_and_arrays_without_changing_keys() {
|
||||
let mut dotenv = std::collections::BTreeMap::<String, String>::new();
|
||||
dotenv.insert("KSP_LOGS_DIRECTORY".to_owned(), "runtime-logs".to_owned());
|
||||
let environment = super::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), dotenv);
|
||||
let environment = crate::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), dotenv);
|
||||
let source = serde_json::json!({"path": "${KSP_LOGS_DIRECTORY}", "items": [1, "${KSP_LOGS_DIRECTORY}"], "enabled": true});
|
||||
let resolved = environment.resolve_json(&source);
|
||||
assert!(resolved.is_ok(), "recursive JSON resolution should succeed");
|
||||
@@ -137,7 +137,7 @@ fn json_resolver_walks_objects_and_arrays_without_changing_keys() {
|
||||
fn dotenv_parser_supports_comments_export_quotes_empty_values_and_ignores_external_keys() {
|
||||
let path = std::path::Path::new("fixture.env");
|
||||
let content = "# comment\nexport KSP_LOGS_DIRECTORY = 'quoted logs'\nOTHER_TOOL=value\n";
|
||||
let parsed = super::parse_dotenv_content(path, content);
|
||||
let parsed = crate::parse_dotenv_content(path, content);
|
||||
assert!(parsed.is_ok(), "dotenv fixture should parse");
|
||||
let parsed = match parsed {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -149,7 +149,7 @@ fn dotenv_parser_supports_comments_export_quotes_empty_values_and_ignores_extern
|
||||
|
||||
#[test]
|
||||
fn dotenv_duplicate_ksp_key_is_rejected() {
|
||||
let parsed = super::parse_dotenv_content(std::path::Path::new("fixture.env"), "KSP_LOGS_DIRECTORY=one\nKSP_LOGS_DIRECTORY=two\n");
|
||||
let parsed = crate::parse_dotenv_content(std::path::Path::new("fixture.env"), "KSP_LOGS_DIRECTORY=one\nKSP_LOGS_DIRECTORY=two\n");
|
||||
let error = match parsed {
|
||||
std::result::Result::Ok(_) => return,
|
||||
std::result::Result::Err(error) => error,
|
||||
@@ -199,7 +199,7 @@ fn logging_fixture_profile_resolves_environment_fallback_without_changing_source
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let environment = super::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
|
||||
let environment = crate::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
|
||||
let effective = profile.resolve_effective_environment(&environment);
|
||||
assert!(effective.is_ok(), "committed Logging environment fallback should resolve");
|
||||
let effective = match effective {
|
||||
@@ -245,7 +245,7 @@ fn secret_environment_value_keeps_real_value_but_redacts_safe_and_debug_views()
|
||||
let canary = "KSP_SECRET_CANARY_91b7c6";
|
||||
let mut process = std::collections::BTreeMap::<String, String>::new();
|
||||
process.insert("KSP_SECRET_TEST_TOKEN".to_owned(), canary.to_owned());
|
||||
let environment = super::ConfigEnvironment::from_maps(process, std::collections::BTreeMap::new());
|
||||
let environment = crate::ConfigEnvironment::from_maps(process, std::collections::BTreeMap::new());
|
||||
let resolved = environment.resolve_variable("KSP_SECRET_TEST_TOKEN", std::option::Option::None);
|
||||
assert!(resolved.is_ok(), "secret process value should resolve");
|
||||
let resolved = match resolved {
|
||||
@@ -255,7 +255,7 @@ fn secret_environment_value_keeps_real_value_but_redacts_safe_and_debug_views()
|
||||
assert_eq!(resolved.value(), canary);
|
||||
assert_eq!(resolved.safe_value(), crate::REDACTED_CONFIG_VALUE);
|
||||
assert_eq!(resolved.sensitivity(), crate::ConfigSensitivity::Secret);
|
||||
assert_eq!(resolved.source(), super::ConfigEnvironmentSource::Process);
|
||||
assert_eq!(resolved.source(), crate::ConfigEnvironmentSource::Process);
|
||||
let debug = format!("{resolved:?}");
|
||||
assert!(!debug.contains(canary), "Debug must not reveal the secret canary");
|
||||
assert!(debug.contains(crate::REDACTED_CONFIG_VALUE));
|
||||
@@ -267,7 +267,7 @@ fn detailed_text_redacts_only_secret_segments_and_keeps_ordered_provenance() {
|
||||
let mut process = std::collections::BTreeMap::<String, String>::new();
|
||||
process.insert("KSP_PUBLIC_HOST".to_owned(), "rpc.example.test".to_owned());
|
||||
process.insert("KSP_SECRET_TOKEN".to_owned(), secret.to_owned());
|
||||
let environment = super::ConfigEnvironment::from_maps(process, std::collections::BTreeMap::new());
|
||||
let environment = crate::ConfigEnvironment::from_maps(process, std::collections::BTreeMap::new());
|
||||
let resolved = environment.resolve_text_detailed("https://${KSP_PUBLIC_HOST}/?token=${KSP_SECRET_TOKEN}");
|
||||
assert!(resolved.is_ok(), "composed secret URL should resolve");
|
||||
let resolved = match resolved {
|
||||
@@ -288,7 +288,7 @@ fn detailed_text_redacts_only_secret_segments_and_keeps_ordered_provenance() {
|
||||
|
||||
#[test]
|
||||
fn secret_fallback_inherits_secret_sensitivity_and_is_redacted() {
|
||||
let environment = super::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
|
||||
let environment = crate::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
|
||||
let resolved = environment.resolve_text_detailed("token=${KSP_SECRET_TOKEN:-false-secret}");
|
||||
assert!(resolved.is_ok(), "secret fallback should resolve");
|
||||
let resolved = match resolved {
|
||||
@@ -298,7 +298,7 @@ fn secret_fallback_inherits_secret_sensitivity_and_is_redacted() {
|
||||
assert_eq!(resolved.value(), "token=false-secret");
|
||||
assert_eq!(resolved.safe_value(), "token=********");
|
||||
assert_eq!(resolved.sensitivity(), crate::ConfigSensitivity::Secret);
|
||||
assert_eq!(resolved.provenance()[1].environment_source(), std::option::Option::Some(super::ConfigEnvironmentSource::Fallback));
|
||||
assert_eq!(resolved.provenance()[1].environment_source(), std::option::Option::Some(crate::ConfigEnvironmentSource::Fallback));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -306,7 +306,7 @@ fn detailed_json_preserves_safe_tree_sensitivity_and_pointer_provenance() {
|
||||
let secret = "nested-secret-canary-2d11";
|
||||
let mut dotenv = std::collections::BTreeMap::<String, String>::new();
|
||||
dotenv.insert("KSP_SECRET_TOKEN".to_owned(), secret.to_owned());
|
||||
let environment = super::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), dotenv);
|
||||
let environment = crate::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), dotenv);
|
||||
let source = serde_json::json!({"transport": {"url": "https://host/?token=${KSP_SECRET_TOKEN}"}, "items": ["plain", 7]});
|
||||
let resolved = environment.resolve_json_detailed(&source);
|
||||
assert!(resolved.is_ok(), "detailed JSON should resolve");
|
||||
@@ -349,7 +349,7 @@ fn detailed_fixture_profile_environment_keeps_global_origin_and_adds_environment
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let environment = super::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
|
||||
let environment = crate::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
|
||||
let effective = profile.resolve_effective_environment_detailed(&environment);
|
||||
assert!(effective.is_ok(), "detailed committed Logging profile should resolve");
|
||||
let effective = match effective {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-config-lib/unit_tests/profile.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
#[test]
|
||||
fn fixture_default_profile_resolves_globals_profile_and_provenance() {
|
||||
@@ -15,9 +15,9 @@ fn fixture_default_profile_resolves_globals_profile_and_provenance() {
|
||||
if let (std::result::Result::Ok(document), std::result::Result::Ok(resolved)) = (document, resolved) {
|
||||
let default_profile = document.value().get("default_profile").and_then(serde_json::Value::as_str);
|
||||
assert_eq!(default_profile, std::option::Option::Some(resolved.profile_id()));
|
||||
assert_eq!(resolved.selection_source(), super::ConfigProfileSelectionSource::DefaultProfile);
|
||||
assert_eq!(resolved.origin("logs_directory"), std::option::Option::Some(super::ConfigValueOrigin::Global));
|
||||
assert_eq!(resolved.origin("default_filter"), std::option::Option::Some(super::ConfigValueOrigin::Profile));
|
||||
assert_eq!(resolved.selection_source(), crate::ConfigProfileSelectionSource::DefaultProfile);
|
||||
assert_eq!(resolved.origin("logs_directory"), std::option::Option::Some(crate::ConfigValueOrigin::Global));
|
||||
assert_eq!(resolved.origin("default_filter"), std::option::Option::Some(crate::ConfigValueOrigin::Profile));
|
||||
assert_eq!(
|
||||
resolved.profile().get("default_filter").and_then(serde_json::Value::as_str),
|
||||
resolved.effective().get("default_filter").and_then(serde_json::Value::as_str),
|
||||
@@ -45,7 +45,7 @@ fn explicit_profile_selection_is_distinct_from_default_selection() {
|
||||
assert!(resolved.is_ok(), "explicit committed profile should resolve: {resolved:?}");
|
||||
if let std::result::Result::Ok(resolved) = resolved {
|
||||
assert_eq!(resolved.profile_id(), profile_id);
|
||||
assert_eq!(resolved.selection_source(), super::ConfigProfileSelectionSource::Explicit);
|
||||
assert_eq!(resolved.selection_source(), crate::ConfigProfileSelectionSource::Explicit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +1,49 @@
|
||||
// file: crates/ksp-config-lib/unit_tests/registry.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
#[test]
|
||||
fn descriptors_expose_complete_registry_in_deterministic_file_id_order() {
|
||||
let registry = super::ConfigFileRegistry::defaults();
|
||||
let registry = crate::ConfigFileRegistry::defaults();
|
||||
assert!(registry.is_ok(), "default registry should be valid: {registry:?}");
|
||||
if let std::result::Result::Ok(registry) = registry {
|
||||
let descriptors: std::vec::Vec<&super::ConfigFileDescriptor> = registry.descriptors().collect();
|
||||
let descriptors: std::vec::Vec<&crate::ConfigFileDescriptor> = registry.descriptors().collect();
|
||||
assert_eq!(descriptors.len(), 5);
|
||||
assert_eq!(descriptors[0].file_id().as_str(), super::FILE_ID_STD_LOGGING);
|
||||
assert_eq!(descriptors[0].kind(), super::ConfigFileKind::Config);
|
||||
assert_eq!(descriptors[0].filename(), std::path::Path::new(super::DEFAULT_STD_LOGGING_FILENAME));
|
||||
assert_eq!(descriptors[0].file_id().as_str(), crate::FILE_ID_STD_LOGGING);
|
||||
assert_eq!(descriptors[0].kind(), crate::ConfigFileKind::Config);
|
||||
assert_eq!(descriptors[0].filename(), std::path::Path::new(crate::DEFAULT_STD_LOGGING_FILENAME));
|
||||
let logging_schema_file_id = descriptors[0].schema_file_id();
|
||||
assert!(logging_schema_file_id.is_some(), "logging descriptor should expose its validation schema");
|
||||
if let std::option::Option::Some(schema_file_id) = logging_schema_file_id {
|
||||
assert_eq!(schema_file_id.as_str(), super::FILE_ID_SCHEMA_STD_LOGGING);
|
||||
assert_eq!(schema_file_id.as_str(), crate::FILE_ID_SCHEMA_STD_LOGGING);
|
||||
}
|
||||
assert_eq!(descriptors[1].file_id().as_str(), super::FILE_ID_STD_TRANSPORT);
|
||||
assert_eq!(descriptors[1].kind(), super::ConfigFileKind::Config);
|
||||
assert_eq!(descriptors[1].filename(), std::path::Path::new(super::DEFAULT_STD_TRANSPORT_FILENAME));
|
||||
assert_eq!(descriptors[1].file_id().as_str(), crate::FILE_ID_STD_TRANSPORT);
|
||||
assert_eq!(descriptors[1].kind(), crate::ConfigFileKind::Config);
|
||||
assert_eq!(descriptors[1].filename(), std::path::Path::new(crate::DEFAULT_STD_TRANSPORT_FILENAME));
|
||||
let transport_schema_file_id = descriptors[1].schema_file_id();
|
||||
assert!(transport_schema_file_id.is_some(), "transport descriptor should expose its validation schema");
|
||||
if let std::option::Option::Some(schema_file_id) = transport_schema_file_id {
|
||||
assert_eq!(schema_file_id.as_str(), super::FILE_ID_SCHEMA_STD_TRANSPORT);
|
||||
assert_eq!(schema_file_id.as_str(), crate::FILE_ID_SCHEMA_STD_TRANSPORT);
|
||||
}
|
||||
assert_eq!(descriptors[2].file_id().as_str(), super::FILE_ID_SCHEMA_COMPOSITE);
|
||||
assert_eq!(descriptors[2].kind(), super::ConfigFileKind::Schema);
|
||||
assert_eq!(descriptors[3].file_id().as_str(), super::FILE_ID_SCHEMA_STD_LOGGING);
|
||||
assert_eq!(descriptors[3].kind(), super::ConfigFileKind::Schema);
|
||||
assert_eq!(descriptors[4].file_id().as_str(), super::FILE_ID_SCHEMA_STD_TRANSPORT);
|
||||
assert_eq!(descriptors[4].kind(), super::ConfigFileKind::Schema);
|
||||
assert_eq!(descriptors[2].file_id().as_str(), crate::FILE_ID_SCHEMA_COMPOSITE);
|
||||
assert_eq!(descriptors[2].kind(), crate::ConfigFileKind::Schema);
|
||||
assert_eq!(descriptors[3].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_LOGGING);
|
||||
assert_eq!(descriptors[3].kind(), crate::ConfigFileKind::Schema);
|
||||
assert_eq!(descriptors[4].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_TRANSPORT);
|
||||
assert_eq!(descriptors[4].kind(), crate::ConfigFileKind::Schema);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn descriptors_reflect_filename_overrides_without_changing_logical_metadata() {
|
||||
let registry = super::ConfigFileRegistry::defaults();
|
||||
let file_id = super::ConfigFileId::new(super::FILE_ID_STD_LOGGING);
|
||||
let registry = crate::ConfigFileRegistry::defaults();
|
||||
let file_id = crate::ConfigFileId::new(crate::FILE_ID_STD_LOGGING);
|
||||
assert!(registry.is_ok(), "default registry should be valid: {registry:?}");
|
||||
assert!(file_id.is_ok(), "logging file_id should be valid: {file_id:?}");
|
||||
if let (std::result::Result::Ok(registry), std::result::Result::Ok(file_id)) = (registry, file_id) {
|
||||
let overridden = registry.with_filename_override(&file_id, "profiles/desktop.logging.json");
|
||||
assert!(overridden.is_ok(), "filename override should remain valid: {overridden:?}");
|
||||
if let std::result::Result::Ok(overridden) = overridden {
|
||||
let mut found: std::option::Option<&super::ConfigFileDescriptor> = std::option::Option::None;
|
||||
let mut found: std::option::Option<&crate::ConfigFileDescriptor> = std::option::Option::None;
|
||||
for descriptor in overridden.descriptors() {
|
||||
if descriptor.file_id() == &file_id {
|
||||
found = std::option::Option::Some(descriptor);
|
||||
@@ -52,12 +52,12 @@ fn descriptors_reflect_filename_overrides_without_changing_logical_metadata() {
|
||||
assert!(found.is_some(), "public descriptor inventory should retain the overridden logging descriptor");
|
||||
if let std::option::Option::Some(descriptor) = found {
|
||||
assert_eq!(descriptor.file_id(), &file_id);
|
||||
assert_eq!(descriptor.kind(), super::ConfigFileKind::Config);
|
||||
assert_eq!(descriptor.kind(), crate::ConfigFileKind::Config);
|
||||
assert_eq!(descriptor.filename(), std::path::Path::new("profiles/desktop.logging.json"));
|
||||
let schema_file_id = descriptor.schema_file_id();
|
||||
assert!(schema_file_id.is_some(), "filename override should preserve schema association");
|
||||
if let std::option::Option::Some(schema_file_id) = schema_file_id {
|
||||
assert_eq!(schema_file_id.as_str(), super::FILE_ID_SCHEMA_STD_LOGGING);
|
||||
assert_eq!(schema_file_id.as_str(), crate::FILE_ID_SCHEMA_STD_LOGGING);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,11 +66,11 @@ fn descriptors_reflect_filename_overrides_without_changing_logical_metadata() {
|
||||
|
||||
#[test]
|
||||
fn defaults_register_logging_document_and_schema_with_distinct_roots() {
|
||||
let registry = super::ConfigFileRegistry::defaults();
|
||||
let registry = crate::ConfigFileRegistry::defaults();
|
||||
assert!(registry.is_ok(), "default registry should be valid: {registry:?}");
|
||||
if let std::result::Result::Ok(registry) = registry {
|
||||
let logging_id = super::ConfigFileId::new(super::FILE_ID_STD_LOGGING);
|
||||
let schema_id = super::ConfigFileId::new(super::FILE_ID_SCHEMA_STD_LOGGING);
|
||||
let logging_id = crate::ConfigFileId::new(crate::FILE_ID_STD_LOGGING);
|
||||
let schema_id = crate::ConfigFileId::new(crate::FILE_ID_SCHEMA_STD_LOGGING);
|
||||
assert!(logging_id.is_ok(), "logging file_id should be valid: {logging_id:?}");
|
||||
assert!(schema_id.is_ok(), "logging schema file_id should be valid: {schema_id:?}");
|
||||
if let (std::result::Result::Ok(logging_id), std::result::Result::Ok(schema_id)) = (logging_id, schema_id) {
|
||||
@@ -79,15 +79,15 @@ fn defaults_register_logging_document_and_schema_with_distinct_roots() {
|
||||
assert!(logging.is_ok(), "logging descriptor should exist: {logging:?}");
|
||||
assert!(schema.is_ok(), "logging schema descriptor should exist: {schema:?}");
|
||||
if let (std::result::Result::Ok(logging), std::result::Result::Ok(schema)) = (logging, schema) {
|
||||
assert_eq!(logging.kind(), super::ConfigFileKind::Config);
|
||||
assert_eq!(logging.filename(), std::path::Path::new(super::DEFAULT_STD_LOGGING_FILENAME));
|
||||
assert_eq!(logging.kind(), crate::ConfigFileKind::Config);
|
||||
assert_eq!(logging.filename(), std::path::Path::new(crate::DEFAULT_STD_LOGGING_FILENAME));
|
||||
let logging_schema = logging.schema_file_id();
|
||||
assert!(logging_schema.is_some(), "logging document should declare its validation schema");
|
||||
if let std::option::Option::Some(logging_schema) = logging_schema {
|
||||
assert_eq!(logging_schema, &schema_id);
|
||||
}
|
||||
assert_eq!(schema.kind(), super::ConfigFileKind::Schema);
|
||||
assert_eq!(schema.filename(), std::path::Path::new(super::DEFAULT_STD_LOGGING_SCHEMA_FILENAME));
|
||||
assert_eq!(schema.kind(), crate::ConfigFileKind::Schema);
|
||||
assert_eq!(schema.filename(), std::path::Path::new(crate::DEFAULT_STD_LOGGING_SCHEMA_FILENAME));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,11 +95,11 @@ fn defaults_register_logging_document_and_schema_with_distinct_roots() {
|
||||
|
||||
#[test]
|
||||
fn defaults_register_transport_document_and_schema_with_distinct_roots() {
|
||||
let registry = super::ConfigFileRegistry::defaults();
|
||||
let registry = crate::ConfigFileRegistry::defaults();
|
||||
assert!(registry.is_ok(), "default registry should be valid: {registry:?}");
|
||||
if let std::result::Result::Ok(registry) = registry {
|
||||
let transport_id = super::ConfigFileId::new(super::FILE_ID_STD_TRANSPORT);
|
||||
let schema_id = super::ConfigFileId::new(super::FILE_ID_SCHEMA_STD_TRANSPORT);
|
||||
let transport_id = crate::ConfigFileId::new(crate::FILE_ID_STD_TRANSPORT);
|
||||
let schema_id = crate::ConfigFileId::new(crate::FILE_ID_SCHEMA_STD_TRANSPORT);
|
||||
assert!(transport_id.is_ok(), "transport file_id should be valid: {transport_id:?}");
|
||||
assert!(schema_id.is_ok(), "transport schema file_id should be valid: {schema_id:?}");
|
||||
if let (std::result::Result::Ok(transport_id), std::result::Result::Ok(schema_id)) = (transport_id, schema_id) {
|
||||
@@ -108,11 +108,11 @@ fn defaults_register_transport_document_and_schema_with_distinct_roots() {
|
||||
assert!(transport.is_ok(), "transport descriptor should exist: {transport:?}");
|
||||
assert!(schema.is_ok(), "transport schema descriptor should exist: {schema:?}");
|
||||
if let (std::result::Result::Ok(transport), std::result::Result::Ok(schema)) = (transport, schema) {
|
||||
assert_eq!(transport.kind(), super::ConfigFileKind::Config);
|
||||
assert_eq!(transport.filename(), std::path::Path::new(super::DEFAULT_STD_TRANSPORT_FILENAME));
|
||||
assert_eq!(transport.kind(), crate::ConfigFileKind::Config);
|
||||
assert_eq!(transport.filename(), std::path::Path::new(crate::DEFAULT_STD_TRANSPORT_FILENAME));
|
||||
assert_eq!(transport.schema_file_id(), std::option::Option::Some(&schema_id));
|
||||
assert_eq!(schema.kind(), super::ConfigFileKind::Schema);
|
||||
assert_eq!(schema.filename(), std::path::Path::new(super::DEFAULT_STD_TRANSPORT_SCHEMA_FILENAME));
|
||||
assert_eq!(schema.kind(), crate::ConfigFileKind::Schema);
|
||||
assert_eq!(schema.filename(), std::path::Path::new(crate::DEFAULT_STD_TRANSPORT_SCHEMA_FILENAME));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,13 +120,13 @@ fn defaults_register_transport_document_and_schema_with_distinct_roots() {
|
||||
|
||||
#[test]
|
||||
fn resolve_path_uses_descriptor_kind_to_select_bootstrap_root() {
|
||||
let registry = super::ConfigFileRegistry::defaults();
|
||||
let registry = crate::ConfigFileRegistry::defaults();
|
||||
let bootstrap = crate::ConfigBootstrapOptions::from_paths("runtime-config", "runtime-schemas");
|
||||
assert!(registry.is_ok(), "default registry should be valid: {registry:?}");
|
||||
assert!(bootstrap.is_ok(), "bootstrap paths should be valid: {bootstrap:?}");
|
||||
if let (std::result::Result::Ok(registry), std::result::Result::Ok(bootstrap)) = (registry, bootstrap) {
|
||||
let logging_id = super::ConfigFileId::new(super::FILE_ID_STD_LOGGING);
|
||||
let schema_id = super::ConfigFileId::new(super::FILE_ID_SCHEMA_STD_LOGGING);
|
||||
let logging_id = crate::ConfigFileId::new(crate::FILE_ID_STD_LOGGING);
|
||||
let schema_id = crate::ConfigFileId::new(crate::FILE_ID_SCHEMA_STD_LOGGING);
|
||||
if let (std::result::Result::Ok(logging_id), std::result::Result::Ok(schema_id)) = (logging_id, schema_id) {
|
||||
let logging = registry.resolve_path(&bootstrap, &logging_id);
|
||||
let schema = registry.resolve_path(&bootstrap, &schema_id);
|
||||
@@ -150,16 +150,16 @@ fn cli_filemap_override_replaces_filename_and_last_value_wins() {
|
||||
std::ffi::OsString::from("--other-option"),
|
||||
std::ffi::OsString::from("--filemap=cfg.std.logging=profiles/custom.logging.json"),
|
||||
];
|
||||
let registry = super::ConfigFileRegistry::from_args(&args);
|
||||
let registry = crate::ConfigFileRegistry::from_args(&args);
|
||||
assert!(registry.is_ok(), "filemap overrides should parse: {registry:?}");
|
||||
if let std::result::Result::Ok(registry) = registry {
|
||||
let file_id = super::ConfigFileId::new(super::FILE_ID_STD_LOGGING);
|
||||
let file_id = crate::ConfigFileId::new(crate::FILE_ID_STD_LOGGING);
|
||||
if let std::result::Result::Ok(file_id) = file_id {
|
||||
let descriptor = registry.descriptor(&file_id);
|
||||
assert!(descriptor.is_ok(), "logging descriptor should remain registered: {descriptor:?}");
|
||||
if let std::result::Result::Ok(descriptor) = descriptor {
|
||||
assert_eq!(descriptor.filename(), std::path::Path::new("profiles/custom.logging.json"));
|
||||
assert_eq!(descriptor.kind(), super::ConfigFileKind::Config);
|
||||
assert_eq!(descriptor.kind(), crate::ConfigFileKind::Config);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,8 +167,8 @@ fn cli_filemap_override_replaces_filename_and_last_value_wins() {
|
||||
|
||||
#[test]
|
||||
fn programmatic_override_preserves_file_id_and_kind() {
|
||||
let registry = super::ConfigFileRegistry::defaults();
|
||||
let file_id = super::ConfigFileId::new(super::FILE_ID_SCHEMA_STD_LOGGING);
|
||||
let registry = crate::ConfigFileRegistry::defaults();
|
||||
let file_id = crate::ConfigFileId::new(crate::FILE_ID_SCHEMA_STD_LOGGING);
|
||||
assert!(registry.is_ok(), "default registry should be valid: {registry:?}");
|
||||
assert!(file_id.is_ok(), "schema file_id should be valid: {file_id:?}");
|
||||
if let (std::result::Result::Ok(registry), std::result::Result::Ok(file_id)) = (registry, file_id) {
|
||||
@@ -178,7 +178,7 @@ fn programmatic_override_preserves_file_id_and_kind() {
|
||||
let descriptor = overridden.descriptor(&file_id);
|
||||
if let std::result::Result::Ok(descriptor) = descriptor {
|
||||
assert_eq!(descriptor.file_id(), &file_id);
|
||||
assert_eq!(descriptor.kind(), super::ConfigFileKind::Schema);
|
||||
assert_eq!(descriptor.kind(), crate::ConfigFileKind::Schema);
|
||||
assert_eq!(descriptor.filename(), std::path::Path::new("alternate/logging.schema.json"));
|
||||
}
|
||||
}
|
||||
@@ -188,7 +188,7 @@ fn programmatic_override_preserves_file_id_and_kind() {
|
||||
#[test]
|
||||
fn unknown_file_id_override_is_rejected() {
|
||||
let args = [std::ffi::OsString::from("ksp-app"), std::ffi::OsString::from("--filemap=cfg.unknown=unknown.json")];
|
||||
let result = super::ConfigFileRegistry::from_args(&args);
|
||||
let result = crate::ConfigFileRegistry::from_args(&args);
|
||||
assert!(result.is_err(), "unknown logical files must not be introduced by CLI override");
|
||||
if let std::result::Result::Err(error) = result {
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_FILE_ID_UNKNOWN);
|
||||
@@ -198,7 +198,7 @@ fn unknown_file_id_override_is_rejected() {
|
||||
#[test]
|
||||
fn invalid_file_ids_are_rejected() {
|
||||
for value in ["", ".cfg", "cfg.", "cfg..logging", "CFG.logging", "cfg/logging"] {
|
||||
let result = super::ConfigFileId::new(value);
|
||||
let result = crate::ConfigFileId::new(value);
|
||||
assert!(result.is_err(), "invalid file_id must be rejected: {value}");
|
||||
if let std::result::Result::Err(error) = result {
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_FILE_ID_INVALID);
|
||||
@@ -208,8 +208,8 @@ fn invalid_file_ids_are_rejected() {
|
||||
|
||||
#[test]
|
||||
fn absolute_and_traversing_filenames_are_rejected() {
|
||||
let registry = super::ConfigFileRegistry::defaults();
|
||||
let file_id = super::ConfigFileId::new(super::FILE_ID_STD_LOGGING);
|
||||
let registry = crate::ConfigFileRegistry::defaults();
|
||||
let file_id = crate::ConfigFileId::new(crate::FILE_ID_STD_LOGGING);
|
||||
assert!(registry.is_ok(), "default registry should be valid: {registry:?}");
|
||||
assert!(file_id.is_ok(), "logging file_id should be valid: {file_id:?}");
|
||||
if let (std::result::Result::Ok(registry), std::result::Result::Ok(file_id)) = (registry, file_id) {
|
||||
@@ -226,7 +226,7 @@ fn absolute_and_traversing_filenames_are_rejected() {
|
||||
fn malformed_filemap_arguments_are_rejected() {
|
||||
for argument in ["--filemap", "--filemap=cfg.std.logging", "--filemap==logging.json", "--filemap=cfg.std.logging="] {
|
||||
let args = [std::ffi::OsString::from("ksp-app"), std::ffi::OsString::from(argument)];
|
||||
let result = super::ConfigFileRegistry::from_args(&args);
|
||||
let result = crate::ConfigFileRegistry::from_args(&args);
|
||||
assert!(result.is_err(), "malformed filemap argument must be rejected: {argument}");
|
||||
if let std::result::Result::Err(error) = result {
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_FILE_MAPPING_INVALID);
|
||||
@@ -236,12 +236,12 @@ fn malformed_filemap_arguments_are_rejected() {
|
||||
|
||||
#[test]
|
||||
fn duplicate_registry_ids_are_rejected() {
|
||||
let first = super::ConfigFileDescriptor::new("cfg.duplicate", super::ConfigFileKind::Config, "first.json", std::option::Option::None);
|
||||
let second = super::ConfigFileDescriptor::new("cfg.duplicate", super::ConfigFileKind::Config, "second.json", std::option::Option::None);
|
||||
let first = crate::ConfigFileDescriptor::new("cfg.duplicate", crate::ConfigFileKind::Config, "first.json", std::option::Option::None);
|
||||
let second = crate::ConfigFileDescriptor::new("cfg.duplicate", crate::ConfigFileKind::Config, "second.json", std::option::Option::None);
|
||||
assert!(first.is_ok(), "first descriptor should be valid: {first:?}");
|
||||
assert!(second.is_ok(), "second descriptor should be valid: {second:?}");
|
||||
if let (std::result::Result::Ok(first), std::result::Result::Ok(second)) = (first, second) {
|
||||
let result = super::build_registry([first, second]);
|
||||
let result = crate::build_registry([first, second]);
|
||||
assert!(result.is_err(), "duplicate file_ids must be rejected");
|
||||
if let std::result::Result::Err(error) = result {
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_FILE_ID_DUPLICATE);
|
||||
@@ -251,7 +251,7 @@ fn duplicate_registry_ids_are_rejected() {
|
||||
|
||||
#[test]
|
||||
fn descriptor_kind_must_match_file_id_namespace() {
|
||||
let result = super::ConfigFileDescriptor::new("schema.invalid-kind", super::ConfigFileKind::Config, "invalid.json", std::option::Option::None);
|
||||
let result = crate::ConfigFileDescriptor::new("schema.invalid-kind", crate::ConfigFileKind::Config, "invalid.json", std::option::Option::None);
|
||||
assert!(result.is_err(), "descriptor kind mismatch must be rejected");
|
||||
if let std::result::Result::Err(error) = result {
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_FILE_MAPPING_INVALID);
|
||||
@@ -260,10 +260,10 @@ fn descriptor_kind_must_match_file_id_namespace() {
|
||||
|
||||
#[test]
|
||||
fn config_schema_association_must_reference_registered_schema_descriptor() {
|
||||
let config = super::ConfigFileDescriptor::new("cfg.test", super::ConfigFileKind::Config, "test.json", std::option::Option::Some("schema.test"));
|
||||
let config = crate::ConfigFileDescriptor::new("cfg.test", crate::ConfigFileKind::Config, "test.json", std::option::Option::Some("schema.test"));
|
||||
assert!(config.is_ok(), "config descriptor should be valid before registry association validation: {config:?}");
|
||||
if let std::result::Result::Ok(config) = config {
|
||||
let result = super::build_registry([config]);
|
||||
let result = crate::build_registry([config]);
|
||||
assert!(result.is_err(), "registry must reject a missing schema association");
|
||||
if let std::result::Result::Err(error) = result {
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_FILE_MAPPING_INVALID);
|
||||
@@ -286,9 +286,9 @@ fn absolute_fixture_path() -> std::path::PathBuf {
|
||||
|
||||
#[test]
|
||||
fn defaults_register_generic_composite_schema_without_runtime_composite() {
|
||||
let registry = super::ConfigFileRegistry::defaults();
|
||||
let schema_id = super::ConfigFileId::new(super::FILE_ID_SCHEMA_COMPOSITE);
|
||||
let runtime_id = super::ConfigFileId::new("cfg.composite.ksp-app-wallet-desk");
|
||||
let registry = crate::ConfigFileRegistry::defaults();
|
||||
let schema_id = crate::ConfigFileId::new(crate::FILE_ID_SCHEMA_COMPOSITE);
|
||||
let runtime_id = crate::ConfigFileId::new("cfg.composite.ksp-app-wallet-desk");
|
||||
assert!(registry.is_ok(), "default registry should be valid: {registry:?}");
|
||||
assert!(schema_id.is_ok(), "composite schema file_id should be valid: {schema_id:?}");
|
||||
assert!(runtime_id.is_ok(), "future composite runtime file_id syntax should be valid: {runtime_id:?}");
|
||||
@@ -298,8 +298,8 @@ fn defaults_register_generic_composite_schema_without_runtime_composite() {
|
||||
assert!(schema.is_ok(), "generic composite schema should be registered: {schema:?}");
|
||||
assert!(runtime.is_err(), "no fictitious runtime composite should be registered");
|
||||
if let std::result::Result::Ok(schema) = schema {
|
||||
assert_eq!(schema.kind(), super::ConfigFileKind::Schema);
|
||||
assert_eq!(schema.filename(), std::path::Path::new(super::DEFAULT_COMPOSITE_SCHEMA_FILENAME));
|
||||
assert_eq!(schema.kind(), crate::ConfigFileKind::Schema);
|
||||
assert_eq!(schema.filename(), std::path::Path::new(crate::DEFAULT_COMPOSITE_SCHEMA_FILENAME));
|
||||
}
|
||||
if let std::result::Result::Err(error) = runtime {
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_FILE_ID_UNKNOWN);
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
// file: crates/ksp-config-lib/unit_tests/sensitivity.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
#[test]
|
||||
fn environment_names_map_to_expected_sensitivity() {
|
||||
assert_eq!(super::ConfigSensitivity::from_variable_name("KSP_PUBLIC_ENDPOINT").ok(), std::option::Option::Some(super::ConfigSensitivity::Public));
|
||||
assert_eq!(super::ConfigSensitivity::from_variable_name("KSP_MODE").ok(), std::option::Option::Some(super::ConfigSensitivity::Internal));
|
||||
assert_eq!(super::ConfigSensitivity::from_variable_name("KSP_SECRET_PASSWORD").ok(), std::option::Option::Some(super::ConfigSensitivity::Secret));
|
||||
assert_eq!(super::ConfigSensitivity::from_variable_name("KSPB_PUBLIC_ENDPOINT").ok(), std::option::Option::Some(super::ConfigSensitivity::Public));
|
||||
assert_eq!(super::ConfigSensitivity::from_variable_name("KSPB_MODE").ok(), std::option::Option::Some(super::ConfigSensitivity::Internal));
|
||||
assert_eq!(super::ConfigSensitivity::from_variable_name("KSPB_SECRET_PASSWORD").ok(), std::option::Option::Some(super::ConfigSensitivity::Secret));
|
||||
assert_eq!(crate::ConfigSensitivity::from_variable_name("KSP_PUBLIC_ENDPOINT").ok(), std::option::Option::Some(crate::ConfigSensitivity::Public));
|
||||
assert_eq!(crate::ConfigSensitivity::from_variable_name("KSP_MODE").ok(), std::option::Option::Some(crate::ConfigSensitivity::Internal));
|
||||
assert_eq!(crate::ConfigSensitivity::from_variable_name("KSP_SECRET_PASSWORD").ok(), std::option::Option::Some(crate::ConfigSensitivity::Secret));
|
||||
assert_eq!(crate::ConfigSensitivity::from_variable_name("KSPB_PUBLIC_ENDPOINT").ok(), std::option::Option::Some(crate::ConfigSensitivity::Public));
|
||||
assert_eq!(crate::ConfigSensitivity::from_variable_name("KSPB_MODE").ok(), std::option::Option::Some(crate::ConfigSensitivity::Internal));
|
||||
assert_eq!(crate::ConfigSensitivity::from_variable_name("KSPB_SECRET_PASSWORD").ok(), std::option::Option::Some(crate::ConfigSensitivity::Secret));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strongest_sensitivity_follows_secret_internal_public_order() {
|
||||
assert_eq!(super::ConfigSensitivity::Public.strongest(super::ConfigSensitivity::Internal), super::ConfigSensitivity::Internal);
|
||||
assert_eq!(super::ConfigSensitivity::Internal.strongest(super::ConfigSensitivity::Secret), super::ConfigSensitivity::Secret);
|
||||
assert_eq!(super::ConfigSensitivity::Secret.strongest(super::ConfigSensitivity::Public), super::ConfigSensitivity::Secret);
|
||||
assert_eq!(crate::ConfigSensitivity::Public.strongest(crate::ConfigSensitivity::Internal), crate::ConfigSensitivity::Internal);
|
||||
assert_eq!(crate::ConfigSensitivity::Internal.strongest(crate::ConfigSensitivity::Secret), crate::ConfigSensitivity::Secret);
|
||||
assert_eq!(crate::ConfigSensitivity::Secret.strongest(crate::ConfigSensitivity::Public), crate::ConfigSensitivity::Secret);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provenance_exposes_names_and_sources_without_values() {
|
||||
let process = super::ConfigValueProvenance::EnvironmentProcess { variable_name: "KSP_SECRET_TOKEN".to_owned() };
|
||||
let dotenv = super::ConfigValueProvenance::EnvironmentDotEnv { variable_name: "KSP_MODE".to_owned() };
|
||||
let fallback = super::ConfigValueProvenance::EnvironmentFallback { variable_name: "KSP_PUBLIC_HOST".to_owned() };
|
||||
let process = crate::ConfigValueProvenance::EnvironmentProcess { variable_name: "KSP_SECRET_TOKEN".to_owned() };
|
||||
let dotenv = crate::ConfigValueProvenance::EnvironmentDotEnv { variable_name: "KSP_MODE".to_owned() };
|
||||
let fallback = crate::ConfigValueProvenance::EnvironmentFallback { variable_name: "KSP_PUBLIC_HOST".to_owned() };
|
||||
assert_eq!(process.variable_name(), std::option::Option::Some("KSP_SECRET_TOKEN"));
|
||||
assert_eq!(process.environment_source(), std::option::Option::Some(crate::ConfigEnvironmentSource::Process));
|
||||
assert_eq!(dotenv.environment_source(), std::option::Option::Some(crate::ConfigEnvironmentSource::DotEnv));
|
||||
assert_eq!(fallback.environment_source(), std::option::Option::Some(crate::ConfigEnvironmentSource::Fallback));
|
||||
assert_eq!(super::ConfigValueProvenance::DocumentLiteral.variable_name(), std::option::Option::None);
|
||||
assert_eq!(crate::ConfigValueProvenance::DocumentLiteral.variable_name(), std::option::Option::None);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// file: crates/ksp-core-lib/src/lib.rs
|
||||
// version: 6
|
||||
// version: 8
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-core-lib/src/program_ids.rs
|
||||
// version: 2
|
||||
// version: 4
|
||||
|
||||
const DOMAIN_SOLANA: &str = "solana";
|
||||
const FAMILY_CONSENSUS: &str = "consensus";
|
||||
@@ -266,6 +266,12 @@ impl<'a> ProgramIdFilter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the canonical KSP Program ID registry.
|
||||
#[must_use]
|
||||
pub const fn entries() -> &'static [crate::ProgramIdEntry] {
|
||||
return PROGRAM_ID_ENTRIES;
|
||||
}
|
||||
|
||||
const PROGRAM_ID_ENTRIES: &[crate::ProgramIdEntry] = &[
|
||||
crate::ProgramIdEntry::new(
|
||||
"solana.address_lookup_table",
|
||||
@@ -438,12 +444,6 @@ const PROGRAM_ID_ENTRIES: &[crate::ProgramIdEntry] = &[
|
||||
),
|
||||
];
|
||||
|
||||
/// Returns the canonical KSP Program ID registry.
|
||||
#[must_use]
|
||||
pub const fn entries() -> &'static [crate::ProgramIdEntry] {
|
||||
return PROGRAM_ID_ENTRIES;
|
||||
}
|
||||
|
||||
/// Returns a lazy view of Program IDs matching all configured filter axes.
|
||||
pub fn program_ids<'a>(filter: crate::ProgramIdFilter<'a>) -> impl std::iter::Iterator<Item = &'static crate::ProgramIdEntry> + 'a {
|
||||
return PROGRAM_ID_ENTRIES.iter().filter(move |entry| {
|
||||
|
||||
@@ -1,37 +1,15 @@
|
||||
// file: crates/ksp-logging-lib/src/domain.rs
|
||||
// version: 1
|
||||
// version: 3
|
||||
|
||||
std::thread_local! {
|
||||
static CURRENT_DOMAIN: std::cell::RefCell<std::option::Option<std::string::String>> = const { std::cell::RefCell::new(std::option::Option::None) };
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
struct SpanDomain {
|
||||
value: std::option::Option<std::string::String>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct DomainVisitor {
|
||||
value: std::option::Option<std::string::String>,
|
||||
}
|
||||
|
||||
impl tracing::field::Visit for DomainVisitor {
|
||||
fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
|
||||
if field.name() == "domain" {
|
||||
self.value = std::option::Option::Some(value.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
|
||||
if field.name() == "domain" {
|
||||
self.value = std::option::Option::Some(format!("{value:?}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Crate-internal `DomainContextLayer` state shared across the owning crate.
|
||||
pub(crate) struct DomainContextLayer;
|
||||
|
||||
impl DomainContextLayer {
|
||||
/// Creates a new `DomainContextLayer` value.
|
||||
pub(crate) const fn new() -> Self {
|
||||
return Self;
|
||||
}
|
||||
@@ -99,6 +77,31 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
struct SpanDomain {
|
||||
value: std::option::Option<std::string::String>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct DomainVisitor {
|
||||
value: std::option::Option<std::string::String>,
|
||||
}
|
||||
|
||||
impl tracing::field::Visit for DomainVisitor {
|
||||
fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
|
||||
if field.name() == "domain" {
|
||||
self.value = std::option::Option::Some(value.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
|
||||
if field.name() == "domain" {
|
||||
self.value = std::option::Option::Some(format!("{value:?}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes the crate-internal current domain matches operation for the owning module.
|
||||
pub(crate) fn current_domain_matches(selectors: &[std::string::String]) -> bool {
|
||||
if let [selector] = selectors
|
||||
&& selector == "*"
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
// file: crates/ksp-logging-lib/src/error.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
/// Error code used when runtime logging settings are invalid.
|
||||
pub const ERROR_CODE_INVALID_SETTINGS: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("logging", "invalid_settings");
|
||||
/// Error code used when a global logging subscriber is already installed.
|
||||
pub const ERROR_CODE_ALREADY_INITIALIZED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("logging", "already_initialized");
|
||||
/// Error code used when a hot reload cannot replace the active runtime layers.
|
||||
pub const ERROR_CODE_RELOAD_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("logging", "reload_failed");
|
||||
/// Error code used when the rolling file output cannot be initialized.
|
||||
pub const ERROR_CODE_FILE_OUTPUT_INITIALIZATION_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("logging", "file_output_initialization_failed");
|
||||
/// Error code used when an application Logging runtime identity is invalid.
|
||||
pub const ERROR_CODE_INVALID_RUNTIME_IDENTITY: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("logging", "invalid_runtime_identity");
|
||||
/// Error code used when runtime logging settings are invalid.
|
||||
pub const ERROR_CODE_INVALID_SETTINGS: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("logging", "invalid_settings");
|
||||
/// Error code used when a hot reload cannot replace the active runtime layers.
|
||||
pub const ERROR_CODE_RELOAD_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("logging", "reload_failed");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-logging-lib/src/identity.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Stable runtime identity used to separate persistent file outputs between application launches.
|
||||
|
||||
@@ -41,6 +41,7 @@ impl LoggingRuntimeIdentity {
|
||||
return self.launch_timestamp.as_str();
|
||||
}
|
||||
|
||||
/// Executes the crate-internal file name prefix operation for `LoggingRuntimeIdentity`.
|
||||
pub(crate) fn file_name_prefix(&self, configured_prefix: &str) -> std::string::String {
|
||||
return format!("{}.{}.{}", self.application_id, self.launch_timestamp, configured_prefix);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// file: crates/ksp-logging-lib/src/lib.rs
|
||||
// version: 8
|
||||
// version: 10
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
#![forbid(unsafe_code)]
|
||||
@@ -69,6 +70,17 @@ pub use self::span::Span;
|
||||
/// Instruments an asynchronous future with a KSP span.
|
||||
pub use self::span::instrument;
|
||||
|
||||
/// Crate-internal `DomainContextLayer` state shared across the owning crate.
|
||||
pub(crate) use self::domain::DomainContextLayer;
|
||||
/// Executes the crate-internal current domain matches operation for the owning module.
|
||||
pub(crate) use self::domain::current_domain_matches;
|
||||
/// Crate-internal `RouteMakeWriter` state shared across the owning crate.
|
||||
pub(crate) use self::writer::RouteMakeWriter;
|
||||
/// Crate-internal `RoutedWriter` variants used by the owning crate.
|
||||
pub(crate) use self::writer::RoutedWriter;
|
||||
/// Crate-internal `StripAnsiWriter` state shared across the owning crate.
|
||||
pub(crate) use self::writer::StripAnsiWriter;
|
||||
|
||||
/// Hidden tracing crate bridge used exclusively by exported KSP logging macros.
|
||||
#[doc(hidden)]
|
||||
/// Internal macro bridge. KSP consumers must not use this reexport directly.
|
||||
pub extern crate tracing as __private_tracing;
|
||||
pub extern crate tracing;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// file: crates/ksp-logging-lib/src/macros.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
/// Emits a KSP error event with an explicit owning target.
|
||||
#[macro_export]
|
||||
macro_rules! error {
|
||||
(target: $target:expr, $($argument:tt)+) => {{
|
||||
$crate::__private_tracing::error!(target: $target, $($argument)+);
|
||||
$crate::tracing::error!(target: $target, $($argument)+);
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ macro_rules! error {
|
||||
#[macro_export]
|
||||
macro_rules! warn {
|
||||
(target: $target:expr, $($argument:tt)+) => {{
|
||||
$crate::__private_tracing::warn!(target: $target, $($argument)+);
|
||||
$crate::tracing::warn!(target: $target, $($argument)+);
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ macro_rules! warn {
|
||||
#[macro_export]
|
||||
macro_rules! info {
|
||||
(target: $target:expr, $($argument:tt)+) => {{
|
||||
$crate::__private_tracing::info!(target: $target, $($argument)+);
|
||||
$crate::tracing::info!(target: $target, $($argument)+);
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ macro_rules! info {
|
||||
#[macro_export]
|
||||
macro_rules! debug {
|
||||
(target: $target:expr, $($argument:tt)+) => {{
|
||||
$crate::__private_tracing::debug!(target: $target, $($argument)+);
|
||||
$crate::tracing::debug!(target: $target, $($argument)+);
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ macro_rules! debug {
|
||||
#[macro_export]
|
||||
macro_rules! trace {
|
||||
(target: $target:expr, $($argument:tt)+) => {{
|
||||
$crate::__private_tracing::trace!(target: $target, $($argument)+);
|
||||
$crate::tracing::trace!(target: $target, $($argument)+);
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -45,10 +45,10 @@ macro_rules! trace {
|
||||
#[macro_export]
|
||||
macro_rules! error_span {
|
||||
(target: $target:expr, $name:expr) => {{
|
||||
$crate::Span::__from_tracing($crate::__private_tracing::error_span!(target: $target, $name))
|
||||
$crate::Span::__from_tracing($crate::tracing::error_span!(target: $target, $name))
|
||||
}};
|
||||
(target: $target:expr, $name:expr, $($field:tt)+) => {{
|
||||
$crate::Span::__from_tracing($crate::__private_tracing::error_span!(target: $target, $name, $($field)+))
|
||||
$crate::Span::__from_tracing($crate::tracing::error_span!(target: $target, $name, $($field)+))
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -56,10 +56,10 @@ macro_rules! error_span {
|
||||
#[macro_export]
|
||||
macro_rules! warn_span {
|
||||
(target: $target:expr, $name:expr) => {{
|
||||
$crate::Span::__from_tracing($crate::__private_tracing::warn_span!(target: $target, $name))
|
||||
$crate::Span::__from_tracing($crate::tracing::warn_span!(target: $target, $name))
|
||||
}};
|
||||
(target: $target:expr, $name:expr, $($field:tt)+) => {{
|
||||
$crate::Span::__from_tracing($crate::__private_tracing::warn_span!(target: $target, $name, $($field)+))
|
||||
$crate::Span::__from_tracing($crate::tracing::warn_span!(target: $target, $name, $($field)+))
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -67,10 +67,10 @@ macro_rules! warn_span {
|
||||
#[macro_export]
|
||||
macro_rules! info_span {
|
||||
(target: $target:expr, $name:expr) => {{
|
||||
$crate::Span::__from_tracing($crate::__private_tracing::info_span!(target: $target, $name))
|
||||
$crate::Span::__from_tracing($crate::tracing::info_span!(target: $target, $name))
|
||||
}};
|
||||
(target: $target:expr, $name:expr, $($field:tt)+) => {{
|
||||
$crate::Span::__from_tracing($crate::__private_tracing::info_span!(target: $target, $name, $($field)+))
|
||||
$crate::Span::__from_tracing($crate::tracing::info_span!(target: $target, $name, $($field)+))
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -78,10 +78,10 @@ macro_rules! info_span {
|
||||
#[macro_export]
|
||||
macro_rules! debug_span {
|
||||
(target: $target:expr, $name:expr) => {{
|
||||
$crate::Span::__from_tracing($crate::__private_tracing::debug_span!(target: $target, $name))
|
||||
$crate::Span::__from_tracing($crate::tracing::debug_span!(target: $target, $name))
|
||||
}};
|
||||
(target: $target:expr, $name:expr, $($field:tt)+) => {{
|
||||
$crate::Span::__from_tracing($crate::__private_tracing::debug_span!(target: $target, $name, $($field)+))
|
||||
$crate::Span::__from_tracing($crate::tracing::debug_span!(target: $target, $name, $($field)+))
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -89,9 +89,9 @@ macro_rules! debug_span {
|
||||
#[macro_export]
|
||||
macro_rules! trace_span {
|
||||
(target: $target:expr, $name:expr) => {{
|
||||
$crate::Span::__from_tracing($crate::__private_tracing::trace_span!(target: $target, $name))
|
||||
$crate::Span::__from_tracing($crate::tracing::trace_span!(target: $target, $name))
|
||||
}};
|
||||
(target: $target:expr, $name:expr, $($field:tt)+) => {{
|
||||
$crate::Span::__from_tracing($crate::__private_tracing::trace_span!(target: $target, $name, $($field)+))
|
||||
$crate::Span::__from_tracing($crate::tracing::trace_span!(target: $target, $name, $($field)+))
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-logging-lib/src/runtime.rs
|
||||
// version: 13
|
||||
// version: 16
|
||||
|
||||
use tracing_subscriber::Layer; // rust-rules: trait-import
|
||||
use tracing_subscriber::layer::SubscriberExt; // rust-rules: trait-import
|
||||
@@ -228,31 +228,6 @@ pub fn initialize_with_identity(settings: &crate::LoggingSettings, identity: &cr
|
||||
return initialize_runtime(settings, std::option::Option::Some(identity.clone()));
|
||||
}
|
||||
|
||||
fn initialize_runtime(
|
||||
settings: &crate::LoggingSettings,
|
||||
runtime_identity: std::option::Option<crate::LoggingRuntimeIdentity>,
|
||||
) -> ksp_core_lib::Result<crate::LoggingGuard> {
|
||||
return prepare_runtime_with_identity(settings, runtime_identity.as_ref()).and_then(|prepared| -> ksp_core_lib::Result<crate::LoggingGuard> {
|
||||
let PreparedRuntime { layers, outputs } = prepared;
|
||||
let (reload_layer, reload_handle) = tracing_subscriber::reload::Layer::new(layers);
|
||||
let subscriber = tracing_subscriber::registry().with(reload_layer);
|
||||
let install_result = tracing::subscriber::set_global_default(subscriber);
|
||||
return match install_result {
|
||||
std::result::Result::Ok(()) => std::result::Result::Ok(crate::LoggingGuard {
|
||||
reload_handle,
|
||||
settings: settings.clone(),
|
||||
runtime_identity,
|
||||
outputs,
|
||||
retired_dropped_lines: crate::DroppedLines::zero(),
|
||||
retired_file_dropped_lines: std::collections::HashMap::new(),
|
||||
}),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_ALREADY_INITIALIZED, "the global KSP tracing subscriber is already installed").with_source(error),
|
||||
),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/// Replaces the active KSP logging settings and non-blocking outputs without reinstalling the global subscriber.
|
||||
///
|
||||
/// New runtime layers, writers and guards are fully prepared before the reload is attempted. If validation or preparation fails, the currently active
|
||||
@@ -282,6 +257,31 @@ pub fn reinitialize(guard: &mut crate::LoggingGuard, settings: &crate::LoggingSe
|
||||
});
|
||||
}
|
||||
|
||||
fn initialize_runtime(
|
||||
settings: &crate::LoggingSettings,
|
||||
runtime_identity: std::option::Option<crate::LoggingRuntimeIdentity>,
|
||||
) -> ksp_core_lib::Result<crate::LoggingGuard> {
|
||||
return prepare_runtime_with_identity(settings, runtime_identity.as_ref()).and_then(|prepared| -> ksp_core_lib::Result<crate::LoggingGuard> {
|
||||
let PreparedRuntime { layers, outputs } = prepared;
|
||||
let (reload_layer, reload_handle) = tracing_subscriber::reload::Layer::new(layers);
|
||||
let subscriber = tracing_subscriber::registry().with(reload_layer);
|
||||
let install_result = tracing::subscriber::set_global_default(subscriber);
|
||||
return match install_result {
|
||||
std::result::Result::Ok(()) => std::result::Result::Ok(crate::LoggingGuard {
|
||||
reload_handle,
|
||||
settings: settings.clone(),
|
||||
runtime_identity,
|
||||
outputs,
|
||||
retired_dropped_lines: crate::DroppedLines::zero(),
|
||||
retired_file_dropped_lines: std::collections::HashMap::new(),
|
||||
}),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_ALREADY_INITIALIZED, "the global KSP tracing subscriber is already installed").with_source(error),
|
||||
),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
fn prepare_runtime_with_identity(
|
||||
settings: &crate::LoggingSettings,
|
||||
runtime_identity: std::option::Option<&crate::LoggingRuntimeIdentity>,
|
||||
@@ -315,7 +315,7 @@ fn prepare_runtime_with_identity(
|
||||
if output_layers.is_empty() {
|
||||
return std::result::Result::Ok(PreparedRuntime { layers: RuntimeLayers::new(), outputs });
|
||||
}
|
||||
output_layers.insert(0, crate::domain::DomainContextLayer::new().boxed());
|
||||
output_layers.insert(0, crate::DomainContextLayer::new().boxed());
|
||||
let takeover_layer = build_target_filter(settings).and_then(output_layers).boxed();
|
||||
return std::result::Result::Ok(PreparedRuntime { layers: vec![takeover_layer], outputs });
|
||||
}
|
||||
@@ -368,7 +368,7 @@ fn build_file_output(
|
||||
);
|
||||
},
|
||||
};
|
||||
let stripped_writer = crate::writer::StripAnsiWriter::new(appender);
|
||||
let stripped_writer = crate::StripAnsiWriter::new(appender);
|
||||
let thread_name = format!("ksp-logging-{}", file.output_id());
|
||||
let prepared = build_non_blocking_output(stripped_writer, thread_name.as_str(), settings.span_events(), false, false, file.format(), file.filter());
|
||||
let metadata = crate::RuntimeFileMetadata {
|
||||
@@ -413,7 +413,7 @@ fn build_format_layer(
|
||||
let span_events = map_span_events(span_events);
|
||||
return match format {
|
||||
crate::LogFormat::Human => tracing_subscriber::fmt::layer()
|
||||
.with_writer(crate::writer::RouteMakeWriter::new(writer, filter.clone()))
|
||||
.with_writer(crate::RouteMakeWriter::new(writer, filter.clone()))
|
||||
.with_ansi(ansi)
|
||||
.with_ansi_sanitization(ansi_sanitization)
|
||||
.with_target(true)
|
||||
@@ -423,7 +423,7 @@ fn build_format_layer(
|
||||
.boxed(),
|
||||
crate::LogFormat::Compact => tracing_subscriber::fmt::layer()
|
||||
.compact()
|
||||
.with_writer(crate::writer::RouteMakeWriter::new(writer, filter.clone()))
|
||||
.with_writer(crate::RouteMakeWriter::new(writer, filter.clone()))
|
||||
.with_ansi(ansi)
|
||||
.with_ansi_sanitization(ansi_sanitization)
|
||||
.with_target(true)
|
||||
@@ -433,7 +433,7 @@ fn build_format_layer(
|
||||
.boxed(),
|
||||
crate::LogFormat::Pretty => tracing_subscriber::fmt::layer()
|
||||
.pretty()
|
||||
.with_writer(crate::writer::RouteMakeWriter::new(writer, filter.clone()))
|
||||
.with_writer(crate::RouteMakeWriter::new(writer, filter.clone()))
|
||||
.with_ansi(ansi)
|
||||
.with_ansi_sanitization(ansi_sanitization)
|
||||
.with_target(true)
|
||||
@@ -443,7 +443,7 @@ fn build_format_layer(
|
||||
.boxed(),
|
||||
crate::LogFormat::Json => tracing_subscriber::fmt::layer()
|
||||
.json()
|
||||
.with_writer(crate::writer::RouteMakeWriter::new(writer, filter.clone()))
|
||||
.with_writer(crate::RouteMakeWriter::new(writer, filter.clone()))
|
||||
.with_ansi(false)
|
||||
.with_target(true)
|
||||
.with_file(true)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-logging-lib/src/writer.rs
|
||||
// version: 3
|
||||
// version: 5
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum StripAnsiState {
|
||||
@@ -12,12 +12,14 @@ enum StripAnsiState {
|
||||
StringEscape,
|
||||
}
|
||||
|
||||
/// Crate-internal `StripAnsiWriter` state shared across the owning crate.
|
||||
pub(crate) struct StripAnsiWriter<W> {
|
||||
inner: W,
|
||||
state: StripAnsiState,
|
||||
}
|
||||
|
||||
impl<W> StripAnsiWriter<W> {
|
||||
/// Creates a new `StripAnsiWriter` value.
|
||||
pub(crate) const fn new(inner: W) -> Self {
|
||||
return Self { inner, state: StripAnsiState::Text };
|
||||
}
|
||||
@@ -100,6 +102,7 @@ impl<W> StripAnsiWriter<W> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Crate-internal `RouteMakeWriter` state shared across the owning crate.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct RouteMakeWriter<W> {
|
||||
inner: W,
|
||||
@@ -107,17 +110,19 @@ pub(crate) struct RouteMakeWriter<W> {
|
||||
}
|
||||
|
||||
impl<W> RouteMakeWriter<W> {
|
||||
/// Creates a new `RouteMakeWriter` value.
|
||||
pub(crate) fn new(inner: W, filter: crate::OutputFilter) -> Self {
|
||||
return Self { inner, filter };
|
||||
}
|
||||
}
|
||||
|
||||
/// Crate-internal `RoutedWriter` variants used by the owning crate.
|
||||
pub(crate) enum RoutedWriter<W> {
|
||||
Enabled(W),
|
||||
Disabled,
|
||||
}
|
||||
|
||||
impl<W> std::io::Write for RoutedWriter<W>
|
||||
impl<W> std::io::Write for crate::RoutedWriter<W>
|
||||
where
|
||||
W: std::io::Write,
|
||||
{
|
||||
@@ -140,17 +145,17 @@ impl<'writer, W> tracing_subscriber::fmt::MakeWriter<'writer> for RouteMakeWrite
|
||||
where
|
||||
W: tracing_subscriber::fmt::MakeWriter<'writer>,
|
||||
{
|
||||
type Writer = RoutedWriter<W::Writer>;
|
||||
type Writer = crate::RoutedWriter<W::Writer>;
|
||||
|
||||
fn make_writer(&'writer self) -> Self::Writer {
|
||||
return RoutedWriter::Enabled(tracing_subscriber::fmt::MakeWriter::make_writer(&self.inner));
|
||||
return crate::RoutedWriter::Enabled(tracing_subscriber::fmt::MakeWriter::make_writer(&self.inner));
|
||||
}
|
||||
|
||||
fn make_writer_for(&'writer self, metadata: &tracing::Metadata<'_>) -> Self::Writer {
|
||||
if metadata_matches_filter(metadata, &self.filter) {
|
||||
return RoutedWriter::Enabled(tracing_subscriber::fmt::MakeWriter::make_writer_for(&self.inner, metadata));
|
||||
return crate::RoutedWriter::Enabled(tracing_subscriber::fmt::MakeWriter::make_writer_for(&self.inner, metadata));
|
||||
}
|
||||
return RoutedWriter::Disabled;
|
||||
return crate::RoutedWriter::Disabled;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +168,7 @@ fn metadata_matches_filter(metadata: &tracing::Metadata<'_>, filter: &crate::Out
|
||||
}) {
|
||||
return false;
|
||||
}
|
||||
return crate::domain::current_domain_matches(filter.domains());
|
||||
return crate::current_domain_matches(filter.domains());
|
||||
}
|
||||
|
||||
fn level_is_enabled(level: &tracing::Level, filter: crate::LogFilterLevel) -> bool {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// file: crates/ksp-logging-lib/tests/overhead.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Diagnostic gross-overhead probe for the reload layer used by KSP Logging.
|
||||
|
||||
use tracing_subscriber::layer::SubscriberExt; // rust-rules: trait-import
|
||||
|
||||
const TEST_TARGET: &str = "ksp-logging-lib";
|
||||
const ITERATIONS: u64 = 200_000;
|
||||
const TEST_TARGET: &str = "ksp-logging-lib";
|
||||
|
||||
fn emit_probe_events() {
|
||||
for sequence in 0..ITERATIONS {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-logging-lib/tests/ownership.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
//! Integration audit ensuring KSP crates do not bypass the logging facade.
|
||||
|
||||
@@ -157,6 +157,11 @@ fn workspace_crates_do_not_bypass_ksp_logging_facade() {
|
||||
std::result::Result::Err(_) => continue,
|
||||
};
|
||||
assert!(!source_uses_direct_path(source.as_str(), "tracing::"), "{} bypasses ksp-logging-lib via tracing", rust_file.display());
|
||||
assert!(
|
||||
!source_uses_direct_path(source.as_str(), "ksp_logging_lib::tracing::"),
|
||||
"{} bypasses ksp-logging-lib via its hidden tracing bridge",
|
||||
rust_file.display(),
|
||||
);
|
||||
assert!(
|
||||
!source_uses_direct_path(source.as_str(), "tracing_subscriber::"),
|
||||
"{} bypasses ksp-logging-lib via tracing-subscriber",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// file: crates/ksp-logging-lib/tests/runtime.rs
|
||||
// version: 9
|
||||
// version: 10
|
||||
|
||||
//! Integration tests for global initialization, takeover filtering, non-blocking outputs and hot reload.
|
||||
|
||||
const EXTERNAL_TARGET: &str = "sqlx";
|
||||
const JSON_KSP_TARGET: &str = "ksp-logging-json-test";
|
||||
const LOGGING_TARGET: &str = "ksp-logging-lib";
|
||||
const OTHER_KSP_TARGET: &str = "ksp-store-lib";
|
||||
const JSON_KSP_TARGET: &str = "ksp-logging-json-test";
|
||||
const EXTERNAL_TARGET: &str = "sqlx";
|
||||
|
||||
fn logging_trace_enabled() -> bool {
|
||||
return tracing::enabled!(target: LOGGING_TARGET, tracing::Level::TRACE);
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
// file: crates/ksp-logging-lib/unit_tests/domain.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
#[test]
|
||||
fn wildcard_domain_matches_with_or_without_current_domain() {
|
||||
super::set_current_domain(std::option::Option::None);
|
||||
assert!(super::current_domain_matches(&["*".to_string()]));
|
||||
assert!(crate::current_domain_matches(&["*".to_string()]));
|
||||
super::set_current_domain(std::option::Option::Some("logging"));
|
||||
assert!(super::current_domain_matches(&["*".to_string()]));
|
||||
assert!(crate::current_domain_matches(&["*".to_string()]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn named_domain_requires_current_matching_prefix() {
|
||||
super::set_current_domain(std::option::Option::None);
|
||||
assert!(!super::current_domain_matches(&["logging".to_string()]));
|
||||
assert!(!crate::current_domain_matches(&["logging".to_string()]));
|
||||
super::set_current_domain(std::option::Option::Some("logging.runtime"));
|
||||
assert!(super::current_domain_matches(&["logging".to_string()]));
|
||||
assert!(super::current_domain_matches(&["store".to_string(), "logging.runtime".to_string()]));
|
||||
assert!(!super::current_domain_matches(&["store".to_string()]));
|
||||
assert!(crate::current_domain_matches(&["logging".to_string()]));
|
||||
assert!(crate::current_domain_matches(&["store".to_string(), "logging.runtime".to_string()]));
|
||||
assert!(!crate::current_domain_matches(&["store".to_string()]));
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// file: crates/ksp-logging-lib/unit_tests/writer.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
#[test]
|
||||
fn ansi_writer_strips_csi_sequences() {
|
||||
let mut writer = super::StripAnsiWriter::new(std::vec::Vec::<u8>::new());
|
||||
let mut writer = crate::StripAnsiWriter::new(std::vec::Vec::<u8>::new());
|
||||
let write_result = std::io::Write::write_all(&mut writer, b"before\x1b[31mred\x1b[0mafter");
|
||||
assert!(write_result.is_ok());
|
||||
assert_eq!(writer.into_inner(), b"beforeredafter");
|
||||
@@ -11,7 +11,7 @@ fn ansi_writer_strips_csi_sequences() {
|
||||
|
||||
#[test]
|
||||
fn ansi_writer_preserves_state_across_split_writes() {
|
||||
let mut writer = super::StripAnsiWriter::new(std::vec::Vec::<u8>::new());
|
||||
let mut writer = crate::StripAnsiWriter::new(std::vec::Vec::<u8>::new());
|
||||
let first = std::io::Write::write_all(&mut writer, b"a\x1b[");
|
||||
let second = std::io::Write::write_all(&mut writer, b"32mb");
|
||||
assert!(first.is_ok());
|
||||
@@ -21,7 +21,7 @@ fn ansi_writer_preserves_state_across_split_writes() {
|
||||
|
||||
#[test]
|
||||
fn ansi_writer_strips_osc_sequences_terminated_by_bell_or_st() {
|
||||
let mut writer = super::StripAnsiWriter::new(std::vec::Vec::<u8>::new());
|
||||
let mut writer = crate::StripAnsiWriter::new(std::vec::Vec::<u8>::new());
|
||||
let first = std::io::Write::write_all(&mut writer, b"a\x1b]0;title\x07b");
|
||||
let second = std::io::Write::write_all(&mut writer, b"c\x1b]8;;https://example.invalid\x1b\\d");
|
||||
assert!(first.is_ok());
|
||||
@@ -43,7 +43,7 @@ fn output_level_routing_covers_all_filter_levels() {
|
||||
|
||||
#[test]
|
||||
fn disabled_routed_writer_discards_bytes_without_error() {
|
||||
let mut writer = super::RoutedWriter::<std::vec::Vec<u8>>::Disabled;
|
||||
let mut writer = crate::RoutedWriter::<std::vec::Vec<u8>>::Disabled;
|
||||
let write_result = std::io::Write::write_all(&mut writer, b"discarded");
|
||||
let flush_result = std::io::Write::flush(&mut writer);
|
||||
assert!(write_result.is_ok());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-onchain-transport-lib/README.md -->
|
||||
<!-- version: 1 -->
|
||||
<!-- version: 9 -->
|
||||
|
||||
# `ksp-onchain-transport-lib`
|
||||
|
||||
@@ -69,16 +69,19 @@ Le registre porte notamment :
|
||||
- remplacement historique éventuel ;
|
||||
- release de couverture typée KSP.
|
||||
|
||||
Les quatre wrappers typés de la foundation sont :
|
||||
La release stable `0.2.4` complète la surface typée des **52 méthodes courantes** :
|
||||
|
||||
```text
|
||||
getBalance
|
||||
getGenesisHash
|
||||
getHealth
|
||||
getVersion
|
||||
0.2.1 foundation : 4
|
||||
0.2.2 Accounts/Tokens/Cluster : 22
|
||||
0.2.3 Transactions : 11
|
||||
0.2.4 Blocks/Economics : 15
|
||||
total : 52
|
||||
```
|
||||
|
||||
Les autres méthodes courantes peuvent déjà passer par l'exécuteur JSON-RPC standard générique lorsqu'un consumer fournit explicitement descriptor et paramètres JSON. Cette surface raw/générique **ne vaut pas couverture typée** : les wrappers et DTOs typés restants sont introduits selon la matrice HTTP KSP.
|
||||
`0.2.4` ajoute les dix wrappers Blocks et les cinq wrappers Economics. Les points sensibles restent notamment `getBlock` moderne + bare encoding legacy deprecated, les quatre `transactionDetails`, les versions transaction numériques génériques, `numRewardPartitions`, `commissionBps`, les overloads de `getBlocks`, les ranges de production, les valeurs d'inflation/minimum de délégation fournies par le runtime et les `null` positionnels de `getInflationReward`.
|
||||
|
||||
`KSP-TRANSPORT-007` impose qu'un wrapper typé couvre toutes les possibilités RPC supportées retenues par l'audit : paramètres/options, overloads et formes legacy encore supportées, contraintes déterministes utiles et variantes de réponse pertinentes sans perte. Le réaudit final `0.2.4` agrège le réaudit 37/37 de `0.2.3` avec les 15 nouveaux wrappers et porte la preuve stable à **52/52**.
|
||||
|
||||
Les 14 méthodes historiques restent découvrables pour la compliance mais sont `Removed` et ne sont pas simulées comme appelables.
|
||||
|
||||
@@ -118,18 +121,31 @@ La configuration Logging de référence conserve un fichier dédié Transport à
|
||||
|
||||
Les tests par défaut sont déterministes et n'exigent pas Internet : fixtures JSON et serveur HTTP local couvrent requêtes, réponses, retry, 429, timeout, redaction et routing.
|
||||
|
||||
Un smoke Devnet live existe côté `ksp-config-lib` afin de tester la chaîne réelle :
|
||||
Deux smokes Devnet opt-in sont séparés par responsabilité :
|
||||
|
||||
```text
|
||||
Config -> std.transport/devnet_public -> HttpTransportPool
|
||||
Transport pur : settings programmatiques -> HttpTransportPool
|
||||
-> Accounts/Tokens/Cluster représentatifs
|
||||
-> trois reads Transactions
|
||||
-> getBlockHeight
|
||||
-> getInflationRate/getStakeMinimumDelegation
|
||||
|
||||
Composition historique : Config -> std.transport/devnet_public -> HttpTransportPool
|
||||
-> getHealth/getGenesisHash/getVersion/getBalance
|
||||
```
|
||||
|
||||
Il est `ignored` par défaut et doit être exécuté explicitement.
|
||||
Le smoke Transport utilise pour sa branche Token la forme Devnet documentée `getTokenAccountsByOwner(owner, { programId }, { commitment: finalized, encoding: jsonParsed })`. L'owner est une Pubkey ordinaire de l'exemple officiel ; aucune présence de token account n'est exigée, donc une liste vide reste valide.
|
||||
|
||||
Les deux sont `ignored` par défaut. Le smoke Transport appartient durablement à cette crate ; le smoke cross-crates hébergé dans Config reste transitoire jusqu'à l'existence d'une surface KSP d'intégration/orchestration appropriée.
|
||||
|
||||
## Documentation
|
||||
|
||||
- [`USAGE.md`](USAGE.md) — consommation directe, Config -> Transport, API typed/raw et inspection runtime ;
|
||||
- [`../../docs/plans/008-V0_2_1_ONCHAIN_HTTP_PLAN.md`](../../docs/plans/008-V0_2_1_ONCHAIN_HTTP_PLAN.md) — plan et matrice HTTP ;
|
||||
- [`../../docs/validation/003-V0_2_1_ONCHAIN_HTTP.md`](../../docs/validation/003-V0_2_1_ONCHAIN_HTTP.md) — matrice de clôture ;
|
||||
- [`USAGE.md`](USAGE.md) — consommation directe, Config -> Transport, API typed/raw, smokes et inspection runtime ;
|
||||
- [`../../docs/plans/008-V0_2_1_ONCHAIN_HTTP_PLAN.md`](../../docs/plans/008-V0_2_1_ONCHAIN_HTTP_PLAN.md) — foundation HTTP stable ;
|
||||
- [`../../docs/plans/009-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER_PLAN.md`](../../docs/plans/009-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER_PLAN.md) — extension typed Accounts/Tokens/Cluster ;
|
||||
- [`../../docs/validation/004-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER.md`](../../docs/validation/004-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER.md) — matrice finale validée `0.2.2` ;
|
||||
- [`../../docs/plans/010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md`](../../docs/plans/010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md) — plan historique clôturé de `0.2.3` ;
|
||||
- [`../../docs/validation/006-V0_2_3_HTTP_TRANSACTIONS.md`](../../docs/validation/006-V0_2_3_HTTP_TRANSACTIONS.md) — matrice finale validée `0.2.3` ;
|
||||
- [`../../docs/plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md`](../../docs/plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md) — plan Blocks/Economics et compliance HTTP finale ;
|
||||
- [`../../docs/validation/007-V0_2_4_HTTP_FINAL_COMPLIANCE.md`](../../docs/validation/007-V0_2_4_HTTP_FINAL_COMPLIANCE.md) — matrice finale validée `52/52 + 14/14` et audit `KSP-TRANSPORT-007` global ;
|
||||
- [`../../config/std.transport.json`](../../config/std.transport.json) — configuration standard HTTP.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-onchain-transport-lib/USAGE.md -->
|
||||
<!-- version: 1 -->
|
||||
<!-- version: 9 -->
|
||||
|
||||
# Utilisation de `ksp-onchain-transport-lib`
|
||||
|
||||
@@ -86,17 +86,44 @@ let balance = pool
|
||||
.await;
|
||||
```
|
||||
|
||||
Les types de retour associés sont :
|
||||
Les quatre canaris `0.2.1` restent disponibles. `0.2.2` ajoute les wrappers typés Accounts, Tokens et Cluster. Exemples représentatifs :
|
||||
|
||||
```text
|
||||
SolanaNodeHealth
|
||||
SolanaGenesisHash
|
||||
SolanaNodeVersion
|
||||
GetBalanceResult
|
||||
SolanaRpcContext
|
||||
```rust
|
||||
let account = pool
|
||||
.get_account_info(&role, &ksp_core_lib::PRGIDPK_SOLANA_SYSTEM, None)
|
||||
.await;
|
||||
let epoch = pool.get_epoch_info(&role, None).await;
|
||||
let vote_accounts = pool.get_vote_accounts(&role, None).await;
|
||||
```
|
||||
|
||||
`GetBalanceResult::value()` renvoie les lamports et `context()` fournit le slot/API version retournés par Solana.
|
||||
La release stable `0.2.4` contient les **52 wrappers typés courants** : 4 foundation + 22 Accounts/Tokens/Cluster + 11 Transactions + 10 Blocks + 5 Economics. Les DTOs Transport conservent les `null`, options, overloads et formes wire sans décodage Program/SPL métier.
|
||||
|
||||
Exemples Transaction représentatifs :
|
||||
|
||||
```rust
|
||||
let context = ksp_onchain_transport_lib::SolanaContextConfig::new(
|
||||
std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Finalized),
|
||||
std::option::Option::None,
|
||||
);
|
||||
let latest = pool
|
||||
.get_latest_blockhash(&role, std::option::Option::Some(&context))
|
||||
.await;
|
||||
let transaction_count = pool
|
||||
.get_transaction_count(&role, std::option::Option::Some(&context))
|
||||
.await;
|
||||
```
|
||||
|
||||
`getTransaction` expose la config moderne complète et une forme bare-encoding legacy séparée et deprecated. `requestAirdrop` et `sendTransaction` sont des write submissions : elles utilisent la protection centrale `NeverAfterDispatch`. `simulateTransaction` reste une simulation retry-safe et conserve son résultat riche sans introduire de décodage Program.
|
||||
|
||||
Exemples Blocks/Economics représentatifs :
|
||||
|
||||
```rust
|
||||
let block_height = pool.get_block_height(&role, Some(&context)).await;
|
||||
let inflation_rate = pool.get_inflation_rate(&role).await;
|
||||
let stake_minimum = pool.get_stake_minimum_delegation(&role, Some(&context)).await;
|
||||
```
|
||||
|
||||
`getBlock` possède également une forme bare-encoding legacy séparée et deprecated. Les valeurs Economics restent celles du runtime : le consumer ne doit pas supposer localement un taux d'inflation ou un minimum de délégation constant.
|
||||
|
||||
## 4. Exécution JSON-RPC standard générique
|
||||
|
||||
@@ -108,7 +135,7 @@ if let Some(descriptor) = ksp_onchain_transport_lib::find_http_rpc_method("getSl
|
||||
}
|
||||
```
|
||||
|
||||
Cette API retourne un `serde_json::Value`. Elle est utile pour les consumers techniques et pour préparer les futures surfaces typées, mais elle ne remplace pas le wrapper typé d'une méthode dans la matrice de couverture KSP.
|
||||
Cette API retourne un `serde_json::Value`. Elle reste utile pour les extensions provider, les diagnostics et les méthodes hors registre standard, mais elle ne remplace jamais le wrapper typé d'une méthode HTTP standard désormais couverte par KSP.
|
||||
|
||||
Avant exécution, `ensure_runtime_supported()` est appliqué. Une méthode historique `Removed` retourne `ERROR_CODE_METHOD_REMOVED` au lieu d'émettre un appel réseau fictif.
|
||||
|
||||
@@ -153,12 +180,22 @@ Ne jamais journaliser l'URL complète, un token provider, un body massif, une tr
|
||||
|
||||
La configuration standard route les événements `info` de Transport vers un fichier dédié. Pour une investigation temporaire, élever uniquement ce target/sink à `debug` ou `trace`, puis revenir à `info` avant clôture du développement.
|
||||
|
||||
## 9. Smoke Devnet opt-in
|
||||
## 9. Smokes Devnet opt-in
|
||||
|
||||
Le smoke live est volontairement hors des tests par défaut :
|
||||
Le smoke **Transport pur** construit ses settings programmatiquement et exerce un sous-ensemble représentatif d'Accounts/Tokens/Cluster, trois reads Transactions, puis des reads Blocks/Economics de la release stable `0.2.4` :
|
||||
|
||||
```bash
|
||||
cargo test -p ksp-onchain-transport-lib --test transport_devnet_smoke -- --ignored --nocapture
|
||||
```
|
||||
|
||||
Il appelle `getAccountInfo`, `getTokenAccountsByOwner`, `getEpochInfo`, `getVoteAccounts`, puis `getLatestBlockhash`, `isBlockhashValid`, `getTransactionCount`, `getBlockHeight`, `getInflationRate` et `getStakeMinimumDelegation`. La branche Token suit la forme Devnet documentée : owner Pubkey ordinaire de l'exemple officiel, selector `programId` avec l'ID canonique du programme SPL Token, puis config explicite `commitment: finalized` + `encoding: jsonParsed`. Une réponse vide reste acceptable. La branche Transaction reste read-only : elle ne déclenche ni airdrop ni soumission de transaction et ne remplace pas les fixtures déterministes couvrant les 11 wrappers.
|
||||
|
||||
Le smoke historique de **composition Config -> Transport** reste également disponible :
|
||||
|
||||
```bash
|
||||
cargo test -p ksp-config-lib --test transport_devnet_smoke -- --ignored --nocapture
|
||||
```
|
||||
|
||||
Il charge le profil Config `devnet_public`, construit le pool puis appelle les quatre wrappers typés. Les endpoints publics Solana étant rate-limités et non destinés à la production, un échec réseau externe n'est pas interprété comme un échec déterministe de la suite locale.
|
||||
Il valide le profil committé `devnet_public` et les quatre canaris foundation. Il reste transitoirement hébergé dans Config : les futurs smokes cross-crates ne doivent pas faire de Config leur destination générale et devront migrer vers une surface d'intégration/orchestration dédiée lorsqu'elle existera.
|
||||
|
||||
Les endpoints publics Solana sont rate-limités et non destinés à la production. Un échec réseau externe n'est pas assimilé automatiquement à une régression locale ; les fixtures HTTP locales restent les gates reproductibles.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
[
|
||||
{"lamports":1,"data":"3MN5","owner":"11111111111111111111111111111111","executable":false,"rentEpoch":0,"space":null},
|
||||
{"lamports":2,"data":["KLUv/Q==","base64+zstd"],"owner":"11111111111111111111111111111111","executable":false,"rentEpoch":1,"space":4},
|
||||
{"lamports":3,"data":{"program":"spl-token","parsed":{"type":"account","info":{"state":"initialized"}},"space":165},"owner":"11111111111111111111111111111111","executable":false,"rentEpoch":2,"space":165}
|
||||
]
|
||||
@@ -0,0 +1,4 @@
|
||||
[
|
||||
{"commitment":[1,2,3,4],"totalStake":1000000000},
|
||||
{"commitment":null,"totalStake":1000000000}
|
||||
]
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"byIdentity": {
|
||||
"11111111111111111111111111111111": [8,7],
|
||||
"Vote111111111111111111111111111111111111111": [4,4]
|
||||
},
|
||||
"range": {"firstSlot":430000000,"lastSlot":430000099}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"pubkey":"11111111111111111111111111111111","featureSet":123,"gossip":"127.0.0.1:8001","pubsub":null,"rpc":"127.0.0.1:8899","serveRepair":"127.0.0.1:8003","shredVersion":456,"tpu":"127.0.0.1:8004","tpuForwards":null,"tpuForwardsQuic":"127.0.0.1:8006","tpuQuic":"127.0.0.1:8005","tpuVote":null,"tvu":"127.0.0.1:8002","version":"4.2.1","clientId":"Agave"}
|
||||
@@ -0,0 +1,20 @@
|
||||
[
|
||||
{
|
||||
"previousBlockhash":"previous-a",
|
||||
"blockhash":"block-a",
|
||||
"parentSlot":1,
|
||||
"blockTime":null,
|
||||
"blockHeight":null
|
||||
},
|
||||
{
|
||||
"previousBlockhash":"previous-b",
|
||||
"blockhash":"block-b",
|
||||
"parentSlot":2,
|
||||
"transactions":null,
|
||||
"signatures":null,
|
||||
"rewards":null,
|
||||
"numRewardPartitions":null,
|
||||
"blockTime":null,
|
||||
"blockHeight":null
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"previousBlockhash":"previous-blockhash-fixture",
|
||||
"blockhash":"blockhash-fixture",
|
||||
"parentSlot":430000122,
|
||||
"transactions":[
|
||||
{
|
||||
"transaction":{"signatures":["signature-fixture"],"message":{"accountKeys":[],"recentBlockhash":"recent-fixture","instructions":[]}},
|
||||
"meta":{"err":null,"fee":5000},
|
||||
"version":1
|
||||
},
|
||||
{
|
||||
"transaction":["AQID","base64"],
|
||||
"meta":null
|
||||
}
|
||||
],
|
||||
"rewards":[
|
||||
{
|
||||
"pubkey":"11111111111111111111111111111111",
|
||||
"lamports":2500,
|
||||
"postBalance":1000002500,
|
||||
"rewardType":"Fee",
|
||||
"commission":null,
|
||||
"commissionBps":1234
|
||||
},
|
||||
{
|
||||
"pubkey":"Vote111111111111111111111111111111111111111",
|
||||
"lamports":1250,
|
||||
"postBalance":2000001250,
|
||||
"rewardType":"Voting",
|
||||
"commission":5
|
||||
}
|
||||
],
|
||||
"numRewardPartitions":4,
|
||||
"blockTime":1787072400,
|
||||
"blockHeight":410000000
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
[
|
||||
{
|
||||
"slot": 430000123,
|
||||
"blockTime": 1787000000,
|
||||
"transaction": ["AQIDBAUGBwg=", "base64"],
|
||||
"meta": {
|
||||
"err": null,
|
||||
"status": {"Ok": null},
|
||||
"fee": 5000,
|
||||
"preBalances": [10000],
|
||||
"postBalances": [5000]
|
||||
},
|
||||
"version": 0,
|
||||
"transactionIndex": 7
|
||||
},
|
||||
{
|
||||
"slot": 430000100,
|
||||
"blockTime": null,
|
||||
"transaction": "3Bxs4NN8M2Yn4TLb",
|
||||
"meta": null,
|
||||
"version": "legacy",
|
||||
"transactionIndex": null
|
||||
},
|
||||
{
|
||||
"slot": 430000050,
|
||||
"blockTime": null,
|
||||
"transaction": {
|
||||
"signatures": [],
|
||||
"message": {
|
||||
"accountKeys": [],
|
||||
"recentBlockhash": "11111111111111111111111111111111",
|
||||
"instructions": []
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,12 @@
|
||||
[
|
||||
"3Bxs4NN8M2Yn4TLb",
|
||||
["AQIDBAUGBwg=", "base64"],
|
||||
{
|
||||
"signatures": ["5h6xBEauJ3PK6SWQkgLxHcQpWyRZyG7c4pUpK9fd2B2B7hRQ1S6F9J7BqvQPEmxQxK4DThfYF5f5f5f5f5f5f5f5"],
|
||||
"message": {
|
||||
"accountKeys": [],
|
||||
"recentBlockhash": "11111111111111111111111111111111",
|
||||
"instructions": []
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":{"context":{"apiVersion":"4.2.1","slot":410000003},"value":{"data":["","base64"],"executable":false,"lamports":1,"owner":"not-a-pubkey","rentEpoch":0,"space":0}},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":{"context":{"apiVersion":"4.2.1","slot":410000002},"value":null},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":{"context":{"apiVersion":"4.2.1","slot":410000001},"value":{"data":["AQIDBA==","base64"],"executable":false,"lamports":2039280,"owner":"11111111111111111111111111111111","rentEpoch":18446744073709551615,"space":4}},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-blockhash-fixture","blockhash":"blockhash-fixture","parentSlot":430000122,"numRewardPartitions":4,"blockTime":1787072400,"blockHeight":410000000,"transactions":[{"transaction":{"signatures":["sig-accounts"],"accountKeys":[{"pubkey":"11111111111111111111111111111111","writable":true,"signer":true,"source":"transaction"}]},"meta":{"err":null,"status":{"Ok":null},"fee":5000,"preBalances":[10],"postBalances":[5]},"version":1}]},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-blockhash-fixture","blockhash":"blockhash-fixture","parentSlot":430000122,"rewards":[{"pubkey":"11111111111111111111111111111111","lamports":2500,"postBalance":1000002500,"rewardType":"Fee","commission":null,"commissionBps":1234},{"pubkey":"Vote111111111111111111111111111111111111111","lamports":1250,"postBalance":2000001250,"rewardType":"Voting","commission":5}],"numRewardPartitions":4,"blockTime":1787072400,"blockHeight":410000000,"transactions":[{"transaction":["3BxsFixtureBase58","base58"],"meta":{"err":null,"fee":5000},"version":"legacy"}]},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-blockhash-fixture","blockhash":"blockhash-fixture","parentSlot":430000122,"rewards":[{"pubkey":"11111111111111111111111111111111","lamports":2500,"postBalance":1000002500,"rewardType":"Fee","commission":null,"commissionBps":1234},{"pubkey":"Vote111111111111111111111111111111111111111","lamports":1250,"postBalance":2000001250,"rewardType":"Voting","commission":5}],"numRewardPartitions":4,"blockTime":1787072400,"blockHeight":410000000,"transactions":[{"transaction":["AQID","base64"],"meta":{"err":null,"fee":5000},"version":"legacy"}]},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-blockhash-fixture","blockhash":"blockhash-fixture","parentSlot":430000122,"rewards":[{"pubkey":"11111111111111111111111111111111","lamports":2500,"postBalance":1000002500,"rewardType":"Fee","commission":null,"commissionBps":1234},{"pubkey":"Vote111111111111111111111111111111111111111","lamports":1250,"postBalance":2000001250,"rewardType":"Voting","commission":5}],"numRewardPartitions":4,"blockTime":1787072400,"blockHeight":410000000,"transactions":[{"transaction":"3BxsFixtureLegacyBinary","meta":{"err":null,"fee":5000},"version":"legacy"}]},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","error":{"code":-32004,"message":"Block not available for slot 430000123"},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","error":{"code":-32015,"message":"Transaction version (1) is not supported by the requesting client. Please try the request again with maxSupportedTransactionVersion 1"},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-blockhash-fixture","blockhash":"blockhash-fixture","parentSlot":430000122,"rewards":[{"pubkey":"11111111111111111111111111111111","lamports":2500,"postBalance":1000002500,"rewardType":"Fee","commission":null,"commissionBps":1234},{"pubkey":"Vote111111111111111111111111111111111111111","lamports":1250,"postBalance":2000001250,"rewardType":"Voting","commission":5}],"numRewardPartitions":4,"blockTime":1787072400,"blockHeight":410000000,"transactions":[{"transaction":{"signatures":["sig-v1"],"message":{"accountKeys":[],"recentBlockhash":"recent-v1","instructions":[],"transactionConfig":{"priorityFee":1,"computeUnitLimit":200000}}},"meta":{"err":null,"fee":5000,"status":{"Ok":null},"rewards":[{"pubkey":"11111111111111111111111111111111","lamports":1,"postBalance":2,"rewardType":"Fee","commission":null,"commissionBps":1234}]},"version":1},{"transaction":["AQID","base64"],"meta":null}]},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-blockhash-fixture","blockhash":"blockhash-fixture","parentSlot":430000122,"numRewardPartitions":4,"blockTime":1787072400,"blockHeight":410000000,"transactions":[{"transaction":{"signatures":["sig-full-no-rewards"],"message":{"accountKeys":[],"recentBlockhash":"recent-full-no-rewards","instructions":[]}},"meta":{"err":null,"fee":5000,"rewards":null},"version":1}]},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-blockhash-fixture","blockhash":"blockhash-fixture","parentSlot":430000122,"rewards":[{"pubkey":"11111111111111111111111111111111","lamports":2500,"postBalance":1000002500,"rewardType":"Fee","commission":null,"commissionBps":1234},{"pubkey":"Vote111111111111111111111111111111111111111","lamports":1250,"postBalance":2000001250,"rewardType":"Voting","commission":5}],"numRewardPartitions":4,"blockTime":1787072400,"blockHeight":410000000,"transactions":[{"transaction":{"signatures":["sig-json"],"message":{"accountKeys":[],"recentBlockhash":"recent-json","instructions":[]}},"meta":{"err":null,"fee":5000},"version":"legacy"}]},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-blockhash-fixture","blockhash":"blockhash-fixture","parentSlot":430000122,"rewards":[{"pubkey":"11111111111111111111111111111111","lamports":2500,"postBalance":1000002500,"rewardType":"Fee","commission":null,"commissionBps":1234},{"pubkey":"Vote111111111111111111111111111111111111111","lamports":1250,"postBalance":2000001250,"rewardType":"Voting","commission":5}],"numRewardPartitions":4,"blockTime":1787072400,"blockHeight":410000000,"transactions":[{"transaction":{"signatures":["sig-parsed"],"message":{"accountKeys":[],"recentBlockhash":"recent-parsed","instructions":[]}},"meta":{"err":null,"fee":5000},"version":"legacy"}]},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-blockhash-fixture","blockhash":"blockhash-fixture","parentSlot":430000122,"numRewardPartitions":4,"blockTime":1787072400,"blockHeight":410000000},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":null,"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-b","blockhash":"block-b","parentSlot":2,"transactions":null,"signatures":null,"rewards":null,"numRewardPartitions":null,"blockTime":null,"blockHeight":null},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-a","blockhash":"block-a","parentSlot":1,"blockTime":null,"blockHeight":null},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":{"previousBlockhash":"previous-blockhash-fixture","blockhash":"blockhash-fixture","parentSlot":430000122,"numRewardPartitions":4,"blockTime":1787072400,"blockHeight":410000000,"signatures":["sig-a","sig-b"]},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":{"commitment":[1,2,3,4],"totalStake":1000000000},"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":410000000,"id":1}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"jsonrpc":"2.0",
|
||||
"result":{
|
||||
"context":{"slot":430000123,"apiVersion":"4.2.1"},
|
||||
"value":{
|
||||
"byIdentity":{"not-a-pubkey":[8,7]},
|
||||
"range":{"firstSlot":430000000,"lastSlot":430000099}
|
||||
}
|
||||
},
|
||||
"id":1
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"jsonrpc":"2.0",
|
||||
"result":{
|
||||
"context":{"slot":430000123,"apiVersion":"4.2.1"},
|
||||
"value":{
|
||||
"byIdentity":{"11111111111111111111111111111111":[8,7]},
|
||||
"range":{"firstSlot":430000000,"lastSlot":430000099}
|
||||
}
|
||||
},
|
||||
"id":1
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":null,"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":1787072400,"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":[],"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":[430000100,430000103,430000109],"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":[],"id":1}
|
||||
@@ -0,0 +1 @@
|
||||
{"jsonrpc":"2.0","result":[430000200,430000201,430000205],"id":1}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user