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 {}