Use # as SlateDB key separator instead of /

File paths in node asset keys contain / (e.g. src/main.rs), which
conflicted with the / segment separator. Using # eliminates the
ambiguity — the filename is always the trailing segment after the
last #, so embedded slashes parse correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-04-03 16:39:24 -07:00
parent 4c4d8482e3
commit 91d789a9f3

View file

@ -1,9 +1,9 @@
use crate::NodeVisitRef;
pub(crate) const INIT_KEY: &str = "_init.json";
pub(crate) const EVENTS_PREFIX: &str = "events/";
pub(crate) const ARTIFACT_VALUES_PREFIX: &str = "artifacts/values/";
pub(crate) const ARTIFACT_NODES_PREFIX: &str = "artifacts/nodes/";
pub(crate) const EVENTS_PREFIX: &str = "events#";
pub(crate) const ARTIFACT_VALUES_PREFIX: &str = "artifacts#values#";
pub(crate) const ARTIFACT_NODES_PREFIX: &str = "artifacts#nodes#";
pub(crate) fn init() -> &'static str {
INIT_KEY
@ -19,13 +19,13 @@ pub(crate) fn artifact_value(artifact_id: &str) -> String {
pub(crate) fn node_asset_prefix(node: &NodeVisitRef<'_>) -> String {
format!(
"{ARTIFACT_NODES_PREFIX}{}/visit-{}",
"{ARTIFACT_NODES_PREFIX}{}#visit-{}",
node.node_id, node.visit
)
}
pub(crate) fn node_asset(node: &NodeVisitRef<'_>, filename: &str) -> String {
format!("{}/{filename}", node_asset_prefix(node))
format!("{}#{filename}", node_asset_prefix(node))
}
pub(crate) fn parse_event_seq(key: &str) -> Option<u32> {
@ -48,8 +48,8 @@ fn parse_seq(key: &str, prefix: &str) -> Option<u32> {
fn parse_visit_scoped_key(key: &str, prefix: &str) -> Option<(String, u32, String)> {
let rest = key.strip_prefix(prefix)?;
let (node_id, rest) = rest.split_once("/visit-")?;
let (visit, file) = rest.split_once('/')?;
let (node_id, rest) = rest.split_once("#visit-")?;
let (visit, file) = rest.split_once('#')?;
Some((node_id.to_string(), visit.parse().ok()?, file.to_string()))
}
@ -60,12 +60,12 @@ mod tests {
#[test]
fn top_level_keys_match_spec() {
assert_eq!(init(), "_init.json");
assert_eq!(event_key(7, 123), "events/000007-123.json");
assert_eq!(event_key(7, 123), "events#000007-123.json");
}
#[test]
fn sequence_keys_are_zero_padded() {
assert_eq!(event_key(7, 123), "events/000007-123.json");
assert_eq!(event_key(7, 123), "events#000007-123.json");
}
#[test]
@ -74,36 +74,48 @@ mod tests {
node_id: "code",
visit: 2,
};
assert_eq!(artifact_value("summary"), "artifacts/values/summary.json");
assert_eq!(artifact_value("summary"), "artifacts#values#summary.json");
assert_eq!(
node_asset(&node, "src/main.rs"),
"artifacts/nodes/code/visit-2/src/main.rs"
"artifacts#nodes#code#visit-2#src/main.rs"
);
}
#[test]
fn parse_helpers_extract_sequences_and_node_visits() {
assert_eq!(parse_event_seq("events/000007-123.json"), Some(7));
assert_eq!(parse_event_seq("events#000007-123.json"), Some(7));
assert_eq!(
parse_artifact_value_id("artifacts/values/summary.json"),
parse_artifact_value_id("artifacts#values#summary.json"),
Some("summary".to_string())
);
assert_eq!(
parse_node_asset_key("artifacts/nodes/code/visit-2/src/main.rs"),
parse_node_asset_key("artifacts#nodes#code#visit-2#src/main.rs"),
Some(("code".to_string(), 2, "src/main.rs".to_string()))
);
}
#[test]
fn parse_helpers_reject_invalid_keys() {
assert_eq!(parse_event_seq("events/not-a-seq.json"), None);
assert_eq!(parse_event_seq("events#not-a-seq.json"), None);
assert_eq!(
parse_artifact_value_id("artifacts/values/summary.txt"),
parse_artifact_value_id("artifacts#values#summary.txt"),
None
);
assert_eq!(
parse_node_asset_key("artifacts/nodes/code/status.json"),
parse_node_asset_key("artifacts#nodes#code#status.json"),
None
);
}
#[test]
fn asset_filename_with_slashes_parses_correctly() {
assert_eq!(
parse_node_asset_key("artifacts#nodes#build#visit-1#deep/nested/path/file.rs"),
Some((
"build".to_string(),
1,
"deep/nested/path/file.rs".to_string()
))
);
}
}