orbok_core/id.rs
1//! Typed application-level identifiers.
2//!
3//! External design §9.2: application-level IDs are UUIDv7 strings with a
4//! readable prefix; external interfaces expose opaque strings rather than
5//! SQLite row ids. The newtype-per-entity pattern prevents accidentally
6//! passing a `FileId` where a `SourceId` is expected.
7
8use serde::{Deserialize, Serialize};
9use std::fmt;
10
11macro_rules! typed_id {
12 ($(#[$doc:meta])* $name:ident, $prefix:literal) => {
13 $(#[$doc])*
14 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
15 #[serde(transparent)]
16 pub struct $name(String);
17
18 impl $name {
19 /// Generate a fresh identifier (UUIDv7, time-ordered).
20 pub fn generate() -> Self {
21 Self(format!("{}_{}", $prefix, uuid::Uuid::now_v7()))
22 }
23
24 /// Wrap an existing identifier string (e.g. read from the catalog).
25 pub fn from_string(s: impl Into<String>) -> Self {
26 Self(s.into())
27 }
28
29 /// Borrow the identifier as a string slice.
30 pub fn as_str(&self) -> &str {
31 &self.0
32 }
33 }
34
35 impl fmt::Display for $name {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 f.write_str(&self.0)
38 }
39 }
40
41 impl From<$name> for String {
42 fn from(id: $name) -> String {
43 id.0
44 }
45 }
46 };
47}
48
49typed_id!(
50 /// Identifier of a registered source (RFC-003).
51 SourceId,
52 "src"
53);
54typed_id!(
55 /// Identifier of a cataloged file (RFC-004).
56 FileId,
57 "file"
58);
59typed_id!(
60 /// Identifier of an extraction record (RFC-005).
61 ExtractionId,
62 "ext"
63);
64typed_id!(
65 /// Identifier of a chunk (RFC-006).
66 ChunkId,
67 "chunk"
68);
69typed_id!(
70 /// Identifier of an index job (RFC-002 §7.9).
71 JobId,
72 "job"
73);
74typed_id!(
75 /// Identifier of a registered local model (RFC-012).
76 ModelId,
77 "model"
78);
79typed_id!(
80 /// Identifier of a search query record (RFC-002 §7.10).
81 QueryId,
82 "query"
83);
84typed_id!(
85 /// Identifier of an application event (RFC-002 §7.13).
86 EventId,
87 "evt"
88);