v0.2.6-pre.003
This commit is contained in:
@@ -37,9 +37,9 @@
|
||||
<div class="d-flex align-items-center justify-content-between mb-4">
|
||||
<div>
|
||||
<h1 id="viewTitle" class="h3 mb-1">Dashboard</h1>
|
||||
<p class="text-body-secondary mb-0">Fondation UI de Wallet Desk — fonctionnalités Wallet branchées dans les tranches suivantes.</p>
|
||||
<p class="text-body-secondary mb-0">Composition Config Wallet Desk active — inventory Wallet branché en pre.004.</p>
|
||||
</div>
|
||||
<span class="badge text-bg-secondary">0.2.6-pre.002</span>
|
||||
<span class="badge text-bg-secondary">0.2.6-pre.003</span>
|
||||
</div>
|
||||
<section data-view-panel="dashboard">
|
||||
<div class="row g-3">
|
||||
@@ -51,8 +51,14 @@
|
||||
<dt class="col-sm-5">Version</dt><dd id="runtimeVersion" class="col-sm-7">—</dd>
|
||||
<dt class="col-sm-5">Phase</dt><dd id="runtimeShellPhase" class="col-sm-7">—</dd>
|
||||
<dt class="col-sm-5">Documents Config enregistrés</dt><dd id="runtimeConfigDocuments" class="col-sm-7">—</dd>
|
||||
<dt class="col-sm-5">Profil composite</dt><dd id="runtimeCompositeProfile" class="col-sm-7">—</dd>
|
||||
<dt class="col-sm-5">Profil Logging</dt><dd id="runtimeLoggingProfile" class="col-sm-7">—</dd>
|
||||
<dt class="col-sm-5">Profil Wallet</dt><dd id="runtimeWalletProfile" class="col-sm-7">—</dd>
|
||||
<dt class="col-sm-5">Fallback Logging</dt><dd id="runtimeLoggingFallback" class="col-sm-7">—</dd>
|
||||
<dt class="col-sm-5">Racine Wallet</dt><dd id="runtimeWalletsDirectory" class="col-sm-7 text-break">—</dd>
|
||||
<dt class="col-sm-5">Sous-répertoire Wallet</dt><dd id="runtimeWalletsSubdirectory" class="col-sm-7 text-break">—</dd>
|
||||
<dt class="col-sm-5">Répertoire effectif</dt><dd id="runtimeEffectiveWalletsDirectory" class="col-sm-7 text-break">—</dd>
|
||||
<dt class="col-sm-5">Créé au démarrage</dt><dd id="runtimeWalletDirectoryCreated" class="col-sm-7">—</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
@@ -96,7 +102,7 @@
|
||||
</div>
|
||||
</main>
|
||||
<footer class="app-footer bg-dark text-white-50 d-flex align-items-center px-4">
|
||||
<span>KSP Wallet Desk · shell pre.002</span>
|
||||
<span>KSP Wallet Desk · Config composition pre.003</span>
|
||||
</footer>
|
||||
<script type="module" src="./ts/main.ts"></script>
|
||||
</body>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-wallet-desk/frontend/ts/main.ts
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
import "bootstrap";
|
||||
import DataTable from "datatables.net-bs5";
|
||||
@@ -81,21 +81,33 @@ function initializeWalletTable(): void {
|
||||
zeroRecords: "Aucun wallet correspondant.",
|
||||
},
|
||||
});
|
||||
frontendDebug("main", "Wallet inventory DataTable initialized", { phase: "pre.002-shell" });
|
||||
frontendDebug("main", "Wallet inventory DataTable initialized", { phase: "pre.003-config-wallet" });
|
||||
}
|
||||
|
||||
function renderRuntimeStatus(status: RuntimeStatusDto): void {
|
||||
const version = document.querySelector<HTMLElement>("#runtimeVersion");
|
||||
const profile = document.querySelector<HTMLElement>("#runtimeLoggingProfile");
|
||||
const compositeProfile = document.querySelector<HTMLElement>("#runtimeCompositeProfile");
|
||||
const loggingProfile = document.querySelector<HTMLElement>("#runtimeLoggingProfile");
|
||||
const walletProfile = document.querySelector<HTMLElement>("#runtimeWalletProfile");
|
||||
const fallback = document.querySelector<HTMLElement>("#runtimeLoggingFallback");
|
||||
const documents = document.querySelector<HTMLElement>("#runtimeConfigDocuments");
|
||||
const walletsRoot = document.querySelector<HTMLElement>("#runtimeWalletsDirectory");
|
||||
const walletsSubdirectory = document.querySelector<HTMLElement>("#runtimeWalletsSubdirectory");
|
||||
const effectiveWalletsDirectory = document.querySelector<HTMLElement>("#runtimeEffectiveWalletsDirectory");
|
||||
const walletDirectoryCreated = document.querySelector<HTMLElement>("#runtimeWalletDirectoryCreated");
|
||||
const phase = document.querySelector<HTMLElement>("#runtimeShellPhase");
|
||||
const shellStatus = document.querySelector<HTMLElement>("#shellStatus");
|
||||
if (version) {
|
||||
version.textContent = status.applicationVersion;
|
||||
}
|
||||
if (profile) {
|
||||
profile.textContent = status.activeLoggingProfile ?? "fallback transitoire";
|
||||
if (compositeProfile) {
|
||||
compositeProfile.textContent = status.activeCompositeProfile;
|
||||
}
|
||||
if (loggingProfile) {
|
||||
loggingProfile.textContent = status.activeLoggingProfile ?? "fallback transitoire";
|
||||
}
|
||||
if (walletProfile) {
|
||||
walletProfile.textContent = status.activeWalletProfile;
|
||||
}
|
||||
if (fallback) {
|
||||
fallback.textContent = status.fallbackLoggingActive ? "oui" : "non";
|
||||
@@ -103,15 +115,30 @@ function renderRuntimeStatus(status: RuntimeStatusDto): void {
|
||||
if (documents) {
|
||||
documents.textContent = status.configDocumentCount.toString();
|
||||
}
|
||||
if (walletsRoot) {
|
||||
walletsRoot.textContent = status.walletsDirectory;
|
||||
}
|
||||
if (walletsSubdirectory) {
|
||||
walletsSubdirectory.textContent = status.walletsSubdirectory ?? "— (racine globale)";
|
||||
}
|
||||
if (effectiveWalletsDirectory) {
|
||||
effectiveWalletsDirectory.textContent = status.effectiveWalletsDirectory;
|
||||
}
|
||||
if (walletDirectoryCreated) {
|
||||
walletDirectoryCreated.textContent = status.effectiveWalletsDirectoryCreatedOnStartup ? "oui" : "non, déjà présent";
|
||||
}
|
||||
if (phase) {
|
||||
phase.textContent = status.shellPhase;
|
||||
}
|
||||
if (shellStatus) {
|
||||
shellStatus.textContent = "Shell Wallet Desk prêt.";
|
||||
shellStatus.textContent = "Config Wallet Desk résolue ; répertoire Wallet prêt.";
|
||||
}
|
||||
frontendTrace("main", "Wallet Desk runtime status rendered", {
|
||||
compositeProfile: status.activeCompositeProfile,
|
||||
fallbackLoggingActive: status.fallbackLoggingActive,
|
||||
shellPhase: status.shellPhase,
|
||||
walletDirectoryCreatedOnStartup: status.effectiveWalletsDirectoryCreatedOnStartup,
|
||||
walletProfile: status.activeWalletProfile,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ksp-app-wallet-desk",
|
||||
"private": true,
|
||||
"version": "0.2.6-pre.2",
|
||||
"version": "0.2.6-pre.3",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-wallet-desk/src/app_state.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
//! Shared backend state owned by the Wallet Desk Tauri application.
|
||||
|
||||
@@ -9,10 +9,11 @@ pub(crate) struct AppState {
|
||||
logging_runtime: std::sync::Mutex<LoggingRuntimeState>,
|
||||
splash_settings: crate::SplashSettings,
|
||||
splash_sequence_started: std::sync::atomic::AtomicBool,
|
||||
wallet_config_startup: crate::WalletConfigStartup,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
/// Initializes Config ownership, Logging and the common desktop splash state.
|
||||
/// Initializes Config composition, Logging, Wallet directory preparation and the common desktop splash state.
|
||||
pub(crate) fn initialize(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<Self> {
|
||||
let config_management = crate::config_management(arguments);
|
||||
let config_management = match config_management {
|
||||
@@ -29,6 +30,14 @@ impl AppState {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let wallet_config_startup = crate::initialize_wallet_config(&config_management);
|
||||
let wallet_config_startup = match wallet_config_startup {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
ksp_logging_lib::error!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_BOOTSTRAP, error_domain = error.code().domain(), error_code = error.code().code(), "Wallet Desk Config/Wallet directory bootstrap failed");
|
||||
return std::result::Result::Err(error);
|
||||
},
|
||||
};
|
||||
let splash_settings = crate::SplashSettings::load();
|
||||
let splash_settings = match splash_settings {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -48,10 +57,11 @@ impl AppState {
|
||||
}),
|
||||
splash_settings,
|
||||
splash_sequence_started: std::sync::atomic::AtomicBool::new(false),
|
||||
wallet_config_startup,
|
||||
});
|
||||
}
|
||||
|
||||
/// Builds the safe shell status DTO exposed during pre.002.
|
||||
/// Builds the safe Config-composition status DTO exposed during pre.003.
|
||||
pub(crate) fn runtime_status(&self) -> ksp_core_lib::Result<crate::RuntimeStatusDto> {
|
||||
let document_count = self.config_management.engine().registry().descriptors().count();
|
||||
let document_count = u32::try_from(document_count);
|
||||
@@ -61,7 +71,7 @@ impl AppState {
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_APP_STATE_INVALID,
|
||||
"Config registry contains too many descriptors for the Wallet Desk shell DTO",
|
||||
"Config registry contains too many descriptors for the Wallet Desk runtime DTO",
|
||||
)
|
||||
.with_source(error),
|
||||
);
|
||||
@@ -78,13 +88,22 @@ impl AppState {
|
||||
},
|
||||
};
|
||||
let _keep_guard_alive = &runtime.guard;
|
||||
let resolved = self.wallet_config_startup.resolved();
|
||||
let wallets_subdirectory = resolved.wallets_subdirectory().map(|value| return value.to_string_lossy().into_owned());
|
||||
return std::result::Result::Ok(crate::RuntimeStatusDto {
|
||||
application_version: env!("CARGO_PKG_VERSION").to_owned(),
|
||||
config_document_count: document_count,
|
||||
active_composite_profile: self.wallet_config_startup.composite_profile_id().to_owned(),
|
||||
active_logging_profile: runtime.active_profile_id.clone(),
|
||||
active_wallet_profile: resolved.profile_id().to_owned(),
|
||||
config_document_count: document_count,
|
||||
effective_wallets_directory: resolved.effective_wallets_directory().to_string_lossy().into_owned(),
|
||||
effective_wallets_directory_created_on_startup: self.wallet_config_startup.effective_directory_created_on_startup(),
|
||||
fallback_logging_active: runtime.fallback_active,
|
||||
root_wallets_directory_created_on_startup: self.wallet_config_startup.root_directory_created_on_startup(),
|
||||
shell_phase: "pre.003-config-wallet".to_owned(),
|
||||
startup_diagnostic: runtime.startup_diagnostic.clone(),
|
||||
shell_phase: "pre.002-shell".to_owned(),
|
||||
wallets_directory: resolved.wallets_directory().to_string_lossy().into_owned(),
|
||||
wallets_subdirectory,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// file: crates/ksp-app-wallet-desk/src/bootstrap.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Config and Logging bootstrap for Wallet Desk.
|
||||
//! Config composite and Logging bootstrap for Wallet Desk.
|
||||
|
||||
/// Crate-internal Logging startup state shared by the application state.
|
||||
pub(crate) struct LoggingStartup {
|
||||
@@ -43,7 +43,7 @@ pub(crate) fn config_management(arguments: &[std::ffi::OsString]) -> ksp_core_li
|
||||
return std::result::Result::Ok(ksp_config_lib::ConfigManagement::new(engine));
|
||||
}
|
||||
|
||||
/// Initializes the Logging runtime from Config, with the same bounded fallback policy as Config Desk.
|
||||
/// Initializes the Logging runtime from the Wallet Desk composite, with the same bounded fallback policy as Config Desk.
|
||||
pub(crate) fn initialize_logging(
|
||||
management: &ksp_config_lib::ConfigManagement,
|
||||
runtime_identity: &ksp_logging_lib::LoggingRuntimeIdentity,
|
||||
@@ -54,7 +54,7 @@ pub(crate) fn initialize_logging(
|
||||
let guard = ksp_logging_lib::initialize_with_identity(&settings, runtime_identity);
|
||||
match guard {
|
||||
std::result::Result::Ok(guard) => {
|
||||
ksp_logging_lib::info!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_BOOTSTRAP, active_profile = active_profile_id.as_str(), "initialized Wallet Desk logging from managed configuration");
|
||||
ksp_logging_lib::info!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_BOOTSTRAP, active_profile = active_profile_id.as_str(), "initialized Wallet Desk logging from composite-managed configuration");
|
||||
std::result::Result::Ok(LoggingStartup {
|
||||
guard,
|
||||
active_profile_id: std::option::Option::Some(active_profile_id),
|
||||
@@ -71,6 +71,47 @@ pub(crate) fn initialize_logging(
|
||||
};
|
||||
}
|
||||
|
||||
/// Loads the concrete Wallet Desk Config composite using its registered logical `file_id` and autonomous default profile.
|
||||
pub(crate) fn load_wallet_desk_composite(management: &ksp_config_lib::ConfigManagement) -> ksp_core_lib::Result<ksp_config_lib::ResolvedConfigComposite> {
|
||||
let file_id = ksp_config_lib::ConfigFileId::new(ksp_config_lib::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK);
|
||||
let file_id = match file_id {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return management.engine().load_resolved_composite(&file_id, std::option::Option::None);
|
||||
}
|
||||
|
||||
/// Returns one required standard profile from the Wallet Desk composite after validating the referenced logical `file_id`.
|
||||
pub(crate) fn required_composite_component_profile(
|
||||
composite: &ksp_config_lib::ResolvedConfigComposite,
|
||||
component_id: &str,
|
||||
expected_file_id: &str,
|
||||
) -> ksp_core_lib::Result<ksp_config_lib::ResolvedConfigProfile> {
|
||||
let component = composite.component(component_id);
|
||||
let component = match component {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_CONFIG_COMPOSITE_INVALID, "Wallet Desk composite is missing a required component")
|
||||
.with_context("composite_file_id", composite.file_id().as_str())
|
||||
.with_context("composite_profile_id", composite.profile_id())
|
||||
.with_context("component_id", component_id),
|
||||
);
|
||||
},
|
||||
};
|
||||
if component.resolved().file_id().as_str() != expected_file_id {
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_CONFIG_COMPOSITE_INVALID, "Wallet Desk composite component references an unexpected Config document")
|
||||
.with_context("composite_file_id", composite.file_id().as_str())
|
||||
.with_context("composite_profile_id", composite.profile_id())
|
||||
.with_context("component_id", component_id)
|
||||
.with_context("expected_file_id", expected_file_id)
|
||||
.with_context("actual_file_id", component.resolved().file_id().as_str()),
|
||||
);
|
||||
}
|
||||
return std::result::Result::Ok(component.resolved().clone());
|
||||
}
|
||||
|
||||
fn fallback_logging_settings() -> ksp_logging_lib::LoggingSettings {
|
||||
return ksp_logging_lib::LoggingSettings::new(
|
||||
ksp_logging_lib::LogFilterLevel::Info,
|
||||
@@ -86,7 +127,26 @@ fn resolve_logging_startup(management: &ksp_config_lib::ConfigManagement) -> Log
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return fallback_startup_plan(error),
|
||||
};
|
||||
let resolved = management.engine().load_resolved_logging_config(std::option::Option::None, &environment);
|
||||
let composite = crate::load_wallet_desk_composite(management);
|
||||
let composite = match composite {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return fallback_startup_plan(error),
|
||||
};
|
||||
let logging_profile = crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_LOGGING, ksp_config_lib::FILE_ID_STD_LOGGING);
|
||||
let logging_profile = match logging_profile {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return fallback_startup_plan(error),
|
||||
};
|
||||
let transport_profile =
|
||||
crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_TRANSPORT, ksp_config_lib::FILE_ID_STD_TRANSPORT);
|
||||
if let std::result::Result::Err(error) = transport_profile {
|
||||
return fallback_startup_plan(error);
|
||||
}
|
||||
let wallet_profile = crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_WALLET, ksp_config_lib::FILE_ID_STD_WALLET);
|
||||
if let std::result::Result::Err(error) = wallet_profile {
|
||||
return fallback_startup_plan(error);
|
||||
}
|
||||
let resolved = management.engine().resolve_logging_config_profile(&logging_profile, &environment);
|
||||
let resolved = match resolved {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return fallback_startup_plan(error),
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
// file: crates/ksp-app-wallet-desk/src/constants.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Logging targets and domains owned by Wallet Desk.
|
||||
//! Logging targets, domains and composite component identifiers owned by Wallet Desk.
|
||||
|
||||
/// Composite-local identifier for the Logging standard document.
|
||||
pub(crate) const COMPOSITE_COMPONENT_ID_LOGGING: &str = "logging";
|
||||
/// Composite-local identifier for the HTTP Transport standard document.
|
||||
pub(crate) const COMPOSITE_COMPONENT_ID_TRANSPORT: &str = "transport";
|
||||
/// Composite-local identifier for the Wallet standard document.
|
||||
pub(crate) const COMPOSITE_COMPONENT_ID_WALLET: &str = "wallet";
|
||||
/// Structured domain used while bootstrapping Config and Logging.
|
||||
pub(crate) const TRACING_DOMAIN_BOOTSTRAP: &str = "wallet.bootstrap";
|
||||
/// Structured domain used by technical frontend events.
|
||||
pub(crate) const TRACING_DOMAIN_FRONTEND: &str = "frontend";
|
||||
/// Structured domain used by the Wallet Desk shell.
|
||||
pub(crate) const TRACING_DOMAIN_SHELL: &str = "wallet.shell";
|
||||
/// Structured domain used while preparing Wallet filesystem roots.
|
||||
pub(crate) const TRACING_DOMAIN_WALLET_CONFIG: &str = "wallet.config";
|
||||
/// Structured domain used by Tauri window lifecycle operations.
|
||||
pub(crate) const TRACING_DOMAIN_WINDOWS: &str = "desktop.window";
|
||||
/// Owning target for backend events emitted by Wallet Desk.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-wallet-desk/src/dto_common.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Common Tauri DTOs shared by Wallet Desk shell commands.
|
||||
|
||||
@@ -30,23 +30,37 @@ impl CommandErrorDto {
|
||||
}
|
||||
}
|
||||
|
||||
/// Initial application/runtime snapshot exposed to the Wallet Desk shell.
|
||||
/// Safe application/runtime snapshot exposed to the Wallet Desk shell.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_wallet_desk/dto_common/RuntimeStatusDto.ts")]
|
||||
pub(crate) struct RuntimeStatusDto {
|
||||
/// Cargo application version.
|
||||
pub(crate) application_version: String,
|
||||
/// Number of documents registered by Config before Wallet-specific documents are introduced.
|
||||
pub(crate) config_document_count: u32,
|
||||
/// Wallet Desk composite profile selected during bootstrap.
|
||||
pub(crate) active_composite_profile: String,
|
||||
/// Active configured Logging profile, or `None` while the transient fallback runtime is active.
|
||||
pub(crate) active_logging_profile: std::option::Option<String>,
|
||||
/// Standard Wallet profile selected by the active composite.
|
||||
pub(crate) active_wallet_profile: String,
|
||||
/// Number of logical Config resources registered by the current application runtime.
|
||||
pub(crate) config_document_count: u32,
|
||||
/// Effective Wallet directory after applying the optional profile subdirectory.
|
||||
pub(crate) effective_wallets_directory: String,
|
||||
/// Whether bootstrap created any missing component of the effective Wallet profile directory.
|
||||
pub(crate) effective_wallets_directory_created_on_startup: bool,
|
||||
/// Whether Wallet Desk had to install its transient in-memory Logging fallback.
|
||||
pub(crate) fallback_logging_active: bool,
|
||||
/// Whether bootstrap created the configured global Wallet root.
|
||||
pub(crate) root_wallets_directory_created_on_startup: bool,
|
||||
/// Current implementation phase exposed for the Config composition tranche.
|
||||
pub(crate) shell_phase: String,
|
||||
/// Safe startup diagnostic that caused fallback Logging, when applicable.
|
||||
pub(crate) startup_diagnostic: std::option::Option<CommandErrorDto>,
|
||||
/// Current implementation phase exposed for the pre.002 shell.
|
||||
pub(crate) shell_phase: String,
|
||||
/// Resolved global Wallet root configured by `cfg.std.wallet`.
|
||||
pub(crate) wallets_directory: String,
|
||||
/// Optional profile-relative Wallet subdirectory selected by the active composite.
|
||||
pub(crate) wallets_subdirectory: std::option::Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
// file: crates/ksp-app-wallet-desk/src/errors.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Application-local error codes for the Wallet Desk shell.
|
||||
//! Application-local error codes for Wallet Desk composition and desktop runtime surfaces.
|
||||
|
||||
/// Shared Wallet Desk application state is internally inconsistent.
|
||||
pub(crate) const ERROR_CODE_APP_STATE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet_desk", "app_state_invalid");
|
||||
/// Shared Wallet 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("wallet_desk", "app_state_lock_failed");
|
||||
/// The Wallet Desk Config composite does not expose one of its required standard components with the expected `file_id`.
|
||||
pub(crate) const ERROR_CODE_CONFIG_COMPOSITE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet_desk", "config_composite_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("wallet_desk", "frontend_log_level_invalid");
|
||||
/// Frontend logging requested a target outside the application whitelist.
|
||||
@@ -24,3 +26,8 @@ pub(crate) const ERROR_CODE_TAURI_WINDOW_MISSING: ksp_core_lib::ErrorCode = ksp_
|
||||
/// 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("wallet_desk", "tauri_window_operation_failed");
|
||||
/// The configured Wallet root or effective profile directory resolves to an unsupported filesystem object.
|
||||
pub(crate) const ERROR_CODE_WALLET_DIRECTORY_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet_desk", "wallet_directory_invalid");
|
||||
/// Wallet Desk could not inspect or create the configured Wallet directory tree.
|
||||
pub(crate) const ERROR_CODE_WALLET_DIRECTORY_PREPARE_FAILED: ksp_core_lib::ErrorCode =
|
||||
ksp_core_lib::ErrorCode::new("wallet_desk", "wallet_directory_prepare_failed");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-wallet-desk/src/lib.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Tauri desktop application shell for KSP Wallet management and inspection.
|
||||
|
||||
@@ -18,6 +18,7 @@ mod splash;
|
||||
mod tauri;
|
||||
mod tw_main;
|
||||
mod tw_splash;
|
||||
mod wallet_config;
|
||||
|
||||
/// Runs the KSP wallet desktop application.
|
||||
pub use self::tauri::run;
|
||||
@@ -30,12 +31,24 @@ pub(crate) use self::bootstrap::LoggingStartup;
|
||||
pub(crate) use self::bootstrap::config_management;
|
||||
/// Initializes the Logging runtime from Config, with the same bounded fallback policy as Config Desk.
|
||||
pub(crate) use self::bootstrap::initialize_logging;
|
||||
/// Loads the concrete Wallet Desk Config composite.
|
||||
pub(crate) use self::bootstrap::load_wallet_desk_composite;
|
||||
/// Resolves and validates one required standard-document component from the Wallet Desk composite.
|
||||
pub(crate) use self::bootstrap::required_composite_component_profile;
|
||||
/// Composite-local identifier for the Logging standard document.
|
||||
pub(crate) use self::constants::COMPOSITE_COMPONENT_ID_LOGGING;
|
||||
/// Composite-local identifier for the HTTP Transport standard document.
|
||||
pub(crate) use self::constants::COMPOSITE_COMPONENT_ID_TRANSPORT;
|
||||
/// Composite-local identifier for the Wallet standard document.
|
||||
pub(crate) use self::constants::COMPOSITE_COMPONENT_ID_WALLET;
|
||||
/// Structured domain used while bootstrapping Config and Logging.
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_BOOTSTRAP;
|
||||
/// Structured domain used by technical frontend events.
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_FRONTEND;
|
||||
/// Structured domain used by the Wallet Desk shell.
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_SHELL;
|
||||
/// Structured domain used while preparing Wallet filesystem roots.
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_WALLET_CONFIG;
|
||||
/// Structured domain used by Tauri window lifecycle operations.
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_WINDOWS;
|
||||
/// Owning target for backend events emitted by Wallet Desk.
|
||||
@@ -54,6 +67,8 @@ pub(crate) use self::dto_common::RuntimeStatusDto;
|
||||
pub(crate) use self::errors::ERROR_CODE_APP_STATE_INVALID;
|
||||
/// Shared Wallet Desk runtime state cannot be locked safely.
|
||||
pub(crate) use self::errors::ERROR_CODE_APP_STATE_LOCK_FAILED;
|
||||
/// The Wallet Desk Config composite is missing or misroutes one required standard component.
|
||||
pub(crate) use self::errors::ERROR_CODE_CONFIG_COMPOSITE_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.
|
||||
@@ -70,6 +85,10 @@ pub(crate) use self::errors::ERROR_CODE_TAURI_RUNTIME_FAILED;
|
||||
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;
|
||||
/// The configured Wallet root or effective profile directory resolves to an unsupported filesystem object.
|
||||
pub(crate) use self::errors::ERROR_CODE_WALLET_DIRECTORY_INVALID;
|
||||
/// Wallet Desk could not inspect or create the configured Wallet directory tree.
|
||||
pub(crate) use self::errors::ERROR_CODE_WALLET_DIRECTORY_PREPARE_FAILED;
|
||||
/// Log payload sent by Wallet Desk frontend scripts.
|
||||
pub(crate) use self::frontend_logging::FrontendLogPayloadDto;
|
||||
/// Emits one validated frontend event through the KSP Logging facade.
|
||||
@@ -88,3 +107,7 @@ pub(crate) use self::tw_main::show_main_window;
|
||||
pub(crate) use self::tw_splash::require_splash_window;
|
||||
/// Starts the one-shot splash lifecycle after the splash frontend reports readiness.
|
||||
pub(crate) use self::tw_splash::splash_frontend_ready_service;
|
||||
/// Resolved Wallet Config and directory preparation status captured during application bootstrap.
|
||||
pub(crate) use self::wallet_config::WalletConfigStartup;
|
||||
/// Resolves the composite-selected Wallet Config and prepares its application-owned directory tree.
|
||||
pub(crate) use self::wallet_config::initialize_wallet_config;
|
||||
|
||||
178
crates/ksp-app-wallet-desk/src/wallet_config.rs
Normal file
178
crates/ksp-app-wallet-desk/src/wallet_config.rs
Normal file
@@ -0,0 +1,178 @@
|
||||
// file: crates/ksp-app-wallet-desk/src/wallet_config.rs
|
||||
// version: 1
|
||||
|
||||
//! Wallet Desk composition adapter for the standard Wallet Config and its application-owned directory preparation.
|
||||
|
||||
/// Resolved Wallet Config and directory preparation status captured during application bootstrap.
|
||||
pub(crate) struct WalletConfigStartup {
|
||||
composite_profile_id: String,
|
||||
effective_directory_created_on_startup: bool,
|
||||
resolved: ksp_config_lib::ResolvedWalletConfig,
|
||||
root_directory_created_on_startup: bool,
|
||||
}
|
||||
|
||||
impl WalletConfigStartup {
|
||||
/// Returns the selected Wallet Desk composite profile.
|
||||
pub(crate) fn composite_profile_id(&self) -> &str {
|
||||
return self.composite_profile_id.as_str();
|
||||
}
|
||||
|
||||
/// Reports whether bootstrap created at least one missing component of the effective Wallet profile directory.
|
||||
pub(crate) const fn effective_directory_created_on_startup(&self) -> bool {
|
||||
return self.effective_directory_created_on_startup;
|
||||
}
|
||||
|
||||
/// Returns the resolved standard Wallet configuration.
|
||||
pub(crate) const fn resolved(&self) -> &ksp_config_lib::ResolvedWalletConfig {
|
||||
return &self.resolved;
|
||||
}
|
||||
|
||||
/// Reports whether bootstrap created the configured global Wallet root.
|
||||
pub(crate) const fn root_directory_created_on_startup(&self) -> bool {
|
||||
return self.root_directory_created_on_startup;
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolves the Wallet component selected by the Wallet Desk composite and prepares its global/effective directory tree.
|
||||
pub(crate) fn initialize_wallet_config(management: &ksp_config_lib::ConfigManagement) -> ksp_core_lib::Result<WalletConfigStartup> {
|
||||
let environment = ksp_config_lib::ConfigEnvironment::load();
|
||||
let environment = match environment {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let composite = crate::load_wallet_desk_composite(management);
|
||||
let composite = match composite {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let logging_profile = crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_LOGGING, ksp_config_lib::FILE_ID_STD_LOGGING);
|
||||
if let std::result::Result::Err(error) = logging_profile {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let transport_profile =
|
||||
crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_TRANSPORT, ksp_config_lib::FILE_ID_STD_TRANSPORT);
|
||||
if let std::result::Result::Err(error) = transport_profile {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let wallet_profile = crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_WALLET, ksp_config_lib::FILE_ID_STD_WALLET);
|
||||
let wallet_profile = match wallet_profile {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let resolved = management.engine().resolve_wallet_config_profile(&wallet_profile, &environment);
|
||||
let resolved = match resolved {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let preparation = prepare_wallet_directories(&resolved);
|
||||
let preparation = match preparation {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let root_path = resolved.wallets_directory().to_string_lossy().into_owned();
|
||||
let effective_path = resolved.effective_wallets_directory().to_string_lossy().into_owned();
|
||||
ksp_logging_lib::debug!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WALLET_CONFIG, composite_profile = composite.profile_id(), wallet_profile = resolved.profile_id(), root_path = root_path.as_str(), effective_path = effective_path.as_str(), root_created = preparation.root_created, effective_created = preparation.effective_created, "Wallet Desk Wallet directories are ready");
|
||||
return std::result::Result::Ok(WalletConfigStartup {
|
||||
composite_profile_id: composite.profile_id().to_owned(),
|
||||
effective_directory_created_on_startup: preparation.effective_created,
|
||||
resolved,
|
||||
root_directory_created_on_startup: preparation.root_created,
|
||||
});
|
||||
}
|
||||
|
||||
struct WalletDirectoryPreparation {
|
||||
effective_created: bool,
|
||||
root_created: bool,
|
||||
}
|
||||
|
||||
fn prepare_wallet_directories(resolved: &ksp_config_lib::ResolvedWalletConfig) -> ksp_core_lib::Result<WalletDirectoryPreparation> {
|
||||
let root_created = ensure_global_wallet_root(resolved.wallets_directory());
|
||||
let root_created = match root_created {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let effective_created = match resolved.wallets_subdirectory() {
|
||||
std::option::Option::Some(subdirectory) => {
|
||||
let created = ensure_profile_wallet_directory(resolved.wallets_directory(), subdirectory);
|
||||
match created {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
},
|
||||
std::option::Option::None => root_created,
|
||||
};
|
||||
return std::result::Result::Ok(WalletDirectoryPreparation { effective_created, root_created });
|
||||
}
|
||||
|
||||
fn ensure_global_wallet_root(path: &std::path::Path) -> ksp_core_lib::Result<bool> {
|
||||
let metadata = std::fs::metadata(path);
|
||||
return match metadata {
|
||||
std::result::Result::Ok(metadata) if metadata.is_dir() => std::result::Result::Ok(false),
|
||||
std::result::Result::Ok(_) => directory_invalid(path, "configured Wallet root exists but is not a directory"),
|
||||
std::result::Result::Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
|
||||
let creation = std::fs::create_dir_all(path);
|
||||
match creation {
|
||||
std::result::Result::Ok(()) => std::result::Result::Ok(true),
|
||||
std::result::Result::Err(error) => directory_prepare_error(path, "configured Wallet root cannot be created", error),
|
||||
}
|
||||
},
|
||||
std::result::Result::Err(error) => directory_prepare_error(path, "configured Wallet root cannot be inspected", error),
|
||||
};
|
||||
}
|
||||
|
||||
fn ensure_profile_wallet_directory(root: &std::path::Path, subdirectory: &std::path::Path) -> ksp_core_lib::Result<bool> {
|
||||
let mut current = root.to_path_buf();
|
||||
let mut created = false;
|
||||
for component in subdirectory.components() {
|
||||
let normal = match component {
|
||||
std::path::Component::Normal(value) => value,
|
||||
_ => return directory_invalid(subdirectory, "Wallet profile subdirectory contains an unsafe path component"),
|
||||
};
|
||||
current.push(normal);
|
||||
let metadata = std::fs::symlink_metadata(current.as_path());
|
||||
match metadata {
|
||||
std::result::Result::Ok(metadata) if metadata.file_type().is_symlink() => {
|
||||
return directory_invalid(current.as_path(), "Wallet profile directory path traverses a symbolic link");
|
||||
},
|
||||
std::result::Result::Ok(metadata) if metadata.is_dir() => {},
|
||||
std::result::Result::Ok(_) => return directory_invalid(current.as_path(), "Wallet profile directory path contains a non-directory entry"),
|
||||
std::result::Result::Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
|
||||
let creation = std::fs::create_dir(current.as_path());
|
||||
if let std::result::Result::Err(error) = creation {
|
||||
return directory_prepare_error(current.as_path(), "Wallet profile directory component cannot be created", error);
|
||||
}
|
||||
created = true;
|
||||
},
|
||||
std::result::Result::Err(error) => {
|
||||
return directory_prepare_error(current.as_path(), "Wallet profile directory component cannot be inspected", error);
|
||||
},
|
||||
}
|
||||
}
|
||||
return std::result::Result::Ok(created);
|
||||
}
|
||||
|
||||
fn directory_invalid<T>(path: &std::path::Path, reason: &'static str) -> ksp_core_lib::Result<T> {
|
||||
let path_text = path.to_string_lossy().into_owned();
|
||||
ksp_logging_lib::error!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WALLET_CONFIG, path = path_text.as_str(), reason, "Wallet directory preparation rejected an invalid filesystem object");
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_WALLET_DIRECTORY_INVALID, "Configured Wallet directory is invalid")
|
||||
.with_context("path", path_text)
|
||||
.with_context("reason", reason),
|
||||
);
|
||||
}
|
||||
|
||||
fn directory_prepare_error<T>(path: &std::path::Path, reason: &'static str, source: std::io::Error) -> ksp_core_lib::Result<T> {
|
||||
let path_text = path.to_string_lossy().into_owned();
|
||||
let source_kind = std::format!("{:?}", source.kind());
|
||||
ksp_logging_lib::error!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WALLET_CONFIG, path = path_text.as_str(), reason, source_kind = source_kind.as_str(), "Wallet directory preparation failed");
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_WALLET_DIRECTORY_PREPARE_FAILED, "Configured Wallet directory cannot be prepared")
|
||||
.with_context("path", path_text)
|
||||
.with_context("reason", reason)
|
||||
.with_source(source),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/wallet_config.rs"]
|
||||
mod tests;
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "KSP Wallet Desk",
|
||||
"version": "0.2.6-pre.2",
|
||||
"version": "0.2.6-pre.3",
|
||||
"identifier": "com.sasedev.ksp-app-wallet-desk",
|
||||
"build": {
|
||||
"beforeDevCommand": "npm run dev",
|
||||
|
||||
48
crates/ksp-app-wallet-desk/tests/config_composition.rs
Normal file
48
crates/ksp-app-wallet-desk/tests/config_composition.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
// file: crates/ksp-app-wallet-desk/tests/config_composition.rs
|
||||
// version: 1
|
||||
|
||||
//! Config composition contracts introduced for Wallet Desk pre.003.
|
||||
|
||||
fn workspace_engine() -> ksp_core_lib::Result<ksp_config_lib::ConfigDocumentEngine> {
|
||||
let workspace = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..");
|
||||
let bootstrap = ksp_config_lib::ConfigBootstrapOptions::from_paths(workspace.join("config"), workspace.join("config/schemas"));
|
||||
let bootstrap = match bootstrap {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let registry = ksp_config_lib::ConfigFileRegistry::defaults();
|
||||
return match registry {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(ksp_config_lib::ConfigDocumentEngine::new(bootstrap, value)),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(error),
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wallet_desk_composite_selects_logging_transport_and_wallet_by_file_id() {
|
||||
let engine = workspace_engine();
|
||||
assert!(engine.is_ok(), "workspace Config engine should be constructible: {engine:?}");
|
||||
if let std::result::Result::Ok(engine) = engine {
|
||||
let file_id = ksp_config_lib::ConfigFileId::new(ksp_config_lib::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK);
|
||||
assert!(file_id.is_ok(), "Wallet Desk composite file_id should be valid: {file_id:?}");
|
||||
if let std::result::Result::Ok(file_id) = file_id {
|
||||
let composite = engine.load_resolved_composite(&file_id, std::option::Option::None);
|
||||
assert!(composite.is_ok(), "committed Wallet Desk composite should resolve: {composite:?}");
|
||||
if let std::result::Result::Ok(composite) = composite {
|
||||
assert_eq!(composite.profile_id(), "devnet");
|
||||
assert_component(&composite, "logging", ksp_config_lib::FILE_ID_STD_LOGGING, "local_dev");
|
||||
assert_component(&composite, "transport", ksp_config_lib::FILE_ID_STD_TRANSPORT, "devnet_public");
|
||||
assert_component(&composite, "wallet", ksp_config_lib::FILE_ID_STD_WALLET, "default");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_component(composite: &ksp_config_lib::ResolvedConfigComposite, component_id: &str, file_id: &str, profile_id: &str) {
|
||||
let component = composite.component(component_id);
|
||||
assert!(component.is_some(), "missing component {component_id}");
|
||||
if let std::option::Option::Some(component) = component {
|
||||
assert_eq!(component.resolved().file_id().as_str(), file_id);
|
||||
assert_eq!(component.resolved().profile_id(), profile_id);
|
||||
assert_eq!(component.resolved().selection_source(), ksp_config_lib::ConfigProfileSelectionSource::Composite);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// file: crates/ksp-app-wallet-desk/tests/desktop_contract.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Desktop build and shell contract audits for Wallet Desk pre.002.
|
||||
//! Desktop build, shell and Config-status contract audits for Wallet Desk.
|
||||
|
||||
fn app_root() -> std::path::PathBuf {
|
||||
return std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
@@ -78,6 +78,8 @@ fn shell_contains_wallet_navigation_datatable_and_lock_state_icons() {
|
||||
assert!(html.contains("fa-lock"));
|
||||
assert!(html.contains("fa-lock-open"));
|
||||
assert!(html.contains("data-view=\"wallets\""));
|
||||
assert!(html.contains("id=\"runtimeCompositeProfile\""));
|
||||
assert!(html.contains("id=\"runtimeEffectiveWalletsDirectory\""));
|
||||
assert!(main.contains("new DataTable"));
|
||||
assert!(main.contains("datatables.net-select-bs5"));
|
||||
assert!(main.contains("ResizeObserver"));
|
||||
|
||||
79
crates/ksp-app-wallet-desk/unit_tests/wallet_config.rs
Normal file
79
crates/ksp-app-wallet-desk/unit_tests/wallet_config.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
// file: crates/ksp-app-wallet-desk/unit_tests/wallet_config.rs
|
||||
// version: 1
|
||||
|
||||
#[test]
|
||||
fn directory_preparation_creates_missing_root_and_nested_profile_path() {
|
||||
let root = fixture_path("create");
|
||||
cleanup_fixture(root.as_path());
|
||||
let root_created = super::ensure_global_wallet_root(root.as_path());
|
||||
assert_eq!(root_created.as_ref().ok().copied(), std::option::Option::Some(true));
|
||||
let profile_created = super::ensure_profile_wallet_directory(root.as_path(), std::path::Path::new("tests/ephemeral"));
|
||||
assert_eq!(profile_created.as_ref().ok().copied(), std::option::Option::Some(true));
|
||||
assert!(root.join("tests/ephemeral").is_dir());
|
||||
let root_again = super::ensure_global_wallet_root(root.as_path());
|
||||
assert_eq!(root_again.as_ref().ok().copied(), std::option::Option::Some(false));
|
||||
let profile_again = super::ensure_profile_wallet_directory(root.as_path(), std::path::Path::new("tests/ephemeral"));
|
||||
assert_eq!(profile_again.as_ref().ok().copied(), std::option::Option::Some(false));
|
||||
cleanup_fixture(root.as_path());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn directory_preparation_rejects_existing_non_directory_root() {
|
||||
let root = fixture_path("file-root");
|
||||
cleanup_fixture(root.as_path());
|
||||
let parent = root.parent();
|
||||
assert!(parent.is_some());
|
||||
if let std::option::Option::Some(parent) = parent {
|
||||
let created = std::fs::create_dir_all(parent);
|
||||
assert!(created.is_ok(), "fixture parent should be creatable: {created:?}");
|
||||
let written = std::fs::write(root.as_path(), b"not-a-directory");
|
||||
assert!(written.is_ok(), "fixture file should be writable: {written:?}");
|
||||
let result = super::ensure_global_wallet_root(root.as_path());
|
||||
assert!(result.is_err(), "non-directory Wallet root should fail: {result:?}");
|
||||
if let std::result::Result::Err(error) = result {
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_WALLET_DIRECTORY_INVALID);
|
||||
}
|
||||
}
|
||||
cleanup_fixture(root.as_path());
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn profile_directory_preparation_rejects_symbolic_link_components() {
|
||||
let root = fixture_path("symlink-root");
|
||||
let outside = fixture_path("symlink-outside");
|
||||
cleanup_fixture(root.as_path());
|
||||
cleanup_fixture(outside.as_path());
|
||||
let root_created = std::fs::create_dir_all(root.as_path());
|
||||
let outside_created = std::fs::create_dir_all(outside.as_path());
|
||||
assert!(root_created.is_ok(), "root fixture should be creatable: {root_created:?}");
|
||||
assert!(outside_created.is_ok(), "outside fixture should be creatable: {outside_created:?}");
|
||||
if root_created.is_ok() && outside_created.is_ok() {
|
||||
let linked = std::os::unix::fs::symlink(outside.as_path(), root.join("tests"));
|
||||
assert!(linked.is_ok(), "symlink fixture should be creatable: {linked:?}");
|
||||
let result = super::ensure_profile_wallet_directory(root.as_path(), std::path::Path::new("tests/ephemeral"));
|
||||
assert!(result.is_err(), "profile path must reject symlink traversal: {result:?}");
|
||||
if let std::result::Result::Err(error) = result {
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_WALLET_DIRECTORY_INVALID);
|
||||
}
|
||||
}
|
||||
cleanup_fixture(root.as_path());
|
||||
cleanup_fixture(outside.as_path());
|
||||
}
|
||||
|
||||
fn fixture_path(name: &str) -> std::path::PathBuf {
|
||||
return std::env::temp_dir().join(std::format!("ksp-wallet-desk-{name}-{}", std::process::id()));
|
||||
}
|
||||
|
||||
fn cleanup_fixture(path: &std::path::Path) {
|
||||
let metadata = std::fs::symlink_metadata(path);
|
||||
match metadata {
|
||||
std::result::Result::Ok(metadata) if metadata.is_dir() && !metadata.file_type().is_symlink() => {
|
||||
let _ = std::fs::remove_dir_all(path);
|
||||
},
|
||||
std::result::Result::Ok(_) => {
|
||||
let _ = std::fs::remove_file(path);
|
||||
},
|
||||
std::result::Result::Err(_) => {},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user