use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub struct UnitError {
pub unit: &'static str,
pub value: f64,
}
impl fmt::Display for UnitError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"invalid {} value: {} (must be finite and non-negative)",
self.unit, self.value
)
}
}
impl std::error::Error for UnitError {}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Weight {
pub(crate) value: f64,
}
impl Weight {
pub const ZERO: Self = Self { value: 0.0 };
pub fn try_new(value: f64) -> Result<Self, UnitError> {
if value.is_finite() && value >= 0.0 {
Ok(Self { value })
} else {
Err(UnitError {
unit: "Weight",
value,
})
}
}
#[must_use]
pub const fn value(self) -> f64 {
self.value
}
}
impl fmt::Display for Weight {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.2}kg", self.value)
}
}
#[allow(clippy::panic)]
impl From<f64> for Weight {
fn from(value: f64) -> Self {
Self::try_new(value).unwrap_or_else(|e| panic!("{e}"))
}
}
impl std::ops::Add for Weight {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self {
value: self.value + rhs.value,
}
}
}
impl std::ops::AddAssign for Weight {
fn add_assign(&mut self, rhs: Self) {
self.value += rhs.value;
}
}
impl std::ops::Sub for Weight {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Self {
value: (self.value - rhs.value).max(0.0),
}
}
}
impl std::ops::SubAssign for Weight {
fn sub_assign(&mut self, rhs: Self) {
self.value = (self.value - rhs.value).max(0.0);
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Speed {
pub(crate) value: f64,
}
impl Speed {
pub fn try_new(value: f64) -> Result<Self, UnitError> {
if value.is_finite() && value >= 0.0 {
Ok(Self { value })
} else {
Err(UnitError {
unit: "Speed",
value,
})
}
}
#[must_use]
pub const fn value(self) -> f64 {
self.value
}
}
impl fmt::Display for Speed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.2}m/s", self.value)
}
}
#[allow(clippy::panic)]
impl From<f64> for Speed {
fn from(value: f64) -> Self {
Self::try_new(value).unwrap_or_else(|e| panic!("{e}"))
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Accel {
pub(crate) value: f64,
}
impl Accel {
pub fn try_new(value: f64) -> Result<Self, UnitError> {
if value.is_finite() && value >= 0.0 {
Ok(Self { value })
} else {
Err(UnitError {
unit: "Accel",
value,
})
}
}
#[must_use]
pub const fn value(self) -> f64 {
self.value
}
}
impl fmt::Display for Accel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.2}m/s²", self.value)
}
}
#[allow(clippy::panic)]
impl From<f64> for Accel {
fn from(value: f64) -> Self {
Self::try_new(value).unwrap_or_else(|e| panic!("{e}"))
}
}