pmcp_workbook_runtime/range_ref.rs
1//! The owned, structured A1 range reference [`RangeRef`] + the canonical cell-key
2//! helper [`cell_key`] — RELOCATED into `workbook-runtime` (Phase 11, Plan 05).
3//!
4//! These two were originally in `workbook-compiler`'s `ingest/cell_map.rs`, but
5//! the IR (`Expr::Range`) and the runtime executor reach them, and `ingest`
6//! links `umya`. Relocating the TYPE + the key builder here keeps the runtime
7//! crate umya-free; `workbook-compiler` re-exports both so existing
8//! `crate::ingest::RangeRef` / `cell_key` call sites resolve unchanged.
9//!
10//! Owned, serde/schemars-clean: no `umya`/`quick-xml`/`zip`/`pmcp-code-mode`
11//! type appears here.
12
13use serde::{Deserialize, Serialize};
14
15/// Build the canonical manifest cell key `sheet!addr` (e.g. `"1_Inputs!E6"`).
16///
17/// The single home for this shape: the linter, synthesis overlap check, range
18/// resolution, and the runtime executor all key cells the same way, so they
19/// share one helper instead of re-inlining `format!("{}!{}", …)`.
20pub fn cell_key(sheet: &str, addr: &str) -> String {
21 format!("{sheet}!{addr}")
22}
23
24/// A structured, owned A1-range reference — the SINGLE range type reused for
25/// merges, CF ranges, tables, named-range targets, and the IR's `Expr::Range`
26/// (replaces all `(String,String)` tuples; Codex HIGH). For a single-cell range
27/// `start == end`.
28///
29/// Example: `RangeRef { sheet: "1_Inputs", start: "E6", end: "E6" }`.
30#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
31pub struct RangeRef {
32 /// The sheet the range lives on.
33 pub sheet: String,
34 /// The top-left A1 cell of the range (e.g. `"E6"`).
35 pub start: String,
36 /// The bottom-right A1 cell of the range (`== start` for a single cell).
37 pub end: String,
38}
39
40#[cfg(test)]
41mod tests {
42 use super::*;
43
44 #[test]
45 fn cell_key_builds_sheet_bang_addr() {
46 assert_eq!(cell_key("2_Constants", "C17"), "2_Constants!C17");
47 }
48
49 #[test]
50 fn range_ref_round_trips_through_serde() {
51 let r = RangeRef {
52 sheet: "5_Quantities".to_string(),
53 start: "B2".to_string(),
54 end: "B10".to_string(),
55 };
56 let v = serde_json::to_value(&r).expect("serialize");
57 assert_eq!(v["sheet"], "5_Quantities");
58 assert_eq!(v["start"], "B2");
59 assert_eq!(v["end"], "B10");
60 let back: RangeRef = serde_json::from_value(v).expect("deserialize");
61 assert_eq!(r, back);
62 }
63}