180 lines
9.6 KiB
Rust
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;
|