use crate::value::Value;
use std::collections::BTreeMap;
use std::sync::Mutex;
const DEFAULT_THRESHOLD_BYTES: usize = 2048;
const PREVIEW_ROWS: usize = 3;
#[derive(Clone, Debug)]
pub struct Handle {
pub id: String,
pub shape: String,
pub items: usize,
pub bytes: usize,
pub value: Value,
}
lazy_static::lazy_static! {
static ref STORE: Mutex<Vec<Handle>> = Mutex::new(Vec::new());
}
pub fn threshold_bytes() -> usize {
match std::env::var("AETHER_HANDLE_BYTES") {
Ok(v) => v.trim().parse().unwrap_or(DEFAULT_THRESHOLD_BYTES),
Err(_) => DEFAULT_THRESHOLD_BYTES,
}
}
pub fn worth_handling(v: &Value, rendered_bytes: usize) -> bool {
let t = threshold_bytes();
if t == 0 || rendered_bytes <= t {
return false;
}
matches!(v, Value::Array(_) | Value::Record(_) | Value::Table(_))
}
fn item_count(v: &Value) -> usize {
match v {
Value::Array(a) => a.len(),
Value::Record(m) => m.len(),
_ => 1,
}
}
pub fn put(v: Value, rendered_bytes: usize) -> Handle {
let mut store = STORE.lock().unwrap_or_else(|e| e.into_inner());
let id = format!("h{}", store.len() + 1);
let h = Handle {
id: id.clone(),
shape: crate::shapes::observe(&v),
items: item_count(&v),
bytes: rendered_bytes,
value: v,
};
store.push(h.clone());
h
}
pub fn get(id: &str) -> Option<Value> {
let store = STORE.lock().unwrap_or_else(|e| e.into_inner());
store.iter().find(|h| h.id == id).map(|h| h.value.clone())
}
pub fn list() -> Vec<Handle> {
let store = STORE.lock().unwrap_or_else(|e| e.into_inner());
store
.iter()
.map(|h| Handle {
value: Value::Null,
..h.clone()
})
.collect()
}
pub fn drop_handle(id: &str) -> bool {
let mut store = STORE.lock().unwrap_or_else(|e| e.into_inner());
let before = store.len();
store.retain(|h| h.id != id);
store.len() != before
}
pub fn clear() {
STORE.lock().unwrap_or_else(|e| e.into_inner()).clear();
}
pub fn preview_of(v: &Value) -> Value {
match v {
Value::Array(items) => Value::Array(items.iter().take(PREVIEW_ROWS).cloned().collect()),
Value::Record(m) => {
let mut out = BTreeMap::new();
for (k, val) in m.iter().take(PREVIEW_ROWS) {
out.insert(k.clone(), Value::Str(crate::shapes::observe(val)));
}
Value::Record(out)
}
other => other.clone(),
}
}
pub fn omitted(v: &Value) -> usize {
item_count(v).saturating_sub(PREVIEW_ROWS)
}
#[cfg(test)]
mod tests {
use super::*;
fn big_array(n: usize) -> Value {
Value::Array((0..n).map(|i| Value::Int(i as i64)).collect())
}
#[test]
fn a_stored_value_comes_back_exactly() {
clear();
let v = big_array(100);
let h = put(v.clone(), 9999);
assert_eq!(get(&h.id), Some(v), "a handle must be lossless");
}
#[test]
fn small_results_are_never_handled() {
assert!(!worth_handling(&big_array(2), 10));
}
#[test]
fn an_unstructured_value_is_not_handled_however_large() {
let huge = Value::Str("x".repeat(100_000));
assert!(!worth_handling(&huge, 100_000));
}
#[test]
fn a_preview_reports_what_it_omits() {
let v = big_array(50);
assert_eq!(omitted(&v), 50 - PREVIEW_ROWS);
match preview_of(&v) {
Value::Array(a) => assert_eq!(a.len(), PREVIEW_ROWS),
other => panic!("expected an array preview, got {other:?}"),
}
}
#[test]
fn dropping_a_handle_frees_it_and_reports_whether_it_existed() {
clear();
let h = put(big_array(10), 9999);
assert!(drop_handle(&h.id));
assert!(!drop_handle(&h.id), "dropping twice must report absence");
assert_eq!(get(&h.id), None);
}
#[test]
fn a_zero_threshold_disables_handling() {
std::env::set_var("AETHER_HANDLE_BYTES", "0");
assert!(!worth_handling(&big_array(1000), 1_000_000));
std::env::remove_var("AETHER_HANDLE_BYTES");
}
}