use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[non_exhaustive]
pub enum MaterialKind {
Filament,
Resin,
}
impl MaterialKind {
pub fn price_unit(self) -> &'static str {
match self {
MaterialKind::Filament => "kg",
MaterialKind::Resin => "L",
}
}
pub fn usage_unit(self) -> &'static str {
match self {
MaterialKind::Filament => "g",
MaterialKind::Resin => "ml",
}
}
}
pub const DEFAULT_PRICE: f64 = 20.0;
pub const DEFAULT_EFFICIENCY_FACTOR: f64 = 1.5;
fn default_price() -> f64 {
DEFAULT_PRICE
}
fn default_efficiency_factor() -> f64 {
DEFAULT_EFFICIENCY_FACTOR
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct MaterialProfile {
pub name: String,
pub kind: MaterialKind,
#[serde(default = "default_price")]
pub price: f64,
#[serde(default = "default_efficiency_factor")]
pub efficiency_factor: f64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub color: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub vendor: Option<String>,
}
impl MaterialProfile {
pub fn new(name: impl Into<String>, kind: MaterialKind) -> Self {
MaterialProfile {
name: name.into(),
kind,
price: DEFAULT_PRICE,
efficiency_factor: DEFAULT_EFFICIENCY_FACTOR,
color: None,
vendor: None,
}
}
pub fn with_price(mut self, price: f64) -> Self {
self.price = price;
self
}
pub fn with_efficiency_factor(mut self, factor: f64) -> Self {
self.efficiency_factor = factor;
self
}
pub fn with_color(mut self, color: impl Into<String>) -> Self {
self.color = Some(color.into());
self
}
pub fn with_vendor(mut self, vendor: impl Into<String>) -> Self {
self.vendor = Some(vendor.into());
self
}
pub fn to_toml(&self) -> Result<String, toml::ser::Error> {
toml::to_string(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn omitted_price_and_factor_take_the_documented_defaults() {
let profile: MaterialProfile = toml::from_str(
r#"
name = "sparse"
kind = "filament"
"#,
)
.unwrap();
assert_eq!(profile.price, 20.0);
assert_eq!(profile.efficiency_factor, 1.5);
}
#[test]
fn a_misspelled_field_is_reported_as_unknown() {
let (profile, unknown) = crate::parse_toml::<MaterialProfile>(
"name = \"typo\"\nkind = \"filament\"\npryce = 31.0\n",
)
.unwrap();
assert_eq!(unknown, ["pryce"]);
assert!((profile.price - DEFAULT_PRICE).abs() < f64::EPSILON);
}
}