use std::{fmt::Debug, ops::Deref};
use serde::{Deserialize, Deserializer, Serialize};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum LimError<T: Debug> {
#[error("invalid bounds: min ({min:?}) > max ({max:?})")]
InvalidBounds {
min: T,
max: T,
},
#[error("value ({value:?}) out of bounds (min: {min:?}, max: {max:?})")]
OutOfBounds {
value: T,
min: T,
max: T,
},
#[error("cannot clamp to exclusive bound ({bound:?})")]
ExclusiveBoundClamp {
bound: T,
},
#[error("ambiguous clamp at exclusive bound ({value:?})")]
AmbiguousClamp {
value: T,
},
}
#[derive(Deserialize)]
struct LimHelper<T> {
value: T,
min: T,
max: T,
include_min: bool,
include_max: bool,
}
#[derive(Serialize, Debug, Clone)]
pub struct Lim<T: PartialOrd + Clone + Debug> {
value: T,
min: T,
max: T,
include_min: bool,
include_max: bool,
}
impl<T: Clone + Copy + PartialOrd + Debug> Copy for Lim<T> {}
impl<T: Clone + PartialOrd + Debug> Lim<T> {
pub fn new(value: T, min: T, max: T, include_min: bool, include_max: bool) -> Result<Self, LimError<T>> {
if min > max {
return Err(LimError::InvalidBounds {
min,
max,
});
}
let min_ok = if include_min {
value >= min
} else {
value > min
};
let max_ok = if include_max {
value <= max
} else {
value < max
};
if min_ok && max_ok {
Ok(Self {
value,
min,
max,
include_min,
include_max,
})
} else {
Err(LimError::OutOfBounds { value, min, max })
}
}
pub fn value(&self) -> &T {
&self.value
}
pub fn min(&self) -> &T {
&self.min
}
pub fn max(&self) -> &T {
&self.max
}
pub fn include_min(&self) -> bool {
self.include_min
}
pub fn include_max(&self) -> bool {
self.include_max
}
pub fn contains(&self, value: &T) -> bool {
let min_ok = if self.include_min {
value >= &self.min
} else {
value > &self.min
};
let max_ok = if self.include_max {
value <= &self.max
} else {
value < &self.max
};
min_ok && max_ok
}
pub fn set_value(&mut self, value: T) -> Result<(), LimError<T>> {
if self.contains(&value) {
self.value = value;
} else {
return Err(LimError::OutOfBounds {
value,
min: self.min.clone(),
max: self.max.clone(),
});
}
Ok(())
}
pub fn set_min(&mut self, min: T, include_min: bool, clamp: bool) -> Result<(), LimError<T>> {
if min > self.max {
return Err(LimError::InvalidBounds {
min,
max: self.max.clone(),
});
}
let value_ok = if include_min {
self.value >= min
} else {
self.value > min
};
if value_ok {
self.min = min;
self.include_min = include_min;
Ok(())
} else if include_min && clamp {
self.value = min.clone();
self.min = min;
self.include_min = include_min;
Ok(())
} else {
if clamp {
if self.value == min && !include_min {
Err(LimError::AmbiguousClamp {
value: self.value.clone(),
})
} else {
Err(LimError::ExclusiveBoundClamp {
bound: min,
})
}
} else if !include_min {
Err(LimError::OutOfBounds {
value: self.value.clone(),
min,
max: self.max.clone(),
})
} else {
Err(LimError::OutOfBounds {
value: self.value.clone(),
min,
max: self.max.clone(),
})
}
}
}
pub fn set_max(&mut self, max: T, include_max: bool, clamp: bool) -> Result<(), LimError<T>> {
if max < self.min {
return Err(LimError::InvalidBounds {
min: self.min.clone(),
max,
});
}
let value_ok = if include_max {
self.value <= max
} else {
self.value < max
};
if value_ok {
self.max = max;
self.include_max = include_max;
Ok(())
} else if include_max && clamp {
self.value = max.clone();
self.max = max;
self.include_max = include_max;
Ok(())
} else {
if clamp {
if self.value == max && !include_max {
Err(LimError::AmbiguousClamp {
value: self.value.clone(),
})
} else {
Err(LimError::ExclusiveBoundClamp {
bound: max,
})
}
} else if !include_max {
Err(LimError::OutOfBounds {
value: self.value.clone(),
min: self.min.clone(),
max,
})
} else {
Err(LimError::OutOfBounds {
value: self.value.clone(),
min: self.min.clone(),
max,
})
}
}
}
pub fn set_bounds(
&mut self,
min: T,
max: T,
include_min: bool,
include_max: bool,
clamp: bool
) -> Result<(), LimError<T>> {
if min > max {
return Err(LimError::InvalidBounds {
min,
max,
});
}
let min_ok = if include_min {
self.value >= min
} else {
self.value > min
};
let max_ok = if include_max {
self.value <= max
} else {
self.value < max
};
let value_ok = min_ok && max_ok;
if value_ok {
self.min = min;
self.max = max;
self.include_min = include_min;
self.include_max = include_max;
Ok(())
} else if clamp {
let mut new_value = self.value.clone();
if !min_ok {
if self.value == min && !include_min {
return Err(LimError::AmbiguousClamp {
value: self.value.clone(),
});
}
if !include_min {
return Err(LimError::ExclusiveBoundClamp {
bound: min,
});
}
new_value = min.clone();
}
if !max_ok {
if new_value == max && !include_max {
return Err(LimError::AmbiguousClamp {
value: new_value.clone(),
});
}
if !include_max {
return Err(LimError::ExclusiveBoundClamp {
bound: max,
});
}
if new_value > max || (!include_max && new_value == max) {
if new_value == max && !include_max {
return Err(LimError::AmbiguousClamp {
value: new_value.clone(),
});
}
new_value = max.clone();
}
}
self.value = new_value;
self.min = min;
self.max = max;
self.include_min = include_min;
self.include_max = include_max;
Ok(())
} else {
if !min_ok {
if !include_min && self.value == min {
Err(LimError::OutOfBounds {
value: self.value.clone(),
min,
max,
})
} else {
Err(LimError::OutOfBounds {
value: self.value.clone(),
min,
max,
})
}
} else {
if !include_max && self.value == max {
Err(LimError::OutOfBounds {
value: self.value.clone(),
min,
max,
})
} else {
Err(LimError::OutOfBounds {
value: self.value.clone(),
min,
max,
})
}
}
}
}
}
impl<T: PartialOrd + Clone + Debug> Deref for Lim<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.value
}
}
impl<'de, T> Deserialize<'de> for Lim<T>
where
T: Deserialize<'de> + PartialOrd + Clone + Debug,
{
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let helper = LimHelper::deserialize(deserializer)?;
Lim::new(
helper.value,
helper.min,
helper.max,
helper.include_min,
helper.include_max,
)
.map_err(|e| serde::de::Error::custom(format!("Lim invariant violated: {}", e)))
}
}