use std::borrow::Cow;
use std::path::PathBuf;
const DEFAULT_MAX_AGE_SECS: u64 = 4 * 60 * 60;
fn plate_path() -> Option<PathBuf> {
let root = std::env::var("SHANTY_ROOT").ok()?;
let agent = std::env::var("SHANTY_AGENT").ok()?;
if root.is_empty() || agent.is_empty() {
return None;
}
Some(
PathBuf::from(root)
.join("crew")
.join(agent)
.join("plate.json"),
)
}
#[must_use]
pub fn parse(doc: &str, now: u64, max_age: u64) -> Option<String> {
let value: serde_json::Value = serde_json::from_str(doc).ok()?;
let item = value.get("item")?.as_str()?.trim().to_string();
if item.is_empty() {
return None;
}
let at = value.get("at").and_then(serde_json::Value::as_u64)?;
if now.saturating_sub(at) > max_age {
return None;
}
Some(item)
}
#[must_use]
pub fn current() -> Option<String> {
let path = plate_path()?;
let doc = std::fs::read_to_string(path).ok()?;
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.ok()?
.as_secs();
let max_age = std::env::var("BOBBIN_PLATE_MAX_AGE_SECS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(DEFAULT_MAX_AGE_SECS);
parse(&doc, now, max_age)
}
#[must_use]
pub fn scope_query<'a>(query: &'a str, item: Option<&str>) -> Cow<'a, str> {
match item {
Some(item) => Cow::Owned(format!("{query} {item}")),
None => Cow::Borrowed(query),
}
}
#[cfg(test)]
mod tests {
use super::{parse, scope_query};
#[test]
fn a_fresh_plate_yields_its_item() {
let doc = r#"{"item":"aegis-1","at":1000,"session":"s"}"#;
assert_eq!(parse(doc, 1_500, 3_600), Some("aegis-1".to_string()));
}
#[test]
fn a_stale_plate_is_UNKNOWN_not_a_best_guess() {
let doc = r#"{"item":"aegis-1","at":1000,"session":"s"}"#;
assert_eq!(parse(doc, 100_000, 3_600), None);
}
#[test]
fn malformed_empty_and_undated_plates_all_abstain() {
for doc in [
"not json",
"{}",
r#"{"item":""}"#,
r#"{"item":" "}"#,
r#"{"item":"aegis-1"}"#, ] {
assert_eq!(parse(doc, 1_500, 3_600), None, "should abstain on: {doc}");
}
}
#[test]
fn scope_is_an_additive_retrieval_prior() {
assert_eq!(
scope_query("how does dispatch work", Some("aegis-368cu.9")),
"how does dispatch work aegis-368cu.9"
);
}
#[test]
fn unknown_scope_preserves_the_query_byte_for_byte() {
let query = " keep my exact whitespace ";
let scoped = scope_query(query, None);
assert!(matches!(scoped, std::borrow::Cow::Borrowed(_)));
assert_eq!(scoped, query);
}
}