use std::cell::RefCell;
pub const HATCHERY_PREFIX: &str = "fyi.hatchery.";
thread_local! {
static CURRENT: RefCell<Option<serde_json::Value>> = const { RefCell::new(None) };
static INTERRUPTED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
}
pub fn mark_interrupted() {
INTERRUPTED.with(|flag| flag.set(true));
}
pub(crate) fn was_interrupted() -> bool {
INTERRUPTED.with(|flag| flag.get())
}
pub fn hatchery_fields(content: &serde_json::Value) -> Option<serde_json::Value> {
let fields: serde_json::Map<String, serde_json::Value> = content
.as_object()?
.iter()
.filter(|(key, _)| key.starts_with(HATCHERY_PREFIX))
.map(|(key, value)| (key.clone(), value.clone()))
.collect();
(!fields.is_empty()).then_some(serde_json::Value::Object(fields))
}
pub fn current() -> Option<serde_json::Value> {
CURRENT.with(|slot| slot.borrow().clone())
}
pub fn current_is_interrupt() -> bool {
current().is_some_and(|c| c.get("fyi.hatchery.interrupt") == Some(&serde_json::Value::Bool(true)))
}
pub(crate) fn scoped<R>(content: Option<serde_json::Value>, f: impl FnOnce() -> R) -> R {
struct Restore(Option<serde_json::Value>);
impl Drop for Restore {
fn drop(&mut self) {
let previous = self.0.take();
CURRENT.with(|slot| *slot.borrow_mut() = previous);
}
}
let previous = CURRENT.with(|slot| slot.replace(content));
INTERRUPTED.with(|flag| flag.set(false));
let _restore = Restore(previous);
f()
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn only_hatchery_keys_are_kept() {
let content = json!({"msgtype": "m.text", "body": "hi", "fyi.hatchery.interrupt": true, "fyi.hatchery.task": 95, "fyi.other": 1});
assert_eq!(hatchery_fields(&content), Some(json!({"fyi.hatchery.interrupt": true, "fyi.hatchery.task": 95})));
assert_eq!(hatchery_fields(&json!({"msgtype": "m.text", "body": "hi"})), None);
assert_eq!(hatchery_fields(&json!("not an object")), None);
}
#[test]
fn current_is_scoped_and_restored() {
assert_eq!(current(), None);
scoped(Some(json!({"fyi.hatchery.interrupt": true})), || {
assert!(current_is_interrupt());
scoped(None, || assert_eq!(current(), None));
assert!(current_is_interrupt(), "inner scope restored the outer letter");
});
assert_eq!(current(), None);
scoped(Some(json!({"fyi.hatchery.interrupt": "yes"})), || assert!(!current_is_interrupt(), "only literal true interrupts"));
}
#[test]
fn the_interrupted_mark_starts_clear_for_each_letter() {
scoped(Some(json!({"fyi.hatchery.interrupt": true})), || {
assert!(!was_interrupted());
mark_interrupted();
assert!(was_interrupted());
});
scoped(None, || assert!(!was_interrupted(), "the next letter starts clear"));
}
}