use std::fmt;
use bigdecimal::BigDecimal;
use chrono::{DateTime, Utc};
#[derive(Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Point {
pub timestamp: DateTime<Utc>,
pub value: BigDecimal,
}
impl Point {
#[must_use]
pub const fn new(timestamp: DateTime<Utc>, value: BigDecimal) -> Self {
Self { timestamp, value }
}
}
impl fmt::Debug for Point {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Point").field("timestamp", &self.timestamp).field("value", &Bounded(&self.value)).finish()
}
}
struct Bounded<'a>(&'a BigDecimal);
impl fmt::Debug for Bounded<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (digits, scale) = self.0.as_bigint_and_exponent();
if scale.unsigned_abs() <= 64 {
fmt::Display::fmt(self.0, f)
} else {
write!(f, "{digits}e{}", -i128::from(scale))
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "lowercase"))]
pub enum PointKind {
Raw,
Interpolated,
Extrapolated,
}
impl PointKind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Raw => "raw",
Self::Interpolated => "interpolated",
Self::Extrapolated => "extrapolated",
}
}
}
impl fmt::Display for PointKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[cfg(test)]
mod tests {
use chrono::{DateTime, Utc};
use super::*;
#[test]
fn debug_is_bounded_by_the_digits() {
let t = DateTime::<Utc>::UNIX_EPOCH;
for (value, expected) in [(BigDecimal::new(125.into(), 1), "12.5"), (BigDecimal::new(1.into(), i64::MAX), "1e-9223372036854775807"), (BigDecimal::new(7.into(), i64::MIN), "7e9223372036854775808")] {
let text = format!("{:?}", Point::new(t, value));
assert!(text.contains(expected), "{text}");
assert!(text.len() < 120, "{text}");
}
}
}