v0.5.2-pre.006

This commit is contained in:
2026-08-10 23:14:17 +02:00
parent e3acb8ceb1
commit 066d969f5a
51 changed files with 1937 additions and 88 deletions

View File

@@ -1,5 +1,5 @@
// file: ks-config/src/lib.rs
// version: 9
// version: 10
//! Khadhroony Solana shared configuration contracts and composition helpers.
#![warn(missing_docs)]
@@ -234,5 +234,7 @@ pub(crate) use self::settings::validate_http_endpoint;
pub(crate) use self::settings::validate_listeners;
/// Keeps `validate_wallet` available to internal configuration modules.
pub(crate) use self::settings::validate_wallet;
/// Keeps wallet-alias validation shared by source and resolved configuration modules.
pub(crate) use self::settings::validate_wallet_alias;
/// Keeps `validate_ws_endpoint` available to internal configuration modules.
pub(crate) use self::settings::validate_ws_endpoint;

View File

@@ -1,5 +1,5 @@
// file: ks-config/src/settings.rs
// version: 23
// version: 24
//! Resolved runtime configuration models retained during the `0.5.1` source-document split.
@@ -226,6 +226,8 @@ pub struct WalletConfig {
pub wallet_dir: std::string::String,
/// Selected cluster name.
pub cluster: std::string::String,
/// Optional persistent native wallet alias selected by this profile.
pub wallet_alias: std::option::Option<std::string::String>,
/// Enables the managed temporary wallet for this profile.
pub temporary_wallet_enabled: bool,
/// Alias used for the managed temporary wallet.
@@ -736,7 +738,13 @@ pub(crate) fn validate_wallet(config: &WalletConfig) -> ks_core::Result<()> {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
match validate_wallet_alias(&config.temporary_wallet_alias) {
if let std::option::Option::Some(alias) = config.wallet_alias.as_deref() {
match validate_wallet_alias(alias, "wallet.wallet_alias") {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
}
match validate_wallet_alias(&config.temporary_wallet_alias, "wallet.temporary_wallet_alias") {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
@@ -755,11 +763,11 @@ pub(crate) fn validate_wallet(config: &WalletConfig) -> ks_core::Result<()> {
return std::result::Result::Ok(());
}
fn validate_wallet_alias(value: &str) -> ks_core::Result<()> {
pub(crate) fn validate_wallet_alias(value: &str, field: &str) -> ks_core::Result<()> {
if value.is_empty() || value.len() > 64 {
return std::result::Result::Err(ks_core::Error::new(
"wallet_alias_length_invalid",
"temporary wallet alias length must be between 1 and 64 bytes",
format!("{field} length must be between 1 and 64 bytes"),
));
}
if !value.bytes().enumerate().all(|(index, byte)| {
@@ -767,7 +775,7 @@ fn validate_wallet_alias(value: &str) -> ks_core::Result<()> {
}) {
return std::result::Result::Err(ks_core::Error::new(
"wallet_alias_invalid",
"temporary wallet alias contains invalid characters",
format!("{field} contains invalid characters"),
));
}
return std::result::Result::Ok(());
@@ -1016,6 +1024,16 @@ mod tests {
assert!(result.is_err());
}
#[test]
fn parser_rejects_invalid_persistent_wallet_alias() {
let mut value = parse_default_value();
value["profiles"][0]["wallet"]["wallet_alias"] =
serde_json::Value::String("../wallet".to_string());
let raw_json = value_to_json(&value);
let result = super::parse_config_json(&raw_json);
assert!(result.is_err());
}
#[test]
fn parser_rejects_invalid_temporary_wallet_alias() {
let mut value = parse_default_value();

View File

@@ -1,5 +1,5 @@
// file: ks-config/src/wallet.rs
// version: 3
// version: 4
//! Independent wallet configuration document loading and validation.
@@ -25,6 +25,8 @@ pub struct WalletProfileConfig {
pub directory: std::string::String,
/// Selected cluster name.
pub cluster: std::string::String,
/// Optional persistent native wallet alias selected by this profile.
pub wallet_alias: std::option::Option<std::string::String>,
/// Enables the managed temporary wallet for this profile.
pub temporary_wallet_enabled: bool,
/// Alias used for the managed temporary wallet.
@@ -150,6 +152,7 @@ pub fn resolved_wallet_profile(
let resolved = crate::WalletConfig {
wallet_dir: wallet_dir.display().to_string(),
cluster: profile.cluster.clone(),
wallet_alias: profile.wallet_alias.clone(),
temporary_wallet_enabled: profile.temporary_wallet_enabled,
temporary_wallet_alias: profile.temporary_wallet_alias.clone(),
temporary_wallet_persist: profile.temporary_wallet_persist,
@@ -186,6 +189,12 @@ pub fn validate_wallet_document(document: &WalletConfigDocument) -> ks_core::Res
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
if let std::option::Option::Some(alias) = profile.wallet_alias.as_deref() {
match crate::validate_wallet_alias(alias, "wallet.profile.wallet_alias") {
std::result::Result::Ok(()) => (),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
}
if !names.insert(profile.name.clone()) {
return std::result::Result::Err(ks_core::Error::new(
"wallet_profile_name_duplicate",
@@ -210,7 +219,55 @@ mod tests {
#[test]
fn default_and_example_wallet_configs_validate() {
assert!(super::parse_wallet_json(DEFAULT_WALLET).is_ok());
assert!(super::parse_wallet_json(EXAMPLE_WALLET).is_ok());
let default = super::parse_wallet_json(DEFAULT_WALLET);
assert!(default.is_ok());
let example = super::parse_wallet_json(EXAMPLE_WALLET);
assert!(example.is_ok());
let example = match example {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(
example.profiles[0].wallet_alias.as_deref(),
std::option::Option::Some("example-operator")
);
}
#[test]
fn wallet_profile_without_persistent_alias_remains_backward_compatible() {
let mut value = match serde_json::from_str::<serde_json::Value>(DEFAULT_WALLET) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("default wallet JSON failed: {error}"),
};
let profile = match value["profiles"][0].as_object_mut() {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("default wallet profile must be an object"),
};
assert!(profile.remove("wallet_alias").is_some());
let raw = match serde_json::to_string(&value) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("wallet JSON encode failed: {error}"),
};
let parsed = super::parse_wallet_json(raw.as_str());
assert!(parsed.is_ok());
let parsed = match parsed {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert!(parsed.profiles[0].wallet_alias.is_none());
}
#[test]
fn wallet_profile_rejects_invalid_persistent_alias() {
let mut value = match serde_json::from_str::<serde_json::Value>(EXAMPLE_WALLET) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("example wallet JSON failed: {error}"),
};
value["profiles"][0]["wallet_alias"] = serde_json::Value::String("../wallet".to_string());
let raw = match serde_json::to_string(&value) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("wallet JSON encode failed: {error}"),
};
assert!(super::parse_wallet_json(raw.as_str()).is_err());
}
}