0.5.1-pre.002
This commit is contained in:
184
ks-pipeline-demo-scenarios/src/environment.rs
Normal file
184
ks-pipeline-demo-scenarios/src/environment.rs
Normal file
@@ -0,0 +1,184 @@
|
||||
// file: ks-pipeline-demo-scenarios/src/environment.rs
|
||||
// version: 3
|
||||
|
||||
//! Environment initialization for opt-in demonstration scenarios.
|
||||
|
||||
/// Loads the workspace `.env` selected by `ks-config`.
|
||||
pub fn initialize_demo_scenario_environment(
|
||||
workspace_root: &std::path::Path,
|
||||
) -> ks_core::Result<ks_config::EnvironmentLoadReport> {
|
||||
return ks_config::load_workspace_environment(workspace_root);
|
||||
}
|
||||
|
||||
/// Resolves one compatible Devnet profile for demo scenarios.
|
||||
///
|
||||
/// An explicit name has priority, followed by `KB_DEVNET_PROFILE`, then the
|
||||
/// first compatible Devnet profile declared by the configuration.
|
||||
pub fn resolve_demo_devnet_profile(
|
||||
config: &ks_config::AppConfig,
|
||||
requested_profile_name: std::option::Option<&str>,
|
||||
) -> ks_core::Result<ks_config::ProfileConfig> {
|
||||
let explicit = requested_profile_name
|
||||
.map(str::trim)
|
||||
.filter(|value| return !value.is_empty())
|
||||
.map(std::string::ToString::to_string);
|
||||
let selected_name = match explicit {
|
||||
std::option::Option::Some(value) => std::option::Option::Some(value),
|
||||
std::option::Option::None => std::env::var("KB_DEVNET_PROFILE")
|
||||
.ok()
|
||||
.map(|value| return value.trim().to_string())
|
||||
.filter(|value| return !value.is_empty()),
|
||||
};
|
||||
for profile in &config.profiles {
|
||||
let compatible = profile.wallet.cluster == "devnet"
|
||||
&& profile.wallet.temporary_wallet_enabled
|
||||
&& profile.wallet.temporary_wallet_persist;
|
||||
if !compatible {
|
||||
continue;
|
||||
}
|
||||
if let std::option::Option::Some(name) = selected_name.as_ref()
|
||||
&& profile.name != *name
|
||||
{
|
||||
continue;
|
||||
}
|
||||
return std::result::Result::Ok(profile.clone());
|
||||
}
|
||||
return match selected_name {
|
||||
std::option::Option::Some(name) => std::result::Result::Err(ks_core::Error::config(
|
||||
format!("compatible Devnet profile '{name}' is unavailable"),
|
||||
)),
|
||||
std::option::Option::None => std::result::Result::Err(ks_core::Error::config(
|
||||
"no compatible Devnet profile is configured",
|
||||
)),
|
||||
};
|
||||
}
|
||||
|
||||
/// Readiness report for one Devnet profile PostgreSQL store.
|
||||
#[derive(Clone, Debug, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DevnetProfileStoreReadiness {
|
||||
/// Selected Devnet profile.
|
||||
pub profile_name: std::string::String,
|
||||
/// Whether schema creation was allowed by configuration.
|
||||
pub auto_initialize_schema: bool,
|
||||
/// Number of known tables already present before preparation.
|
||||
pub existing_tables_before: usize,
|
||||
/// Number of known tables created during preparation.
|
||||
pub created_tables: usize,
|
||||
/// Number of known tables present after preparation.
|
||||
pub existing_tables_after: usize,
|
||||
/// Total number of known tables expected by the current store.
|
||||
pub expected_tables: usize,
|
||||
}
|
||||
|
||||
/// Verifies or initializes the PostgreSQL schema selected by one Devnet profile.
|
||||
pub async fn prepare_demo_devnet_profile_store(
|
||||
profile: &ks_config::ProfileConfig,
|
||||
) -> ks_core::Result<DevnetProfileStoreReadiness> {
|
||||
if profile.wallet.cluster != "devnet" {
|
||||
return std::result::Result::Err(ks_core::Error::config(format!(
|
||||
"profile '{}' is not configured for Devnet",
|
||||
profile.name
|
||||
)));
|
||||
}
|
||||
if !profile.database.enabled {
|
||||
return std::result::Result::Err(ks_core::Error::config(format!(
|
||||
"Devnet profile '{}' has database storage disabled",
|
||||
profile.name
|
||||
)));
|
||||
}
|
||||
if profile.database.backend != "postgres" {
|
||||
return std::result::Result::Err(ks_core::Error::config(format!(
|
||||
"Devnet profile '{}' must use the PostgreSQL backend",
|
||||
profile.name
|
||||
)));
|
||||
}
|
||||
let options = match ks_store::PostgresStoreOptions::new(
|
||||
profile.database.postgres.url.clone(),
|
||||
profile.database.postgres.max_connections,
|
||||
profile.database.postgres.connect_timeout_ms,
|
||||
false,
|
||||
) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let store = match ks_store::PostgresStore::connect(options).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let before = match store.known_table_diagnostics().await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let existing_tables_before = before.iter().filter(|table| return table.exists).count();
|
||||
if profile.database.postgres.auto_initialize_schema
|
||||
&& let std::result::Result::Err(error) = store.initialize_store_schema().await
|
||||
{
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let after = match store.known_table_diagnostics().await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let existing_tables_after = after.iter().filter(|table| return table.exists).count();
|
||||
if existing_tables_after != after.len() {
|
||||
let missing = after
|
||||
.iter()
|
||||
.filter(|table| return !table.exists)
|
||||
.map(|table| return table.table_name.clone())
|
||||
.collect::<std::vec::Vec<_>>();
|
||||
return std::result::Result::Err(ks_core::Error::config(format!(
|
||||
"Devnet profile '{}' PostgreSQL schema is incomplete and auto initialization is {}: missing {}",
|
||||
profile.name,
|
||||
if profile.database.postgres.auto_initialize_schema {
|
||||
"enabled"
|
||||
} else {
|
||||
"disabled"
|
||||
},
|
||||
missing.join(", ")
|
||||
)));
|
||||
}
|
||||
return std::result::Result::Ok(DevnetProfileStoreReadiness {
|
||||
profile_name: profile.name.clone(),
|
||||
auto_initialize_schema: profile.database.postgres.auto_initialize_schema,
|
||||
existing_tables_before,
|
||||
created_tables: existing_tables_after.saturating_sub(existing_tables_before),
|
||||
existing_tables_after,
|
||||
expected_tables: after.len(),
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
fn example_config() -> ks_config::AppConfig {
|
||||
return match ks_config::parse_config_json(include_str!("../../config/example.config.json"))
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("example config parse failed: {error}"),
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolver_finds_a_compatible_devnet_profile_without_a_fixed_name() {
|
||||
let config = example_config();
|
||||
let profile = match super::resolve_demo_devnet_profile(&config, std::option::Option::None) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("Devnet profile resolution failed: {error}"),
|
||||
};
|
||||
assert_eq!(profile.wallet.cluster, "devnet");
|
||||
assert!(profile.wallet.temporary_wallet_enabled);
|
||||
assert!(profile.wallet.temporary_wallet_persist);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolver_rejects_an_explicit_non_devnet_profile() {
|
||||
let config = example_config();
|
||||
assert!(
|
||||
super::resolve_demo_devnet_profile(
|
||||
&config,
|
||||
std::option::Option::Some("mainnet_research"),
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user