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}