This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

weir / src / json_cbor.zig
3.3 kB 89 lines
1//! JSON <-> DAG-CBOR bridge. 2//! 3//! atproto's data model has no float type, so floats are encoded as their 4//! decimal string form on the way into a record. API responses never round 5//! trip through CBOR (they're served from the appview), so this lossiness 6//! only affects the canonical record body. 7 8const std = @import("std"); 9const zat = @import("zat"); 10 11pub fn jsonToCbor(arena: std.mem.Allocator, v: std.json.Value) error{OutOfMemory}!zat.cbor.Value { 12 return switch (v) { 13 .null => .null, 14 .bool => |b| .{ .boolean = b }, 15 .integer => |i| if (i >= 0) 16 .{ .unsigned = @intCast(i) } 17 else 18 .{ .negative = i }, 19 .float => |f| .{ .text = try std.fmt.allocPrint(arena, "{d}", .{f}) }, 20 .number_string => |s| .{ .text = s }, 21 .string => |s| .{ .text = s }, 22 .array => |arr| blk: { 23 const items = try arena.alloc(zat.cbor.Value, arr.items.len); 24 for (arr.items, 0..) |item, i| items[i] = try jsonToCbor(arena, item); 25 break :blk .{ .array = items }; 26 }, 27 .object => |obj| blk: { 28 const entries = try arena.alloc(zat.cbor.Value.MapEntry, obj.count()); 29 var it = obj.iterator(); 30 var i: usize = 0; 31 while (it.next()) |e| : (i += 1) { 32 entries[i] = .{ .key = e.key_ptr.*, .value = try jsonToCbor(arena, e.value_ptr.*) }; 33 } 34 break :blk .{ .map = entries }; 35 }, 36 }; 37} 38 39/// write a decoded cbor value as JSON. cids render as {"$link": "<base32...>"} 40/// is out of scope for now — records written by this server contain none. 41pub fn cborToJson(jw: *std.json.Stringify, v: zat.cbor.Value) !void { 42 switch (v) { 43 .null => try jw.write(null), 44 .boolean => |b| try jw.write(b), 45 .unsigned => |u| try jw.write(u), 46 .negative => |n| try jw.write(n), 47 .text => |s| try jw.write(s), 48 .bytes => |b| { 49 var buf: [512]u8 = undefined; 50 const enc = std.base64.standard.Encoder; 51 if (enc.calcSize(b.len) <= buf.len) { 52 try jw.write(enc.encode(&buf, b)); 53 } else { 54 try jw.write("<bytes>"); 55 } 56 }, 57 .cid => try jw.write("<cid>"), 58 .array => |items| { 59 try jw.beginArray(); 60 for (items) |item| try cborToJson(jw, item); 61 try jw.endArray(); 62 }, 63 .map => |entries| { 64 try jw.beginObject(); 65 for (entries) |e| { 66 try jw.objectField(e.key); 67 try cborToJson(jw, e.value); 68 } 69 try jw.endObject(); 70 }, 71 } 72} 73 74test "json -> cbor -> json round trip" { 75 var arena = std.heap.ArenaAllocator.init(std.testing.allocator); 76 defer arena.deinit(); 77 const alloc = arena.allocator(); 78 79 const parsed = try std.json.parseFromSliceLeaky(std.json.Value, alloc, 80 \\{"name":"test","count":3,"nested":{"ok":true},"items":[1,2]} 81 , .{}); 82 83 const cb = try jsonToCbor(alloc, parsed); 84 const encoded = try zat.cbor.encodeAlloc(alloc, cb); 85 const decoded = try zat.cbor.decodeAll(alloc, encoded); 86 87 try std.testing.expectEqualStrings("test", decoded.getString("name").?); 88 try std.testing.expectEqual(@as(i64, 3), decoded.getInt("count").?); 89}