1use std::sync::OnceLock;
4use std::sync::atomic::{AtomicU64, Ordering};
5
6#[cfg(not(target_arch = "wasm32"))]
7use std::time::{SystemTime, UNIX_EPOCH};
8
9pub const NONE: &str = "null";
11
12pub fn escape(s: &str) -> String {
13 let mut out = String::with_capacity(s.len());
14 for c in s.chars() {
15 match c {
16 '"' => out.push_str("\\\""),
17 '\\' => out.push_str("\\\\"),
18 '\n' => out.push_str("\\n"),
19 '\r' => out.push_str("\\r"),
20 '\t' => out.push_str("\\t"),
21 c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
22 c => out.push(c),
23 }
24 }
25 out
26}
27
28pub fn num(v: f64) -> String {
30 if v.is_finite() {
31 format!("{v}")
32 } else {
33 NONE.to_string()
34 }
35}
36
37fn collection(
40 items: impl IntoIterator<Item = String>,
41 held: usize,
42 next: Option<String>,
43) -> String {
44 let written: Vec<String> = items.into_iter().collect();
45 let cursor = next
46 .as_ref()
47 .map_or_else(|| NONE.to_string(), |at| format!("\"{}\"", escape(at)));
48 format!(
49 "{{ \"items\": [{}], \"pagination\": {{ \"count\": {held}, \"has_more\": {}, \"next_cursor\": {cursor} }} }}",
50 written.join(", "),
51 written.len() < held
52 )
53}
54
55pub fn list<T>(items: &[T], f: impl Fn(&T) -> String) -> String {
56 collection(items.iter().map(f), items.len(), None)
57}
58
59pub fn latest<T>(items: &[T], made: usize, f: impl Fn(&T) -> String) -> String {
61 collection(items.iter().map(f), made, None)
62}
63
64pub fn capped<T>(
66 items: &[T],
67 shown: usize,
68 resumes_at: impl Fn(usize) -> f64,
69 f: impl Fn(&T) -> String,
70) -> String {
71 let next = (shown < items.len()).then(|| format!("{}s", resumes_at(shown)));
72 collection(items[..shown].iter().map(f), items.len(), next)
73}
74
75pub fn pair_list(items: &[(&str, &str)], key: &str, value: &str) -> String {
76 list(items, |(a, b)| {
77 format!(
78 "{{ \"{key}\": \"{}\", \"{value}\": \"{}\" }}",
79 escape(a),
80 escape(b)
81 )
82 })
83}
84
85pub fn strings(items: &[impl AsRef<str>]) -> String {
86 list(items, |s| format!("\"{}\"", escape(s.as_ref())))
87}
88
89#[cfg(not(target_arch = "wasm32"))]
90fn timestamp() -> u64 {
91 SystemTime::now()
92 .duration_since(UNIX_EPOCH)
93 .map(|d| d.as_secs())
94 .unwrap_or(0)
95}
96
97#[cfg(target_arch = "wasm32")]
98fn timestamp() -> u64 {
99 0
100}
101
102fn origin() -> u64 {
105 #[cfg(not(target_arch = "wasm32"))]
106 {
107 let nanos = SystemTime::now()
108 .duration_since(UNIX_EPOCH)
109 .map(|d| d.as_nanos() as u64)
110 .unwrap_or(0);
111 nanos ^ ((std::process::id() as u64) << 40)
112 }
113 #[cfg(target_arch = "wasm32")]
114 {
115 0
116 }
117}
118
119fn request_id() -> String {
121 static ORIGIN: OnceLock<u64> = OnceLock::new();
122 static ANSWERED: AtomicU64 = AtomicU64::new(0);
123 let n = ANSWERED.fetch_add(1, Ordering::Relaxed);
124 format!("req_{:016x}{n:06x}", ORIGIN.get_or_init(origin))
125}
126
127pub fn meta() -> String {
128 format!(
129 "{{ \"request_id\": \"{}\", \"timestamp\": {} }}",
130 request_id(),
131 timestamp()
132 )
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn a_non_finite_measurement_reads_as_null_in_every_emitter() {
141 assert_eq!(num(0.5), "0.5");
142 assert_eq!(num(f64::NAN), "null");
143 assert_eq!(num(f64::INFINITY), "null");
144 assert_eq!(num(f64::NEG_INFINITY), "null");
145 }
146
147 #[test]
148 fn a_control_character_is_escaped_rather_than_emitted_raw() {
149 assert_eq!(escape("a\"b\\c\nd\u{1}"), "a\\\"b\\\\c\\nd\\u0001");
150 }
151
152 #[test]
153 fn every_response_carries_its_own_request_id_beside_the_timestamp() {
154 let (first, second) = (meta(), meta());
155 assert!(first.contains("\"request_id\": \"req_"), "{first}");
156 assert!(first.contains("\"timestamp\": "), "{first}");
157 assert_ne!(first, second, "two responses are never the same request");
158 }
159
160 #[test]
161 fn the_origin_carries_more_than_the_whole_second_two_runs_would_share() {
162 let (a, b) = (origin(), origin());
163 assert_ne!(a, b, "the origin carries more than whole seconds");
164 }
165
166 #[test]
167 fn an_empty_collection_is_an_empty_items_list_with_a_zero_count() {
168 assert!(list(&[] as &[f64], |v| num(*v)).contains("\"items\": []"));
169 assert!(strings(&[] as &[&str]).contains("\"count\": 0"));
170 assert!(strings(&["a", "b\"c"]).contains("[\"a\", \"b\\\"c\"]"));
171 let cut = capped(&[1.0, 2.0, 3.0], 2, |n| n as f64 / 4.0, |v| num(*v));
172 assert!(
173 cut.contains("\"count\": 3") && cut.contains("\"has_more\": true"),
174 "{cut}"
175 );
176 assert!(
177 cut.contains("\"items\": [1, 2]"),
178 "the page, not the whole: {cut}"
179 );
180 assert!(
181 cut.contains("\"next_cursor\": \"0.5s\""),
182 "an interval start: {cut}"
183 );
184 }
185}