sequence.rs

5.1 kB · rust · 163 lines

1use super::{kind, Bang, Named};2use mrlycore::errors::{value_error, Result};3use serde::{Deserialize, Serialize};45kind!("sequence");67const MEASURES: [&str; 12] = [8    "fills",9    "voids",10    "surface",11    "peak",12    "heights",13    "vertices",14    "edges",15    "faces",16    "euler",17    "triangles",18    "holes",19    "pieces",20];2122const AXES: [&str; 2] = ["level", "side"];2324fn two() -> usize {25    226}2728fn is_two(base: &usize) -> bool {29    *base == 230}3132/// A design sequence's address: the design, the reading taken off it and the index it runs along.33///34/// ```35/// use mrlymath::name::{Named, Sequence};36/// let carpet = Sequence::new(7, 2, 2, "fills", "side");37/// assert_eq!(38///     carpet.to_json(),39///     r#"{"kind":"sequence","dim":2,"code":7,"measure":"fills","axis":"side"}"#40/// );41/// assert_eq!(carpet.to_file(), "sequence_dim=2_code=7_measure=fills_axis=side");42/// ```43#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]44#[serde(deny_unknown_fields)]45pub struct Sequence {46    /// The kind word.47    pub kind: Kind,48    /// The number of axes.49    pub dim: usize,50    /// The digits per axis, 2 unless said.51    #[serde(default = "two", skip_serializing_if = "is_two")]52    pub base: usize,53    /// The design as a number.54    pub code: u128,55    /// The reading taken.56    pub measure: String,57    /// The index the reading runs along.58    pub axis: String,59}6061impl Sequence {62    /// Pins a design's reading to its measure and axis.63    pub fn new(code: u128, dim: usize, base: usize, measure: &str, axis: &str) -> Sequence {64        Sequence {65            kind: Kind,66            dim,67            base,68            code,69            measure: measure.to_string(),70            axis: axis.to_string(),71        }72    }73    /// Returns the design pinned to its dimension and base.74    pub fn design(&self) -> Bang {75        Bang::new(self.code, self.dim, self.base)76    }77}7879impl Named for Sequence {80    const KIND: &'static str = "sequence";81    const BARE: &'static [&'static str] = &["measure", "axis"];82    fn checked(self) -> Result<Sequence> {83        self.design().checked()?;84        if !MEASURES.contains(&self.measure.as_str()) {85            return value_error(format!("unknown measure {:?}.", self.measure));86        }87        if !AXES.contains(&self.axis.as_str()) {88            return value_error(format!("unknown axis {:?}.", self.axis));89        }90        Ok(self)91    }92}9394#[cfg(test)]95mod tests {96    use super::*;9798    const CARPET: &str = r#"{"kind":"sequence","dim":2,"code":7,"measure":"fills","axis":"side"}"#;99100    fn carpet() -> Sequence {101        Sequence::new(7, 2, 2, "fills", "side")102    }103104    #[test]105    fn defaults_elide() {106        assert_eq!(carpet().to_json(), CARPET);107        assert_eq!(108            Sequence::new(23, 3, 2, "surface", "level").to_json(),109            r#"{"kind":"sequence","dim":3,"code":23,"measure":"surface","axis":"level"}"#110        );111        assert_eq!(112            Sequence::new(4, 2, 3, "voids", "side").to_json(),113            r#"{"kind":"sequence","dim":2,"base":3,"code":4,"measure":"voids","axis":"side"}"#114        );115    }116    #[test]117    fn a_decoded_value_folds_to_the_checked_one() {118        assert_eq!(Sequence::from_json(&carpet().to_json()).unwrap(), carpet());119        assert_eq!(Sequence::from_json(CARPET).unwrap().to_json(), CARPET);120        let spelt =121            r#"{"axis":"side", "measure":"fills", "code":7, "base":2, "dim":2, "kind":"sequence"}"#;122        assert_eq!(Sequence::from_json(spelt).unwrap(), carpet());123    }124    #[test]125    fn the_carpet_row_holds_through_every_view() {126        assert_eq!(127            carpet().to_file(),128            "sequence_dim=2_code=7_measure=fills_axis=side"129        );130        assert_eq!(131            carpet().to_url(),132            "/sequence?dim=2&code=7&measure=fills&axis=side"133        );134        assert_eq!(carpet().to_mrly(), "sequence dim 2, code 7, fills, side");135        assert_eq!(Sequence::from_file(&carpet().to_file()).unwrap(), carpet());136        assert_eq!(Sequence::from_url(&carpet().to_url()).unwrap(), carpet());137    }138    #[test]139    fn the_id_is_eight_stable_hex_digits() {140        let id = carpet().to_id();141        assert_eq!(id, "8a9e4ce8");142        assert_eq!(id.len(), 8);143        assert!(id144            .chars()145            .all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase()));146        assert_ne!(id, Sequence::new(7, 2, 2, "fills", "level").to_id());147    }148    #[test]149    fn only_a_fitting_sequence_parses() {150        for bad in [151            r#"{"kind":"bang","dim":2,"code":7}"#,152            r#"{"kind":"sequence","dim":2,"code":7,"measure":"area","axis":"side"}"#,153            r#"{"kind":"sequence","dim":2,"code":7,"measure":"fills","axis":"depth"}"#,154            r#"{"kind":"sequence","dim":2,"code":16,"measure":"fills","axis":"side"}"#,155            r#"{"kind":"sequence","dim":2,"code":7,"axis":"side"}"#,156            r#"{"kind":"sequence","dim":2,"code":7,"measure":"fills"}"#,157            r#"{"kind":"sequence","dim":2,"code":7,"measure":"fills","axis":"side","level":2}"#,158            "sequence_dim=2_code=7_measure=fills_axis=side",159        ] {160            assert!(Sequence::from_json(bad).is_err(), "{bad}");161        }162    }163}