use std::collections::BTreeMap;
use std::collections::HashMap;
use std::sync::LazyLock;
use anyhow::Result;
#[derive(Clone, Debug, PartialEq, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Inventory {
pub authors: Vec<String>,
pub documents: BTreeMap<String, Metadata>,
}
#[derive(Clone, Debug, PartialEq, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Metadata {
pub title: Option<String>,
#[serde(default)]
pub authors: Vec<String>,
pub module: Module,
}
impl Inventory {
pub fn load(s: String) -> Result<Self> {
Ok(serde_json::from_str(&s)?)
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Module {
Semester1(Semester1),
Semester2(Semester2),
Misc,
}
impl Module {
pub fn category(&self) -> String {
String::from(match self {
Module::Semester1(_) => "Semester 1",
Module::Semester2(_) => "Semester 2",
Module::Misc => "Verschiedenes",
})
}
pub fn name(&self) -> String {
for (name, module) in MODULES.clone() {
if *self == module {
return name.to_string();
}
}
unreachable!();
}
}
impl<'de> serde::Deserialize<'de> for Module {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let name = <&str>::deserialize(deserializer)?;
match MODULES.get(name) {
Some(module) => Ok(module.clone()),
None => Err(serde::de::Error::custom(format!(
"unknown module `{}`",
name
))),
}
}
}
static MODULES: LazyLock<HashMap<&str, Module>> = LazyLock::new(|| {
HashMap::from([
("Analysis 1", Module::Semester1(Semester1::Analysis1)),
(
"Schaltungstheorie",
Module::Semester1(Semester1::CircuitTheory),
),
(
"Digitaltechnik",
Module::Semester1(Semester1::DigitalTechnology),
),
(
"Computertechnik",
Module::Semester1(Semester1::ComputerTechnology),
),
(
"Lineare Algebra",
Module::Semester1(Semester1::LinearAlgebra),
),
("Analysis 2", Module::Semester2(Semester2::Analysis2)),
("Systemtheorie", Module::Semester2(Semester2::SystemsTheory)),
(
"Elektrizität und Magnetismus",
Module::Semester2(Semester2::ElectrityAndMagnetism),
),
("Physik", Module::Semester2(Semester2::Physics)),
(
"Algorithmen und Datenstrukturen",
Module::Semester2(Semester2::AlgorithmsAndDataStructures),
),
("Verschiedenes", Module::Misc),
])
});
#[derive(Clone, Debug, PartialEq, serde::Deserialize)]
pub enum Semester1 {
Analysis1,
CircuitTheory,
ComputerTechnology,
DigitalTechnology,
LinearAlgebra,
}
#[derive(Clone, Debug, PartialEq, serde::Deserialize)]
pub enum Semester2 {
AlgorithmsAndDataStructures,
Analysis2,
ElectrityAndMagnetism,
Physics,
SystemsTheory,
}
#[cfg(test)]
mod test {
use parameterized::parameterized;
use super::*;
#[test]
fn test_load_minimal_inventory() {
let expected_inventory = Inventory {
authors: vec![],
documents: BTreeMap::new(),
};
let actual_inventory = Inventory::load(
r#"
{
"authors": [],
"documents": {}
}
"#
.to_string(),
)
.unwrap();
pretty_assertions::assert_eq!(expected_inventory, actual_inventory);
}
#[test]
fn test_load_example_inventory() {
let expected_inventory = example_inventory();
let actual_inventory = Inventory::load(raw_example_inventory()).unwrap();
pretty_assertions::assert_eq!(expected_inventory, actual_inventory);
}
#[parameterized(raw_module = {
"Analysis 1",
"Computertechnik",
"Digitaltechnik",
"Lineare Algebra",
"Schaltungstheorie",
"Algorithmen und Datenstrukturen",
"Analysis 2",
"Elektrizität und Magnetismus",
"Physik",
"Systemtheorie",
"Verschiedenes",
}, module = {
Module::Semester1(Semester1::Analysis1),
Module::Semester1(Semester1::ComputerTechnology),
Module::Semester1(Semester1::DigitalTechnology),
Module::Semester1(Semester1::LinearAlgebra),
Module::Semester1(Semester1::CircuitTheory),
Module::Semester2(Semester2::AlgorithmsAndDataStructures),
Module::Semester2(Semester2::Analysis2),
Module::Semester2(Semester2::ElectrityAndMagnetism),
Module::Semester2(Semester2::Physics),
Module::Semester2(Semester2::SystemsTheory),
Module::Misc,
})]
fn test_load_inventory_with_different_modules(raw_module: &str, module: Module) {
let expected_inventory = Inventory {
authors: vec![],
documents: BTreeMap::from([(
"document.pdf".to_string(),
Metadata {
title: None,
authors: vec![],
module: module,
},
)]),
};
let actual_inventory = Inventory::load(format!(
r#"
{{
"authors": [],
"documents": {{
"document.pdf": {{
"module": "{}"
}}
}}
}}
"#,
raw_module
))
.unwrap();
pretty_assertions::assert_eq!(expected_inventory, actual_inventory);
}
#[parameterized(module = {
Module::Semester1(Semester1::Analysis1),
Module::Semester1(Semester1::ComputerTechnology),
Module::Semester1(Semester1::DigitalTechnology),
Module::Semester1(Semester1::LinearAlgebra),
Module::Semester1(Semester1::CircuitTheory),
Module::Semester2(Semester2::AlgorithmsAndDataStructures),
Module::Semester2(Semester2::Analysis2),
Module::Semester2(Semester2::ElectrityAndMagnetism),
Module::Semester2(Semester2::Physics),
Module::Semester2(Semester2::SystemsTheory),
Module::Misc,
}, expected_category = {
"Semester 1",
"Semester 1",
"Semester 1",
"Semester 1",
"Semester 1",
"Semester 2",
"Semester 2",
"Semester 2",
"Semester 2",
"Semester 2",
"Verschiedenes",
})]
fn test_module_category(module: Module, expected_category: &str) {
assert_eq!(expected_category, module.category());
}
#[parameterized(module = {
Module::Semester1(Semester1::Analysis1),
Module::Semester1(Semester1::ComputerTechnology),
Module::Semester1(Semester1::DigitalTechnology),
Module::Semester1(Semester1::LinearAlgebra),
Module::Semester1(Semester1::CircuitTheory),
Module::Semester2(Semester2::AlgorithmsAndDataStructures),
Module::Semester2(Semester2::Analysis2),
Module::Semester2(Semester2::ElectrityAndMagnetism),
Module::Semester2(Semester2::Physics),
Module::Semester2(Semester2::SystemsTheory),
Module::Misc,
}, expected_name = {
"Analysis 1",
"Computertechnik",
"Digitaltechnik",
"Lineare Algebra",
"Schaltungstheorie",
"Algorithmen und Datenstrukturen",
"Analysis 2",
"Elektrizität und Magnetismus",
"Physik",
"Systemtheorie",
"Verschiedenes",
})]
fn test_module_name(module: Module, expected_name: &str) {
assert_eq!(expected_name, module.name());
}
fn example_inventory() -> Inventory {
Inventory {
authors: vec!["Alex".to_string()],
documents: BTreeMap::from([
(
"sem1-analysis-1.pdf".to_string(),
Metadata {
title: None,
authors: vec![],
module: Module::Semester1(Semester1::Analysis1),
},
),
(
"sem1-digitaltechnik.pdf".to_string(),
Metadata {
title: Some("DT".to_string()),
authors: vec![],
module: Module::Semester1(Semester1::DigitalTechnology),
},
),
(
"sem1-schaltungstheorie.pdf".to_string(),
Metadata {
title: None,
authors: vec!["Alex".to_string(), "Max".to_string()],
module: Module::Semester1(Semester1::CircuitTheory),
},
),
(
"sem2-analysis-2.pdf".to_string(),
Metadata {
title: None,
authors: vec![],
module: Module::Semester2(Semester2::Analysis2),
},
),
(
"sem2-systemtheorie.pdf".to_string(),
Metadata {
title: None,
authors: vec!["Max".to_string()],
module: Module::Semester2(Semester2::SystemsTheory),
},
),
]),
}
}
fn raw_example_inventory() -> String {
r#"
{
"authors": ["Alex"],
"documents": {
"sem1-analysis-1.pdf": {
"module": "Analysis 1"
},
"sem1-digitaltechnik.pdf": {
"title": "DT",
"module": "Digitaltechnik"
},
"sem1-schaltungstheorie.pdf": {
"authors": ["Alex", "Max"],
"module": "Schaltungstheorie"
},
"sem2-analysis-2.pdf": {
"module": "Analysis 2"
},
"sem2-systemtheorie.pdf": {
"authors": ["Max"],
"module": "Systemtheorie"
}
}
}
"#
.to_string()
}
}