use crate::domain::model::entity_ref::EntityRef;
use crate::domain::model::record_ref::{DecisionRecordRef, IssueRef};
fn cross_version_hint(schema_version: u32, raw: &str) -> anyhow::Error {
anyhow::anyhow!(
"id '{raw}' does not match the v{schema_version} schema shape; \
run 'cartu migrate' to bring the workspace to the current version"
)
}
pub fn parse_entity_ref_for_schema(raw: &str, schema_version: u32) -> anyhow::Result<EntityRef> {
match schema_version {
0..=3 => EntityRef::parse_v3(raw).map_err(|_| cross_version_hint(schema_version, raw)),
4 => EntityRef::parse_v4(raw).or_else(|_| EntityRef::parse_v3(raw)),
_ => EntityRef::parse_any(raw),
}
}
pub fn parse_issue_ref_for_schema(raw: &str, schema_version: u32) -> anyhow::Result<IssueRef> {
match schema_version {
0..=3 => IssueRef::parse_v3(raw).map_err(|_| cross_version_hint(schema_version, raw)),
4 => IssueRef::parse_v4(raw).or_else(|_| IssueRef::parse_v3(raw)),
_ => IssueRef::parse_any(raw),
}
}
pub fn parse_decision_record_ref_for_schema(
raw: &str,
schema_version: u32,
) -> anyhow::Result<DecisionRecordRef> {
match schema_version {
0..=3 => {
DecisionRecordRef::parse_v3(raw).map_err(|_| cross_version_hint(schema_version, raw))
}
4 => DecisionRecordRef::parse_v4(raw).or_else(|_| DecisionRecordRef::parse_v3(raw)),
_ => DecisionRecordRef::parse_any(raw),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn v3_workspace_accepts_legacy_id() {
assert!(parse_entity_ref_for_schema("ISSUE-0042", 3).is_ok());
}
#[test]
fn v3_workspace_refuses_tsid_id_with_migrate_hint() {
let err = parse_entity_ref_for_schema("ISSUE-0DCT3MKW5T2K0", 3).unwrap_err();
let msg = err.to_string();
assert!(msg.contains("v3"), "got: {msg}");
assert!(msg.contains("cartu migrate"), "got: {msg}");
}
#[test]
fn v4_workspace_accepts_tsid_id() {
assert!(parse_entity_ref_for_schema("ISSUE-0DCT3MKW5T2K0", 4).is_ok());
}
#[test]
fn v4_workspace_tolerates_legacy_id_at_canonical_position() {
assert!(parse_entity_ref_for_schema("ISSUE-0042", 4).is_ok());
}
#[test]
fn issue_ref_parser_routes_by_version() {
assert!(parse_issue_ref_for_schema("ISSUE-0042", 3).is_ok());
assert!(parse_issue_ref_for_schema("ISSUE-0DCT3MKW5T2K0", 4).is_ok());
assert!(parse_issue_ref_for_schema("ISSUE-0DCT3MKW5T2K0", 3).is_err());
}
#[test]
fn decision_record_ref_parser_routes_by_version() {
assert!(parse_decision_record_ref_for_schema("ADR-0001", 3).is_ok());
assert!(parse_decision_record_ref_for_schema("ADR-0DCT3MKW5T2K0", 4).is_ok());
assert!(parse_decision_record_ref_for_schema("ADR-0DCT3MKW5T2K0", 3).is_err());
}
#[test]
fn unknown_future_version_falls_back_to_tolerant() {
assert!(parse_entity_ref_for_schema("ISSUE-0042", 99).is_ok());
assert!(parse_entity_ref_for_schema("ISSUE-0DCT3MKW5T2K0", 99).is_ok());
}
}