typestar

inventory.rs in Rust

A small module tree with a builder, private invariants and its own tests.

mod store {
    use std::collections::HashMap;

    #[derive(Debug, Clone, PartialEq)]
    pub struct Item {
        sku: String,
        quantity: u32,
    }

    impl Item {
        pub fn new(sku: &str, quantity: u32) -> Result<Item, String> {
            if sku.is_empty() {
                return Err("sku is required".to_string());
            }
            Ok(Item { sku: sku.to_string(), quantity })
        }

        pub fn sku(&self) -> &str {
            &self.sku
        }

        pub fn quantity(&self) -> u32 {
            self.quantity
        }
    }

    #[derive(Default)]
    pub struct Inventory {
        items: HashMap<String, Item>,
    }

    impl Inventory {
        pub fn add(&mut self, item: Item) {
            self.items
                .entry(item.sku().to_string())
                .and_modify(|held| held.quantity += item.quantity())
                .or_insert(item);
        }

        pub fn total(&self) -> u32 {
            self.items.values().map(|i| i.quantity()).sum()
        }

        pub fn low_stock(&self, floor: u32) -> Vec<&str> {
            let mut low: Vec<&str> = self
                .items
                .values()
                .filter(|i| i.quantity() < floor)
                .map(|i| i.sku())
                .collect();
            low.sort();
            low
        }
    }
}

use store::{Inventory, Item};

fn main() {
    let mut inventory = Inventory::default();
    for (sku, qty) in [("TS-001", 5), ("TS-002", 1), ("TS-001", 3)] {
        match Item::new(sku, qty) {
            Ok(item) => inventory.add(item),
            Err(e) => println!("skipped: {e}"),
        }
    }

    println!("total units: {}", inventory.total());
    println!("low stock: {:?}", inventory.low_stock(4));
    println!("{:?}", Item::new("", 1));
}

#[cfg(test)]
mod tests {
    use super::store::{Inventory, Item};

    #[test]
    fn merges_quantities_for_one_sku() {
        let mut inv = Inventory::default();
        inv.add(Item::new("A", 2).unwrap());
        inv.add(Item::new("A", 3).unwrap());
        assert_eq!(inv.total(), 5);
    }

    #[test]
    fn rejects_an_empty_sku() {
        assert!(Item::new("", 1).is_err());
    }
}

How it works

  1. The store module owns the data and keeps its fields private.
  2. A builder assembles items, validating as it goes.
  3. The test module exercises both the happy and the failing path.

Keywords and builtins used here

The run, in numbers

Lines
89
Characters to type
1633
Tokens
556
Three-star pace
115 tpm

At the three-star pace of 115 tokens a minute, this run takes about 290 seconds.

Type this snippet

Step 1 of 1 in Encore, step 12 of 12 in Modules, tests & macros.

← Previous