Skip to main content

blockworx/doc_ng/
paths.rs

1//! The register catalog: every writable path in the document, flattened and
2//! grouped by the value type its register holds. 1:1 with the update
3//! vocabularies in `operands.rs`; the `path()` impls here are the seam the
4//! compiler checks — an update variant without a path does not build.
5
6use crate::doc_ng::operands::{
7    BlockUpdate, CommentUpdate, DocumentUpdate, ImageUpdate, LabelUpdate, PinUpdate,
8    RouteLabelUpdate, RouteUpdate, TextUpdate,
9};
10
11#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
12pub enum StringPaths {
13    DocumentName,
14    BlockTitleName,
15    BlockTypeLabelName,
16    PinName,
17    PinTypeName,
18    PinTag,
19    RouteName,
20    TextText,
21    CommentTitleName,
22}
23
24#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
25pub enum BoolPaths {
26    BlockLocked,
27    BlockTitleHidden,
28    BlockTypeLabelHidden,
29    PinTagHidden,
30    PinFlipLR,
31    CommentTitleHidden,
32}
33
34#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
35pub enum FracValPaths {
36    BlockTitleOffset,
37    BlockTypeLabelOffset,
38    RouteLabelPos,
39    CommentTitleOffset,
40}
41
42#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
43pub enum LabelSidePaths {
44    BlockTitleSide,
45    BlockTypeLabelSide,
46    CommentTitleSide,
47}
48
49#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
50pub enum BlockIdPaths {
51    BlockParent,
52    PinOwner,
53    RouteOwner,
54    TextOwner,
55    CommentOwner,
56    ImageOwner,
57}
58
59#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
60pub enum RouteIdPaths {
61    RouteLabelOwner,
62}
63
64#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
65pub enum RolePaths {
66    PinPinAccent,
67    PinPortAccent,
68    PinPortPinAccent,
69    RouteRole,
70    TextRole,
71    CommentRole,
72}
73
74#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
75pub enum PinDirPaths {
76    PinDir,
77}
78
79#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
80pub enum GridRectPaths {
81    BlockRect,
82    PinRect,
83    CommentRect,
84}
85
86#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
87pub enum GridPointPaths {
88    TextPos,
89}
90
91#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
92pub enum ScreenRectPaths {
93    ImageRect,
94}
95
96#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
97pub enum IconPaths {
98    BlockIcon,
99}
100
101#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
102pub enum AssetHashPaths {
103    ImageAsset,
104}
105
106#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
107pub enum WaypointsPaths {
108    RouteWaypoints,
109}
110
111/// One register, named — the union of the typed groups, for keys that must
112/// span value types (a commit builder's map, a collision report).
113#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
114pub enum Path {
115    String(StringPaths),
116    Bool(BoolPaths),
117    FracVal(FracValPaths),
118    LabelSide(LabelSidePaths),
119    BlockId(BlockIdPaths),
120    RouteId(RouteIdPaths),
121    Role(RolePaths),
122    PinDir(PinDirPaths),
123    GridRect(GridRectPaths),
124    GridPoint(GridPointPaths),
125    ScreenRect(ScreenRectPaths),
126    Icon(IconPaths),
127    AssetHash(AssetHashPaths),
128    Waypoints(WaypointsPaths),
129}
130
131impl DocumentUpdate {
132    pub fn path(&self) -> Path {
133        match self {
134            DocumentUpdate::Name(_) => Path::String(StringPaths::DocumentName),
135        }
136    }
137}
138
139impl BlockUpdate {
140    pub fn path(&self) -> Path {
141        match self {
142            BlockUpdate::Parent(_) => Path::BlockId(BlockIdPaths::BlockParent),
143            BlockUpdate::Rect(_) => Path::GridRect(GridRectPaths::BlockRect),
144            BlockUpdate::Locked(_) => Path::Bool(BoolPaths::BlockLocked),
145            BlockUpdate::Title(label) => match label {
146                LabelUpdate::Name(_) => Path::String(StringPaths::BlockTitleName),
147                LabelUpdate::Side(_) => Path::LabelSide(LabelSidePaths::BlockTitleSide),
148                LabelUpdate::Offset(_) => Path::FracVal(FracValPaths::BlockTitleOffset),
149                LabelUpdate::Hidden(_) => Path::Bool(BoolPaths::BlockTitleHidden),
150            },
151            BlockUpdate::TypeLabel(label) => match label {
152                LabelUpdate::Name(_) => Path::String(StringPaths::BlockTypeLabelName),
153                LabelUpdate::Side(_) => Path::LabelSide(LabelSidePaths::BlockTypeLabelSide),
154                LabelUpdate::Offset(_) => Path::FracVal(FracValPaths::BlockTypeLabelOffset),
155                LabelUpdate::Hidden(_) => Path::Bool(BoolPaths::BlockTypeLabelHidden),
156            },
157            BlockUpdate::Icon(_) => Path::Icon(IconPaths::BlockIcon),
158        }
159    }
160}
161
162impl PinUpdate {
163    pub fn path(&self) -> Path {
164        match self {
165            PinUpdate::Owner(_) => Path::BlockId(BlockIdPaths::PinOwner),
166            PinUpdate::Name(_) => Path::String(StringPaths::PinName),
167            PinUpdate::TypeName(_) => Path::String(StringPaths::PinTypeName),
168            PinUpdate::Tag(_) => Path::String(StringPaths::PinTag),
169            PinUpdate::TagHidden(_) => Path::Bool(BoolPaths::PinTagHidden),
170            PinUpdate::Rect(_) => Path::GridRect(GridRectPaths::PinRect),
171            PinUpdate::Dir(_) => Path::PinDir(PinDirPaths::PinDir),
172            PinUpdate::PinAccent(_) => Path::Role(RolePaths::PinPinAccent),
173            PinUpdate::PortAccent(_) => Path::Role(RolePaths::PinPortAccent),
174            PinUpdate::PortPinAccent(_) => Path::Role(RolePaths::PinPortPinAccent),
175            PinUpdate::FlipLR(_) => Path::Bool(BoolPaths::PinFlipLR),
176        }
177    }
178}
179
180impl RouteUpdate {
181    pub fn path(&self) -> Path {
182        match self {
183            RouteUpdate::Owner(_) => Path::BlockId(BlockIdPaths::RouteOwner),
184            RouteUpdate::Name(_) => Path::String(StringPaths::RouteName),
185            RouteUpdate::Role(_) => Path::Role(RolePaths::RouteRole),
186            RouteUpdate::Waypoints(_) => Path::Waypoints(WaypointsPaths::RouteWaypoints),
187        }
188    }
189}
190
191impl RouteLabelUpdate {
192    pub fn path(&self) -> Path {
193        match self {
194            RouteLabelUpdate::Owner(_) => Path::RouteId(RouteIdPaths::RouteLabelOwner),
195            RouteLabelUpdate::Pos(_) => Path::FracVal(FracValPaths::RouteLabelPos),
196        }
197    }
198}
199
200impl TextUpdate {
201    pub fn path(&self) -> Path {
202        match self {
203            TextUpdate::Owner(_) => Path::BlockId(BlockIdPaths::TextOwner),
204            TextUpdate::Text(_) => Path::String(StringPaths::TextText),
205            TextUpdate::Pos(_) => Path::GridPoint(GridPointPaths::TextPos),
206            TextUpdate::Role(_) => Path::Role(RolePaths::TextRole),
207        }
208    }
209}
210
211impl CommentUpdate {
212    pub fn path(&self) -> Path {
213        match self {
214            CommentUpdate::Owner(_) => Path::BlockId(BlockIdPaths::CommentOwner),
215            CommentUpdate::Rect(_) => Path::GridRect(GridRectPaths::CommentRect),
216            CommentUpdate::Role(_) => Path::Role(RolePaths::CommentRole),
217            CommentUpdate::Title(label) => match label {
218                LabelUpdate::Name(_) => Path::String(StringPaths::CommentTitleName),
219                LabelUpdate::Side(_) => Path::LabelSide(LabelSidePaths::CommentTitleSide),
220                LabelUpdate::Offset(_) => Path::FracVal(FracValPaths::CommentTitleOffset),
221                LabelUpdate::Hidden(_) => Path::Bool(BoolPaths::CommentTitleHidden),
222            },
223        }
224    }
225}
226
227impl ImageUpdate {
228    pub fn path(&self) -> Path {
229        match self {
230            ImageUpdate::Owner(_) => Path::BlockId(BlockIdPaths::ImageOwner),
231            ImageUpdate::Asset(_) => Path::AssetHash(AssetHashPaths::ImageAsset),
232            ImageUpdate::Rect(_) => Path::ScreenRect(ScreenRectPaths::ImageRect),
233        }
234    }
235}
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240    use crate::doc_ng::id::{BlockId, RouteId};
241    use crate::doc_ng::operands::Swap;
242    use std::collections::BTreeSet;
243
244    fn s<T: Default>() -> Swap<T> {
245        Swap {
246            old: T::default(),
247            new: T::default(),
248        }
249    }
250
251    fn s_block() -> Swap<BlockId> {
252        Swap {
253            old: BlockId::NULL,
254            new: BlockId::NULL,
255        }
256    }
257
258    fn s_route() -> Swap<RouteId> {
259        Swap {
260            old: RouteId::NULL,
261            new: RouteId::NULL,
262        }
263    }
264
265    fn label_updates() -> Vec<LabelUpdate> {
266        vec![
267            LabelUpdate::Name(s()),
268            LabelUpdate::Side(s()),
269            LabelUpdate::Offset(s()),
270            LabelUpdate::Hidden(s()),
271        ]
272    }
273
274    /// Every update variant maps to a path, and no two share one: the
275    /// catalog is a bijection over the 44 registers. A new register must
276    /// extend a path enum, a `path()` match, and this count together.
277    #[test]
278    fn the_catalog_is_one_to_one_with_the_update_vocabularies() {
279        let mut paths: Vec<Path> = Vec::new();
280
281        paths.push(DocumentUpdate::Name(s()).path());
282
283        paths.extend(
284            [
285                BlockUpdate::Parent(s_block()),
286                BlockUpdate::Rect(s()),
287                BlockUpdate::Locked(s()),
288                BlockUpdate::Icon(s()),
289            ]
290            .iter()
291            .map(BlockUpdate::path),
292        );
293        paths.extend(
294            label_updates()
295                .into_iter()
296                .map(|w| BlockUpdate::Title(w).path()),
297        );
298        paths.extend(
299            label_updates()
300                .into_iter()
301                .map(|w| BlockUpdate::TypeLabel(w).path()),
302        );
303
304        paths.extend(
305            [
306                PinUpdate::Owner(s_block()),
307                PinUpdate::Name(s()),
308                PinUpdate::TypeName(s()),
309                PinUpdate::Tag(s()),
310                PinUpdate::TagHidden(s()),
311                PinUpdate::Rect(s()),
312                PinUpdate::Dir(s()),
313                PinUpdate::PinAccent(s()),
314                PinUpdate::PortAccent(s()),
315                PinUpdate::PortPinAccent(s()),
316                PinUpdate::FlipLR(s()),
317            ]
318            .iter()
319            .map(PinUpdate::path),
320        );
321
322        paths.extend(
323            [
324                RouteUpdate::Owner(s_block()),
325                RouteUpdate::Name(s()),
326                RouteUpdate::Role(s()),
327                RouteUpdate::Waypoints(s()),
328            ]
329            .iter()
330            .map(RouteUpdate::path),
331        );
332
333        paths.extend(
334            [
335                RouteLabelUpdate::Owner(s_route()),
336                RouteLabelUpdate::Pos(s()),
337            ]
338            .iter()
339            .map(RouteLabelUpdate::path),
340        );
341
342        paths.extend(
343            [
344                TextUpdate::Owner(s_block()),
345                TextUpdate::Text(s()),
346                TextUpdate::Pos(s()),
347                TextUpdate::Role(s()),
348            ]
349            .iter()
350            .map(TextUpdate::path),
351        );
352
353        paths.extend(
354            [
355                CommentUpdate::Owner(s_block()),
356                CommentUpdate::Rect(s()),
357                CommentUpdate::Role(s()),
358            ]
359            .iter()
360            .map(CommentUpdate::path),
361        );
362        paths.extend(
363            label_updates()
364                .into_iter()
365                .map(|w| CommentUpdate::Title(w).path()),
366        );
367
368        paths.extend(
369            [
370                ImageUpdate::Owner(s_block()),
371                ImageUpdate::Asset(s()),
372                ImageUpdate::Rect(s()),
373            ]
374            .iter()
375            .map(ImageUpdate::path),
376        );
377
378        assert_eq!(paths.len(), 44, "the register catalog holds 44 registers");
379        let distinct: BTreeSet<Path> = paths.iter().copied().collect();
380        assert_eq!(
381            distinct.len(),
382            paths.len(),
383            "no two update variants may share a path"
384        );
385    }
386}