mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Add self-validation framework for config
This commit is contained in:
@@ -5,6 +5,8 @@ use serde::{Serialize, Deserialize};
|
||||
use polariton::operation::{Typed, Dict};
|
||||
use polariton::serdes::TypePrefix;
|
||||
|
||||
use crate::persist::config::SelfValidator;
|
||||
|
||||
use super::super::{MovementCategoryData, MovementData, Cube, ItemCategory, ItemTier, BattleConfig, Settings, ChatConfig, FactoryConfig};
|
||||
|
||||
const CUBE_CONFIG_FILENAME: &str = "config.json";
|
||||
@@ -34,6 +36,41 @@ impl CubeConfig {
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Performs configuration checks
|
||||
/// Returns true if validation succeeds, false if failed
|
||||
pub fn self_validate(&self, data_path: impl AsRef<std::path::Path>) -> bool {
|
||||
let mut validation_info = super::ValidationInfo::default();
|
||||
validation_info.info(super::ValidationMessage {
|
||||
path: vec![],
|
||||
message: format!("Validation started at {}", chrono::Utc::now()),
|
||||
});
|
||||
let token_path = data_path.as_ref().join(crate::persist::user::TOKEN_SECRET_FILENAME);
|
||||
if !token_path.exists() {
|
||||
validation_info.error(crate::persist::config::ValidationMessage {
|
||||
path: vec![],
|
||||
message: format!("Token secret file does not exist; create it at {}", token_path.display()),
|
||||
});
|
||||
}
|
||||
// TODO cubes
|
||||
// TODO movement
|
||||
let battle_res = self.battle.validate_in(&mut validation_info, self, "battle");
|
||||
let chat_res = self.chat.validate_in(&mut validation_info, self, "chat");
|
||||
let factory_res = self.factory.validate_in(&mut validation_info, self, "factory");
|
||||
let settings_res = self.settings.validate_in(&mut validation_info, self, "settings");
|
||||
|
||||
validation_info.info(super::ValidationMessage {
|
||||
path: vec![],
|
||||
message: format!("Validation ended at {}", chrono::Utc::now()),
|
||||
});
|
||||
|
||||
validation_info.print_messages();
|
||||
battle_res
|
||||
&& chat_res
|
||||
&& factory_res
|
||||
&& settings_res
|
||||
&& validation_info.is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
|
||||
@@ -4,6 +4,9 @@ pub use cubes_json::CubeConfig;
|
||||
mod traits;
|
||||
pub use traits::{ConfigProvider, CompleteCampaignProvider, DevMessageProvider, ServerConfig, GarageUpgrades, GarageUpgradeIncrement, ChatSystemConfig, GameEventSequence, GameEvents, GameRotationStrategy, GameEvent, GameMap, GameVisibility, GameType, SingleplayerConfig, VehicleInfo, VehicleDescriptor, QueueChangeMode, Point, Sphere, MapConfig, LinksConfig, FakePlayer, ClientEmulator, EnergyConfig, BattleArenaResolver, PitSettings, PitWinCondition, TeamDeathMatchSettings};
|
||||
|
||||
mod validation;
|
||||
pub use validation::{SelfValidator, ValidationInfo, ValidationMessage};
|
||||
|
||||
pub type ConfigImpl = CubeConfig;
|
||||
|
||||
fn __must_impl<T: ConfigProvider<()>>() {}
|
||||
|
||||
93
rc_core/src/persist/config/validation.rs
Normal file
93
rc_core/src/persist/config/validation.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ValidationInfo {
|
||||
errors: Vec<ValidationMessage>,
|
||||
warnings: Vec<ValidationMessage>,
|
||||
infos: Vec<ValidationMessage>,
|
||||
context: Vec<String>,
|
||||
}
|
||||
|
||||
impl ValidationInfo {
|
||||
pub fn error(&mut self, mut msg: ValidationMessage) {
|
||||
self.prepend_context_path(&mut msg);
|
||||
self.errors.push(msg);
|
||||
}
|
||||
|
||||
pub fn warn(&mut self, mut msg: ValidationMessage) {
|
||||
self.prepend_context_path(&mut msg);
|
||||
self.warnings.push(msg);
|
||||
}
|
||||
|
||||
pub fn info(&mut self, mut msg: ValidationMessage) {
|
||||
self.prepend_context_path(&mut msg);
|
||||
self.infos.push(msg);
|
||||
}
|
||||
|
||||
pub fn push_context(&mut self, path: &str) {
|
||||
self.context.push(path.to_owned());
|
||||
}
|
||||
|
||||
pub fn pop_context(&mut self) -> String {
|
||||
self.context.pop().expect("Bad validation context state")
|
||||
}
|
||||
|
||||
fn prepend_context_path(&self, msg: &mut ValidationMessage) {
|
||||
let mut new_path = self.context.clone();
|
||||
new_path.append(&mut msg.path);
|
||||
msg.path = new_path;
|
||||
}
|
||||
|
||||
pub(super) fn print_messages(&self) {
|
||||
const ERR_LABEL: &str = "ERROR";
|
||||
const WARN_LABEL: &str = "WARN";
|
||||
const INFO_LABEL: &str = "INFO";
|
||||
Self::print_infos(&self.infos, INFO_LABEL);
|
||||
Self::print_infos(&self.warnings, WARN_LABEL);
|
||||
Self::print_infos(&self.errors, ERR_LABEL);
|
||||
}
|
||||
|
||||
fn print_infos(infos: &[ValidationMessage], label: &str) {
|
||||
for info in infos {
|
||||
println!("[{}]{}", label, info.display());
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn is_ok(&self) -> bool {
|
||||
self.errors.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ValidationMessage {
|
||||
pub path: Vec<String>,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl ValidationMessage {
|
||||
pub fn new(path: Vec<String>, message: String) -> Self {
|
||||
Self {
|
||||
path,
|
||||
message,
|
||||
}
|
||||
}
|
||||
|
||||
fn display(&self) -> String {
|
||||
let path = if self.path.is_empty() {
|
||||
"<root>".to_owned()
|
||||
} else {
|
||||
self.path.join(".")
|
||||
};
|
||||
format!("{}: {}", path, self.message)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SelfValidator {
|
||||
type Context: ?Sized;
|
||||
fn validate(&self, info: &mut ValidationInfo, ctx: &Self::Context) -> bool;
|
||||
|
||||
fn validate_in(&self, info: &mut ValidationInfo, ctx: &Self::Context, path: &str) -> bool {
|
||||
info.push_context(path);
|
||||
let res = self.validate(info, ctx);
|
||||
assert_eq!(info.pop_context(), path);
|
||||
res
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user