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