use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use serde_json::{Map, Value};
use crate::error::{self, Result};
use crate::event::{phase_of, Dimensions, EventKind, Labels, Source, SOURCE_WORD};
use crate::registry::Registry;
use crate::session::Lifecycle;
use crate::vocabulary::Emitter;
use crate::workspace::{self, Record};
pub use crate::git::ObjectId;
pub use crate::workspace::{Ref, Token};
pub struct SessionSeed {
pub token: Token,
pub identity: String,
pub alias: String,
pub branch: Ref,
pub checkout: PathBuf,
pub clone: PathBuf,
pub worktree: PathBuf,
pub state: Lifecycle,
pub labels: BTreeMap<String, String>,
}
pub fn write_session(home: &Path, seed: &SessionSeed) -> Result<()> {
crate::label::validate(&seed.labels)?;
let record = Record {
version: workspace::RECORD_VERSION,
token: seed.token.clone(),
identity: seed.identity.clone(),
alias: seed.alias.clone(),
branch: seed.branch.clone(),
base: Ref::try_from("main".to_owned()).map_err(error::invalid)?,
change_base: None,
stack_tip: None,
worktree: seed.worktree.clone(),
clone: seed.clone.clone(),
run_root: seed
.clone
.parent()
.ok_or_else(|| error::invalid("fixture clone has no run root"))?
.to_owned(),
slot: None,
execution_checkout: seed.checkout.clone(),
publication_checkout: seed.checkout.clone(),
state: seed.state,
owner_pid: if seed.state == Lifecycle::Open {
std::process::id()
} else {
0
},
owner_started: (seed.state == Lifecycle::Open)
.then(|| workspace::process_started(std::process::id()))
.flatten(),
retried_by: None,
labels: seed.labels.clone(),
carried: Default::default(),
};
write_document(
&home.join("sessions").join(format!("{}.json", seed.token)),
&record,
)
}
pub fn write_registry(home: &Path, registry: &Registry) -> Result<()> {
if registry.version != crate::store::VERSION {
return Err(error::invalid(
"fixture registry must use the current production version",
));
}
write_document(&home.join("registry.json"), registry)
}
pub fn registry_version() -> u32 {
crate::store::VERSION
}
pub fn landing_trailer() -> String {
crate::provenance::Trailers::default().landed().to_owned()
}
pub fn write_events(
home: &Path,
token: &str,
identity: &str,
events: &[(EventKind, Map<String, Value>)],
) -> Result<()> {
Token::try_from(token.to_owned()).map_err(error::invalid)?;
let path = home.join("streams").join(format!("{token}.ndjson"));
std::fs::create_dir_all(path.parent().expect("stream parent"))
.map_err(|error| error::invalid(error.to_string()))?;
let mut labels = Labels::default();
labels
.extra
.insert("session".into(), Value::String(token.to_owned()));
labels
.extra
.insert("identity".into(), Value::String(identity.to_owned()));
let emitter = Emitter::shared(token, Source::from(SOURCE_WORD), &path)
.with_version(crate::stream::ENVELOPE_VERSION)
.with_labels(labels);
for (kind, payload) in events {
emitter
.try_emit_stamped(
*kind,
Dimensions::at(phase_of(*kind)),
payload.clone(),
Vec::new(),
)
.map_err(|error| {
error::invalid(format!("fixture stream {}: {error}", path.display()))
})?;
}
Ok(())
}
fn write_document(path: &Path, document: &impl serde::Serialize) -> Result<()> {
let bytes =
serde_json::to_vec_pretty(document).map_err(|error| error::invalid(error.to_string()))?;
std::fs::create_dir_all(path.parent().expect("document parent"))
.and_then(|()| std::fs::write(path, bytes))
.map_err(|error| error::invalid(format!("fixture document {}: {error}", path.display())))
}