From 91d789a9f30352e0aedcb14fbc808444c6807031 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Fri, 3 Apr 2026 16:39:24 -0700 Subject: [PATCH] Use # as SlateDB key separator instead of / MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- lib/crates/fabro-store/src/keys.rs | 46 +++++++++++++++++++----------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/lib/crates/fabro-store/src/keys.rs b/lib/crates/fabro-store/src/keys.rs index 0c8d4b6bf..bb145994b 100644 --- a/lib/crates/fabro-store/src/keys.rs +++ b/lib/crates/fabro-store/src/keys.rs @@ -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 { @@ -48,8 +48,8 @@ fn parse_seq(key: &str, prefix: &str) -> Option { 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() + )) + ); + } }