An HTTP/1.1 server for zig
0

Configure Feed

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

http.zig / src / key_value.zig
6.5 kB 222 lines
1const std = @import("std"); 2 3const mem = std.mem; 4const ascii = std.ascii; 5const Allocator = std.mem.Allocator; 6 7fn KeyValue(V: type, hashFn: fn (key: []const u8) callconv(.@"inline") u8) type { 8 return struct { 9 len: usize, 10 keys: [][]const u8, 11 values: []V, 12 hashes: []u8, 13 14 const Self = @This(); 15 pub const Value = V; 16 17 const alignment = @max(@alignOf([]const u8), @alignOf(V)); 18 const size = @sizeOf([]const u8) + @sizeOf(V) + @sizeOf(u8); 19 const kFirst = @alignOf([]const u8) >= @alignOf(V); 20 21 pub fn init(allocator: Allocator, max: usize) Allocator.Error!Self { 22 // we want type with bigger alignment to be first. 23 // Since alignment is always a power of 2, the second type is guaranteed to have correct alignment. 24 25 const allocation = try allocator.alignedAlloc(u8, std.mem.Alignment.fromByteUnits(alignment), max * size); 26 return .{ 27 .len = 0, 28 .keys = @as([*][]const u8, @ptrCast(@alignCast(if (kFirst) allocation.ptr else allocation[max * @sizeOf(V) ..].ptr)))[0..max], 29 .values = @as([*]V, @ptrCast(@alignCast(if (kFirst) allocation[max * @sizeOf([]const u8) ..].ptr else allocation.ptr)))[0..max], 30 .hashes = allocation[max * @sizeOf([]const u8) + max * @sizeOf(V) ..], 31 }; 32 } 33 34 pub fn deinit(self: *Self, allocator: Allocator) void { 35 const allocation = @as([*]align(alignment) u8, @ptrCast(@alignCast(if (kFirst) self.keys.ptr else self.values.ptr))); 36 allocator.free(allocation[0 .. self.keys.len * size]); 37 } 38 39 pub fn add(self: *Self, key: []const u8, value: V) void { 40 const len = self.len; 41 const max = self.keys.len; 42 var keys = self.keys; 43 if (len == max) { 44 return; 45 } 46 47 keys[len] = key; 48 self.values[len] = value; 49 self.hashes[len] = hashFn(key); 50 self.len = len + 1; 51 } 52 53 pub fn get(self: *const Self, key: []const u8) ?V { 54 const hash = hashFn(key); 55 for (self.hashes[0..self.len], 0..) |h, i| { 56 if (h == hash and std.mem.eql(u8, self.keys[i], key)) { 57 return self.values[i]; 58 } 59 } 60 return null; 61 } 62 63 pub fn has(self: *const Self, key: []const u8) bool { 64 return self.get(key) != null; 65 } 66 67 pub fn reset(self: *Self) void { 68 self.len = 0; 69 } 70 71 pub fn iterator(self: *const Self) Iterator { 72 const len = self.len; 73 return .{ 74 .pos = 0, 75 .keys = self.keys[0..len], 76 .values = self.values[0..len], 77 }; 78 } 79 80 pub const Iterator = struct { 81 pos: usize, 82 keys: [][]const u8, 83 values: []V, 84 85 const KV = struct { 86 key: []const u8, 87 value: V, 88 }; 89 90 pub fn next(self: *Iterator) ?KV { 91 const pos = self.pos; 92 if (pos == self.keys.len) { 93 return null; 94 } 95 96 self.pos = pos + 1; 97 return .{ 98 .key = self.keys[pos], 99 .value = self.values[pos], 100 }; 101 } 102 }; 103 }; 104} 105 106inline fn strHash(key: []const u8) u8 { 107 if (key.len == 0) { 108 return 0; 109 } 110 111 var h: u8 = 5; 112 for (key) |c| { 113 h = h *% 31 +% c; 114 } 115 return h; 116} 117 118pub const StringKeyValue = KeyValue([]const u8, strHash); 119 120const MultiForm = struct { 121 value: []const u8, 122 filename: ?[]const u8 = null, 123}; 124pub const MultiFormKeyValue = KeyValue(MultiForm, strHash); 125 126const t = @import("t.zig"); 127test "KeyValue: get" { 128 var kv = try StringKeyValue.init(t.allocator, 2); 129 defer kv.deinit(t.allocator); 130 131 var key = "content-type".*; 132 kv.add(&key, "application/json"); 133 134 try t.expectEqual("application/json", kv.get("content-type").?); 135 136 kv.reset(); 137 try t.expectEqual(null, kv.get("content-type")); 138 kv.add(&key, "application/json2"); 139 try t.expectEqual("application/json2", kv.get("content-type").?); 140} 141 142test "KeyValue: ignores beyond max" { 143 var kv = try StringKeyValue.init(t.allocator, 2); 144 defer kv.deinit(t.allocator); 145 var n1 = "content-length".*; 146 kv.add(&n1, "cl"); 147 148 var n2 = "host".*; 149 kv.add(&n2, "www"); 150 151 var n3 = "authorization".*; 152 kv.add(&n3, "hack"); 153 154 try t.expectEqual("cl", kv.get("content-length").?); 155 try t.expectEqual("www", kv.get("host").?); 156 try t.expectEqual(null, kv.get("authorization")); 157 158 { 159 var it = kv.iterator(); 160 { 161 const field = it.next().?; 162 try t.expectString("content-length", field.key); 163 try t.expectString("cl", field.value); 164 } 165 166 { 167 const field = it.next().?; 168 try t.expectString("host", field.key); 169 try t.expectString("www", field.value); 170 } 171 try t.expectEqual(null, it.next()); 172 } 173} 174 175test "MultiFormKeyValue: get" { 176 var kv = try MultiFormKeyValue.init(t.allocator, 2); 177 defer kv.deinit(t.allocator); 178 179 var key = "username".*; 180 kv.add(&key, .{ .value = "leto" }); 181 182 try t.expectEqual("leto", kv.get("username").?.value); 183 184 kv.reset(); 185 try t.expectEqual(null, kv.get("username")); 186 kv.add(&key, .{ .value = "leto2" }); 187 try t.expectEqual("leto2", kv.get("username").?.value); 188} 189 190test "MultiFormKeyValue: ignores beyond max" { 191 var kv = try MultiFormKeyValue.init(t.allocator, 2); 192 defer kv.deinit(t.allocator); 193 194 var n1 = "username".*; 195 kv.add(&n1, .{ .value = "leto" }); 196 197 var n2 = "password".*; 198 kv.add(&n2, .{ .value = "ghanima" }); 199 200 var n3 = "other".*; 201 kv.add(&n3, .{ .value = "hack" }); 202 203 try t.expectEqual("leto", kv.get("username").?.value); 204 try t.expectEqual("ghanima", kv.get("password").?.value); 205 try t.expectEqual(null, kv.get("other")); 206 207 { 208 var it = kv.iterator(); 209 { 210 const field = it.next().?; 211 try t.expectString("username", field.key); 212 try t.expectString("leto", field.value.value); 213 } 214 215 { 216 const field = it.next().?; 217 try t.expectString("password", field.key); 218 try t.expectString("ghanima", field.value.value); 219 } 220 try t.expectEqual(null, it.next()); 221 } 222}