1use crate::{ContentHash, Identity, RecordKey, Revision};
4use serde::{Deserialize, Serialize};
5use std::collections::BTreeMap;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
9pub enum RecordKind {
10 MemoryTier,
11 Topic,
12 Session,
13 Checkpoint,
14 Ticket,
16 TicketEvent,
18 GraphManifest,
21}
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub enum MergeClass {
26 AppendOnly,
28 Structured,
30 Opaque,
32 Derived,
38}
39
40impl RecordKind {
41 pub fn merge_class(self) -> MergeClass {
42 match self {
43 RecordKind::Topic | RecordKind::Session | RecordKind::TicketEvent => {
44 MergeClass::AppendOnly
45 }
46 RecordKind::MemoryTier | RecordKind::Ticket => MergeClass::Structured,
47 RecordKind::Checkpoint => MergeClass::Opaque,
48 RecordKind::GraphManifest => MergeClass::Derived,
49 }
50 }
51}
52
53#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
60pub enum Body {
61 Inline(Vec<u8>),
62 Blob {
68 hash: ContentHash,
69 len: u64,
70 },
71}
72
73impl Body {
74 pub fn blob(content: &[u8]) -> Self {
77 Body::Blob {
78 hash: ContentHash::of(content),
79 len: content.len() as u64,
80 }
81 }
82
83 pub fn bytes(&self) -> &[u8] {
88 match self {
89 Body::Inline(b) => b,
90 Body::Blob { hash, .. } => hash.0.as_bytes(),
91 }
92 }
93}
94
95#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
97pub struct Meta {
98 pub author: Identity,
99 pub origin_system: String,
100 pub created: i64,
101 pub updated: i64,
102 pub labels: BTreeMap<String, String>,
103}
104
105#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
107pub struct Record {
108 pub key: RecordKey,
109 pub kind: RecordKind,
110 pub revision: Revision,
111 pub parent: Option<Revision>,
112 pub body: Body,
113 pub meta: Meta,
114 pub links: Vec<RecordKey>,
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 #[test]
122 fn merge_class_is_assigned_per_kind() {
123 assert_eq!(RecordKind::Topic.merge_class(), MergeClass::AppendOnly);
124 assert_eq!(RecordKind::Session.merge_class(), MergeClass::AppendOnly);
125 assert_eq!(RecordKind::MemoryTier.merge_class(), MergeClass::Structured);
126 assert_eq!(RecordKind::Checkpoint.merge_class(), MergeClass::Opaque);
127 assert_eq!(RecordKind::Ticket.merge_class(), MergeClass::Structured);
128 assert_eq!(
129 RecordKind::TicketEvent.merge_class(),
130 MergeClass::AppendOnly
131 );
132 assert_eq!(RecordKind::GraphManifest.merge_class(), MergeClass::Derived);
133 }
134
135 #[test]
136 fn body_exposes_bytes() {
137 assert_eq!(Body::Inline(b"hi".to_vec()).bytes(), b"hi");
138 }
139
140 #[test]
141 fn blob_body_references_content_by_hash() {
142 let body = Body::blob(b"fn main() {}");
143 match &body {
144 Body::Blob { hash, len } => {
145 assert_eq!(*hash, crate::ContentHash::of(b"fn main() {}"));
146 assert_eq!(*len, 12);
147 }
148 _ => panic!("expected Body::Blob"),
149 }
150 }
151
152 #[test]
153 fn blob_body_bytes_are_stable_per_content() {
154 assert_eq!(Body::blob(b"same").bytes(), Body::blob(b"same").bytes());
157 assert_ne!(Body::blob(b"same").bytes(), Body::blob(b"diff").bytes());
158 }
159}