Files
khadhroony-solana-project/crates/ksp-app-wallet-desk/src/wallet_config.rs
2026-08-20 23:01:41 +02:00

180 lines
9.6 KiB
Rust

// file: crates/ksp-app-wallet-desk/src/wallet_config.rs
// version: 2
//! 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::symlink_metadata(path);
return match metadata {
std::result::Result::Ok(metadata) if metadata.file_type().is_symlink() => directory_invalid(path, "configured Wallet root cannot be a symbolic link"),
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;