use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[non_exhaustive]
pub enum PrinterKind {
Fdm,
Resin,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[non_exhaustive]
pub struct PrinterProfile {
pub name: String,
pub kind: PrinterKind,
#[serde(flatten)]
pub wear: WearProfile,
}
#[derive(Deserialize)]
struct PrinterWire {
name: String,
kind: PrinterKind,
#[serde(default)]
purchase_price: Option<f64>,
#[serde(default)]
additional_upfront_cost: Option<f64>,
#[serde(default)]
estimated_life_years: Option<f64>,
#[serde(default)]
estimated_uptime_percent: Option<f64>,
#[serde(default)]
annual_maintenance_cost: Option<f64>,
#[serde(default)]
power_consumption_watts: Option<f64>,
#[serde(default)]
cost_buffer_factor: Option<f64>,
#[serde(default)]
print_time_rate_override: Option<f64>,
}
impl<'de> Deserialize<'de> for PrinterProfile {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let wire = PrinterWire::deserialize(deserializer)?;
let d = WearProfile::default();
Ok(PrinterProfile {
name: wire.name,
kind: wire.kind,
wear: WearProfile {
purchase_price: wire.purchase_price.unwrap_or(d.purchase_price),
additional_upfront_cost: wire
.additional_upfront_cost
.unwrap_or(d.additional_upfront_cost),
estimated_life_years: wire.estimated_life_years.unwrap_or(d.estimated_life_years),
estimated_uptime_percent: wire
.estimated_uptime_percent
.unwrap_or(d.estimated_uptime_percent),
annual_maintenance_cost: wire
.annual_maintenance_cost
.unwrap_or(d.annual_maintenance_cost),
power_consumption_watts: wire
.power_consumption_watts
.unwrap_or(d.power_consumption_watts),
cost_buffer_factor: wire.cost_buffer_factor.unwrap_or(d.cost_buffer_factor),
print_time_rate_override: wire.print_time_rate_override,
},
})
}
}
pub const DEFAULT_PURCHASE_PRICE: f64 = 800.0;
pub const DEFAULT_ADDITIONAL_UPFRONT_COST: f64 = 0.0;
pub const DEFAULT_ANNUAL_MAINTENANCE_COST: f64 = 80.0;
pub const DEFAULT_LIFE_YEARS: f64 = 5.0;
pub const DEFAULT_UPTIME_PERCENT: f64 = 0.50;
pub const DEFAULT_POWER_CONSUMPTION_WATTS: f64 = 160.0;
pub const DEFAULT_COST_BUFFER_FACTOR: f64 = 1.3;
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[serde(default)]
#[non_exhaustive]
pub struct WearProfile {
pub purchase_price: f64,
pub additional_upfront_cost: f64,
pub estimated_life_years: f64,
pub estimated_uptime_percent: f64,
pub annual_maintenance_cost: f64,
pub power_consumption_watts: f64,
pub cost_buffer_factor: f64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub print_time_rate_override: Option<f64>,
}
impl Default for WearProfile {
fn default() -> Self {
WearProfile {
purchase_price: DEFAULT_PURCHASE_PRICE,
additional_upfront_cost: DEFAULT_ADDITIONAL_UPFRONT_COST,
estimated_life_years: DEFAULT_LIFE_YEARS,
estimated_uptime_percent: DEFAULT_UPTIME_PERCENT,
annual_maintenance_cost: DEFAULT_ANNUAL_MAINTENANCE_COST,
power_consumption_watts: DEFAULT_POWER_CONSUMPTION_WATTS,
cost_buffer_factor: DEFAULT_COST_BUFFER_FACTOR,
print_time_rate_override: None,
}
}
}
impl WearProfile {
pub fn new() -> Self {
Self::default()
}
pub fn with_purchase_price(mut self, value: f64) -> Self {
self.purchase_price = value;
self
}
pub fn with_additional_upfront_cost(mut self, value: f64) -> Self {
self.additional_upfront_cost = value;
self
}
pub fn with_estimated_life_years(mut self, value: f64) -> Self {
self.estimated_life_years = value;
self
}
pub fn with_estimated_uptime_percent(mut self, value: f64) -> Self {
self.estimated_uptime_percent = value;
self
}
pub fn with_annual_maintenance_cost(mut self, value: f64) -> Self {
self.annual_maintenance_cost = value;
self
}
pub fn with_power_consumption_watts(mut self, value: f64) -> Self {
self.power_consumption_watts = value;
self
}
pub fn with_cost_buffer_factor(mut self, value: f64) -> Self {
self.cost_buffer_factor = value;
self
}
pub fn with_print_time_rate_override(mut self, value: Option<f64>) -> Self {
self.print_time_rate_override = value;
self
}
}
impl PrinterProfile {
pub fn new(name: impl Into<String>, kind: PrinterKind, wear: WearProfile) -> Self {
PrinterProfile {
name: name.into(),
kind,
wear,
}
}
pub fn to_toml(&self) -> Result<String, toml::ser::Error> {
toml::to_string(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn omitted_fields_take_the_documented_defaults() {
let profile: PrinterProfile = toml::from_str(
r#"
name = "sparse"
kind = "fdm"
purchase_price = 699.0
"#,
)
.unwrap();
assert_eq!(profile.wear.purchase_price, 699.0);
assert_eq!(
profile.wear,
WearProfile {
purchase_price: 699.0,
..WearProfile::default()
}
);
assert_eq!(profile.wear.annual_maintenance_cost, 80.0);
assert_eq!(profile.wear.estimated_life_years, 5.0);
assert_eq!(profile.wear.estimated_uptime_percent, 0.50);
assert_eq!(profile.wear.power_consumption_watts, 160.0);
assert_eq!(profile.wear.cost_buffer_factor, 1.3);
assert_eq!(profile.wear.additional_upfront_cost, 0.0);
assert_eq!(profile.wear.print_time_rate_override, None);
}
#[test]
fn a_misspelled_wear_field_is_reported_as_unknown() {
let (profile, unknown) = crate::parse_toml::<PrinterProfile>(
r#"
name = "typo"
kind = "fdm"
purchase_pric = 699.0
buffer = 1.2
"#,
)
.unwrap();
let mut unknown = unknown;
unknown.sort();
assert_eq!(unknown, ["buffer", "purchase_pric"]);
assert!((profile.wear.purchase_price - DEFAULT_PURCHASE_PRICE).abs() < f64::EPSILON);
}
#[test]
fn round_trips_through_its_own_toml() {
let profile = PrinterProfile {
name: "a1".into(),
kind: PrinterKind::Fdm,
wear: WearProfile {
purchase_price: 699.0,
print_time_rate_override: Some(0.5),
..WearProfile::default()
},
};
let text = profile.to_toml().unwrap();
let (back, unknown) = crate::parse_toml::<PrinterProfile>(&text).unwrap();
assert_eq!(back, profile);
assert!(unknown.is_empty(), "own output has no unknown keys");
assert!(text.contains("\npurchase_price = 699.0"), "{text}");
}
}