nyandere 0.1.2

i help with keeping track of purchases. meow
Documentation
use crate::aux::Owned;

use super::prelude::*;

#[derive(Owned!)]
pub struct Deliver {
    who: Dir,
    price: Money,
    split: Option<Split>,
}

// separate as we manually need to verify that there is a price, somewhere
#[apply(cmd_args!)]
pub struct DeliverArgs {
    #[pos]
    what: Object,
    #[named]
    who: Dir,
    #[named(opt)]
    price: Option<Money>,
    #[named(opt)]
    split: Option<Split>,
}

impl Command for Deliver {
    type Args = DeliverArgs;
}

impl Resolve<Deliver, error::Construction> for DeliverArgs {
    fn resolve(self, _ctx: &State) -> Result<Deliver, error::Construction> {
        let Self {
            what,
            who,
            price,
            split,
        } = self;

        // is there an explicit price set in the statement?
        #[expect(clippy::unnecessary_lazy_evaluations)] // ??? maybe a clippy bug? there's a clone
        let price = price
            .or_else(|| {
                // nope, does its parent concept have a default price?
                what.parent()
                    .and_then(|concept| concept.default_price().cloned())
            })
            .ok_or_else(|| error::PriceUnspecified { object: what })?;

        Ok(Deliver { who, price, split })
    }
}

impl Run for Deliver {
    fn run(self: Box<Self>, ctx: &mut State) -> Output {
        // at the moment a delivery has no meaningful difference to a payment
        // (other than the split)
        // since possession is not modelled nor deliveries/payments/purchases tracked
        // TODO: track them all ^

        let Self { who, price, split } = *self;
        // manual match because compiler isn't smart enough to allow functional notation here -- nyet
        let receiver_amount = match split {
            Some(split) => split.apply(price).1,
            None => price,
        };

        let implied_payment = super::Pay {
            amount: receiver_amount,
            who,
        };
        Box::new(implied_payment).run(ctx)
    }
}

#[cfg(test)]
mod tests {
    use insta::{assert_snapshot, with_settings};

    use crate::{eval, one};

    #[test]
    fn basic() {
        let setup = "
            create entity A
            create entity B
            create object Something

            deliver from=A to=B price=1€ Something
        ";
        let mut rt = eval(setup).unwrap();

        with_settings!({ description => setup }, {
            assert_snapshot!(
                one(&mut rt, "balance from=A to=B"),
                @"entity A is owed 1.00 € by entity B",
            );
        })
    }
}