use crate::metrics::NullableFloat;
use std::fmt::{Debug, Display, Formatter, Write};
pub trait DeltaValue: Copy + Debug + Display {
type Delta: Copy + Display;
fn delta(self, value: Self) -> Self::Delta;
fn is_delta_non_negative(value: Self::Delta) -> bool;
}
impl DeltaValue for usize {
type Delta = isize;
fn delta(self, value: Self) -> Self::Delta {
let delta = (self as i128) - (value as i128);
delta.clamp(isize::MIN as i128, isize::MAX as i128) as isize
}
fn is_delta_non_negative(value: Self::Delta) -> bool {
!value.is_negative()
}
}
impl DeltaValue for f32 {
type Delta = f32;
fn delta(self, value: Self) -> Self::Delta {
self - value
}
fn is_delta_non_negative(value: Self::Delta) -> bool {
value >= 0.0
}
}
impl DeltaValue for u64 {
type Delta = i64;
fn delta(self, value: Self) -> Self::Delta {
let delta = (self as i128) - (value as i128);
delta.clamp(i64::MIN as i128, i64::MAX as i128) as i64
}
fn is_delta_non_negative(value: Self::Delta) -> bool {
!value.is_negative()
}
}
impl DeltaValue for u16 {
type Delta = i16;
fn delta(self, value: Self) -> Self::Delta {
let delta = self as i32 - value as i32;
delta.clamp(i16::MIN as i32, i16::MAX as i32) as i16
}
fn is_delta_non_negative(value: Self::Delta) -> bool {
!value.is_negative()
}
}
impl DeltaValue for f64 {
type Delta = f64;
fn delta(self, value: Self) -> Self::Delta {
self - value
}
fn is_delta_non_negative(value: Self::Delta) -> bool {
value >= 0.0
}
}
#[derive(Copy, Clone, Debug, serde::Deserialize, PartialEq)]
#[serde(untagged)]
pub enum Value<T: DeltaValue> {
Plain(T),
Delta { value: T, delta: T::Delta },
}
impl<T: DeltaValue + serde::Serialize> serde::Serialize for Value<T> {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.value().serialize(serializer)
}
}
impl<T: DeltaValue> From<T> for Value<T> {
fn from(value: T) -> Self {
Self::Plain(value)
}
}
impl From<f32> for Value<NullableFloat> {
fn from(value: f32) -> Self {
Self::Plain(NullableFloat::from(value))
}
}
impl From<f64> for Value<NullableFloat> {
fn from(value: f64) -> Self {
Self::Plain(NullableFloat(value))
}
}
impl<T: DeltaValue> Value<T> {
pub fn value(&self) -> T {
match self {
Self::Plain(value) | Self::Delta { value, .. } => *value,
}
}
pub fn diff(&mut self, other: T) {
let value = self.value();
*self = Self::Delta {
value,
delta: value.delta(other),
};
}
}
pub trait ApplyBaseline<T: DeltaValue> {
fn eval(&mut self, other: Self);
}
impl<T: DeltaValue> ApplyBaseline<T> for Value<T> {
fn eval(&mut self, other: Self) {
self.diff(other.value())
}
}
impl<T: DeltaValue> ApplyBaseline<T> for Option<Value<T>> {
fn eval(&mut self, other: Self) {
if let (Some(value), Some(other)) = (self, other) {
value.eval(other);
}
}
}
impl<T: DeltaValue> Display for Value<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Plain(value) => Display::fmt(value, f),
Self::Delta { value, delta } => {
Display::fmt(value, f)?;
f.write_str(" (")?;
if T::is_delta_non_negative(*delta) {
f.write_char('+')?;
}
Display::fmt(delta, f)?;
f.write_char(')')?;
Ok(())
}
}
}
}
pub trait DeltaTo {
fn delta_to(&mut self, other: &Self);
}
#[cfg(test)]
mod test {
use super::*;
use crate::metrics::Value;
#[test]
fn eval_optional() {
assert_eq!(
{
let mut value = Some(Value::Plain(10usize));
value.eval(Some(Value::Plain(5usize)));
value
},
Some(Value::Delta {
value: 10usize,
delta: 5isize
})
);
assert_eq!(
{
let mut value: Option<Value<usize>> = None;
value.eval(Some(Value::Plain(5usize)));
value
},
None
);
}
#[test]
fn delta_to_string() {
assert_eq!(format!("{}", 0usize.delta(10usize)), "-10");
assert_eq!(format!("{}", 10usize.delta(10usize)), "0");
assert_eq!(format!("{}", 10usize.delta(0usize)), "10");
}
#[test]
fn value_to_string() {
fn value<T: DeltaValue>(value: T, baseline: T) -> Value<T> {
let mut result = Value::from(value);
result.diff(baseline);
result
}
assert_eq!(format!("{}", value(0usize, 1000usize)), "0 (-1000)");
assert_eq!(format!("{}", value(1000usize, 1000usize)), "1000 (+0)");
assert_eq!(format!("{}", value(1000usize, 0usize)), "1000 (+1000)");
}
#[test]
fn delta_value_f32() {
assert_eq!(100.0f32.delta(50.0f32), 50.0f32);
assert_eq!(50.0f32.delta(100.0f32), -50.0f32);
assert_eq!(0.0f32.delta(0.0f32), 0.0f32);
assert!(f32::is_delta_non_negative(1.0));
assert!(f32::is_delta_non_negative(0.0));
assert!(!f32::is_delta_non_negative(-1.0));
}
#[test]
fn delta_value_f64() {
assert_eq!(100.0f64.delta(50.0f64), 50.0f64);
assert_eq!(50.0f64.delta(100.0f64), -50.0f64);
}
#[test]
fn delta_value_u64() {
assert_eq!(100u64.delta(50u64), 50i64);
assert_eq!(50u64.delta(100u64), -50i64);
assert_eq!(u64::MAX.delta(0u64), i64::MAX); assert_eq!(0u64.delta(u64::MAX), i64::MIN); }
#[test]
fn delta_value_u16() {
assert_eq!(100u16.delta(50u16), 50i16);
assert_eq!(50u16.delta(100u16), -50i16);
assert_eq!(u16::MAX.delta(0u16), i16::MAX); assert_eq!(0u16.delta(u16::MAX), i16::MIN); }
#[test]
fn delta_value_nullable_float() {
use crate::metrics::NullableFloat;
let a = NullableFloat(100.0);
let b = NullableFloat(60.0);
assert_eq!(a.delta(b), 40.0f64);
assert_eq!(b.delta(a), -40.0f64);
}
#[test]
fn value_serde_round_trip() {
let plain = Value::Plain(42usize);
let json = serde_json::to_string(&plain).unwrap();
assert_eq!(json, "42");
let deserialized: Value<usize> = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, plain);
let delta = Value::Delta {
value: 100usize,
delta: 50isize,
};
let json = serde_json::to_string(&delta).unwrap();
assert_eq!(json, "100");
let deserialized: Value<usize> = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, Value::Plain(100usize));
}
}