use std::fmt;
use std::str::FromStr;
use super::id::{MemoryId, ParseIdError};
const ACCEPTED_PROVENANCE_SCHEMES: &str = "cass-session, file, ee-mem, http, https, agent-mail, manual, bench-run, git-sha, flamegraph, ee-reflect, journal, sec-filing, earnings-call, analyst-note, case, pacer, westlaw, ga4, mixpanel, campaign, interview, linear, notion, cve, mitre, incident, pubmed, guideline, emr, crm, salesforce, gong";
const REGISTERED_EXTERNAL_SCHEMES: &[&str] = &[
"manual",
"bench-run",
"git-sha",
"flamegraph",
"ee-reflect",
"journal",
"sec-filing",
"earnings-call",
"analyst-note",
"case",
"pacer",
"westlaw",
"ga4",
"mixpanel",
"campaign",
"interview",
"linear",
"notion",
"cve",
"mitre",
"incident",
"pubmed",
"guideline",
"emr",
"crm",
"salesforce",
"gong",
];
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct LineSpan {
pub start: u64,
pub end: Option<u64>,
}
impl LineSpan {
pub fn single(start: u64) -> Result<Self, ProvenanceUriError> {
if start == 0 {
return Err(ProvenanceUriError::ZeroLineNumber);
}
Ok(Self { start, end: None })
}
pub fn range(start: u64, end: u64) -> Result<Self, ProvenanceUriError> {
if start == 0 || end == 0 {
return Err(ProvenanceUriError::ZeroLineNumber);
}
if end < start {
return Err(ProvenanceUriError::InvertedLineRange { start, end });
}
Ok(Self {
start,
end: Some(end),
})
}
fn fragment(&self) -> String {
match self.end {
Some(end) if end != self.start => format!("L{}-{}", self.start, end),
_ => format!("L{}", self.start),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ProvenanceUri {
CassSession {
session: String,
span: Option<LineSpan>,
},
File {
path: String,
span: Option<LineSpan>,
},
EeMemory(MemoryId),
Web { url: String },
AgentMail {
thread: String,
message: Option<String>,
},
External { scheme: String, body: String },
}
impl ProvenanceUri {
#[must_use]
pub fn scheme(&self) -> &str {
match self {
Self::CassSession { .. } => "cass-session",
Self::File { .. } => "file",
Self::EeMemory(_) => "ee-mem",
Self::Web { url } => {
if url.starts_with("https://") {
"https"
} else {
"http"
}
}
Self::AgentMail { .. } => "agent-mail",
Self::External { scheme, .. } => scheme.as_str(),
}
}
}
impl fmt::Display for ProvenanceUri {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::CassSession { session, span } => {
formatter.write_str("cass-session://")?;
formatter.write_str(session)?;
if let Some(span) = span {
formatter.write_str("#")?;
formatter.write_str(&span.fragment())?;
}
Ok(())
}
Self::File { path, span } => {
formatter.write_str("file://")?;
formatter.write_str(path)?;
if let Some(span) = span {
formatter.write_str("#")?;
formatter.write_str(&span.fragment())?;
}
Ok(())
}
Self::EeMemory(id) => {
formatter.write_str("ee-mem://")?;
fmt::Display::fmt(id, formatter)
}
Self::Web { url } => formatter.write_str(url),
Self::AgentMail { thread, message } => {
formatter.write_str("agent-mail://")?;
formatter.write_str(thread)?;
if let Some(message) = message {
formatter.write_str("/")?;
formatter.write_str(message)?;
}
Ok(())
}
Self::External { scheme, body } => {
formatter.write_str(scheme)?;
formatter.write_str("://")?;
formatter.write_str(body)
}
}
}
}
impl FromStr for ProvenanceUri {
type Err = ProvenanceUriError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
let trimmed = input.trim();
if trimmed.is_empty() {
return Err(ProvenanceUriError::Empty);
}
let lower_scheme_end = match trimmed.find("://") {
Some(index) => index,
None => {
return Err(ProvenanceUriError::MissingScheme {
input: input.to_owned(),
});
}
};
let raw_scheme = &trimmed[..lower_scheme_end];
let scheme = raw_scheme.to_ascii_lowercase();
let body = &trimmed[lower_scheme_end + "://".len()..];
match scheme.as_str() {
"cass-session" => parse_cass_session(input, body),
"file" => parse_file(input, body),
"ee-mem" => parse_ee_memory(input, body),
"http" | "https" => parse_web(input, &scheme, body),
"agent-mail" => parse_agent_mail(input, body),
"journal" => parse_journal(input, body),
external_scheme if is_registered_external_scheme(external_scheme) => {
parse_external(input, &scheme, body)
}
_ => Err(ProvenanceUriError::UnknownScheme {
input: input.to_owned(),
scheme: raw_scheme.to_owned(),
}),
}
}
}
fn parse_journal(input: &str, body: &str) -> Result<ProvenanceUri, ProvenanceUriError> {
if body.is_empty() {
return Err(ProvenanceUriError::EmptyBody {
input: input.to_owned(),
scheme: "journal",
});
}
let uuid = body
.strip_prefix("jrn_")
.and_then(|value| uuid::Uuid::parse_str(value).ok())
.filter(|value| value.get_version_num() == 7)
.ok_or_else(|| ProvenanceUriError::InvalidJournalId {
input: input.to_owned(),
})?;
Ok(ProvenanceUri::External {
scheme: "journal".to_owned(),
body: format!("jrn_{uuid}"),
})
}
fn parse_cass_session(input: &str, body: &str) -> Result<ProvenanceUri, ProvenanceUriError> {
if body.is_empty() {
return Err(ProvenanceUriError::EmptyBody {
input: input.to_owned(),
scheme: "cass-session",
});
}
let (session, span) = split_fragment(input, body, "cass-session")?;
Ok(ProvenanceUri::CassSession {
session: session.to_owned(),
span,
})
}
fn parse_file(input: &str, body: &str) -> Result<ProvenanceUri, ProvenanceUriError> {
if body.is_empty() {
return Err(ProvenanceUriError::EmptyBody {
input: input.to_owned(),
scheme: "file",
});
}
let (path, span) = split_fragment(input, body, "file")?;
Ok(ProvenanceUri::File {
path: path.to_owned(),
span,
})
}
fn parse_ee_memory(input: &str, body: &str) -> Result<ProvenanceUri, ProvenanceUriError> {
if body.is_empty() {
return Err(ProvenanceUriError::EmptyBody {
input: input.to_owned(),
scheme: "ee-mem",
});
}
let id = MemoryId::from_str(body).map_err(|source| ProvenanceUriError::InvalidMemoryId {
input: input.to_owned(),
source,
})?;
Ok(ProvenanceUri::EeMemory(id))
}
fn contains_control_or_whitespace(value: &str) -> bool {
value
.chars()
.any(|character| character.is_ascii_control() || character.is_whitespace())
}
fn parse_web(input: &str, scheme: &str, body: &str) -> Result<ProvenanceUri, ProvenanceUriError> {
if body.is_empty() {
return Err(ProvenanceUriError::EmptyBody {
input: input.to_owned(),
scheme: if scheme == "https" { "https" } else { "http" },
});
}
if !body.contains(|character: char| !character.is_whitespace()) {
return Err(ProvenanceUriError::EmptyBody {
input: input.to_owned(),
scheme: if scheme == "https" { "https" } else { "http" },
});
}
if contains_control_or_whitespace(body) {
return Err(ProvenanceUriError::InvalidWebBody {
input: input.to_owned(),
});
}
let authority_end = body.find(['/', '?', '#']).unwrap_or(body.len());
if authority_end == 0 {
return Err(ProvenanceUriError::InvalidWebBody {
input: input.to_owned(),
});
}
Ok(ProvenanceUri::Web {
url: format!("{scheme}://{body}"),
})
}
fn parse_agent_mail(input: &str, body: &str) -> Result<ProvenanceUri, ProvenanceUriError> {
if body.is_empty() {
return Err(ProvenanceUriError::EmptyBody {
input: input.to_owned(),
scheme: "agent-mail",
});
}
if contains_control_or_whitespace(body) {
return Err(ProvenanceUriError::InvalidAgentMail {
input: input.to_owned(),
});
}
if let Some((thread, message)) = body.split_once('/') {
if thread.is_empty() || message.is_empty() {
return Err(ProvenanceUriError::InvalidAgentMail {
input: input.to_owned(),
});
}
Ok(ProvenanceUri::AgentMail {
thread: thread.to_owned(),
message: Some(message.to_owned()),
})
} else {
Ok(ProvenanceUri::AgentMail {
thread: body.to_owned(),
message: None,
})
}
}
fn parse_external(
input: &str,
scheme: &str,
body: &str,
) -> Result<ProvenanceUri, ProvenanceUriError> {
if body.is_empty() || !body.contains(|character: char| !character.is_whitespace()) {
return Err(ProvenanceUriError::EmptyBody {
input: input.to_owned(),
scheme: external_scheme_name(scheme),
});
}
if body.contains(|character: char| character.is_ascii_control()) {
return Err(ProvenanceUriError::InvalidExternalBody {
input: input.to_owned(),
});
}
Ok(ProvenanceUri::External {
scheme: scheme.to_owned(),
body: body.to_owned(),
})
}
fn is_registered_external_scheme(scheme: &str) -> bool {
REGISTERED_EXTERNAL_SCHEMES.contains(&scheme)
}
fn external_scheme_name(scheme: &str) -> &'static str {
REGISTERED_EXTERNAL_SCHEMES
.iter()
.copied()
.find(|candidate| *candidate == scheme)
.unwrap_or("external")
}
fn split_fragment<'a>(
input: &str,
body: &'a str,
scheme: &'static str,
) -> Result<(&'a str, Option<LineSpan>), ProvenanceUriError> {
if let Some((value, fragment)) = body.split_once('#') {
if value.is_empty() {
return Err(ProvenanceUriError::EmptyBody {
input: input.to_owned(),
scheme,
});
}
let span = parse_line_fragment(input, fragment)?;
Ok((value, Some(span)))
} else {
Ok((body, None))
}
}
fn parse_line_fragment(input: &str, fragment: &str) -> Result<LineSpan, ProvenanceUriError> {
let rest = match fragment.strip_prefix('L') {
Some(value) => value,
None => {
return Err(ProvenanceUriError::InvalidFragment {
input: input.to_owned(),
fragment: fragment.to_owned(),
});
}
};
if let Some((start_text, end_text)) = rest.split_once('-') {
let start = parse_line_number(input, start_text)?;
let end_text = end_text.strip_prefix('L').unwrap_or(end_text);
let end = parse_line_number(input, end_text)?;
LineSpan::range(start, end)
} else {
let start = parse_line_number(input, rest)?;
LineSpan::single(start)
}
}
fn parse_line_number(input: &str, text: &str) -> Result<u64, ProvenanceUriError> {
if text.is_empty() {
return Err(ProvenanceUriError::InvalidFragment {
input: input.to_owned(),
fragment: text.to_owned(),
});
}
text.parse::<u64>()
.map_err(|_| ProvenanceUriError::InvalidFragment {
input: input.to_owned(),
fragment: text.to_owned(),
})
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ProvenanceUriError {
Empty,
MissingScheme { input: String },
UnknownScheme { input: String, scheme: String },
EmptyBody { input: String, scheme: &'static str },
InvalidWebBody { input: String },
InvalidAgentMail { input: String },
InvalidJournalId { input: String },
InvalidExternalBody { input: String },
InvalidFragment { input: String, fragment: String },
ZeroLineNumber,
InvertedLineRange { start: u64, end: u64 },
InvalidMemoryId { input: String, source: ParseIdError },
}
impl fmt::Display for ProvenanceUriError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => formatter.write_str("provenance URI cannot be empty"),
Self::MissingScheme { input } => write!(
formatter,
"provenance URI `{input}` is missing a `<scheme>://` prefix"
),
Self::UnknownScheme { input, scheme } => write!(
formatter,
"unknown provenance scheme `{scheme}` in `{input}`; expected one of {ACCEPTED_PROVENANCE_SCHEMES}"
),
Self::EmptyBody { input, scheme } => write!(
formatter,
"provenance URI `{input}` has an empty body for scheme `{scheme}`"
),
Self::InvalidWebBody { input } => write!(
formatter,
"web provenance URI `{input}` contains whitespace or control characters"
),
Self::InvalidAgentMail { input } => write!(
formatter,
"agent-mail URI `{input}` must be `agent-mail://<thread>` or `agent-mail://<thread>/<message>`"
),
Self::InvalidJournalId { input } => write!(
formatter,
"journal provenance URI `{input}` must be `journal://jrn_<uuidv7>`"
),
Self::InvalidExternalBody { input } => write!(
formatter,
"external provenance URI `{input}` contains control characters"
),
Self::InvalidFragment { input, fragment } => write!(
formatter,
"provenance URI `{input}` has an invalid line fragment `{fragment}`; expected `L<n>` or `L<n>-<m>`"
),
Self::ZeroLineNumber => formatter.write_str("line numbers must be 1 or greater"),
Self::InvertedLineRange { start, end } => write!(
formatter,
"line range {start}-{end} is inverted; end must be >= start"
),
Self::InvalidMemoryId { input, source } => write!(
formatter,
"ee-mem URI `{input}` has an invalid memory id: {source}"
),
}
}
}
impl std::error::Error for ProvenanceUriError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::InvalidMemoryId { source, .. } => Some(source),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::super::id::MemoryId;
use super::{LineSpan, ProvenanceUri, ProvenanceUriError};
fn must_parse(input: &str) -> ProvenanceUri {
match ProvenanceUri::from_str(input) {
Ok(value) => value,
Err(error) => panic!("expected valid URI for `{input}`, got {error:?}"),
}
}
fn must_fail(input: &str) -> ProvenanceUriError {
match ProvenanceUri::from_str(input) {
Ok(value) => panic!("expected error for `{input}`, got Ok({value:?})"),
Err(error) => error,
}
}
#[test]
fn cass_session_round_trip_without_span() {
let parsed = must_parse("cass-session://7f4ec0ff");
match &parsed {
ProvenanceUri::CassSession { session, span } => {
assert_eq!(session, "7f4ec0ff");
assert!(span.is_none());
}
other => panic!("wrong variant: {other:?}"),
}
assert_eq!(parsed.to_string(), "cass-session://7f4ec0ff");
}
#[test]
fn cass_session_with_single_line_span() {
let parsed = must_parse("cass-session://abc#L42");
match &parsed {
ProvenanceUri::CassSession { session, span } => {
assert_eq!(session, "abc");
assert_eq!(
*span,
Some(LineSpan {
start: 42,
end: None
})
);
}
other => panic!("wrong variant: {other:?}"),
}
assert_eq!(parsed.to_string(), "cass-session://abc#L42");
}
#[test]
fn cass_session_with_range_span() {
let parsed = must_parse("cass-session://abc#L40-50");
match &parsed {
ProvenanceUri::CassSession { span, .. } => {
assert_eq!(
*span,
Some(LineSpan {
start: 40,
end: Some(50)
})
);
}
other => panic!("wrong variant: {other:?}"),
}
assert_eq!(parsed.to_string(), "cass-session://abc#L40-50");
}
#[test]
fn cass_session_with_github_style_range_span_canonicalizes() {
let parsed = must_parse("cass-session://abc#L40-L50");
match &parsed {
ProvenanceUri::CassSession { span, .. } => {
assert_eq!(
*span,
Some(LineSpan {
start: 40,
end: Some(50)
})
);
}
other => panic!("wrong variant: {other:?}"),
}
assert_eq!(parsed.to_string(), "cass-session://abc#L40-50");
}
#[test]
fn file_with_path_and_span() {
let parsed = must_parse("file:///etc/ee/config.toml#L7-12");
match &parsed {
ProvenanceUri::File { path, span } => {
assert_eq!(path, "/etc/ee/config.toml");
assert_eq!(
*span,
Some(LineSpan {
start: 7,
end: Some(12)
})
);
}
other => panic!("wrong variant: {other:?}"),
}
assert_eq!(parsed.to_string(), "file:///etc/ee/config.toml#L7-12");
}
#[test]
fn ee_memory_round_trips_through_full_id() {
let id = MemoryId::from_uuid(uuid_with_seed(9));
let rendered = format!("ee-mem://{id}");
let parsed = must_parse(&rendered);
match &parsed {
ProvenanceUri::EeMemory(parsed_id) => assert_eq!(*parsed_id, id),
other => panic!("wrong variant: {other:?}"),
}
assert_eq!(parsed.to_string(), rendered);
}
#[test]
fn ee_memory_rejects_invalid_id() {
let err = must_fail("ee-mem://not-a-real-id");
assert!(matches!(err, ProvenanceUriError::InvalidMemoryId { .. }));
}
#[test]
fn ee_memory_rejects_wrong_prefix() {
let bad = format!("ee-mem://{}", "wsp_00000000000000000000000000");
let err = must_fail(&bad);
match err {
ProvenanceUriError::InvalidMemoryId { source, .. } => {
let rendered = source.to_string();
assert!(rendered.contains("expected `mem`"));
}
other => panic!("wrong variant: {other:?}"),
}
}
#[test]
fn web_round_trip_for_https_and_http() {
for url in [
"https://example.com/",
"http://example.com/path?x=1",
"https://docs.example.com/page#section",
] {
let parsed = must_parse(url);
assert!(matches!(parsed, ProvenanceUri::Web { .. }));
assert_eq!(parsed.to_string(), url);
}
}
#[test]
fn scheme_case_is_canonicalized() {
for (input, expected) in [
("HTTPS://example.com/path", "https://example.com/path"),
(
"FILE:///etc/ee/config.toml#L7",
"file:///etc/ee/config.toml#L7",
),
(
"Git-SHA://9af3c21-pre-revert",
"git-sha://9af3c21-pre-revert",
),
(
"EE-REFLECT://reflect_req_0123456789abcdef",
"ee-reflect://reflect_req_0123456789abcdef",
),
("AGENT-MAIL://br-123/msg-42", "agent-mail://br-123/msg-42"),
] {
let parsed = must_parse(input);
assert_eq!(parsed.to_string(), expected);
}
}
#[test]
fn web_rejects_whitespace_or_controls() {
for bad in [
"https://exam ple.com",
"http://example.com\u{0001}/x",
"https://example.com\u{00a0}/x",
] {
let err = must_fail(bad);
assert!(matches!(err, ProvenanceUriError::InvalidWebBody { .. }));
}
}
#[test]
fn web_rejects_missing_authority() {
for bad in ["https:///path", "http://?q=1", "https://#section"] {
let err = must_fail(bad);
assert!(
matches!(err, ProvenanceUriError::InvalidWebBody { .. }),
"expected InvalidWebBody for `{bad}`, got {err:?}"
);
}
}
#[test]
fn agent_mail_thread_only_round_trips() {
let parsed = must_parse("agent-mail://onboarding-2026-04-29");
match &parsed {
ProvenanceUri::AgentMail { thread, message } => {
assert_eq!(thread, "onboarding-2026-04-29");
assert!(message.is_none());
}
other => panic!("wrong variant: {other:?}"),
}
assert_eq!(parsed.to_string(), "agent-mail://onboarding-2026-04-29");
}
#[test]
fn agent_mail_thread_and_message_round_trips() {
let parsed = must_parse("agent-mail://br-123/msg-42");
match &parsed {
ProvenanceUri::AgentMail { thread, message } => {
assert_eq!(thread, "br-123");
assert_eq!(message.as_deref(), Some("msg-42"));
}
other => panic!("wrong variant: {other:?}"),
}
assert_eq!(parsed.to_string(), "agent-mail://br-123/msg-42");
}
#[test]
fn agent_mail_rejects_empty_thread_or_message() {
for bad in ["agent-mail:///msg", "agent-mail://thread/"] {
let err = must_fail(bad);
assert!(matches!(err, ProvenanceUriError::InvalidAgentMail { .. }));
}
}
#[test]
fn agent_mail_rejects_whitespace_or_controls() {
for bad in [
"agent-mail://thread name",
"agent-mail://thread/message id",
"agent-mail://thread\nname",
"agent-mail://thread/msg\u{00a0}id",
"agent-mail://thread\u{0001}/msg",
] {
let err = must_fail(bad);
assert!(
matches!(err, ProvenanceUriError::InvalidAgentMail { .. }),
"expected InvalidAgentMail for `{bad}`, got {err:?}"
);
}
}
#[test]
fn external_evidence_schemes_round_trip() {
for input in [
"manual://lived-audit/2026-06-02",
"bench-run://2026-09-12T14:23/oltp-mixed-small-n",
"git-sha://9af3c21-pre-revert",
"flamegraph://artifacts/9af3c21/cpu-prof.svg",
"ee-reflect://reflect_req_0123456789abcdef",
"journal://jrn_018ff8dc-3d85-7cc0-a6fd-a2d479e6d401",
] {
let parsed = must_parse(input);
match &parsed {
ProvenanceUri::External { scheme, body } => {
let (expected_scheme, expected_body) =
input.split_once("://").expect("fixture has scheme");
assert_eq!(scheme, expected_scheme);
assert_eq!(body, expected_body);
}
other => panic!("wrong variant for `{input}`: {other:?}"),
}
assert_eq!(parsed.to_string(), input);
}
}
#[test]
fn readme_domain_external_schemes_round_trip() {
for input in [
"sec-filing://AAPL/10-K/2026",
"earnings-call://AAPL/2026-Q1",
"analyst-note://firm/report-123",
"case://smith-v-jones",
"pacer://docket/1-23-cv-456",
"westlaw://2026-WL-123456",
"ga4://property/123/session/456",
"mixpanel://project/event-789",
"campaign://spring-launch/variant-a",
"interview://participant-42",
"linear://TEAM-123",
"notion://workspace/page-abc",
"cve://CVE-2026-1234",
"mitre://attack/T1059",
"incident://IR-2026-06-20",
"pubmed://12345678",
"guideline://org/topic/version",
"emr://patient-redacted/encounter-1",
"crm://account/acme",
"salesforce://opportunity/006xx000004",
"gong://call/abc123",
] {
let parsed = must_parse(input);
match &parsed {
ProvenanceUri::External { scheme, body } => {
let (expected_scheme, expected_body) =
input.split_once("://").expect("fixture has scheme");
assert_eq!(scheme, expected_scheme);
assert_eq!(body, expected_body);
}
other => panic!("wrong variant for `{input}`: {other:?}"),
}
assert_eq!(parsed.to_string(), input);
}
}
#[test]
fn journal_provenance_requires_prefixed_uuidv7() {
for input in [
"journal://018ff8dc-3d85-7cc0-a6fd-a2d479e6d401",
"journal://jrn_550e8400-e29b-41d4-a716-446655440000",
"journal://jrn_not-a-uuid",
] {
let error = must_fail(input);
assert!(
matches!(error, ProvenanceUriError::InvalidJournalId { .. }),
"expected InvalidJournalId for `{input}`, got {error:?}"
);
}
}
#[test]
fn external_evidence_rejects_empty_or_control_body() {
for (input, expected_scheme) in [
("manual://", "manual"),
("bench-run://", "bench-run"),
("git-sha://", "git-sha"),
("flamegraph://", "flamegraph"),
("ee-reflect://", "ee-reflect"),
("journal://", "journal"),
("sec-filing://", "sec-filing"),
("pubmed://", "pubmed"),
("crm://", "crm"),
] {
let err = must_fail(input);
match err {
ProvenanceUriError::EmptyBody { scheme, .. } => {
assert_eq!(scheme, expected_scheme);
}
other => panic!("wrong variant for `{input}`: {other:?}"),
}
}
let err = must_fail("manual://bad\u{0001}body");
assert!(matches!(
err,
ProvenanceUriError::InvalidExternalBody { .. }
));
}
#[test]
fn empty_input_returns_typed_error() {
for bad in ["", " ", "\t\n"] {
let err = must_fail(bad);
assert!(matches!(err, ProvenanceUriError::Empty));
}
}
#[test]
fn missing_scheme_returns_typed_error() {
let err = must_fail("/etc/ee/config.toml");
assert!(matches!(err, ProvenanceUriError::MissingScheme { .. }));
}
#[test]
fn unknown_scheme_returns_typed_error_with_scheme_name() {
let err = must_fail("gopher://example.com/path");
match err {
ProvenanceUriError::UnknownScheme { scheme, .. } => assert_eq!(scheme, "gopher"),
other => panic!("wrong variant: {other:?}"),
}
}
#[test]
fn empty_body_returns_typed_error_per_scheme() {
for (input, expected_scheme) in [
("cass-session://", "cass-session"),
("cass-session://#L1", "cass-session"),
("file://", "file"),
("file://#L1", "file"),
("ee-mem://", "ee-mem"),
("https://", "https"),
("http://", "http"),
("agent-mail://", "agent-mail"),
("manual://", "manual"),
("bench-run://", "bench-run"),
("git-sha://", "git-sha"),
("flamegraph://", "flamegraph"),
("ee-reflect://", "ee-reflect"),
("journal://", "journal"),
] {
let err = must_fail(input);
match err {
ProvenanceUriError::EmptyBody { scheme, .. } => assert_eq!(scheme, expected_scheme),
other => panic!("wrong variant for `{input}`: {other:?}"),
}
}
}
#[test]
fn invalid_line_fragment_returns_typed_error() {
for bad in [
"file:///x#42", "file:///x#L", "file:///x#L-", "file:///x#Lzz", "file:///x#L1-", "file:///x#L-5", ] {
let err = must_fail(bad);
assert!(
matches!(err, ProvenanceUriError::InvalidFragment { .. }),
"expected InvalidFragment for `{bad}`, got {err:?}"
);
}
}
#[test]
fn line_span_constructors_reject_zero_and_inverted_ranges() {
assert!(matches!(
LineSpan::single(0),
Err(ProvenanceUriError::ZeroLineNumber)
));
assert!(matches!(
LineSpan::range(0, 5),
Err(ProvenanceUriError::ZeroLineNumber)
));
assert!(matches!(
LineSpan::range(10, 5),
Err(ProvenanceUriError::InvertedLineRange { .. })
));
}
#[test]
fn line_span_collapses_equal_endpoints_to_single_form() {
let parsed = must_parse("file:///x#L7-7");
assert_eq!(parsed.to_string(), "file:///x#L7");
}
#[test]
fn input_is_trimmed_before_parsing() {
let parsed = must_parse(" https://example.com/ ");
assert_eq!(parsed.to_string(), "https://example.com/");
}
#[test]
fn scheme_method_returns_canonical_name() {
assert_eq!(must_parse("cass-session://x").scheme(), "cass-session");
assert_eq!(must_parse("file:///x").scheme(), "file");
assert_eq!(must_parse("https://example.com/").scheme(), "https");
assert_eq!(must_parse("http://example.com/").scheme(), "http");
assert_eq!(must_parse("agent-mail://thread").scheme(), "agent-mail");
assert_eq!(must_parse("manual://agent-note").scheme(), "manual");
assert_eq!(must_parse("bench-run://run-1").scheme(), "bench-run");
assert_eq!(must_parse("git-sha://abcdef0").scheme(), "git-sha");
assert_eq!(
must_parse("flamegraph://artifact.svg").scheme(),
"flamegraph"
);
assert_eq!(
must_parse("ee-reflect://reflect_req_abcdef").scheme(),
"ee-reflect"
);
assert_eq!(
must_parse("journal://jrn_018ff8dc-3d85-7cc0-a6fd-a2d479e6d401").scheme(),
"journal"
);
assert_eq!(
must_parse("sec-filing://AAPL/10-K/2026").scheme(),
"sec-filing"
);
assert_eq!(must_parse("pubmed://12345678").scheme(), "pubmed");
assert_eq!(
must_parse("salesforce://opportunity/006").scheme(),
"salesforce"
);
}
fn uuid_with_seed(seed: u8) -> uuid::Uuid {
let mut bytes = [0u8; 16];
bytes[0] = seed;
bytes[1] = 0x77;
bytes[6] = 0x70 | (bytes[6] & 0x0F);
bytes[8] = 0x80 | (bytes[8] & 0x3F);
uuid::Uuid::from_bytes(bytes)
}
}