canwu-api 0.13.0

Public Rust API for the Canwu deterministic historical simulation engine
Documentation
use canwu_api::{
    ArmyId, Canwu, CanwuError, Command, CommandContext, CommandEnvelope, EntityRef, ErrorCode,
    Issuer, PayloadProperty, PayloadSchema, PayloadValueType, PluginActionDescriptor,
    PluginRegistrar, SimulationPlugin, SimulationView, StateKey, SystemDirective,
};
use serde_json::{Value, json};
use std::collections::BTreeMap;

struct StancePlugin;

fn set_stance(
    view: &SimulationView<'_>,
    context: &CommandContext,
    payload: &Value,
) -> Result<Vec<SystemDirective>, CanwuError> {
    let army = ArmyId::new(
        payload
            .get("army")
            .and_then(Value::as_u64)
            .expect("payload was validated before the handler ran"),
    );
    let Some(army_state) = view.army(army)? else {
        return Err(CanwuError::new(
            ErrorCode::ArmyNotFound,
            format!("army {army} was not found"),
        ));
    };
    if context.issuer != Issuer::Actor(army_state.commander) {
        return Err(CanwuError::new(
            ErrorCode::InvalidAuthority,
            "only the army commander may set its stance",
        ));
    }
    Ok(vec![SystemDirective::SetComponent {
        state: StateKey::new("military", "stance"),
        entity: EntityRef::Army(army),
        component: "stance".to_owned(),
        value: payload["stance"].clone(),
        summary: format!("Army {army} changed stance"),
    }])
}

impl SimulationPlugin for StancePlugin {
    fn name(&self) -> &'static str {
        "example-stance"
    }

    fn version(&self) -> &'static str {
        "1.0.0"
    }

    fn semantic_hash(&self) -> &'static str {
        "a33fb7c59ae5a17685bd94f99154c5423dc7bf70d3dbde6ef4daf73670a37d26"
    }

    fn register(&self, registrar: &mut PluginRegistrar<'_>) -> Result<(), CanwuError> {
        registrar.register_command(
            PluginActionDescriptor {
                name: "set_stance".to_owned(),
                description: "Set an army stance through an issuer-aware command".to_owned(),
                payload_schema: PayloadSchema::Object {
                    properties: BTreeMap::from([
                        (
                            "army".to_owned(),
                            PayloadProperty {
                                value_type: PayloadValueType::Integer,
                                required: true,
                            },
                        ),
                        (
                            "stance".to_owned(),
                            PayloadProperty {
                                value_type: PayloadValueType::String,
                                required: true,
                            },
                        ),
                    ]),
                    allow_additional: false,
                },
                reads: vec![StateKey::core_armies()],
                writes: vec![StateKey::new("military", "stance")],
            },
            set_stance,
        )
    }
}

fn main() -> Result<(), CanwuError> {
    let mut canwu = Canwu::demo(35)?;
    let ids = Canwu::demo_ids();
    canwu.register_plugin(&StancePlugin)?;
    canwu.submit(CommandEnvelope::new(
        Issuer::Actor(ids.commander),
        Command::Plugin {
            plugin: "example-stance".to_owned(),
            command: "set_stance".to_owned(),
            payload: json!({ "army": ids.army, "stance": "hold" }),
        },
    ))?;
    Ok(())
}