1use serde_json::{Map, Value};
6
7use crate::model::{Frame, Message, MsgKind, OpResult};
8use crate::segment::{ColumnValue, RowsSegment};
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(1)),
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 } => {
60 entries.push(("type", Value::from("REQ_HEADER")));
61 entries.push(("clientId", Value::from(client_id.clone())));
62 entries.push(("schemaVersion", Value::from(*schema_version)));
63 }
64 Frame::PushCommit {
65 client_commit_id,
66 operations,
67 } => {
68 entries.push(("type", Value::from("PUSH_COMMIT")));
69 entries.push(("clientCommitId", Value::from(client_commit_id.clone())));
70 let ops = operations
71 .iter()
72 .map(|op| {
73 let mut e: Vec<(&str, Value)> = vec![
74 ("table", Value::from(op.table.clone())),
75 ("rowId", Value::from(op.row_id.clone())),
76 ("op", Value::from(op.op.name())),
77 ];
78 if let Some(v) = op.base_version {
79 e.push(("baseVersion", Value::from(v)));
80 }
81 if let Some(p) = &op.payload {
82 e.push(("payload", Value::from(base64(p))));
83 }
84 obj(e)
85 })
86 .collect();
87 entries.push(("operations", Value::Array(ops)));
88 }
89 Frame::PullHeader {
90 limit_commits,
91 limit_snapshot_rows,
92 max_snapshot_pages,
93 accept,
94 } => {
95 entries.push(("type", Value::from("PULL_HEADER")));
96 entries.push(("limitCommits", Value::from(*limit_commits)));
97 entries.push(("limitSnapshotRows", Value::from(*limit_snapshot_rows)));
98 entries.push(("maxSnapshotPages", Value::from(*max_snapshot_pages)));
99 entries.push(("accept", Value::from(*accept)));
101 }
102 Frame::Subscription {
103 id,
104 table,
105 scopes,
106 params,
107 cursor,
108 bootstrap_state,
109 } => {
110 entries.push(("type", Value::from("SUBSCRIPTION")));
111 entries.push(("id", Value::from(id.clone())));
112 entries.push(("table", Value::from(table.clone())));
113 entries.push(("scopes", scope_map_value(scopes)));
114 if let Some(p) = params {
115 entries.push(("params", p.parse()));
117 }
118 entries.push(("cursor", Value::from(*cursor)));
119 if let Some(b) = bootstrap_state {
120 entries.push(("bootstrapState", b.parse()));
121 }
122 }
123 Frame::RespHeader {
124 required_schema_version,
125 latest_schema_version,
126 } => {
127 entries.push(("type", Value::from("RESP_HEADER")));
128 if let Some(v) = required_schema_version {
129 entries.push(("requiredSchemaVersion", Value::from(*v)));
130 }
131 if let Some(v) = latest_schema_version {
132 entries.push(("latestSchemaVersion", Value::from(*v)));
133 }
134 }
135 Frame::Lease {
136 lease_id,
137 expires_at_ms,
138 } => {
139 entries.push(("type", Value::from("LEASE")));
140 entries.push(("leaseId", Value::from(lease_id.clone())));
141 entries.push(("expiresAtMs", Value::from(*expires_at_ms)));
142 }
143 Frame::PushResult {
144 client_commit_id,
145 status,
146 commit_seq,
147 results,
148 } => {
149 entries.push(("type", Value::from("PUSH_RESULT")));
150 entries.push(("clientCommitId", Value::from(client_commit_id.clone())));
151 entries.push(("status", Value::from(status.name())));
152 if let Some(v) = commit_seq {
153 entries.push(("commitSeq", Value::from(*v)));
154 }
155 entries.push((
156 "results",
157 Value::Array(results.iter().map(render_result).collect()),
158 ));
159 }
160 Frame::SubStart {
161 id,
162 status,
163 reason_code,
164 effective_scopes,
165 bootstrap,
166 } => {
167 entries.push(("type", Value::from("SUB_START")));
168 entries.push(("id", Value::from(id.clone())));
169 entries.push(("status", Value::from(status.name())));
170 entries.push(("reasonCode", Value::from(reason_code.clone())));
171 entries.push(("effectiveScopes", scope_map_value(effective_scopes)));
172 entries.push(("bootstrap", Value::from(*bootstrap)));
173 }
174 Frame::Commit {
175 commit_seq,
176 created_at_ms,
177 actor_id,
178 tables,
179 changes,
180 } => {
181 entries.push(("type", Value::from("COMMIT")));
182 entries.push(("commitSeq", Value::from(*commit_seq)));
183 entries.push(("createdAtMs", Value::from(*created_at_ms)));
184 entries.push(("actorId", Value::from(actor_id.clone())));
185 entries.push((
186 "tables",
187 Value::Array(tables.iter().map(|t| Value::from(t.clone())).collect()),
188 ));
189 let rendered_changes = changes
190 .iter()
191 .map(|c| {
192 let mut e: Vec<(&str, Value)> = vec![
193 ("tableIndex", Value::from(c.table_index)),
194 ("rowId", Value::from(c.row_id.clone())),
195 ("op", Value::from(c.op.name())),
196 ];
197 if let Some(v) = c.row_version {
198 e.push(("rowVersion", Value::from(v)));
199 }
200 let mut scopes = Map::new();
201 for (k, v) in &c.scopes {
202 scopes.insert(k.clone(), Value::from(v.clone()));
203 }
204 e.push(("scopes", Value::Object(scopes)));
205 if let Some(row) = &c.row {
206 e.push(("row", Value::from(base64(row))));
208 }
209 obj(e)
210 })
211 .collect();
212 entries.push(("changes", Value::Array(rendered_changes)));
213 }
214 Frame::SegmentRef {
215 segment_id,
216 media_type,
217 table,
218 byte_length,
219 row_count,
220 as_of_commit_seq,
221 scope_digest,
222 row_cursor,
223 next_row_cursor,
224 url,
225 url_expires_at_ms,
226 } => {
227 entries.push(("type", Value::from("SEGMENT_REF")));
228 entries.push(("segmentId", Value::from(segment_id.clone())));
229 entries.push(("mediaType", Value::from(media_type.name())));
230 entries.push(("table", Value::from(table.clone())));
231 entries.push(("byteLength", Value::from(*byte_length)));
232 entries.push(("rowCount", Value::from(*row_count)));
233 entries.push(("asOfCommitSeq", Value::from(*as_of_commit_seq)));
234 entries.push(("scopeDigest", Value::from(scope_digest.clone())));
235 if let Some(v) = row_cursor {
236 entries.push(("rowCursor", Value::from(v.clone())));
237 }
238 if let Some(v) = next_row_cursor {
239 entries.push(("nextRowCursor", Value::from(v.clone())));
240 }
241 if let Some(v) = url {
242 entries.push(("url", Value::from(v.clone())));
243 }
244 if let Some(v) = url_expires_at_ms {
245 entries.push(("urlExpiresAtMs", Value::from(*v)));
246 }
247 }
248 Frame::SegmentInline { payload } => {
249 entries.push(("type", Value::from("SEGMENT_INLINE")));
252 entries.push(("payload", Value::from(base64(payload))));
253 }
254 Frame::SubEnd {
255 next_cursor,
256 bootstrap_state,
257 } => {
258 entries.push(("type", Value::from("SUB_END")));
259 entries.push(("nextCursor", Value::from(*next_cursor)));
260 if let Some(b) = bootstrap_state {
261 entries.push(("bootstrapState", b.parse()));
262 }
263 }
264 Frame::Error {
265 code,
266 message,
267 category,
268 retryable,
269 recommended_action,
270 details,
271 } => {
272 entries.push(("type", Value::from("ERROR")));
273 entries.push(("code", Value::from(code.clone())));
274 entries.push(("message", Value::from(message.clone())));
275 entries.push(("category", Value::from(category.clone())));
276 entries.push(("retryable", Value::from(*retryable)));
277 entries.push(("recommendedAction", Value::from(recommended_action.clone())));
278 if let Some(d) = details {
279 entries.push(("details", d.parse()));
280 }
281 }
282 Frame::Unknown {
283 frame_type,
284 payload,
285 } => {
286 entries.push(("type", Value::from("UNKNOWN")));
288 entries.push(("frameType", Value::from(*frame_type)));
289 entries.push(("payload", Value::from(base64(payload))));
290 }
291 }
292 obj(entries)
293}
294
295fn render_result(result: &OpResult) -> Value {
296 match result {
297 OpResult::Applied { op_index } => obj(vec![
298 ("opIndex", Value::from(*op_index)),
299 ("status", Value::from("applied")),
300 ]),
301 OpResult::Conflict {
302 op_index,
303 code,
304 message,
305 server_version,
306 server_row,
307 } => obj(vec![
308 ("opIndex", Value::from(*op_index)),
309 ("status", Value::from("conflict")),
310 ("code", Value::from(code.clone())),
311 ("message", Value::from(message.clone())),
312 ("serverVersion", Value::from(*server_version)),
313 ("serverRow", Value::from(base64(server_row))),
314 ]),
315 OpResult::Error {
316 op_index,
317 code,
318 message,
319 retryable,
320 } => obj(vec![
321 ("opIndex", Value::from(*op_index)),
322 ("status", Value::from("error")),
323 ("code", Value::from(code.clone())),
324 ("message", Value::from(message.clone())),
325 ("retryable", Value::from(*retryable)),
326 ]),
327 }
328}
329
330pub fn render_rows_segment(seg: &RowsSegment) -> Value {
334 let columns = seg
335 .columns
336 .iter()
337 .map(|c| {
338 obj(vec![
339 ("name", Value::from(c.name.clone())),
340 ("type", Value::from(c.ty.name())),
341 ("nullable", Value::from(c.nullable)),
342 ])
343 })
344 .collect();
345 let blocks = seg
346 .blocks
347 .iter()
348 .map(|block| {
349 Value::Array(
350 block
351 .iter()
352 .map(|row| {
353 let mut values = Map::new();
354 for (col, value) in seg.columns.iter().zip(row.values.iter()) {
355 values.insert(col.name.clone(), render_column_value(value));
356 }
357 obj(vec![
358 ("serverVersion", Value::from(row.server_version)),
359 ("values", Value::Object(values)),
360 ])
361 })
362 .collect(),
363 )
364 })
365 .collect();
366 obj(vec![
367 ("magic", Value::from("SSG2")),
368 ("formatVersion", Value::from(1)),
369 ("table", Value::from(seg.table.clone())),
370 ("schemaVersion", Value::from(seg.schema_version)),
371 ("columns", Value::Array(columns)),
372 ("blocks", Value::Array(blocks)),
373 ])
374}
375
376fn render_column_value(value: &Option<ColumnValue>) -> Value {
377 match value {
378 None => Value::Null,
379 Some(ColumnValue::String(s)) => Value::from(s.clone()),
380 Some(ColumnValue::Integer(v)) => Value::from(*v),
381 Some(ColumnValue::Float(v)) => {
382 serde_json::Number::from_f64(*v).map_or(Value::Null, Value::Number)
383 }
384 Some(ColumnValue::Boolean(v)) => Value::from(*v),
385 Some(ColumnValue::Json(j)) => j.parse(),
386 Some(ColumnValue::Bytes(b)) => Value::from(base64(b)),
387 Some(ColumnValue::BlobRef(j)) => j.parse(),
389 Some(ColumnValue::Crdt(b)) => Value::from(base64(b)),
391 }
392}