1use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
2use std::marker::PhantomData;
3use std::str::FromStr;
4
5#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
11pub struct Id<K: IdKind>(u32, PhantomData<K>);
12
13impl<K: IdKind> std::fmt::Display for Id<K> {
14 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15 write!(f, "{}{}", K::MNEMONIC, self.0)
16 }
17}
18
19impl<K: IdKind> std::fmt::Debug for Id<K> {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 write!(f, "{self}")
24 }
25}
26
27impl<K: IdKind> Id<K> {
28 pub const NULL: Id<K> = Id::<K>(0, PhantomData::<K>);
29
30 pub(crate) const fn from_raw(n: u32) -> Self {
35 Id(n, PhantomData)
36 }
37}
38
39impl<K: IdKind> Default for Id<K> {
42 fn default() -> Self {
43 Self::NULL
44 }
45}
46
47#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
48#[error("{0:?} does not name a {1} id")]
49pub struct BadId(String, &'static str);
50
51#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
52#[error("{0:?} does not name anything a commit can touch")]
53pub struct BadEntityRef(String);
54
55impl<K: IdKind> FromStr for Id<K> {
56 type Err = BadId;
57
58 fn from_str(s: &str) -> Result<Self, Self::Err> {
59 s.strip_prefix(K::MNEMONIC)
60 .and_then(|n| n.parse().ok())
61 .map(Id::from_raw)
62 .ok_or_else(|| BadId(s.to_owned(), K::NOUN))
63 }
64}
65
66impl<K: IdKind> Serialize for Id<K> {
67 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
68 serializer.collect_str(self)
69 }
70}
71
72impl<'de, K: IdKind> Deserialize<'de> for Id<K> {
73 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
74 deserializer.deserialize_str(IdVisitor::<K>(PhantomData))
75 }
76}
77
78struct IdVisitor<K: IdKind>(PhantomData<K>);
79
80impl<K: IdKind> de::Visitor<'_> for IdVisitor<K> {
81 type Value = Id<K>;
82
83 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 write!(f, "a {} id, spelled \"{}7\"", K::NOUN, K::MNEMONIC)
85 }
86
87 fn visit_str<E: de::Error>(self, text: &str) -> Result<Self::Value, E> {
88 text.parse().map_err(E::custom)
89 }
90}
91
92impl Serialize for EntityRef {
95 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
96 serializer.collect_str(self)
97 }
98}
99
100impl<'de> Deserialize<'de> for EntityRef {
101 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
102 let text = String::deserialize(deserializer)?;
103 text.parse().map_err(de::Error::custom)
104 }
105}
106
107pub trait IdKind: Copy + Ord + std::hash::Hash {
111 const MNEMONIC: char;
113 const NOUN: &'static str;
115 fn mark(ids: &mut Allocator) -> &mut u32;
117}
118
119macro_rules! id_kinds {
124 ($( $variant:ident => $kind:ident, $alias:ident, $mark:ident, $mnemonic:literal, $noun:literal; )*) => {
125 $(
126 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
127 pub struct $kind;
128
129 impl IdKind for $kind {
130 const MNEMONIC: char = $mnemonic;
131 const NOUN: &'static str = $noun;
132 fn mark(ids: &mut Allocator) -> &mut u32 {
133 &mut ids.$mark
134 }
135 }
136
137 pub type $alias = Id<$kind>;
138 )*
139
140 #[derive(Clone, Copy, PartialEq, Eq, Debug)]
152 pub enum EntityRef {
153 Document,
154 $( $variant($alias), )*
155 Asset(crate::hash::AssetHash),
156 }
157
158 impl std::fmt::Display for EntityRef {
159 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160 match self {
161 EntityRef::Document => f.write_str("document"),
162 $( EntityRef::$variant(id) => write!(f, "{} {id}", $noun), )*
163 EntityRef::Asset(hash) => write!(f, "asset {hash}"),
164 }
165 }
166 }
167
168 impl std::str::FromStr for EntityRef {
169 type Err = BadEntityRef;
170
171 fn from_str(text: &str) -> Result<Self, Self::Err> {
172 let unknown = || BadEntityRef(text.to_owned());
173 if text == "document" {
174 return Ok(EntityRef::Document);
175 }
176 let (noun, name) = text.split_once(' ').ok_or_else(unknown)?;
177 match noun {
178 $( $noun => name.parse().map(EntityRef::$variant).map_err(|_| unknown()), )*
179 "asset" => crate::hash::AssetHash::from_hex(name)
180 .map(EntityRef::Asset)
181 .ok_or_else(unknown),
182 _ => Err(unknown()),
183 }
184 }
185 }
186
187 #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
193 pub struct Allocator {
194 $( $mark: u32, )*
195 }
196
197 impl Allocator {
198 pub fn raise_to(&mut self, other: &Allocator) {
202 $( self.$mark = self.$mark.max(other.$mark); )*
203 }
204
205 pub fn observe(&mut self, target: EntityRef) {
207 match target {
208 $( EntityRef::$variant(id) => self.raise(id), )*
209 EntityRef::Document | EntityRef::Asset(_) => {}
210 }
211 }
212 }
213 };
214}
215
216id_kinds! {
217 Block => BlockKind, BlockId, blocks, 'b', "block";
218 Pin => PinKind, PinId, pins, 'p', "pin";
219 Route => RouteKind, RouteId, routes, 'r', "route";
220 RouteLabel => RouteLabelKind, RouteLabelId, route_labels, 'x', "route-label";
221 Text => TextKind, TextId, texts, 't', "text";
222 Area => AreaKind, AreaId, areas, 'a', "area";
223 Image => ImageKind, ImageId, images, 'i', "image";
224}
225
226impl Allocator {
227 pub fn mint<K: IdKind>(&mut self) -> Id<K> {
230 let mark = K::mark(self);
231 *mark += 1;
232 Id::from_raw(*mark)
233 }
234
235 fn raise<K: IdKind>(&mut self, id: Id<K>) {
236 let mark = K::mark(self);
237 *mark = (*mark).max(id.0);
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244
245 #[test]
246 fn debug_is_the_display_spelling() {
247 let id: BlockId = Allocator::default().mint();
248 assert_eq!(format!("{id:?}"), format!("{id}"));
249 assert!(!format!("{id:?}").contains("PhantomData"));
250 }
251
252 #[test]
255 fn an_id_is_its_display_spelling_on_the_wire() {
256 let id: BlockId = Id::from_raw(7);
257 assert_eq!(id.to_string(), "b7");
258 assert_eq!(
259 serde_json::to_string(&id).expect("an id serializes"),
260 "\"b7\""
261 );
262 assert_eq!("b7".parse::<BlockId>(), Ok(id));
263 assert_eq!(BlockId::NULL.to_string(), "b0");
264 assert_eq!("b0".parse::<BlockId>(), Ok(BlockId::NULL));
265 }
266
267 #[test]
270 fn a_foreign_mnemonic_is_refused() {
271 assert!("p7".parse::<BlockId>().is_err());
272 assert!("7".parse::<BlockId>().is_err());
273 assert!("b".parse::<BlockId>().is_err());
274 assert!("b-1".parse::<BlockId>().is_err());
275 }
276
277 #[test]
280 fn the_kinds_count_independently() {
281 let mut ids = Allocator::default();
282 let spelled = [
283 ids.mint::<BlockKind>().to_string(),
284 ids.mint::<PinKind>().to_string(),
285 ids.mint::<RouteKind>().to_string(),
286 ids.mint::<RouteLabelKind>().to_string(),
287 ids.mint::<TextKind>().to_string(),
288 ids.mint::<AreaKind>().to_string(),
289 ids.mint::<ImageKind>().to_string(),
290 ];
291 assert_eq!(spelled, ["b1", "p1", "r1", "x1", "t1", "a1", "i1"]);
292 }
293
294 #[test]
296 fn minting_never_yields_the_null_id() {
297 let mut ids = Allocator::default();
298 assert_ne!(ids.mint::<BlockKind>(), BlockId::NULL);
299 }
300
301 #[test]
303 fn observing_raises_the_mark_of_that_kind_alone() {
304 let mut ids = Allocator::default();
305 ids.observe(EntityRef::Block(Id::from_raw(41)));
306 assert_eq!(ids.mint::<BlockKind>(), Id::from_raw(42));
307 assert_eq!(ids.mint::<PinKind>(), Id::from_raw(1));
308 ids.observe(EntityRef::Block(Id::from_raw(3)));
310 assert_eq!(ids.mint::<BlockKind>(), Id::from_raw(43));
311 }
312
313 #[test]
314 fn entity_refs_keep_the_narration_spelling() {
315 assert_eq!(
316 EntityRef::RouteLabel(Id::from_raw(2)).to_string(),
317 "route-label x2"
318 );
319 assert_eq!(EntityRef::Block(Id::from_raw(1)).to_string(), "block b1");
320 assert_eq!(EntityRef::Document.to_string(), "document");
321 }
322
323 #[test]
326 fn every_entity_ref_round_trips_through_its_narration_spelling() {
327 let asset = crate::hash::AssetHash::of(b"<svg/>");
328 for reference in [
329 EntityRef::Document,
330 EntityRef::Block(Id::from_raw(7)),
331 EntityRef::Pin(Id::from_raw(7)),
332 EntityRef::Route(Id::from_raw(1)),
333 EntityRef::RouteLabel(Id::from_raw(2)),
334 EntityRef::Text(Id::from_raw(3)),
335 EntityRef::Area(Id::from_raw(4)),
336 EntityRef::Image(Id::from_raw(5)),
337 EntityRef::Asset(asset),
338 ] {
339 let spelled = reference.to_string();
340 assert_eq!(spelled.parse::<EntityRef>(), Ok(reference), "{spelled}");
341 assert_eq!(
342 serde_json::to_string(&reference).expect("it serializes"),
343 format!("\"{spelled}\""),
344 );
345 assert_eq!(
346 serde_json::from_str::<EntityRef>(&format!("\"{spelled}\"")).expect("it parses"),
347 reference,
348 );
349 }
350 assert_eq!(
351 EntityRef::Block(Id::from_raw(7)).to_string(),
352 "block b7",
353 "the spelling §10.1 names",
354 );
355 for bad in ["", "block", "block p7", "blocks b7", "asset zz", "b7"] {
356 assert!(bad.parse::<EntityRef>().is_err(), "{bad:?} parsed");
357 }
358 }
359}