Files
settled-reach/server/src/bin/validate_ron.rs
T
jpmschweitzerandClaude Opus 4.6 5fc9c03683 chore(server): auto-format Rust code (cargo fmt)
Pre-existing formatting issues in generator_spike.rs and
validate_ron.rs caught by the new pre-push lint hook.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 18:26:06 +01:00

165 lines
5.9 KiB
Rust

//! RON content validator CLI (#611).
//!
//! Deserializes a RON file into the actual Rust structs and prints errors.
//! This is the copy team's lint tool — run it to check RON files without
//! needing to compile the full server.
//!
//! # Usage
//!
//! ```sh
//! # Via wrapper script (recommended):
//! tooling/validate-ron server/content/global/zone-identity-spec.example.ron zone
//! tooling/validate-ron server/content/global/culture-van-maanens-star.example.ron culture
//!
//! # Direct:
//! cargo run --bin validate_ron -- <file.ron> <zone|culture>
//! ```
use std::process;
use clap::Parser;
use settled_reach_server::npc::blueprint::{CultureProfile, ZoneSpec, ZoneTypeTemplate};
#[derive(Parser)]
#[command(
name = "validate_ron",
about = "Validate RON content files against Rust struct schemas"
)]
struct Args {
/// Path to the RON file to validate.
file: String,
/// Schema type: "zone" (ZoneSpec), "zone_type" (ZoneTypeTemplate), or "culture" (CultureProfile).
schema: String,
}
fn main() {
let args = Args::parse();
let content = match std::fs::read_to_string(&args.file) {
Ok(c) => c,
Err(e) => {
eprintln!("Error reading {}: {}", args.file, e);
process::exit(1);
}
};
match args.schema.as_str() {
"zone" => match ron::from_str::<ZoneSpec>(&content) {
Ok(spec) => {
println!("Valid ZoneSpec: {} ({})", spec.label, spec.zone_type);
println!(
" {} roles, {} social sites",
spec.roles.len(),
spec.social_sites.len()
);
let mut warnings = 0;
if spec.roles.is_empty() {
eprintln!(" WARNING: no roles defined — generator will reject this");
warnings += 1;
}
if spec.population_density < 1 {
eprintln!(" WARNING: population_density is 0 — generator requires >= 1");
warnings += 1;
}
for site in &spec.social_sites {
if site.min_npcs > site.max_npcs {
eprintln!(
" WARNING: social site '{}' has min_npcs ({}) > max_npcs ({})",
site.site_type, site.min_npcs, site.max_npcs
);
warnings += 1;
}
}
if warnings > 0 {
process::exit(1);
}
}
Err(e) => {
eprintln!("Invalid ZoneSpec in {}:", args.file);
eprintln!(" {}", e);
process::exit(1);
}
},
"culture" => match ron::from_str::<CultureProfile>(&content) {
Ok(profile) => {
println!("Valid CultureProfile: {} ({})", profile.name, profile.id);
println!(
" {} given names, {} family names",
profile.naming.given_names.len(),
profile.naming.family_names.len()
);
println!(
" {} filler words, {} favored traits",
profile.speech.filler_words.len(),
profile.values.favored_traits.len()
);
let mut warnings = 0;
if profile.naming.given_names.is_empty() {
eprintln!(" WARNING: no given_names — generator will reject this");
warnings += 1;
}
if warnings > 0 {
process::exit(1);
}
}
Err(e) => {
eprintln!("Invalid CultureProfile in {}:", args.file);
eprintln!(" {}", e);
process::exit(1);
}
},
"zone_type" => match ron::from_str::<ZoneTypeTemplate>(&content) {
Ok(template) => {
println!(
"Valid ZoneTypeTemplate: {} ({})",
template.label, template.id
);
println!(
" {} roles, {} social site types",
template.roles.len(),
template.social_site_types.len()
);
let mut warnings = 0;
if template.roles.is_empty() {
eprintln!(" WARNING: no roles defined — generator will reject this");
warnings += 1;
}
let role_ids: Vec<&str> = template.roles.iter().map(|r| r.id.as_str()).collect();
for role in &template.roles {
if role.behavior_primitives.is_empty() {
eprintln!(" WARNING: role '{}' has no behavior_primitives", role.id);
warnings += 1;
}
}
for site in &template.social_site_types {
for eligible in &site.eligible_roles {
if !role_ids.contains(&eligible.as_str()) {
eprintln!(
" WARNING: social site '{}' references eligible_role '{}' which is not a defined role",
site.site_type, eligible
);
warnings += 1;
}
}
}
if warnings > 0 {
process::exit(1);
}
}
Err(e) => {
eprintln!("Invalid ZoneTypeTemplate in {}:", args.file);
eprintln!(" {}", e);
process::exit(1);
}
},
other => {
eprintln!(
"Unknown schema type: '{}'. Use 'zone', 'zone_type', or 'culture'.",
other
);
process::exit(1);
}
}
}