├── .gitignore ├── Makefile ├── src ├── msgpack.zig └── encoder.zig ├── .github └── workflows │ └── msgpack-zig.yml ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | zig-cache 2 | .vscode 3 | .idea 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | zig build --verbose 3 | -------------------------------------------------------------------------------- /src/msgpack.zig: -------------------------------------------------------------------------------- 1 | pub usingnamespace @import("encoder.zig"); 2 | -------------------------------------------------------------------------------- /.github/workflows/msgpack-zig.yml: -------------------------------------------------------------------------------- 1 | name: Test msgpack-zig 2 | on: [push] 3 | jobs: 4 | test: 5 | strategy: 6 | fail-fast: false 7 | matrix: 8 | include: 9 | - zig_version: 'master' 10 | experimental: true 11 | - zig_version: '0.7.1' 12 | experimental: false 13 | continue-on-error: '${{ matrix.experimental }}' 14 | runs-on: ubuntu-latest 15 | steps: 16 | - run: >- 17 | curl 18 | `curl https://ziglang.org/download/index.json | jq -r ".\"${{ matrix.zig_version }}\".\"x86_64-linux\".tarball"` 19 | | tar Jxf - -C /opt 20 | - run: sudo sh -c "mv /opt/zig-*/zig /usr/local/bin && mv /opt/zig-*/lib /usr/local/lib/zig" 21 | - uses: actions/checkout@v1 22 | - run: make build 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Oleg Utkin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MessagePack for Zig 2 | ![Test msgpack-zig](https://github.com/oleggator/msgpack-zig/workflows/Test%20msgpack-zig/badge.svg) 3 | 4 | Based on [github.com/tarantool/msgpuck](https://github.com/tarantool/msgpuck). 5 | 6 | ## Stream API implementation progress 7 | | Type | Encoding | Decoding | 8 | |-------------------------|:-----------------------:|:--------:| 9 | | generic | :white_check_mark: | | 10 | | int (7-64 bit) | :white_check_mark: | | 11 | | comptime int (7-64 bit) | :white_check_mark: | | 12 | | float (32, 64 bit) | :white_check_mark: | | 13 | | comptime float (64 bit) | :white_check_mark: | | 14 | | bool | :white_check_mark: | | 15 | | Optional | :white_check_mark: | | 16 | | Struct | :white_check_mark: | | 17 | | Pointer | :ballot_box_with_check: | | 18 | | Map | :ballot_box_with_check: | | 19 | | Array | :white_check_mark: | | 20 | | Slice | :white_check_mark: | | 21 | | Many | :white_check_mark: | | 22 | | string | :white_check_mark: | | 23 | | binary | :white_check_mark: | | 24 | | extension | :white_check_mark: | | 25 | | nil | :white_check_mark: | | 26 | 27 | - :white_check_mark: - implemented with unit tests 28 | - :ballot_box_with_check: - implemented without unit tests 29 | -------------------------------------------------------------------------------- /src/encoder.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | const minInt = std.math.minInt; 4 | const maxInt = std.math.maxInt; 5 | 6 | pub fn encodeStrLen(len: u32, writer: anytype) @TypeOf(writer).Error!void { 7 | if (len <= std.math.maxInt(u5)) { 8 | return writer.writeIntBig(u8, 0xa0 | @truncate(u8, len)); 9 | } 10 | if (len <= std.math.maxInt(u8)) { 11 | try writer.writeIntBig(u8, 0xd9); 12 | return writer.writeIntBig(u8, @truncate(u8, len)); 13 | } 14 | if (len <= std.math.maxInt(u16)) { 15 | try writer.writeIntBig(u8, 0xda); 16 | return writer.writeIntBig(u16, @truncate(u16, len)); 17 | } 18 | if (len <= std.math.maxInt(u32)) { 19 | try writer.writeIntBig(u8, 0xdb); 20 | return writer.writeIntBig(u32, len); 21 | } 22 | unreachable; 23 | } 24 | 25 | test "encode string length" { 26 | try testEncode(encodeStrLen, "\xa0", .{@as(u32, 0x00)}); 27 | try testEncode(encodeStrLen, "\xa1", .{@as(u32, 0x01)}); 28 | try testEncode(encodeStrLen, "\xbe", .{@as(u32, 0x1e)}); 29 | try testEncode(encodeStrLen, "\xbf", .{@as(u32, 0x1f)}); 30 | 31 | try testEncode(encodeStrLen, "\xd9\x20", .{@as(u32, 0x20)}); 32 | try testEncode(encodeStrLen, "\xd9\xfe", .{@as(u32, 0xfe)}); 33 | try testEncode(encodeStrLen, "\xd9\xff", .{@as(u32, 0xff)}); 34 | 35 | try testEncode(encodeStrLen, "\xda\x01\x00", .{@as(u32, 0x0100)}); 36 | try testEncode(encodeStrLen, "\xda\xff\xfe", .{@as(u32, 0xfffe)}); 37 | try testEncode(encodeStrLen, "\xda\xff\xff", .{@as(u32, 0xffff)}); 38 | 39 | try testEncode(encodeStrLen, "\xdb\x00\x01\x00\x00", .{@as(u32, 0x00010000)}); 40 | try testEncode(encodeStrLen, "\xdb\xff\xff\xff\xfe", .{@as(u32, 0xfffffffe)}); 41 | try testEncode(encodeStrLen, "\xdb\xff\xff\xff\xff", .{@as(u32, 0xffffffff)}); 42 | 43 | try testEncode(encodeStrLen, "\xa0", .{@as(comptime_int, 0x00)}); 44 | try testEncode(encodeStrLen, "\xa1", .{@as(comptime_int, 0x01)}); 45 | try testEncode(encodeStrLen, "\xbe", .{@as(comptime_int, 0x1e)}); 46 | try testEncode(encodeStrLen, "\xbf", .{@as(comptime_int, 0x1f)}); 47 | 48 | try testEncode(encodeStrLen, "\xd9\x20", .{@as(comptime_int, 0x20)}); 49 | try testEncode(encodeStrLen, "\xd9\xfe", .{@as(comptime_int, 0xfe)}); 50 | try testEncode(encodeStrLen, "\xd9\xff", .{@as(comptime_int, 0xff)}); 51 | 52 | try testEncode(encodeStrLen, "\xda\x01\x00", .{@as(comptime_int, 0x0100)}); 53 | try testEncode(encodeStrLen, "\xda\xff\xfe", .{@as(comptime_int, 0xfffe)}); 54 | try testEncode(encodeStrLen, "\xda\xff\xff", .{@as(comptime_int, 0xffff)}); 55 | 56 | try testEncode(encodeStrLen, "\xdb\x00\x01\x00\x00", .{@as(comptime_int, 0x00010000)}); 57 | try testEncode(encodeStrLen, "\xdb\xff\xff\xff\xfe", .{@as(comptime_int, 0xfffffffe)}); 58 | try testEncode(encodeStrLen, "\xdb\xff\xff\xff\xff", .{@as(comptime_int, 0xffffffff)}); 59 | } 60 | 61 | pub inline fn encodeStr(str: []const u8, writer: anytype) @TypeOf(writer).Error!void { 62 | try encodeStrLen(@truncate(u32, str.len), writer); 63 | return writer.writeAll(str); 64 | } 65 | 66 | test "encode string" { 67 | try testEncode(encodeStr, "\xa6string", .{"string"}); 68 | } 69 | 70 | pub fn encodeBinLen(len: u32, writer: anytype) @TypeOf(writer).Error!void { 71 | if (len <= std.math.maxInt(u8)) { 72 | try writer.writeIntBig(u8, 0xc4); 73 | return writer.writeIntBig(u8, @truncate(u8, len)); 74 | } 75 | if (len <= std.math.maxInt(u16)) { 76 | try writer.writeIntBig(u8, 0xc5); 77 | return writer.writeIntBig(u16, @truncate(u16, len)); 78 | } 79 | if (len <= std.math.maxInt(u32)) { 80 | try writer.writeIntBig(u8, 0xc6); 81 | return writer.writeIntBig(u32, @truncate(u32, len)); 82 | } 83 | unreachable; 84 | } 85 | 86 | test "encode bin length" { 87 | try testEncode(encodeBinLen, "\xc4\x00", .{@as(u32, 0x00)}); 88 | try testEncode(encodeBinLen, "\xc4\x01", .{@as(u32, 0x01)}); 89 | try testEncode(encodeBinLen, "\xc4\x1e", .{@as(u32, 0x1e)}); 90 | try testEncode(encodeBinLen, "\xc4\x1f", .{@as(u32, 0x1f)}); 91 | 92 | try testEncode(encodeBinLen, "\xc4\x20", .{@as(u32, 0x20)}); 93 | try testEncode(encodeBinLen, "\xc4\xfe", .{@as(u32, 0xfe)}); 94 | try testEncode(encodeBinLen, "\xc4\xff", .{@as(u32, 0xff)}); 95 | 96 | try testEncode(encodeBinLen, "\xc5\x01\x00", .{@as(u32, 0x0100)}); 97 | try testEncode(encodeBinLen, "\xc5\xff\xfe", .{@as(u32, 0xfffe)}); 98 | try testEncode(encodeBinLen, "\xc5\xff\xff", .{@as(u32, 0xffff)}); 99 | 100 | try testEncode(encodeBinLen, "\xc6\x00\x01\x00\x00", .{@as(u32, 0x00010000)}); 101 | try testEncode(encodeBinLen, "\xc6\xff\xff\xff\xfe", .{@as(u32, 0xfffffffe)}); 102 | try testEncode(encodeBinLen, "\xc6\xff\xff\xff\xff", .{@as(u32, 0xffffffff)}); 103 | 104 | try testEncode(encodeBinLen, "\xc4\x00", .{@as(comptime_int, 0x00)}); 105 | try testEncode(encodeBinLen, "\xc4\x01", .{@as(comptime_int, 0x01)}); 106 | try testEncode(encodeBinLen, "\xc4\x1e", .{@as(comptime_int, 0x1e)}); 107 | try testEncode(encodeBinLen, "\xc4\x1f", .{@as(comptime_int, 0x1f)}); 108 | 109 | try testEncode(encodeBinLen, "\xc4\x20", .{@as(comptime_int, 0x20)}); 110 | try testEncode(encodeBinLen, "\xc4\xfe", .{@as(comptime_int, 0xfe)}); 111 | try testEncode(encodeBinLen, "\xc4\xff", .{@as(comptime_int, 0xff)}); 112 | 113 | try testEncode(encodeBinLen, "\xc5\x01\x00", .{@as(comptime_int, 0x0100)}); 114 | try testEncode(encodeBinLen, "\xc5\xff\xfe", .{@as(comptime_int, 0xfffe)}); 115 | try testEncode(encodeBinLen, "\xc5\xff\xff", .{@as(comptime_int, 0xffff)}); 116 | 117 | try testEncode(encodeBinLen, "\xc6\x00\x01\x00\x00", .{@as(comptime_int, 0x00010000)}); 118 | try testEncode(encodeBinLen, "\xc6\xff\xff\xff\xfe", .{@as(comptime_int, 0xfffffffe)}); 119 | try testEncode(encodeBinLen, "\xc6\xff\xff\xff\xff", .{@as(comptime_int, 0xffffffff)}); 120 | } 121 | 122 | pub inline fn encodeBin(bin: []const u8, writer: anytype) @TypeOf(writer).Error!void { 123 | try encodeBinLen(@truncate(u32, bin.len), writer); 124 | return writer.writeAll(bin); 125 | } 126 | 127 | pub fn encodeExtLen(ext_type: i8, len: u32, writer: anytype) @TypeOf(writer).Error!void { 128 | switch (len) { 129 | 1 => try writer.writeIntBig(u8, 0xd4), 130 | 2 => try writer.writeIntBig(u8, 0xd5), 131 | 4 => try writer.writeIntBig(u8, 0xd6), 132 | 8 => try writer.writeIntBig(u8, 0xd7), 133 | 16 => try writer.writeIntBig(u8, 0xd8), 134 | else => if (len <= std.math.maxInt(u8)) { 135 | try writer.writeIntBig(u8, 0xc7); 136 | try writer.writeIntBig(u8, @truncate(u8, len)); 137 | } else if (len <= std.math.maxInt(u16)) { 138 | try writer.writeIntBig(u8, 0xc8); 139 | try writer.writeIntBig(u16, @truncate(u16, len)); 140 | } else if (len <= std.math.maxInt(u32)) { 141 | try writer.writeIntBig(u8, 0xc9); 142 | try writer.writeIntBig(u32, len); 143 | } else { 144 | unreachable; 145 | }, 146 | } 147 | return writer.writeIntBig(i8, ext_type); 148 | } 149 | 150 | test "encode extension length" { 151 | try testEncode(encodeExtLen, "\xd4\x00", .{ @as(i8, 0), @as(u32, 0x01) }); 152 | try testEncode(encodeExtLen, "\xd5\x00", .{ @as(i8, 0), @as(u32, 0x02) }); 153 | try testEncode(encodeExtLen, "\xd6\x00", .{ @as(i8, 0), @as(u32, 0x04) }); 154 | try testEncode(encodeExtLen, "\xd7\x00", .{ @as(i8, 0), @as(u32, 0x08) }); 155 | try testEncode(encodeExtLen, "\xd8\x00", .{ @as(i8, 0), @as(u32, 0x10) }); 156 | 157 | // ext 8 158 | try testEncode(encodeExtLen, "\xc7\x11\x00", .{ @as(i8, 0), @as(u32, 0x11) }); 159 | try testEncode(encodeExtLen, "\xc7\xfe\x00", .{ @as(i8, 0), @as(u32, 0xfe) }); 160 | try testEncode(encodeExtLen, "\xc7\xff\x00", .{ @as(i8, 0), @as(u32, 0xff) }); 161 | 162 | try testEncode(encodeExtLen, "\xc7\x00\x00", .{ @as(i8, 0), @as(u32, 0x00) }); 163 | try testEncode(encodeExtLen, "\xc7\x03\x00", .{ @as(i8, 0), @as(u32, 0x03) }); 164 | try testEncode(encodeExtLen, "\xc7\x05\x00", .{ @as(i8, 0), @as(u32, 0x05) }); 165 | try testEncode(encodeExtLen, "\xc7\x06\x00", .{ @as(i8, 0), @as(u32, 0x06) }); 166 | try testEncode(encodeExtLen, "\xc7\x07\x00", .{ @as(i8, 0), @as(u32, 0x07) }); 167 | try testEncode(encodeExtLen, "\xc7\x09\x00", .{ @as(i8, 0), @as(u32, 0x09) }); 168 | try testEncode(encodeExtLen, "\xc7\x0a\x00", .{ @as(i8, 0), @as(u32, 0x0a) }); 169 | try testEncode(encodeExtLen, "\xc7\x0b\x00", .{ @as(i8, 0), @as(u32, 0x0b) }); 170 | try testEncode(encodeExtLen, "\xc7\x0c\x00", .{ @as(i8, 0), @as(u32, 0x0c) }); 171 | try testEncode(encodeExtLen, "\xc7\x0d\x00", .{ @as(i8, 0), @as(u32, 0x0d) }); 172 | try testEncode(encodeExtLen, "\xc7\x0e\x00", .{ @as(i8, 0), @as(u32, 0x0e) }); 173 | try testEncode(encodeExtLen, "\xc7\x0f\x00", .{ @as(i8, 0), @as(u32, 0x0f) }); 174 | 175 | // ext 16 176 | try testEncode(encodeExtLen, "\xc8\x01\x00\x00", .{ @as(i8, 0), @as(u32, 0x0100) }); 177 | try testEncode(encodeExtLen, "\xc8\x01\x01\x00", .{ @as(i8, 0), @as(u32, 0x0101) }); 178 | try testEncode(encodeExtLen, "\xc8\xff\xfe\x00", .{ @as(i8, 0), @as(u32, 0xfffe) }); 179 | try testEncode(encodeExtLen, "\xc8\xff\xff\x00", .{ @as(i8, 0), @as(u32, 0xffff) }); 180 | 181 | // ext 32 182 | try testEncode(encodeExtLen, "\xc9\x00\x01\x00\x00\x00", .{ @as(i8, 0), @as(u32, 0x00010000) }); 183 | try testEncode(encodeExtLen, "\xc9\x00\x01\x00\x01\x00", .{ @as(i8, 0), @as(u32, 0x00010001) }); 184 | try testEncode(encodeExtLen, "\xc9\xff\xff\xff\xfe\x00", .{ @as(i8, 0), @as(u32, 0xfffffffe) }); 185 | try testEncode(encodeExtLen, "\xc9\xff\xff\xff\xff\x00", .{ @as(i8, 0), @as(u32, 0xffffffff) }); 186 | 187 | try testEncode(encodeExtLen, "\xd4\x00", .{ @as(i8, 0), @as(comptime_int, 0x01) }); 188 | try testEncode(encodeExtLen, "\xd5\x00", .{ @as(i8, 0), @as(comptime_int, 0x02) }); 189 | try testEncode(encodeExtLen, "\xd6\x00", .{ @as(i8, 0), @as(comptime_int, 0x04) }); 190 | try testEncode(encodeExtLen, "\xd7\x00", .{ @as(i8, 0), @as(comptime_int, 0x08) }); 191 | try testEncode(encodeExtLen, "\xd8\x00", .{ @as(i8, 0), @as(comptime_int, 0x10) }); 192 | 193 | // ext 8 194 | try testEncode(encodeExtLen, "\xc7\x11\x00", .{ @as(i8, 0), @as(comptime_int, 0x11) }); 195 | try testEncode(encodeExtLen, "\xc7\xfe\x00", .{ @as(i8, 0), @as(comptime_int, 0xfe) }); 196 | try testEncode(encodeExtLen, "\xc7\xff\x00", .{ @as(i8, 0), @as(comptime_int, 0xff) }); 197 | 198 | try testEncode(encodeExtLen, "\xc7\x00\x00", .{ @as(i8, 0), @as(comptime_int, 0x00) }); 199 | try testEncode(encodeExtLen, "\xc7\x03\x00", .{ @as(i8, 0), @as(comptime_int, 0x03) }); 200 | try testEncode(encodeExtLen, "\xc7\x05\x00", .{ @as(i8, 0), @as(comptime_int, 0x05) }); 201 | try testEncode(encodeExtLen, "\xc7\x06\x00", .{ @as(i8, 0), @as(comptime_int, 0x06) }); 202 | try testEncode(encodeExtLen, "\xc7\x07\x00", .{ @as(i8, 0), @as(comptime_int, 0x07) }); 203 | try testEncode(encodeExtLen, "\xc7\x09\x00", .{ @as(i8, 0), @as(comptime_int, 0x09) }); 204 | try testEncode(encodeExtLen, "\xc7\x0a\x00", .{ @as(i8, 0), @as(comptime_int, 0x0a) }); 205 | try testEncode(encodeExtLen, "\xc7\x0b\x00", .{ @as(i8, 0), @as(comptime_int, 0x0b) }); 206 | try testEncode(encodeExtLen, "\xc7\x0c\x00", .{ @as(i8, 0), @as(comptime_int, 0x0c) }); 207 | try testEncode(encodeExtLen, "\xc7\x0d\x00", .{ @as(i8, 0), @as(comptime_int, 0x0d) }); 208 | try testEncode(encodeExtLen, "\xc7\x0e\x00", .{ @as(i8, 0), @as(comptime_int, 0x0e) }); 209 | try testEncode(encodeExtLen, "\xc7\x0f\x00", .{ @as(i8, 0), @as(comptime_int, 0x0f) }); 210 | 211 | // ext 16 212 | try testEncode(encodeExtLen, "\xc8\x01\x00\x00", .{ @as(i8, 0), @as(comptime_int, 0x0100) }); 213 | try testEncode(encodeExtLen, "\xc8\x01\x01\x00", .{ @as(i8, 0), @as(comptime_int, 0x0101) }); 214 | try testEncode(encodeExtLen, "\xc8\xff\xfe\x00", .{ @as(i8, 0), @as(comptime_int, 0xfffe) }); 215 | try testEncode(encodeExtLen, "\xc8\xff\xff\x00", .{ @as(i8, 0), @as(comptime_int, 0xffff) }); 216 | 217 | // ext 32 218 | try testEncode(encodeExtLen, "\xc9\x00\x01\x00\x00\x00", .{ @as(i8, 0), @as(comptime_int, 0x00010000) }); 219 | try testEncode(encodeExtLen, "\xc9\x00\x01\x00\x01\x00", .{ @as(i8, 0), @as(comptime_int, 0x00010001) }); 220 | try testEncode(encodeExtLen, "\xc9\xff\xff\xff\xfe\x00", .{ @as(i8, 0), @as(comptime_int, 0xfffffffe) }); 221 | try testEncode(encodeExtLen, "\xc9\xff\xff\xff\xff\x00", .{ @as(i8, 0), @as(comptime_int, 0xffffffff) }); 222 | } 223 | 224 | pub inline fn encodeExt(ext_type: i8, bin: []const u8, writer: anytype) @TypeOf(writer).Error!void { 225 | try encodeExtLen(ext_type, @truncate(u32, bin.len), writer); 226 | return writer.writeAll(bin); 227 | } 228 | 229 | pub inline fn encodeNil(writer: anytype) @TypeOf(writer).Error!void { 230 | return writer.writeIntBig(u8, 0xc0); 231 | } 232 | 233 | test "encode nil" { 234 | try testEncode(encodeNil, "\xc0", .{}); 235 | } 236 | 237 | pub inline fn encodeFloat(num: anytype, writer: anytype) @TypeOf(writer).Error!void { 238 | comptime const T = @TypeOf(num); 239 | comptime const bits = switch (@typeInfo(T)) { 240 | .Float => |floatTypeInfo| floatTypeInfo.bits, 241 | .ComptimeFloat => 64, 242 | else => @compileError("Unable to encode type '" ++ @typeName(T) ++ "'"), 243 | }; 244 | 245 | if (bits <= 32) { 246 | try writer.writeIntBig(u8, 0xca); 247 | const casted = @bitCast(u32, @floatCast(f32, num)); 248 | return writer.writeIntBig(u32, casted); 249 | } 250 | if (bits <= 64) { 251 | try writer.writeIntBig(u8, 0xcb); 252 | const casted = @bitCast(u64, @floatCast(f64, num)); 253 | return writer.writeIntBig(u64, casted); 254 | } 255 | @compileError("Unable to encode type '" ++ @typeName(T) ++ "'"); 256 | } 257 | 258 | test "test float and double" { 259 | try testEncode(encodeFloat, "\xca\x3f\x80\x00\x00", .{@as(f32, 1.0)}); 260 | try testEncode(encodeFloat, "\xca\x40\x49\x0f\xdc", .{@as(f32, 3.141593)}); 261 | try testEncode(encodeFloat, "\xca\xfe\x96\x76\x99", .{@as(f32, -1e+38)}); 262 | 263 | try testEncode(encodeFloat, "\xcb\x3f\xf0\x00\x00\x00\x00\x00\x00", .{@as(f64, 1.0)}); 264 | try testEncode(encodeFloat, "\xcb\x40\x09\x21\xfb\x54\x44\x2d\x18", .{@as(f64, 3.141592653589793)}); 265 | try testEncode(encodeFloat, "\xcb\xd4\x7d\x42\xae\xa2\x87\x9f\x2e", .{@as(f64, -1e+99)}); 266 | 267 | try testEncode(encodeFloat, "\xcb\x3f\xf0\x00\x00\x00\x00\x00\x00", .{@as(comptime_float, 1.0)}); 268 | try testEncode(encodeFloat, "\xcb\x40\x09\x21\xfb\x54\x44\x2d\x18", .{@as(comptime_float, 3.141592653589793)}); 269 | try testEncode(encodeFloat, "\xcb\xd4\x7d\x42\xae\xa2\x87\x9f\x2e", .{@as(comptime_float, -1e+99)}); 270 | } 271 | 272 | pub fn encodeInt(num: anytype, writer: anytype) @TypeOf(writer).Error!void { 273 | comptime const T = @TypeOf(num); 274 | comptime const intInfo = switch (@typeInfo(T)) { 275 | .Int => |intInfo| intInfo, 276 | .ComptimeInt => @typeInfo(std.math.IntFittingRange(num, num)).Int, 277 | else => @compileError("Unable to encode type '" ++ @typeName(T) ++ "'"), 278 | }; 279 | comptime var bits = intInfo.bits; 280 | 281 | if (intInfo.is_signed) { 282 | if (num < 0) { 283 | if (bits <= 6 or num >= minInt(i6)) { 284 | const casted = @truncate(i8, num); 285 | return writer.writeIntBig(u8, 0xe0 | @bitCast(u8, casted)); 286 | } 287 | if (bits <= 8 or num >= minInt(i8)) { 288 | const casted = @truncate(i8, num); 289 | try writer.writeIntBig(u8, 0xd0); 290 | return writer.writeIntBig(i8, casted); 291 | } 292 | if (bits <= 16 or num >= minInt(i16)) { 293 | const casted = @truncate(i16, num); 294 | try writer.writeIntBig(u8, 0xd1); 295 | return writer.writeIntBig(i16, casted); 296 | } 297 | if (bits <= 32 or num >= minInt(i32)) { 298 | const casted = @truncate(i32, num); 299 | try writer.writeIntBig(u8, 0xd2); 300 | return writer.writeIntBig(i32, casted); 301 | } 302 | if (bits <= 64 or num >= minInt(i64)) { 303 | const casted = @truncate(i64, num); 304 | try writer.writeIntBig(u8, 0xd3); 305 | return writer.writeIntBig(i64, casted); 306 | } 307 | @compileError("Unable to encode type '" ++ @typeName(T) ++ "'"); 308 | } 309 | 310 | bits -= 1; 311 | } 312 | 313 | if (bits <= 7 or num <= maxInt(u7)) { 314 | return writer.writeIntBig(u8, @intCast(u8, num)); 315 | } 316 | if (bits <= 8 or num <= maxInt(u8)) { 317 | try writer.writeIntBig(u8, 0xcc); 318 | return writer.writeIntBig(u8, @intCast(u8, num)); 319 | } 320 | if (bits <= 16 or num <= maxInt(u16)) { 321 | try writer.writeIntBig(u8, 0xcd); 322 | return writer.writeIntBig(u16, @intCast(u16, num)); 323 | } 324 | if (bits <= 32 or num <= maxInt(u32)) { 325 | try writer.writeIntBig(u8, 0xce); 326 | return writer.writeIntBig(u32, @intCast(u32, num)); 327 | } 328 | if (bits <= 64 or num <= maxInt(u64)) { 329 | try writer.writeIntBig(u8, 0xcf); 330 | return writer.writeIntBig(u64, @intCast(u64, num)); 331 | } 332 | @compileError("Unable to encode type '" ++ @typeName(T) ++ "'"); 333 | } 334 | 335 | test "encode int and uint" { 336 | try testEncode(encodeInt, "\xff", .{@as(i8, -0x01)}); 337 | try testEncode(encodeInt, "\xe2", .{@as(i8, -0x1e)}); 338 | try testEncode(encodeInt, "\xe1", .{@as(i8, -0x1f)}); 339 | try testEncode(encodeInt, "\xe0", .{@as(i8, -0x20)}); 340 | try testEncode(encodeInt, "\xd0\xdf", .{@as(i8, -0x21)}); 341 | 342 | try testEncode(encodeInt, "\xd0\x81", .{@as(i8, -0x7f)}); 343 | try testEncode(encodeInt, "\xd0\x80", .{@as(i8, -0x80)}); 344 | 345 | try testEncode(encodeInt, "\xd1\xff\x7f", .{@as(i16, -0x81)}); 346 | try testEncode(encodeInt, "\xd1\x80\x01", .{@as(i16, -0x7fff)}); 347 | try testEncode(encodeInt, "\xd1\x80\x00", .{@as(i16, -0x8000)}); 348 | 349 | try testEncode(encodeInt, "\xd2\xff\xff\x7f\xff", .{@as(i32, -0x8001)}); 350 | try testEncode(encodeInt, "\xd2\x80\x00\x00\x01", .{@as(i32, -0x7fffffff)}); 351 | try testEncode(encodeInt, "\xd2\x80\x00\x00\x00", .{@as(i32, -0x80000000)}); 352 | 353 | try testEncode(encodeInt, "\xd3\xff\xff\xff\xff\x7f\xff\xff\xff", .{@as(i64, -0x80000001)}); 354 | try testEncode(encodeInt, "\xd3\x80\x00\x00\x00\x00\x00\x00\x01", .{@as(i64, -0x7fffffffffffffff)}); 355 | try testEncode(encodeInt, "\xd3\x80\x00\x00\x00\x00\x00\x00\x00", .{@as(i64, -0x8000000000000000)}); 356 | 357 | try testEncode(encodeInt, "\x00", .{@as(u8, 0)}); 358 | try testEncode(encodeInt, "\x01", .{@as(u8, 1)}); 359 | try testEncode(encodeInt, "\x7e", .{@as(u8, 0x7e)}); 360 | try testEncode(encodeInt, "\x7f", .{@as(u8, 0x7f)}); 361 | 362 | try testEncode(encodeInt, "\xcc\x80", .{@as(u16, 0x80)}); 363 | try testEncode(encodeInt, "\xcc\xfe", .{@as(u16, 0xfe)}); 364 | try testEncode(encodeInt, "\xcc\xff", .{@as(u16, 0xff)}); 365 | 366 | try testEncode(encodeInt, "\xcd\xff\xfe", .{@as(u32, 0xfffe)}); 367 | try testEncode(encodeInt, "\xcd\xff\xff", .{@as(u32, 0xffff)}); 368 | 369 | try testEncode(encodeInt, "\xce\x00\x01\x00\x00", .{@as(u64, 0x10000)}); 370 | try testEncode(encodeInt, "\xce\xff\xff\xff\xfe", .{@as(u64, 0xfffffffe)}); 371 | try testEncode(encodeInt, "\xce\xff\xff\xff\xff", .{@as(u64, 0xffffffff)}); 372 | 373 | try testEncode(encodeInt, "\xff", .{@as(comptime_int, -0x01)}); 374 | try testEncode(encodeInt, "\xe2", .{@as(comptime_int, -0x1e)}); 375 | try testEncode(encodeInt, "\xe1", .{@as(comptime_int, -0x1f)}); 376 | try testEncode(encodeInt, "\xe0", .{@as(comptime_int, -0x20)}); 377 | try testEncode(encodeInt, "\xd0\xdf", .{@as(comptime_int, -0x21)}); 378 | 379 | try testEncode(encodeInt, "\xd0\x81", .{@as(comptime_int, -0x7f)}); 380 | try testEncode(encodeInt, "\xd0\x80", .{@as(comptime_int, -0x80)}); 381 | 382 | try testEncode(encodeInt, "\xd1\xff\x7f", .{@as(comptime_int, -0x81)}); 383 | try testEncode(encodeInt, "\xd1\x80\x01", .{@as(comptime_int, -0x7fff)}); 384 | try testEncode(encodeInt, "\xd1\x80\x00", .{@as(comptime_int, -0x8000)}); 385 | 386 | try testEncode(encodeInt, "\xd2\xff\xff\x7f\xff", .{@as(comptime_int, -0x8001)}); 387 | try testEncode(encodeInt, "\xd2\x80\x00\x00\x01", .{@as(comptime_int, -0x7fffffff)}); 388 | try testEncode(encodeInt, "\xd2\x80\x00\x00\x00", .{@as(comptime_int, -0x80000000)}); 389 | 390 | try testEncode(encodeInt, "\xd3\xff\xff\xff\xff\x7f\xff\xff\xff", .{@as(comptime_int, -0x80000001)}); 391 | try testEncode(encodeInt, "\xd3\x80\x00\x00\x00\x00\x00\x00\x01", .{@as(comptime_int, -0x7fffffffffffffff)}); 392 | try testEncode(encodeInt, "\xd3\x80\x00\x00\x00\x00\x00\x00\x00", .{@as(comptime_int, -0x8000000000000000)}); 393 | 394 | try testEncode(encodeInt, "\x00", .{@as(comptime_int, 0)}); 395 | try testEncode(encodeInt, "\x01", .{@as(comptime_int, 1)}); 396 | try testEncode(encodeInt, "\x7e", .{@as(comptime_int, 0x7e)}); 397 | try testEncode(encodeInt, "\x7f", .{@as(comptime_int, 0x7f)}); 398 | 399 | try testEncode(encodeInt, "\xcc\x80", .{@as(comptime_int, 0x80)}); 400 | try testEncode(encodeInt, "\xcc\xfe", .{@as(comptime_int, 0xfe)}); 401 | try testEncode(encodeInt, "\xcc\xff", .{@as(comptime_int, 0xff)}); 402 | 403 | try testEncode(encodeInt, "\xcd\xff\xfe", .{@as(comptime_int, 0xfffe)}); 404 | try testEncode(encodeInt, "\xcd\xff\xff", .{@as(comptime_int, 0xffff)}); 405 | 406 | try testEncode(encodeInt, "\xce\x00\x01\x00\x00", .{@as(comptime_int, 0x10000)}); 407 | try testEncode(encodeInt, "\xce\xff\xff\xff\xfe", .{@as(comptime_int, 0xfffffffe)}); 408 | try testEncode(encodeInt, "\xce\xff\xff\xff\xff", .{@as(comptime_int, 0xffffffff)}); 409 | } 410 | 411 | pub inline fn encodeBool(val: bool, writer: anytype) @TypeOf(writer).Error!void { 412 | return writer.writeIntBig(u8, @as(u8, if (val) 0xc3 else 0xc2)); 413 | } 414 | 415 | test "encode bool" { 416 | try testEncode(encodeBool, "\xc3", .{true}); 417 | try testEncode(encodeBool, "\xc2", .{false}); 418 | } 419 | 420 | pub fn encodeArrayLen(len: u32, writer: anytype) @TypeOf(writer).Error!void { 421 | if (len <= std.math.maxInt(u4)) { 422 | return writer.writeIntBig(u8, 0x90 | @truncate(u8, len)); 423 | } 424 | if (len <= std.math.maxInt(u16)) { 425 | try writer.writeIntBig(u8, 0xdc); 426 | return writer.writeIntBig(u16, @truncate(u16, len)); 427 | } 428 | try writer.writeIntBig(u8, 0xdd); 429 | return writer.writeIntBig(u32, @truncate(u32, len)); 430 | } 431 | 432 | test "encode array length" { 433 | try testEncode(encodeArrayLen, "\x90", .{@as(u32, 0)}); 434 | try testEncode(encodeArrayLen, "\x91", .{@as(u32, 1)}); 435 | try testEncode(encodeArrayLen, "\x9f", .{@as(u32, 15)}); 436 | try testEncode(encodeArrayLen, "\xdc\x00\x10", .{@as(u32, 16)}); 437 | try testEncode(encodeArrayLen, "\xdc\xff\xfe", .{@as(u32, 0xfffe)}); 438 | try testEncode(encodeArrayLen, "\xdc\xff\xff", .{@as(u32, 0xffff)}); 439 | try testEncode(encodeArrayLen, "\xdd\x00\x01\x00\x00", .{@as(u32, 0x10000)}); 440 | try testEncode(encodeArrayLen, "\xdd\xff\xff\xff\xfe", .{@as(u32, 0xfffffffe)}); 441 | try testEncode(encodeArrayLen, "\xdd\xff\xff\xff\xff", .{@as(u32, 0xffffffff)}); 442 | 443 | try testEncode(encodeArrayLen, "\x90", .{@as(comptime_int, 0)}); 444 | try testEncode(encodeArrayLen, "\x91", .{@as(comptime_int, 1)}); 445 | try testEncode(encodeArrayLen, "\x9f", .{@as(comptime_int, 15)}); 446 | try testEncode(encodeArrayLen, "\xdc\x00\x10", .{@as(comptime_int, 16)}); 447 | try testEncode(encodeArrayLen, "\xdc\xff\xfe", .{@as(comptime_int, 0xfffe)}); 448 | try testEncode(encodeArrayLen, "\xdc\xff\xff", .{@as(comptime_int, 0xffff)}); 449 | try testEncode(encodeArrayLen, "\xdd\x00\x01\x00\x00", .{@as(comptime_int, 0x10000)}); 450 | try testEncode(encodeArrayLen, "\xdd\xff\xff\xff\xfe", .{@as(comptime_int, 0xfffffffe)}); 451 | try testEncode(encodeArrayLen, "\xdd\xff\xff\xff\xff", .{@as(comptime_int, 0xffffffff)}); 452 | } 453 | 454 | pub inline fn encodeArray(arr: anytype, options: EncodingOptions, writer: anytype) @TypeOf(writer).Error!void { 455 | comptime const T = @TypeOf(arr); 456 | switch (@typeInfo(T)) { 457 | .Pointer => |ptr_info| switch (ptr_info.size) { 458 | .One => switch (@typeInfo(ptr_info.child)) { 459 | .Array => { 460 | const Slice = []const std.meta.Elem(ptr_info.child); 461 | return encodeArray(@as(Slice, arr), options, writer); 462 | }, 463 | else => @compileError("Unable to encode type '" ++ @typeName(T) ++ "'"), 464 | }, 465 | .Many, .Slice => { 466 | try encodeArrayLen(@truncate(u32, arr.len), writer); 467 | for (arr) |value| { 468 | try encode(value, options, writer); 469 | } 470 | }, 471 | else => @compileError("Unable to encode type '" ++ @typeName(T) ++ "'"), 472 | }, 473 | .Array => { 474 | return encodeArray(&arr, options, writer); 475 | }, 476 | else => @compileError("Unable to encode type '" ++ @typeName(T) ++ "'"), 477 | } 478 | } 479 | 480 | test "encode array" { 481 | var testArray = [_]i32{ 1, 2, 3, 4 }; 482 | 483 | try testEncode(encodeArray, "\x94\x01\x02\x03\x04", .{ testArray, .{} }); 484 | try testEncode(encodeArray, "\x94\x01\x02\x03\x04", .{ &testArray, .{} }); 485 | try testEncode(encodeArray, "\x94\x01\x02\x03\x04", .{ testArray[0..testArray.len], .{} }); 486 | } 487 | 488 | pub fn encodeMapLen(len: u32, writer: anytype) @TypeOf(writer).Error!void { 489 | if (len <= std.math.maxInt(u4)) { 490 | return writer.writeIntBig(u8, 0x80 | @truncate(u8, len)); 491 | } 492 | if (len <= std.math.maxInt(u16)) { 493 | try writer.writeIntBig(u8, 0xde); 494 | return writer.writeIntBig(u16, @truncate(u16, len)); 495 | } 496 | if (len <= std.math.maxInt(u32)) { 497 | try writer.writeIntBig(u8, 0xdf); 498 | return writer.writeIntBig(u32, @truncate(u32, len)); 499 | } 500 | unreachable; 501 | } 502 | 503 | test "encode map length" { 504 | try testEncode(encodeMapLen, "\x80", .{@as(u32, 0)}); 505 | try testEncode(encodeMapLen, "\x81", .{@as(u32, 1)}); 506 | try testEncode(encodeMapLen, "\x8f", .{@as(u32, 15)}); 507 | try testEncode(encodeMapLen, "\xde\x00\x10", .{@as(u32, 16)}); 508 | try testEncode(encodeMapLen, "\xde\xff\xfe", .{@as(u32, 0xfffe)}); 509 | try testEncode(encodeMapLen, "\xde\xff\xff", .{@as(u32, 0xffff)}); 510 | try testEncode(encodeMapLen, "\xdf\x00\x01\x00\x00", .{@as(u32, 0x10000)}); 511 | try testEncode(encodeMapLen, "\xdf\xff\xff\xff\xfe", .{@as(u32, 0xfffffffe)}); 512 | try testEncode(encodeMapLen, "\xdf\xff\xff\xff\xff", .{@as(u32, 0xffffffff)}); 513 | 514 | try testEncode(encodeMapLen, "\x80", .{@as(comptime_int, 0)}); 515 | try testEncode(encodeMapLen, "\x81", .{@as(comptime_int, 1)}); 516 | try testEncode(encodeMapLen, "\x8f", .{@as(comptime_int, 15)}); 517 | try testEncode(encodeMapLen, "\xde\x00\x10", .{@as(comptime_int, 16)}); 518 | try testEncode(encodeMapLen, "\xde\xff\xfe", .{@as(comptime_int, 0xfffe)}); 519 | try testEncode(encodeMapLen, "\xde\xff\xff", .{@as(comptime_int, 0xffff)}); 520 | try testEncode(encodeMapLen, "\xdf\x00\x01\x00\x00", .{@as(comptime_int, 0x10000)}); 521 | try testEncode(encodeMapLen, "\xdf\xff\xff\xff\xfe", .{@as(comptime_int, 0xfffffffe)}); 522 | try testEncode(encodeMapLen, "\xdf\xff\xff\xff\xff", .{@as(comptime_int, 0xffffffff)}); 523 | } 524 | 525 | pub inline fn encodeStruct( 526 | structure: anytype, 527 | options: EncodingOptions, 528 | writer: anytype, 529 | ) @TypeOf(writer).Error!void { 530 | const T = @TypeOf(structure); 531 | if (comptime std.meta.trait.hasFn("encodeMsgPack")(T)) { 532 | return structure.encodeMsgPack(options, writer); 533 | } 534 | comptime const fields = @typeInfo(T).Struct.fields; 535 | try switch (options.struct_encoding) { 536 | .array => encodeArrayLen(fields.len, writer), 537 | .map => encodeMapLen(fields.len, writer), 538 | }; 539 | 540 | inline for (fields) |Field| { 541 | if (Field.field_type == void) { 542 | continue; 543 | } 544 | 545 | if (options.struct_encoding == StructEncoding.map) { 546 | try encode(Field.name, options, writer); 547 | } 548 | try encode(@field(structure, Field.name), options, writer); 549 | } 550 | } 551 | 552 | test "encode struct" { 553 | const testingStruct = .{ 554 | .int = @as(i32, 65534), 555 | .float = @as(f64, 3.141592653589793), 556 | .boolean = true, 557 | .nil = null, 558 | .string = "string", 559 | .array = @as([4]i16, .{ 11, 22, 33, 44 }), 560 | }; 561 | 562 | try testEncode( 563 | encodeStruct, 564 | "\x96\xCD\xFF\xFE\xCB\x40\x09\x21\xFB\x54\x44\x2D\x18\xC3\xC0\xA6\x73\x74\x72\x69\x6E\x67\x94\x0B\x16\x21\x2C", 565 | .{ testingStruct, .{ .struct_encoding = .array, .u8_array_encoding = .string } }, 566 | ); 567 | try testEncode( 568 | encodeStruct, 569 | "\x86\xA3\x69\x6E\x74\xCD\xFF\xFE\xA5\x66\x6C\x6F\x61\x74\xCB\x40\x09\x21\xFB\x54\x44\x2D\x18\xA7\x62\x6F\x6F\x6C\x65\x61\x6E\xC3\xA3\x6E\x69\x6C\xC0\xA6\x73\x74\x72\x69\x6E\x67\xA6\x73\x74\x72\x69\x6E\x67\xA5\x61\x72\x72\x61\x79\x94\x0B\x16\x21\x2C", 570 | .{ testingStruct, .{ .struct_encoding = .map, .u8_array_encoding = .string } }, 571 | ); 572 | 573 | const CustomStruct = struct { 574 | int: i64 = 0, 575 | float: f64 = 0, 576 | boolean: bool = false, 577 | 578 | const Self = @This(); 579 | pub fn encodeMsgPack(value: Self, options: EncodingOptions, writer: anytype) @TypeOf(writer).Error!void { 580 | return encodeBool(true, writer); 581 | } 582 | }; 583 | try testEncode(encodeStruct, "\xc3", .{ 584 | CustomStruct{}, 585 | .{}, 586 | }); 587 | } 588 | 589 | pub inline fn encodeOptional( 590 | value: anytype, 591 | options: EncodingOptions, 592 | writer: anytype, 593 | ) @TypeOf(writer).Error!void { 594 | return if (value) |payload| encode(payload, options, writer) else encodeNil(writer); 595 | } 596 | 597 | test "encode optional" { 598 | try testEncode(encodeOptional, "\xc0", .{ @as(?i32, null), .{} }); 599 | try testEncode(encodeOptional, "\xcd\xff\xfe", .{ @as(?i32, 65534), .{} }); 600 | } 601 | 602 | pub fn encodeUnion( 603 | value: anytype, 604 | options: EncodingOptions, 605 | writer: anytype, 606 | ) @TypeOf(writer).Error!void { 607 | comptime const T = @TypeOf(value); 608 | if (comptime std.meta.trait.hasFn("encodeMsgPack")(T)) { 609 | return value.encodeMsgPack(options, writer); 610 | } 611 | comptime const info = switch (@typeInfo(T)) { 612 | .Union => |unionInfo| unionInfo, 613 | else => @compileError("Unable to encode type '" ++ @typeName(T) ++ "'"), 614 | }; 615 | 616 | if (info.tag_type) |TagType| { 617 | inline for (info.fields) |field| { 618 | if (value == @field(TagType, field.name)) { 619 | return encode(@field(value, field.name), options, writer); 620 | } 621 | } 622 | } else { 623 | @compileError("Unable to encode untagged union '" ++ @typeName(T) ++ "'"); 624 | } 625 | } 626 | 627 | test "encode tagged union" { 628 | const TaggedUnion = union(enum) { 629 | int: i64, 630 | float: f64, 631 | boolean: bool, 632 | }; 633 | 634 | try testEncode(encodeUnion, "\xd3\xff\xff\xff\xff\x7f\xff\xff\xff", .{ 635 | TaggedUnion{ .int = -0x80000001 }, 636 | .{}, 637 | }); 638 | try testEncode(encodeUnion, "\xcb\x3f\xf0\x00\x00\x00\x00\x00\x00", .{ 639 | TaggedUnion{ .float = 1.0 }, 640 | .{}, 641 | }); 642 | try testEncode(encodeUnion, "\xc3", .{ 643 | TaggedUnion{ .boolean = true }, 644 | .{}, 645 | }); 646 | 647 | const TaggedUnionCustom = union(enum) { 648 | int: i64, 649 | float: f64, 650 | boolean: bool, 651 | 652 | const Self = @This(); 653 | pub fn encodeMsgPack(value: Self, options: EncodingOptions, writer: anytype) @TypeOf(writer).Error!void { 654 | return encodeBool(true, writer); 655 | } 656 | }; 657 | try testEncode(encodeUnion, "\xc3", .{ 658 | TaggedUnionCustom{ .boolean = false }, 659 | .{}, 660 | }); 661 | 662 | const EnumCustom = enum { 663 | a, 664 | b, 665 | c, 666 | const Self = @This(); 667 | pub fn encodeMsgPack(value: Self, options: EncodingOptions, writer: anytype) @TypeOf(writer).Error!void { 668 | return encodeBool(true, writer); 669 | } 670 | }; 671 | try testEncode(encode, "\xc3", .{ 672 | EnumCustom.a, 673 | .{}, 674 | }); 675 | } 676 | 677 | const U8ArrayEncoding = enum { 678 | auto, // encodes as string if valid utf8 string, otherwise encodes as binary 679 | array, 680 | string, 681 | binary, 682 | }; 683 | const StructEncoding = enum { 684 | array, 685 | map, 686 | }; 687 | pub const EncodingOptions = struct { 688 | u8_array_encoding: U8ArrayEncoding = .auto, 689 | struct_encoding: StructEncoding = .array, 690 | }; 691 | 692 | pub inline fn encode( 693 | value: anytype, 694 | options: EncodingOptions, 695 | writer: anytype, 696 | ) @TypeOf(writer).Error!void { 697 | const T = @TypeOf(value); 698 | return switch (@typeInfo(T)) { 699 | .Float, .ComptimeFloat => encodeFloat(value, writer), 700 | .Int, .ComptimeInt => encodeInt(value, writer), 701 | .Bool => encodeBool(value, writer), 702 | .Optional => encodeOptional(value, options, writer), 703 | .Struct => encodeStruct(value, options, writer), 704 | .Pointer => |ptr_info| switch (ptr_info.size) { 705 | .One => switch (@typeInfo(ptr_info.child)) { 706 | .Array => blk: { 707 | const Slice = []const std.meta.Elem(ptr_info.child); 708 | break :blk encode(@as(Slice, value), options, writer); 709 | }, 710 | else => encode(value.*, options, writer), 711 | }, 712 | .Many, .Slice => blk: { 713 | if (ptr_info.child == u8) { 714 | break :blk switch (options.u8_array_encoding) { 715 | .array => encodeArray(value, options, writer), 716 | .auto => { 717 | if (std.unicode.utf8ValidateSlice(value)) { 718 | break :blk encodeStr(value, writer); 719 | } else { 720 | break :blk encodeBin(value, writer); 721 | } 722 | }, 723 | .string => encodeStr(value, writer), 724 | .binary => encodeBin(value, writer), 725 | }; 726 | } 727 | break :blk encodeArray(value, options, writer); 728 | }, 729 | else => @compileError("Unable to encode type '" ++ @typeName(T) ++ "'"), 730 | }, 731 | .Array => encodeArray(&value, options, writer), 732 | .ErrorSet => encodeStr(@errorName(value), writer), 733 | .Enum => { 734 | if (comptime std.meta.trait.hasFn("encodeMsgPack")(T)) { 735 | return value.encodeMsgPack(options, writer); 736 | } 737 | @compileError("Unable to encode enum '" ++ @typeName(T) ++ "'"); 738 | }, 739 | .Union => encodeUnion(value, options, writer), 740 | .Null => encodeNil(writer), 741 | else => @compileError("Unable to encode type '" ++ @typeName(T) ++ "'"), 742 | }; 743 | } 744 | 745 | test "encode" { 746 | const opts = .{}; 747 | 748 | try testEncode(encode, "\xca\x40\x49\x0f\xdc", .{ @as(f32, 3.141593), opts }); 749 | try testEncode(encode, "\xcb\x40\x09\x21\xfb\x54\x44\x2d\x18", .{ @as(f64, 3.141592653589793), opts }); 750 | 751 | try testEncode(encode, "\xd2\xff\xff\x7f\xff", .{ @as(i32, -0x8001), opts }); 752 | try testEncode(encode, "\xcd\xff\xfe", .{ @as(u32, 0xfffe), opts }); 753 | 754 | try testEncode(encode, "\xc0", .{ null, opts }); 755 | 756 | try testEncode(encode, "\xc3", .{ true, opts }); 757 | try testEncode(encode, "\xc2", .{ false, opts }); 758 | 759 | try testEncode(encode, "\xa6string", .{ "string", opts }); 760 | 761 | try testEncode(encode, "\xc0", .{ @as(?i32, null), opts }); 762 | try testEncode(encode, "\xcd\xff\xfe", .{ @as(?i32, 65534), opts }); 763 | 764 | const testingStruct = .{ 765 | .int = @as(i32, 65534), 766 | .float = @as(f64, 3.141592653589793), 767 | .boolean = true, 768 | .nil = null, 769 | .string = "string", 770 | .array = @as([4]i16, .{ 11, 22, 33, 44 }), 771 | }; 772 | try testEncode( 773 | encode, 774 | "\x96\xCD\xFF\xFE\xCB\x40\x09\x21\xFB\x54\x44\x2D\x18\xC3\xC0\xA6\x73\x74\x72\x69\x6E\x67\x94\x0B\x16\x21\x2C", 775 | .{ testingStruct, .{ .struct_encoding = .array, .u8_array_encoding = .string } }, 776 | ); 777 | try testEncode( 778 | encode, 779 | "\x86\xA3\x69\x6E\x74\xCD\xFF\xFE\xA5\x66\x6C\x6F\x61\x74\xCB\x40\x09\x21\xFB\x54\x44\x2D\x18\xA7\x62\x6F\x6F\x6C\x65\x61\x6E\xC3\xA3\x6E\x69\x6C\xC0\xA6\x73\x74\x72\x69\x6E\x67\xA6\x73\x74\x72\x69\x6E\x67\xA5\x61\x72\x72\x61\x79\x94\x0B\x16\x21\x2C", 780 | .{ testingStruct, .{ .struct_encoding = .map, .u8_array_encoding = .string } }, 781 | ); 782 | 783 | var testArray = [_]i32{ 1, 2, 3, 4 }; 784 | try testEncode(encode, "\x94\x01\x02\x03\x04", .{ testArray, opts }); 785 | try testEncode(encode, "\x94\x01\x02\x03\x04", .{ &testArray, opts }); 786 | try testEncode(encode, "\x94\x01\x02\x03\x04", .{ testArray[0..testArray.len], opts }); 787 | 788 | const TaggedUnionCustom = union(enum) { 789 | int: i64, 790 | float: f64, 791 | boolean: bool, 792 | 793 | const Self = @This(); 794 | pub fn encodeMsgPack(value: Self, options: EncodingOptions, writer: anytype) @TypeOf(writer).Error!void { 795 | return encodeBool(true, writer); 796 | } 797 | }; 798 | try testEncode(encode, "\xc3", .{ 799 | TaggedUnionCustom{ .boolean = false }, 800 | .{}, 801 | }); 802 | 803 | const test_string_non_utf8 = "\xff\xff\xff\xff"; 804 | try testEncode(encode, "\xC4\x04" ++ test_string_non_utf8, .{ 805 | test_string_non_utf8, 806 | .{ .u8_array_encoding = .auto }, 807 | }); 808 | const test_string_utf8 = "some string"; 809 | try testEncode(encode, "\xAB" ++ test_string_utf8, .{ 810 | test_string_utf8, 811 | .{ .u8_array_encoding = .auto }, 812 | }); 813 | try testEncode(encode, "\x9B" ++ test_string_utf8, .{ 814 | test_string_utf8, 815 | .{ .u8_array_encoding = .array }, 816 | }); 817 | try testEncode(encode, "\xAB" ++ test_string_utf8, .{ 818 | test_string_utf8, 819 | .{ .u8_array_encoding = .string }, 820 | }); 821 | try testEncode(encode, "\xC4\x0B" ++ test_string_utf8, .{ 822 | test_string_utf8, 823 | .{ .u8_array_encoding = .binary }, 824 | }); 825 | } 826 | 827 | fn testEncode(func: anytype, comptime expected: []const u8, input: anytype) !void { 828 | var buf: [255]u8 = undefined; 829 | var fbs = std.io.fixedBufferStream(&buf); 830 | const writer = fbs.writer(); 831 | 832 | const args = input ++ .{writer}; 833 | try @call(.{}, func, args); 834 | const result = fbs.getWritten(); 835 | testing.expectEqualSlices(u8, expected, result); 836 | } 837 | --------------------------------------------------------------------------------