1use serde_json::{Map, Value};
6
7use crate::model::{Frame, Message, MsgKind, OpResult};
8use crate::segment::{Column, ColumnValue, RowsSegment, SparseRow, SparseSlot};
9use crate::util::base64;
10
11fn obj(entries: Vec<(&str, Value)>) -> Value {
12 let mut map = Map::new();
13 for (k, v) in entries {
14 map.insert(k.to_string(), v);
15 }
16 Value::Object(map)
17}
18
19fn scope_map_value(scopes: &[(String, Vec<String>)]) -> Value {
20 let mut map = Map::new();
21 for (k, vs) in scopes {
22 map.insert(
23 k.clone(),
24 Value::Array(vs.iter().map(|v| Value::String(v.clone())).collect()),
25 );
26 }
27 Value::Object(map)
28}
29
30pub fn render_message(msg: &Message) -> Value {
33 obj(vec![
34 ("magic", Value::from("SSP2")),
35 ("wireVersion", Value::from(msg.wire_version)),
36 (
37 "msgKind",
38 Value::from(match msg.msg_kind {
39 MsgKind::Request => "request",
40 MsgKind::Response => "response",
41 }),
42 ),
43 (
44 "frames",
45 Value::Array(msg.frames.iter().map(render_frame).collect()),
46 ),
47 ])
48}
49
50fn render_frame(frame: &Frame) -> Value {
51 let mut entries: Vec<(&str, Value)> = Vec::new();
55 match frame {
56 Frame::ReqHeader {
57 client_id,
58 schema_version,
59 log_epoch,
60 } => {
61 entries.push(("type", Value::from("REQ_HEADER")));
62 entries.push(("clientId", Value::from(client_id.clone())));
63 entries.push(("schemaVersion", Value::from(*schema_version)));
64 if let Some(value) = log_epoch {
65 entries.push(("logEpoch", Value::from(value.clone())));
66 }
67 }
68 Frame::PushCommit {
69 client_commit_id,
70 operations,
71 } => {
72 entries.push(("type", Value::from("PUSH_COMMIT")));
73 entries.push(("clientCommitId", Value::from(client_commit_id.clone())));
74 let ops = operations
75 .iter()
76 .map(|op| {
77 let mut e: Vec<(&str, Value)> = vec![
78 ("table", Value::from(op.table.clone())),
79 ("rowId", Value::from(op.row_id.clone())),
80 ("op", Value::from(op.op.name())),
81 ];
82 if let Some(v) = op.base_version {
83 e.push(("baseVersion", Value::from(v)));
84 }
85 if let Some(p) = &op.payload {
86 e.push(("payload", Value::from(base64(p))));
87 }
88 obj(e)
89 })
90 .collect();
91 entries.push(("operations", Value::Array(ops)));
92 }
93 Frame::PullHeader {
94 limit_commits,
95 limit_snapshot_rows,
96 max_snapshot_pages,
97 accept,
98 } => {
99 entries.push(("type", Value::from("PULL_HEADER")));
100 entries.push(("limitCommits", Value::from(*limit_commits)));
101 entries.push(("limitSnapshotRows", Value::from(*limit_snapshot_rows)));
102 entries.push(("maxSnapshotPages", Value::from(*max_snapshot_pages)));
103 entries.push(("accept", Value::from(*accept)));
105 }
106 Frame::Subscription {
107 id,
108 table,
109 scopes,
110 params,
111 cursor,
112 bootstrap_state,
113 } => {
114 entries.push(("type", Value::from("SUBSCRIPTION")));
115 entries.push(("id", Value::from(id.clone())));
116 entries.push(("table", Value::from(table.clone())));
117 entries.push(("scopes", scope_map_value(scopes)));
118 if let Some(p) = params {
119 entries.push(("params", p.parse()));
121 }
122 entries.push(("cursor", Value::from(*cursor)));
123 if let Some(b) = bootstrap_state {
124 entries.push(("bootstrapState", b.parse()));
125 }
126 }
127 Frame::RespHeader {
128 required_schema_version,
129 latest_schema_version,
130 log_epoch,
131 reset_required,
132 } => {
133 entries.push(("type", Value::from("RESP_HEADER")));
134 if let Some(v) = required_schema_version {
135 entries.push(("requiredSchemaVersion", Value::from(*v)));
136 }
137 if let Some(v) = latest_schema_version {
138 entries.push(("latestSchemaVersion", Value::from(*v)));
139 }
140 if let Some(value) = log_epoch {
141 entries.push(("logEpoch", Value::from(value.clone())));
142 }
143 if let Some(value) = reset_required {
144 entries.push(("resetRequired", Value::from(*value)));
145 }
146 }
147 Frame::Lease {
148 lease_id,
149 expires_at_ms,
150 } => {
151 entries.push(("type", Value::from("LEASE")));
152 entries.push(("leaseId", Value::from(lease_id.clone())));
153 entries.push(("expiresAtMs", Value::from(*expires_at_ms)));
154 }
155 Frame::PushResult {
156 client_commit_id,
157 status,
158 commit_seq,
159 results,
160 } => {
161 entries.push(("type", Value::from("PUSH_RESULT")));
162 entries.push(("clientCommitId", Value::from(client_commit_id.clone())));
163 entries.push(("status", Value::from(status.name())));
164 if let Some(v) = commit_seq {
165 entries.push(("commitSeq", Value::from(*v)));
166 }
167 entries.push((
168 "results",
169 Value::Array(results.iter().map(render_result).collect()),
170 ));
171 }
172 Frame::PushResultDetails {
173 client_commit_id,
174 entries: details_entries,
175 } => {
176 entries.push(("type", Value::from("PUSH_RESULT_DETAILS")));
177 entries.push(("clientCommitId", Value::from(client_commit_id.clone())));
178 entries.push((
179 "entries",
180 Value::Array(
181 details_entries
182 .iter()
183 .map(|entry| {
184 obj(vec![
185 ("opIndex", Value::from(entry.op_index)),
186 ("details", entry.details.parse()),
187 ])
188 })
189 .collect(),
190 ),
191 ));
192 }
193 Frame::SubStart {
194 id,
195 status,
196 reason_code,
197 effective_scopes,
198 bootstrap,
199 } => {
200 entries.push(("type", Value::from("SUB_START")));
201 entries.push(("id", Value::from(id.clone())));
202 entries.push(("status", Value::from(status.name())));
203 entries.push(("reasonCode", Value::from(reason_code.clone())));
204 entries.push(("effectiveScopes", scope_map_value(effective_scopes)));
205 entries.push(("bootstrap", Value::from(*bootstrap)));
206 }
207 Frame::Commit {
208 commit_seq,
209 created_at_ms,
210 actor_id,
211 tables,
212 changes,
213 } => {
214 entries.push(("type", Value::from("COMMIT")));
215 entries.push(("commitSeq", Value::from(*commit_seq)));
216 entries.push(("createdAtMs", Value::from(*created_at_ms)));
217 entries.push(("actorId", Value::from(actor_id.clone())));
218 entries.push((
219 "tables",
220 Value::Array(tables.iter().map(|t| Value::from(t.clone())).collect()),
221 ));
222 let rendered_changes = changes
223 .iter()
224 .map(|c| {
225 let mut e: Vec<(&str, Value)> = vec![
226 ("tableIndex", Value::from(c.table_index)),
227 ("rowId", Value::from(c.row_id.clone())),
228 ("op", Value::from(c.op.name())),
229 ];
230 if let Some(v) = c.row_version {
231 e.push(("rowVersion", Value::from(v)));
232 }
233 let mut scopes = Map::new();
234 for (k, v) in &c.scopes {
235 scopes.insert(k.clone(), Value::from(v.clone()));
236 }
237 e.push(("scopes", Value::Object(scopes)));
238 if let Some(row) = &c.row {
239 e.push(("row", Value::from(base64(row))));
241 }
242 obj(e)
243 })
244 .collect();
245 entries.push(("changes", Value::Array(rendered_changes)));
246 }
247 Frame::SegmentRef {
248 segment_id,
249 media_type,
250 table,
251 byte_length,
252 row_count,
253 as_of_commit_seq,
254 scope_digest,
255 row_cursor,
256 next_row_cursor,
257 url,
258 url_expires_at_ms,
259 } => {
260 entries.push(("type", Value::from("SEGMENT_REF")));
261 entries.push(("segmentId", Value::from(segment_id.clone())));
262 entries.push(("mediaType", Value::from(media_type.name())));
263 entries.push(("table", Value::from(table.clone())));
264 entries.push(("byteLength", Value::from(*byte_length)));
265 entries.push(("rowCount", Value::from(*row_count)));
266 entries.push(("asOfCommitSeq", Value::from(*as_of_commit_seq)));
267 entries.push(("scopeDigest", Value::from(scope_digest.clone())));
268 if let Some(v) = row_cursor {
269 entries.push(("rowCursor", Value::from(v.clone())));
270 }
271 if let Some(v) = next_row_cursor {
272 entries.push(("nextRowCursor", Value::from(v.clone())));
273 }
274 if let Some(v) = url {
275 entries.push(("url", Value::from(v.clone())));
276 }
277 if let Some(v) = url_expires_at_ms {
278 entries.push(("urlExpiresAtMs", Value::from(*v)));
279 }
280 }
281 Frame::SegmentInline { payload } => {
282 entries.push(("type", Value::from("SEGMENT_INLINE")));
285 entries.push(("payload", Value::from(base64(payload))));
286 }
287 Frame::SubEnd {
288 next_cursor,
289 bootstrap_state,
290 } => {
291 entries.push(("type", Value::from("SUB_END")));
292 entries.push(("nextCursor", Value::from(*next_cursor)));
293 if let Some(b) = bootstrap_state {
294 entries.push(("bootstrapState", b.parse()));
295 }
296 }
297 Frame::Error {
298 code,
299 message,
300 category,
301 retryable,
302 recommended_action,
303 details,
304 } => {
305 entries.push(("type", Value::from("ERROR")));
306 entries.push(("code", Value::from(code.clone())));
307 entries.push(("message", Value::from(message.clone())));
308 entries.push(("category", Value::from(category.clone())));
309 entries.push(("retryable", Value::from(*retryable)));
310 entries.push(("recommendedAction", Value::from(recommended_action.clone())));
311 if let Some(d) = details {
312 entries.push(("details", d.parse()));
313 }
314 }
315 Frame::Unknown {
316 frame_type,
317 payload,
318 } => {
319 entries.push(("type", Value::from("UNKNOWN")));
321 entries.push(("frameType", Value::from(*frame_type)));
322 entries.push(("payload", Value::from(base64(payload))));
323 }
324 }
325 obj(entries)
326}
327
328fn render_result(result: &OpResult) -> Value {
329 match result {
330 OpResult::Applied { op_index } => obj(vec![
331 ("opIndex", Value::from(*op_index)),
332 ("status", Value::from("applied")),
333 ]),
334 OpResult::Conflict {
335 op_index,
336 code,
337 message,
338 server_version,
339 server_row,
340 conflict_columns,
341 } => obj(vec![
342 ("opIndex", Value::from(*op_index)),
343 ("status", Value::from("conflict")),
344 ("code", Value::from(code.clone())),
345 ("message", Value::from(message.clone())),
346 ("serverVersion", Value::from(*server_version)),
347 ("serverRow", Value::from(base64(server_row))),
348 ("conflictColumns", Value::from(base64(conflict_columns))),
349 ]),
350 OpResult::Error {
351 op_index,
352 code,
353 message,
354 retryable,
355 } => obj(vec![
356 ("opIndex", Value::from(*op_index)),
357 ("status", Value::from("error")),
358 ("code", Value::from(code.clone())),
359 ("message", Value::from(message.clone())),
360 ("retryable", Value::from(*retryable)),
361 ]),
362 }
363}
364
365pub fn render_rows_segment(seg: &RowsSegment) -> Value {
369 let blocks = seg
370 .blocks
371 .iter()
372 .map(|block| {
373 Value::Array(
374 block
375 .iter()
376 .map(|row| {
377 let mut values = Map::new();
378 for (col, value) in seg.columns.iter().zip(row.values.iter()) {
379 values.insert(
380 col.name.clone(),
381 match value {
382 None => Value::Null,
383 Some(value) => render_column_value(value),
384 },
385 );
386 }
387 obj(vec![
388 ("serverVersion", Value::from(row.server_version)),
389 ("values", Value::Object(values)),
390 ])
391 })
392 .collect(),
393 )
394 })
395 .collect();
396 obj(vec![
397 ("magic", Value::from("SSG2")),
398 ("formatVersion", Value::from(1)),
399 ("table", Value::from(seg.table.clone())),
400 ("schemaVersion", Value::from(seg.schema_version)),
401 ("columns", render_column_table(&seg.columns)),
402 ("blocks", Value::Array(blocks)),
403 ])
404}
405
406fn render_column_table(columns: &[Column]) -> Value {
407 Value::Array(
408 columns
409 .iter()
410 .map(|c| {
411 obj(vec![
412 ("name", Value::from(c.name.clone())),
413 ("type", Value::from(c.ty.name())),
414 ("nullable", Value::from(c.nullable)),
415 ])
416 })
417 .collect(),
418 )
419}
420
421fn render_column_value(value: &ColumnValue) -> Value {
422 match value {
423 ColumnValue::String(s) => Value::from(s.clone()),
424 ColumnValue::Integer(v) => Value::from(*v),
425 ColumnValue::Float(v) => {
426 serde_json::Number::from_f64(*v).map_or(Value::Null, Value::Number)
427 }
428 ColumnValue::Boolean(v) => Value::from(*v),
429 ColumnValue::Json(j) => j.parse(),
430 ColumnValue::Bytes(b) => Value::from(base64(b)),
431 ColumnValue::BlobRef(j) => j.parse(),
433 ColumnValue::Crdt(b) => Value::from(base64(b)),
435 }
436}
437
438pub fn render_sparse_row(columns: &[Column], row: &SparseRow) -> Value {
442 let mut values = Map::new();
443 for (col, slot) in columns.iter().zip(row.iter()) {
444 match slot {
445 SparseSlot::Absent => {}
446 SparseSlot::Null => {
447 values.insert(col.name.clone(), Value::Null);
448 }
449 SparseSlot::Value(value) => {
450 values.insert(col.name.clone(), render_column_value(value));
451 }
452 }
453 }
454 obj(vec![
455 ("columns", render_column_table(columns)),
456 ("values", Value::Object(values)),
457 ])
458}