Skip to main content

somatize_data/
span.rs

1//! Which rows, and where from.
2
3use somatize_core::Value;
4use std::fmt;
5
6/// The rows a source is being asked for: `take` of them, starting at `at`.
7///
8/// This is what a graph reading from a source is handed as its **input**, and
9/// the input is the one value a cache hashes by content. Two numbers, so naming
10/// it is free; the rows are named by the source's version instead.
11///
12/// And it is what makes a stream cacheable. A span is a **position**, and a
13/// position is repeatable: rows 400..500 are the same rows tomorrow, whatever
14/// has arrived since. What moves is not the source's state, it is which spans
15/// exist. A source answering *whatever is newest* is the other thing, and the
16/// engine already refuses to cache under it.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub struct Span {
19    /// The first row, counting from zero.
20    pub at: u64,
21    /// How many, at most: the last span of a dataset is short and that is not an
22    /// error.
23    pub take: u64,
24}
25
26impl Span {
27    /// `take` rows starting at `at`.
28    pub fn new(at: u64, take: u64) -> Self {
29        Self { at, take }
30    }
31
32    /// The span this value is, if it is one.
33    ///
34    /// A `Map` of two numbers and not a pair of positions in a list: what a
35    /// record shows is what was asked for, and `{"at": 4096, "take": 64}` says
36    /// it where `[4096, 64]` needs the reader to remember the order.
37    pub fn of(value: &Value) -> Result<Self, SpanError> {
38        let (Some(at), Some(take)) = (value.get("at"), value.get("take")) else {
39            return Err(SpanError(format!(
40                "a source is asked for rows, and what arrived was a {}. It takes \
41                 `{{\"at\": <first row>, \"take\": <how many>}}`",
42                value.type_name()
43            )));
44        };
45        Ok(Self::new(whole(at, "at")?, whole(take, "take")?))
46    }
47
48    /// As a value, which is how it is handed to a graph.
49    pub fn value(&self) -> Value {
50        Value::map(vec![
51            ("at".to_string(), Value::number(self.at as f64)),
52            ("take".to_string(), Value::number(self.take as f64)),
53        ])
54    }
55}
56
57/// A number that is a count: whole, and not negative.
58fn whole(value: &Value, field: &str) -> Result<u64, SpanError> {
59    let Value::Number(x) = value else {
60        return Err(SpanError(format!(
61            "`{field}` is a number of rows, and this is a {}",
62            value.type_name()
63        )));
64    };
65    if *x < 0.0 || x.fract() != 0.0 {
66        return Err(SpanError(format!(
67            "`{field}` is a count of rows, and it is {x}"
68        )));
69    }
70    Ok(*x as u64)
71}
72
73/// Why that was not a span.
74#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct SpanError(String);
76
77impl SpanError {
78    /// The message.
79    pub fn message(&self) -> &str {
80        &self.0
81    }
82}
83
84impl fmt::Display for SpanError {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        f.write_str(&self.0)
87    }
88}
89
90impl std::error::Error for SpanError {}