slen 0.1.1

A library for encoding and decoding loadouts from the Splatoon series.
Documentation
mod gear;
use crate::hexstr_to_bin;
use crate::ParseFailure;
use gear::GearItem;
use substring::Substring;

macro_rules! assign_or_fail {
    ($e:expr, $fail:expr) => {
        if let Ok(v) = { $e } {
            v
        } else {
            $fail
        }
    };
}
macro_rules! some_or_fail {
    ($e:expr, $fail:expr) => {
        if let Some(v) = { $e } {
            v
        } else {
            $fail
        }
    };
}

#[derive(Debug, PartialEq, Eq)]
pub struct S2Loadout {
    pub wepset: u32,
    pub wepid: u32,
    pub head: GearItem,
    pub clothes: GearItem,
    pub shoes: GearItem,
}

impl std::fmt::Display for S2Loadout {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "0{:x}{:02x}{}{}{}",
            self.wepset, self.wepid, self.head, self.clothes, self.shoes
        )
    }
}

impl std::str::FromStr for S2Loadout {
    type Err = ParseFailure;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s.len() != 25 {
            return Err(ParseFailure::Length);
        }

        let version = assign_or_fail!(
            u32::from_str_radix(
                &some_or_fail!(
                    hexstr_to_bin(s.substring(0, 1)),
                    return Err(ParseFailure::Value)
                ),
                2
            ),
            return Err(ParseFailure::Value)
        );

        if version != 0 {
            return Err(ParseFailure::Value);
        }

        let wepset = assign_or_fail!(
            u32::from_str_radix(
                &some_or_fail!(
                    hexstr_to_bin(s.substring(1, 2)),
                    return Err(ParseFailure::Value)
                ),
                2
            ),
            return Err(ParseFailure::Value)
        );

        let wepid = assign_or_fail!(
            u32::from_str_radix(
                &some_or_fail!(
                    hexstr_to_bin(s.substring(2, 4)),
                    return Err(ParseFailure::Value)
                ),
                2
            ),
            return Err(ParseFailure::Value)
        );

        let head = s.substring(4, 11).parse()?;
        let clothes = s.substring(11, 18).parse()?;
        let shoes = s.substring(18, 25).parse()?;

        Ok(S2Loadout {
            wepset,
            wepid,
            head,
            clothes,
            shoes,
        })
    }
}

#[cfg(test)]
mod test {
    use super::GearItem;
    use super::S2Loadout as Loadout;

    #[test]
    fn forward_back() {
        let test_str = "080311694ac62098ce6e214e5";
        let res = test_str.parse::<Loadout>().unwrap();
        assert_eq!(
            res,
            Loadout {
                wepset: 8,
                wepid: 3,
                head: GearItem {
                    id: 17,
                    main: 13,
                    subs: [5, 5, 12],
                },
                clothes: GearItem {
                    id: 98,
                    main: 1,
                    subs: [6, 6, 14],
                },
                shoes: GearItem {
                    id: 110,
                    main: 4,
                    subs: [5, 7, 5],
                },
            }
        );
        let re_encode = res.to_string();
        assert_eq!(re_encode, test_str);
    }
    #[test]
    fn identity() {
        let test_cases = [
            "000c495294a864a4a518c20e6",
            "05097d820e64c98000115298c",
            "000122294a736a31063ec39ce",
        ];
        for test in &test_cases {
            assert_eq!(test, &test.parse::<Loadout>().unwrap().to_string());
        }
    }
}