Files
khadhroony-solana-project/crates/ksp-app-config-desk/src/splash.rs

172 lines
7.0 KiB
Rust

// file: crates/ksp-app-config-desk/src/splash.rs
// version: 3
//! Common splash settings and frontend event contracts for Config Desk.
use ts_rs::TS; // rust-rules: derive-import
const ENV_SPLASH_MINIMUM_MS: &str = "KSP_DESK_SPLASH_MINIMUM_MS";
const ENV_SPLASH_FADE_MS: &str = "KSP_DESK_SPLASH_FADE_MS";
const DEFAULT_SPLASH_MINIMUM_MS: u64 = 1200;
const DEFAULT_SPLASH_FADE_MS: u32 = 300;
const MAX_SPLASH_MINIMUM_MS: u64 = 60_000;
const MAX_SPLASH_FADE_MS: u32 = 10_000;
/// Runtime timings used by the common desk splash lifecycle.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct SplashSettings {
minimum_ms: u64,
fade_ms: u32,
minimum_source: ksp_config_lib::ConfigEnvironmentSource,
fade_source: ksp_config_lib::ConfigEnvironmentSource,
}
impl SplashSettings {
/// Resolves splash timings through the Config-owned environment snapshot.
pub(crate) fn load() -> ksp_core_lib::Result<Self> {
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 minimum = environment.resolve_variable(ENV_SPLASH_MINIMUM_MS, std::option::Option::Some("1200"));
let minimum = match minimum {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let fade = environment.resolve_variable(ENV_SPLASH_FADE_MS, std::option::Option::Some("300"));
let fade = match fade {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let minimum_ms = parse_u64_setting(ENV_SPLASH_MINIMUM_MS, minimum.value(), MAX_SPLASH_MINIMUM_MS);
let minimum_ms = match minimum_ms {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let fade_ms = parse_u32_setting(ENV_SPLASH_FADE_MS, fade.value(), MAX_SPLASH_FADE_MS);
let fade_ms = match fade_ms {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(Self { minimum_ms, fade_ms, minimum_source: minimum.source(), fade_source: fade.source() });
}
/// Returns safe in-memory timings used when the managed environment cannot be resolved.
#[must_use]
pub(crate) const fn fallback() -> Self {
return Self {
minimum_ms: DEFAULT_SPLASH_MINIMUM_MS,
fade_ms: DEFAULT_SPLASH_FADE_MS,
minimum_source: ksp_config_lib::ConfigEnvironmentSource::Fallback,
fade_source: ksp_config_lib::ConfigEnvironmentSource::Fallback,
};
}
/// Returns the minimum visible duration after splash frontend readiness.
#[must_use]
pub(crate) const fn minimum_ms(self) -> u64 {
return self.minimum_ms;
}
/// Returns the fade duration used for both fade-in and fade-out.
#[must_use]
pub(crate) const fn fade_ms(self) -> u32 {
return self.fade_ms;
}
/// Returns the safe provenance code for the minimum duration.
#[must_use]
pub(crate) const fn minimum_source(self) -> &'static str {
return environment_source_code(self.minimum_source);
}
/// Returns the safe provenance code for the fade duration.
#[must_use]
pub(crate) const fn fade_source(self) -> &'static str {
return environment_source_code(self.fade_source);
}
/// Returns the minimum backend lifecycle duration from readiness until main activation.
#[must_use]
pub(crate) fn expected_backend_lifecycle_ms(self) -> u64 {
return self.minimum_ms + u64::from(self.fade_ms);
}
}
const fn environment_source_code(source: ksp_config_lib::ConfigEnvironmentSource) -> &'static str {
return match source {
ksp_config_lib::ConfigEnvironmentSource::Process => "process",
ksp_config_lib::ConfigEnvironmentSource::DotEnv => "dotenv",
ksp_config_lib::ConfigEnvironmentSource::Fallback => "fallback",
};
}
/// Command emitted by Rust to the splash frontend.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_config_desk/splash/SplashOrderDto.ts")]
pub(crate) struct SplashOrderDto {
/// Splash action name.
pub(crate) action: String,
/// Optional status text.
pub(crate) message: std::option::Option<String>,
/// Optional animation duration in milliseconds.
pub(crate) duration_ms: std::option::Option<u32>,
}
impl SplashOrderDto {
#[must_use]
pub(crate) fn new(action: &str, message: std::option::Option<&str>, duration_ms: std::option::Option<u32>) -> Self {
return Self { action: action.to_owned(), message: message.map(std::string::ToString::to_string), duration_ms };
}
}
fn parse_u64_setting(variable_name: &str, value: &str, maximum: u64) -> ksp_core_lib::Result<u64> {
let parsed = value.parse::<u64>();
let parsed = match parsed {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(
ksp_core_lib::Error::new(crate::ERROR_CODE_SPLASH_SETTING_INVALID, "KSP desk splash duration is not a valid unsigned integer")
.with_context("variable_name", variable_name)
.with_source(error),
);
},
};
if parsed > maximum {
return std::result::Result::Err(
ksp_core_lib::Error::new(crate::ERROR_CODE_SPLASH_SETTING_INVALID, "KSP desk splash duration exceeds the allowed bound")
.with_context("variable_name", variable_name)
.with_context("maximum_ms", maximum.to_string()),
);
}
return std::result::Result::Ok(parsed);
}
fn parse_u32_setting(variable_name: &str, value: &str, maximum: u32) -> ksp_core_lib::Result<u32> {
let parsed = value.parse::<u32>();
let parsed = match parsed {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(
ksp_core_lib::Error::new(crate::ERROR_CODE_SPLASH_SETTING_INVALID, "KSP desk splash duration is not a valid unsigned integer")
.with_context("variable_name", variable_name)
.with_source(error),
);
},
};
if parsed > maximum {
return std::result::Result::Err(
ksp_core_lib::Error::new(crate::ERROR_CODE_SPLASH_SETTING_INVALID, "KSP desk splash duration exceeds the allowed bound")
.with_context("variable_name", variable_name)
.with_context("maximum_ms", maximum.to_string()),
);
}
return std::result::Result::Ok(parsed);
}
#[cfg(test)]
#[path = "../unit_tests/splash.rs"]
mod tests;