@@ -64,6 +64,9 @@ pub fn run(allocator: std.mem.Allocator, indexes: *MultiIndex, address: []const
64
64
var server = try Server .init (allocator , config , & ctx );
65
65
defer server .deinit ();
66
66
67
+ server .errorHandler (handleError );
68
+ server .notFound (handleNotFound );
69
+
67
70
try installSignalHandlers (& server );
68
71
69
72
var router = server .router ();
@@ -113,9 +116,10 @@ fn getIndex(ctx: *Context, req: *httpz.Request, res: *httpz.Response, send_body:
113
116
const index_name = req .param ("index" ) orelse return null ;
114
117
const index = ctx .indexes .getIndex (index_name ) catch | err | {
115
118
if (err == error .IndexNotFound ) {
116
- res .status = 404 ;
117
119
if (send_body ) {
118
- try res .json (.{ .status = "index not found" }, .{});
120
+ try writeErrorResponse (400 , err , req , res );
121
+ } else {
122
+ res .status = 404 ;
119
123
}
120
124
return null ;
121
125
}
@@ -133,13 +137,16 @@ const ContentType = enum {
133
137
msgpack ,
134
138
};
135
139
136
- fn parseContentTypeHeader (content_type : []const u8 ) ! ContentType {
137
- if (std .mem .eql (u8 , content_type , "application/json" )) {
138
- return .json ;
139
- } else if (std .mem .eql (u8 , content_type , "application/vnd.msgpack" )) {
140
- return .msgpack ;
140
+ fn parseContentTypeHeader (req : * httpz.Request ) ! ContentType {
141
+ if (req .header ("content-type" )) | content_type | {
142
+ if (std .mem .eql (u8 , content_type , "application/json" )) {
143
+ return .json ;
144
+ } else if (std .mem .eql (u8 , content_type , "application/vnd.msgpack" )) {
145
+ return .msgpack ;
146
+ }
147
+ return error .InvalidContentType ;
141
148
}
142
- return error . InvalidContentType ;
149
+ return .json ;
143
150
}
144
151
145
152
fn parseAcceptHeader (req : * httpz.Request ) ContentType {
@@ -165,36 +172,52 @@ fn writeResponse(value: anytype, req: *httpz.Request, res: *httpz.Response) !voi
165
172
}
166
173
}
167
174
175
+ const ErrorResponse = struct {
176
+ @"error" : []const u8 ,
177
+
178
+ pub fn msgpackFormat () msgpack.StructFormat {
179
+ return .{ .as_map = .{ .key = .{ .field_name_prefix = 1 } } };
180
+ }
181
+ };
182
+
183
+ fn handleNotFound (_ : * Context , req : * httpz.Request , res : * httpz.Response ) ! void {
184
+ try writeErrorResponse (404 , error .NotFound , req , res );
185
+ }
186
+
187
+ fn handleError (_ : * Context , req : * httpz.Request , res : * httpz.Response , err : anyerror ) void {
188
+ log .err ("unhandled error in {s}: {any}" , .{ req .url .raw , err });
189
+ writeErrorResponse (500 , err , req , res ) catch {
190
+ res .status = 500 ;
191
+ res .body = "internal error" ;
192
+ };
193
+ }
194
+
195
+ fn writeErrorResponse (status : u16 , err : anyerror , req : * httpz.Request , res : * httpz.Response ) ! void {
196
+ res .status = status ;
197
+ try writeResponse (ErrorResponse { .@"error" = @errorName (err ) }, req , res );
198
+ }
199
+
168
200
fn getRequestBody (comptime T : type , req : * httpz.Request , res : * httpz.Response ) ! ? T {
169
201
const content = req .body () orelse {
170
- res .status = 400 ;
171
- try writeResponse (.{ .status = "no content" }, req , res );
202
+ try writeErrorResponse (400 , error .NoContent , req , res );
172
203
return null ;
173
204
};
174
205
175
- const content_type_name = req .header ("content-type" ) orelse {
176
- res .status = 415 ;
177
- try writeResponse (.{ .status = "missing content type header" }, req , res );
178
- return null ;
179
- };
180
- const content_type = parseContentTypeHeader (content_type_name ) catch {
181
- res .status = 415 ;
182
- try writeResponse (.{ .status = "unsupported content type" }, req , res );
206
+ const content_type = parseContentTypeHeader (req ) catch {
207
+ try writeErrorResponse (415 , error .UnsupportedContentType , req , res );
183
208
return null ;
184
209
};
185
210
186
211
switch (content_type ) {
187
212
.json = > {
188
213
return json .parseFromSliceLeaky (T , req .arena , content , .{}) catch {
189
- res .status = 400 ;
190
- try writeResponse (.{ .status = "invalid body" }, req , res );
214
+ try writeErrorResponse (400 , error .InvalidContent , req , res );
191
215
return null ;
192
216
};
193
217
},
194
218
.msgpack = > {
195
219
return msgpack .decodeFromSliceLeaky (T , req .arena , content ) catch {
196
- res .status = 400 ;
197
- try writeResponse (.{ .status = "invalid body" }, req , res );
220
+ try writeErrorResponse (400 , error .InvalidContent , req , res );
198
221
return null ;
199
222
};
200
223
},
@@ -221,11 +244,7 @@ fn handleSearch(ctx: *Context, req: *httpz.Request, res: *httpz.Response) !void
221
244
222
245
metrics .search ();
223
246
224
- const results = index .search (body .query , req .arena , deadline ) catch | err | {
225
- log .err ("index search error: {}" , .{err });
226
- res .status = 500 ;
227
- return writeResponse (.{ .status = "internal error" }, req , res );
228
- };
247
+ const results = try index .search (body .query , req .arena , deadline );
229
248
230
249
var results_json = SearchResultsJSON { .results = try req .arena .alloc (SearchResultJSON , results .count ()) };
231
250
for (results .values (), 0.. ) | r , i | {
@@ -253,21 +272,14 @@ fn handleUpdate(ctx: *Context, req: *httpz.Request, res: *httpz.Response) !void
253
272
254
273
metrics .update (body .changes .len );
255
274
256
- index .update (body .changes ) catch | err | {
257
- log .err ("index search error: {}" , .{err });
258
- res .status = 500 ;
259
- return writeResponse (.{ .status = "internal error" }, req , res );
260
- };
275
+ try index .update (body .changes );
261
276
262
277
return writeResponse (.{ .status = "ok" }, req , res );
263
278
}
264
279
265
280
fn handleHeadIndex (ctx : * Context , req : * httpz.Request , res : * httpz.Response ) ! void {
266
281
const index_ref = try getIndex (ctx , req , res , false ) orelse return ;
267
282
defer releaseIndex (ctx , index_ref );
268
-
269
- res .status = 200 ;
270
- return ;
271
283
}
272
284
273
285
const Attributes = struct {
@@ -284,47 +296,56 @@ const Attributes = struct {
284
296
}
285
297
try jws .endArray ();
286
298
}
299
+
300
+ pub fn msgpackWrite (self : Attributes , packer : anytype ) ! void {
301
+ try packer .writeMapHeader (self .attributes .count ());
302
+ var iter = self .attributes .iterator ();
303
+ while (iter .next ()) | entry | {
304
+ try packer .write (@TypeOf (entry .key_ptr .* ), entry .key_ptr .* );
305
+ try packer .write (@TypeOf (entry .value_ptr .* ), entry .value_ptr .* );
306
+ }
307
+ }
287
308
};
288
309
289
310
const GetIndexResponse = struct {
290
- status : [] const u8 ,
311
+ version : u64 ,
291
312
attributes : Attributes ,
313
+
314
+ pub fn msgpackFormat () msgpack.StructFormat {
315
+ return .{ .as_map = .{ .key = .{ .field_name_prefix = 1 } } };
316
+ }
292
317
};
293
318
294
319
fn handleGetIndex (ctx : * Context , req : * httpz.Request , res : * httpz.Response ) ! void {
295
320
const index_ref = try getIndex (ctx , req , res , true ) orelse return ;
296
321
defer releaseIndex (ctx , index_ref );
297
322
298
- const attributes = try index_ref .index .getAttributes (req .arena );
323
+ const info = try index_ref .index .getInfo (req .arena );
299
324
const response = GetIndexResponse {
300
- .status = "ok" ,
301
- .attributes = .{ .attributes = attributes },
325
+ .version = info .version ,
326
+ .attributes = .{
327
+ .attributes = info .attributes ,
328
+ },
302
329
};
303
- return res . json ( & response , .{} );
330
+ return writeResponse ( response , req , res );
304
331
}
305
332
333
+ const EmptyResponse = struct {};
334
+
306
335
fn handlePutIndex (ctx : * Context , req : * httpz.Request , res : * httpz.Response ) ! void {
307
336
const index_name = req .param ("index" ) orelse return ;
308
337
309
- ctx .indexes .createIndex (index_name ) catch | err | {
310
- log .err ("index create error: {}" , .{err });
311
- res .status = 500 ;
312
- return res .json (.{ .status = "internal error" }, .{});
313
- };
338
+ try ctx .indexes .createIndex (index_name );
314
339
315
- return res . json (.{ . status = "ok" }, .{} );
340
+ return writeResponse ( EmptyResponse { }, req , res );
316
341
}
317
342
318
343
fn handleDeleteIndex (ctx : * Context , req : * httpz.Request , res : * httpz.Response ) ! void {
319
344
const index_name = req .param ("index" ) orelse return ;
320
345
321
- ctx .indexes .deleteIndex (index_name ) catch | err | {
322
- log .err ("index delete error: {}" , .{err });
323
- res .status = 500 ;
324
- return res .json (.{ .status = "internal error" }, .{});
325
- };
346
+ try ctx .indexes .deleteIndex (index_name );
326
347
327
- return res . json (.{ . status = "ok" }, .{} );
348
+ return writeResponse ( EmptyResponse { }, req , res );
328
349
}
329
350
330
351
fn handlePing (ctx : * Context , req : * httpz.Request , res : * httpz.Response ) ! void {
0 commit comments