use core::fmt;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub type SpringCutoff = (f64, f64);
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Spring {
k: f64,
l_0: f64,
cutoff: Option<SpringCutoff>,
}
#[derive(Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
struct SpringSerde {
k: f64,
l_0: f64,
cutoff: Option<SpringCutoff>,
}
impl Serialize for Spring {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.validate().map_err(serde::ser::Error::custom)?;
SpringSerde {
k: self.k,
l_0: self.l_0,
cutoff: self.cutoff,
}
.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Spring {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = SpringSerde::deserialize(deserializer)?;
Self::new(value.k, value.l_0, value.cutoff).map_err(serde::de::Error::custom)
}
}
impl Spring {
pub fn new(k: f64, l_0: f64, cutoff: Option<SpringCutoff>) -> Result<Self, SpringLawError> {
let spring = Self { k, l_0, cutoff };
spring.validate()?;
Ok(spring)
}
pub const fn spring_constant(&self) -> f64 {
self.k
}
pub const fn rest_length(&self) -> f64 {
self.l_0
}
pub const fn cutoff(&self) -> Option<SpringCutoff> {
self.cutoff
}
pub fn validate(&self) -> Result<(), SpringLawError> {
if !self.k.is_finite() {
return Err(SpringLawError::InvalidSpringConstant { k: self.k });
}
if !self.l_0.is_finite() || self.l_0 < 0.0 {
return Err(SpringLawError::InvalidRestLength { l_0: self.l_0 });
}
if let Some((min, max)) = self.cutoff
&& (!min.is_finite() || !max.is_finite() || min < 0.0 || max < min)
{
return Err(SpringLawError::InvalidCutoff { min, max });
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum SpringLawError {
InvalidSpringConstant {
k: f64,
},
InvalidRestLength {
l_0: f64,
},
InvalidCutoff {
min: f64,
max: f64,
},
}
impl fmt::Display for SpringLawError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidSpringConstant { k } => {
write!(f, "spring constant must be finite; got {k}")
}
Self::InvalidRestLength { l_0 } => {
write!(
f,
"spring rest length must be finite and non-negative; got {l_0}"
)
}
Self::InvalidCutoff { min, max } => write!(
f,
"spring cutoff must satisfy finite 0 <= min <= max; got min={min}, max={max}"
),
}
}
}
impl std::error::Error for SpringLawError {}