open_gpui_canvas/persistence/
codec.rs1use super::{CanvasCheckpoint, CanvasLogEntry};
2use crate::{DocumentError, validate_canvas_document_format_version};
3use std::{error::Error, fmt};
4
5pub const CANVAS_PERSISTENCE_CODEC_VERSION: u32 = 1;
6
7fn default_persistence_codec_version() -> u32 {
8 CANVAS_PERSISTENCE_CODEC_VERSION
9}
10
11#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
12#[serde(rename_all = "snake_case")]
13pub enum CanvasPersistenceRecordKind {
14 Checkpoint,
15 LogEntry,
16}
17
18impl CanvasPersistenceRecordKind {
19 pub fn as_str(self) -> &'static str {
20 match self {
21 Self::Checkpoint => "checkpoint",
22 Self::LogEntry => "log_entry",
23 }
24 }
25}
26
27impl fmt::Display for CanvasPersistenceRecordKind {
28 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29 f.write_str(self.as_str())
30 }
31}
32
33#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
34#[serde(tag = "record_kind", content = "payload", rename_all = "snake_case")]
35pub enum CanvasPersistenceRecord {
36 Checkpoint(CanvasCheckpoint),
37 LogEntry(CanvasLogEntry),
38}
39
40impl CanvasPersistenceRecord {
41 pub fn kind(&self) -> CanvasPersistenceRecordKind {
42 match self {
43 Self::Checkpoint(_) => CanvasPersistenceRecordKind::Checkpoint,
44 Self::LogEntry(_) => CanvasPersistenceRecordKind::LogEntry,
45 }
46 }
47
48 pub fn sequence(&self) -> u64 {
49 match self {
50 Self::Checkpoint(checkpoint) => checkpoint.sequence,
51 Self::LogEntry(entry) => entry.sequence(),
52 }
53 }
54
55 pub fn document_format_version(&self) -> u32 {
56 match self {
57 Self::Checkpoint(checkpoint) => checkpoint.snapshot.format_version,
58 Self::LogEntry(_) => crate::CANVAS_DOCUMENT_FORMAT_VERSION,
59 }
60 }
61}
62
63impl From<CanvasCheckpoint> for CanvasPersistenceRecord {
64 fn from(value: CanvasCheckpoint) -> Self {
65 Self::Checkpoint(value)
66 }
67}
68
69impl From<CanvasLogEntry> for CanvasPersistenceRecord {
70 fn from(value: CanvasLogEntry) -> Self {
71 Self::LogEntry(value)
72 }
73}
74
75#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
76pub struct CanvasPersistenceEnvelope {
77 #[serde(default = "default_persistence_codec_version")]
78 pub codec_version: u32,
79 pub document_format_version: u32,
80 pub record: CanvasPersistenceRecord,
81}
82
83impl CanvasPersistenceEnvelope {
84 pub fn new(record: impl Into<CanvasPersistenceRecord>) -> Self {
85 let record = record.into();
86 Self {
87 codec_version: CANVAS_PERSISTENCE_CODEC_VERSION,
88 document_format_version: record.document_format_version(),
89 record,
90 }
91 }
92
93 pub fn kind(&self) -> CanvasPersistenceRecordKind {
94 self.record.kind()
95 }
96
97 pub fn sequence(&self) -> u64 {
98 self.record.sequence()
99 }
100}
101
102#[derive(Debug, Eq, PartialEq)]
103pub enum CanvasPersistenceCodecError {
104 UnsupportedCodecVersion {
105 expected: u32,
106 found: u32,
107 },
108 UnsupportedDocumentFormatVersion {
109 expected: u32,
110 found: u32,
111 },
112 RecordFormatVersionMismatch {
113 envelope: u32,
114 payload: u32,
115 },
116 UnexpectedRecordKind {
117 expected: CanvasPersistenceRecordKind,
118 found: CanvasPersistenceRecordKind,
119 },
120 Json(String),
121}
122
123impl CanvasPersistenceCodecError {
124 fn json(error: serde_json::Error) -> Self {
125 Self::Json(error.to_string())
126 }
127}
128
129impl fmt::Display for CanvasPersistenceCodecError {
130 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131 match self {
132 Self::UnsupportedCodecVersion { expected, found } => write!(
133 f,
134 "unsupported canvas persistence codec version `{found}`, expected `{expected}`"
135 ),
136 Self::UnsupportedDocumentFormatVersion { expected, found } => write!(
137 f,
138 "unsupported canvas persistence document format version `{found}`, expected `{expected}`"
139 ),
140 Self::RecordFormatVersionMismatch { envelope, payload } => write!(
141 f,
142 "canvas persistence envelope document format version `{envelope}` does not match payload version `{payload}`"
143 ),
144 Self::UnexpectedRecordKind { expected, found } => write!(
145 f,
146 "canvas persistence record kind `{found}` cannot be decoded as `{expected}`"
147 ),
148 Self::Json(error) => write!(f, "canvas persistence JSON codec error: {error}"),
149 }
150 }
151}
152
153impl Error for CanvasPersistenceCodecError {}
154
155pub trait CanvasPersistenceCodec {
156 type Error: fmt::Debug + fmt::Display;
157
158 fn encode_checkpoint(&self, checkpoint: &CanvasCheckpoint) -> Result<Vec<u8>, Self::Error>;
159
160 fn decode_checkpoint(&self, bytes: &[u8]) -> Result<CanvasCheckpoint, Self::Error>;
161
162 fn encode_log_entry(&self, entry: &CanvasLogEntry) -> Result<Vec<u8>, Self::Error>;
163
164 fn decode_log_entry(&self, bytes: &[u8]) -> Result<CanvasLogEntry, Self::Error>;
165}
166
167#[derive(Clone, Copy, Debug, Default)]
168pub struct CanvasJsonPersistenceCodec;
169
170impl CanvasJsonPersistenceCodec {
171 pub fn encode_record(
172 &self,
173 record: impl Into<CanvasPersistenceRecord>,
174 ) -> Result<Vec<u8>, CanvasPersistenceCodecError> {
175 serde_json::to_vec(&CanvasPersistenceEnvelope::new(record))
176 .map_err(CanvasPersistenceCodecError::json)
177 }
178
179 pub fn decode_record(
180 &self,
181 bytes: &[u8],
182 ) -> Result<CanvasPersistenceRecord, CanvasPersistenceCodecError> {
183 Ok(self.decode_envelope(bytes)?.record)
184 }
185
186 pub fn decode_envelope(
187 &self,
188 bytes: &[u8],
189 ) -> Result<CanvasPersistenceEnvelope, CanvasPersistenceCodecError> {
190 let envelope: CanvasPersistenceEnvelope =
191 serde_json::from_slice(bytes).map_err(CanvasPersistenceCodecError::json)?;
192 validate_persistence_envelope(&envelope)?;
193 Ok(envelope)
194 }
195}
196
197impl CanvasPersistenceCodec for CanvasJsonPersistenceCodec {
198 type Error = CanvasPersistenceCodecError;
199
200 fn encode_checkpoint(&self, checkpoint: &CanvasCheckpoint) -> Result<Vec<u8>, Self::Error> {
201 self.encode_record(checkpoint.clone())
202 }
203
204 fn decode_checkpoint(&self, bytes: &[u8]) -> Result<CanvasCheckpoint, Self::Error> {
205 let envelope = self.decode_envelope(bytes)?;
206 let found = envelope.kind();
207 let CanvasPersistenceRecord::Checkpoint(checkpoint) = envelope.record else {
208 return Err(CanvasPersistenceCodecError::UnexpectedRecordKind {
209 expected: CanvasPersistenceRecordKind::Checkpoint,
210 found,
211 });
212 };
213 if checkpoint.snapshot.format_version != envelope.document_format_version {
214 return Err(CanvasPersistenceCodecError::RecordFormatVersionMismatch {
215 envelope: envelope.document_format_version,
216 payload: checkpoint.snapshot.format_version,
217 });
218 }
219 Ok(checkpoint)
220 }
221
222 fn encode_log_entry(&self, entry: &CanvasLogEntry) -> Result<Vec<u8>, Self::Error> {
223 self.encode_record(entry.clone())
224 }
225
226 fn decode_log_entry(&self, bytes: &[u8]) -> Result<CanvasLogEntry, Self::Error> {
227 let envelope = self.decode_envelope(bytes)?;
228 let found = envelope.kind();
229 let CanvasPersistenceRecord::LogEntry(entry) = envelope.record else {
230 return Err(CanvasPersistenceCodecError::UnexpectedRecordKind {
231 expected: CanvasPersistenceRecordKind::LogEntry,
232 found,
233 });
234 };
235 Ok(entry)
236 }
237}
238
239fn validate_persistence_envelope(
240 envelope: &CanvasPersistenceEnvelope,
241) -> Result<(), CanvasPersistenceCodecError> {
242 if envelope.codec_version != CANVAS_PERSISTENCE_CODEC_VERSION {
243 return Err(CanvasPersistenceCodecError::UnsupportedCodecVersion {
244 expected: CANVAS_PERSISTENCE_CODEC_VERSION,
245 found: envelope.codec_version,
246 });
247 }
248
249 if let Err(DocumentError::UnsupportedFormatVersion { expected, found }) =
250 validate_canvas_document_format_version(envelope.document_format_version)
251 {
252 return Err(
253 CanvasPersistenceCodecError::UnsupportedDocumentFormatVersion { expected, found },
254 );
255 }
256
257 let payload_format_version = envelope.record.document_format_version();
258 if payload_format_version != envelope.document_format_version {
259 return Err(CanvasPersistenceCodecError::RecordFormatVersionMismatch {
260 envelope: envelope.document_format_version,
261 payload: payload_format_version,
262 });
263 }
264
265 Ok(())
266}