use lunaris_core::bitemporal::BiTemporal;
use lunaris_core::{Episode, HlcClock, Scope};
use ulid::Ulid;
#[derive(Clone, Debug)]
#[must_use = "EpisodeBuilder is consumed by ScopedLunaris::ingest; constructing it without calling .ingest() is a no-op"]
pub struct EpisodeBuilder {
id: Option<Ulid>,
source: String,
content: String,
t_ref: Option<chrono::DateTime<chrono::Utc>>,
metadata: serde_json::Map<String, serde_json::Value>,
}
impl EpisodeBuilder {
pub fn new(source: impl Into<String>, content: impl Into<String>) -> Self {
Self {
id: None,
source: source.into(),
content: content.into(),
t_ref: None,
metadata: serde_json::Map::new(),
}
}
pub fn id(mut self, id: Ulid) -> Self {
self.id = Some(id);
self
}
pub fn t_ref(mut self, t: chrono::DateTime<chrono::Utc>) -> Self {
self.t_ref = Some(t);
self
}
pub fn metadata(mut self, m: serde_json::Map<String, serde_json::Value>) -> Self {
self.metadata.extend(m);
self
}
pub(crate) fn into_episode(self, scope: Scope, clock: &HlcClock) -> Episode {
Episode {
#[allow(clippy::unwrap_or_default)]
id: self.id.unwrap_or_else(Ulid::new),
scope,
source: self.source,
content: self.content,
t_ref: self.t_ref,
bt: BiTemporal::now(clock),
metadata: self.metadata,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn distinct_builders_get_distinct_ids() {
let clock = HlcClock::new(0);
let scope = Scope::dev();
let a = EpisodeBuilder::new("s", "a").into_episode(scope.clone(), &clock);
let b = EpisodeBuilder::new("s", "b").into_episode(scope, &clock);
assert_ne!(a.id, b.id, "auto-generated episode ids must be unique");
assert_ne!(a.id, Ulid::nil());
}
#[test]
fn explicit_id_is_preserved() {
let clock = HlcClock::new(0);
let id = Ulid::new();
let ep = EpisodeBuilder::new("s", "c").id(id).into_episode(Scope::dev(), &clock);
assert_eq!(ep.id, id);
}
}