From dabd0637f9305eafa54c465ea2360682a7bc1884 Mon Sep 17 00:00:00 2001 From: renerocksai Date: Wed, 23 Jul 2025 20:23:49 +0200 Subject: [PATCH] endpoints, auth endpoints, middleware endpoints: eliminate need for empty stubs --- examples/app/basic.zig | 4 +- examples/endpoint/error.zig | 8 -- examples/endpoint/stopendpoint.zig | 7 -- examples/endpoint/userweb.zig | 3 +- examples/endpoint_auth/endpoint_auth.zig | 10 +-- .../middleware_with_endpoint.zig | 7 -- src/endpoint.zig | 75 +++++++++++-------- src/middleware.zig | 19 ++--- src/tests/test_auth.zig | 6 -- 9 files changed, 59 insertions(+), 80 deletions(-) diff --git a/examples/app/basic.zig b/examples/app/basic.zig index cf203ff..640cae5 100644 --- a/examples/app/basic.zig +++ b/examples/app/basic.zig @@ -59,7 +59,7 @@ const SimpleEndpoint = struct { try r.sendBody(response_text); std.time.sleep(std.time.ns_per_ms * 300); } - }; +}; const StopEndpoint = struct { path: []const u8, @@ -69,7 +69,7 @@ const StopEndpoint = struct { std.debug.print( \\Before I stop, let me dump the app context: \\db_connection='{s}' - \\ + \\ \\ , .{context.*.db_connection}); zap.stop(); diff --git a/examples/endpoint/error.zig b/examples/endpoint/error.zig index dfe0142..9aa34c4 100644 --- a/examples/endpoint/error.zig +++ b/examples/endpoint/error.zig @@ -13,11 +13,3 @@ pub fn get(_: *ErrorEndpoint, _: zap.Request) !void { // --> this error will be shown in the browser, with a nice error trace return error.@"Oh-no!"; } - -// unused: -pub fn post(_: *ErrorEndpoint, _: zap.Request) !void {} -pub fn put(_: *ErrorEndpoint, _: zap.Request) !void {} -pub fn delete(_: *ErrorEndpoint, _: zap.Request) !void {} -pub fn patch(_: *ErrorEndpoint, _: zap.Request) !void {} -pub fn options(_: *ErrorEndpoint, _: zap.Request) !void {} -pub fn head(_: *ErrorEndpoint, _: zap.Request) !void {} diff --git a/examples/endpoint/stopendpoint.zig b/examples/endpoint/stopendpoint.zig index aa83dde..5e611a9 100644 --- a/examples/endpoint/stopendpoint.zig +++ b/examples/endpoint/stopendpoint.zig @@ -17,10 +17,3 @@ pub fn init(path: []const u8) StopEndpoint { pub fn get(_: *StopEndpoint, _: zap.Request) !void { zap.stop(); } - -pub fn post(_: *StopEndpoint, _: zap.Request) !void {} -pub fn put(_: *StopEndpoint, _: zap.Request) !void {} -pub fn delete(_: *StopEndpoint, _: zap.Request) !void {} -pub fn patch(_: *StopEndpoint, _: zap.Request) !void {} -pub fn options(_: *StopEndpoint, _: zap.Request) !void {} -pub fn head(_: *StopEndpoint, _: zap.Request) !void {} diff --git a/examples/endpoint/userweb.zig b/examples/endpoint/userweb.zig index ec1ad78..43ab71e 100644 --- a/examples/endpoint/userweb.zig +++ b/examples/endpoint/userweb.zig @@ -43,7 +43,8 @@ fn userIdFromPath(self: *UserWeb, path: []const u8) ?usize { return null; } -pub fn put(_: *UserWeb, _: zap.Request) !void {} +// not implemented +// pub fn put(_: *UserWeb, _: zap.Request) !void {} pub fn get(self: *UserWeb, r: zap.Request) !void { if (r.path) |path| { diff --git a/examples/endpoint_auth/endpoint_auth.zig b/examples/endpoint_auth/endpoint_auth.zig index fc15e7a..75a5047 100644 --- a/examples/endpoint_auth/endpoint_auth.zig +++ b/examples/endpoint_auth/endpoint_auth.zig @@ -31,14 +31,6 @@ const Endpoint = struct { r.setStatus(.unauthorized); r.sendBody("UNAUTHORIZED ACCESS") catch return; } - - // not implemented, don't care - pub fn post(_: *Endpoint, _: zap.Request) !void {} - pub fn put(_: *Endpoint, _: zap.Request) !void {} - pub fn delete(_: *Endpoint, _: zap.Request) !void {} - pub fn patch(_: *Endpoint, _: zap.Request) !void {} - pub fn options(_: *Endpoint, _: zap.Request) !void {} - pub fn head(_: *Endpoint, _: zap.Request) !void {} }; pub fn main() !void { @@ -74,7 +66,7 @@ pub fn main() !void { listener.listen() catch {}; std.debug.print( \\ Run the following: - \\ + \\ \\ curl http://localhost:3000/test -i -H "Authorization: Bearer ABCDEFG" -v \\ curl http://localhost:3000/test -i -H "Authorization: Bearer invalid" -v \\ diff --git a/examples/middleware_with_endpoint/middleware_with_endpoint.zig b/examples/middleware_with_endpoint/middleware_with_endpoint.zig index 275d7d6..b029ae4 100644 --- a/examples/middleware_with_endpoint/middleware_with_endpoint.zig +++ b/examples/middleware_with_endpoint/middleware_with_endpoint.zig @@ -152,13 +152,6 @@ const HtmlEndpoint = struct { }; } - pub fn post(_: *HtmlEndpoint, _: zap.Request) !void {} - pub fn put(_: *HtmlEndpoint, _: zap.Request) !void {} - pub fn delete(_: *HtmlEndpoint, _: zap.Request) !void {} - pub fn patch(_: *HtmlEndpoint, _: zap.Request) !void {} - pub fn options(_: *HtmlEndpoint, _: zap.Request) !void {} - pub fn head(_: *HtmlEndpoint, _: zap.Request) !void {} - pub fn get(_: *HtmlEndpoint, r: zap.Request) !void { var buf: [1024]u8 = undefined; var userFound: bool = false; diff --git a/src/endpoint.zig b/src/endpoint.zig index 5d95488..30bdde7 100644 --- a/src/endpoint.zig +++ b/src/endpoint.zig @@ -5,7 +5,11 @@ //! Pass an instance of an Endpoint struct to zap.Endpoint.Listener.register() //! function to register with the listener. //! -//! **NOTE**: Endpoints must implement the following "interface": +//! **NOTE**: Endpoints can implement the following "interface": +//! +//! Any method handler that's not implemented will be handled automatically: +//! - zap will log it +//! - a response with status code 405 (method not allowed) is sent to the client //! //! ```zig //! /// The http request path / slug of the endpoint @@ -13,6 +17,7 @@ //! error_strategy: zap.Endpoint.ErrorStrategy, //! //! /// Handlers by request method: +//! /// implement any of the following //! pub fn get(_: *Self, _: zap.Request) !void {} //! pub fn post(_: *Self, _: zap.Request) !void {} //! pub fn put(_: *Self, _: zap.Request) !void {} @@ -44,13 +49,6 @@ //! pub fn get(_: *StopEndpoint, _: zap.Request) !void { //! zap.stop(); //! } -//! -//! pub fn post(_: *StopEndpoint, _: zap.Request) !void {} -//! pub fn put(_: *StopEndpoint, _: zap.Request) !void {} -//! pub fn delete(_: *StopEndpoint, _: zap.Request) !void {} -//! pub fn patch(_: *StopEndpoint, _: zap.Request) !void {} -//! pub fn options(_: *StopEndpoint, _: zap.Request) !void {} -//! pub fn head(_: *StopEndpoint, _: zap.Request) !void {} //! }; //! ``` @@ -154,10 +152,25 @@ pub fn checkEndpointType(T: type) void { @compileError("Expected return type of method `" ++ @typeName(T) ++ "." ++ method ++ "` to be !void, got: !" ++ @typeName(ret_info.error_union.payload)); } } else { - @compileError(@typeName(T) ++ " has no method named `" ++ method ++ "`"); + // it is ok not to implement a method handler + // pass } } } +// This can be resolved at comptime so *perhaps it does affect optimiazation +pub fn callHandlerIfExist(comptime fn_name: []const u8, e: anytype, r: Request) anyerror!void { + const EndPointType = @TypeOf(e.*); + if (@hasDecl(EndPointType, fn_name)) { + return @field(EndPointType, fn_name)(e, r); + } + zap.log.debug( + "Unhandled `{s}` {s} request ({s} not implemented in {s})", + .{ r.method orelse "", r.path orelse "", fn_name, @typeName(EndPointType) }, + ); + r.setStatus(.method_not_allowed); + try r.sendBody("405 - method not allowed\r\n"); + return; +} pub const Binder = struct { pub const Interface = struct { @@ -189,13 +202,13 @@ pub const Binder = struct { pub fn onRequest(self: *Bound, r: zap.Request) !void { const ret = switch (r.methodAsEnum()) { - .GET => self.endpoint.*.get(r), - .POST => self.endpoint.*.post(r), - .PUT => self.endpoint.*.put(r), - .DELETE => self.endpoint.*.delete(r), - .PATCH => self.endpoint.*.patch(r), - .OPTIONS => self.endpoint.*.options(r), - .HEAD => self.endpoint.*.head(r), + .GET => callHandlerIfExist("get", self.endpoint, r), + .POST => callHandlerIfExist("post", self.endpoint, r), + .PUT => callHandlerIfExist("put", self.endpoint, r), + .DELETE => callHandlerIfExist("delete", self.endpoint, r), + .PATCH => callHandlerIfExist("patch", self.endpoint, r), + .OPTIONS => callHandlerIfExist("options", self.endpoint, r), + .HEAD => callHandlerIfExist("head", self.endpoint, r), else => error.UnsupportedHtmlRequestMethod, }; if (ret) { @@ -249,8 +262,8 @@ pub fn Authenticating(EndpointType: type, Authenticator: type) type { /// Authenticates GET requests using the Authenticator. pub fn get(self: *AuthenticatingEndpoint, r: zap.Request) anyerror!void { try switch (self.authenticator.authenticateRequest(&r)) { - .AuthFailed => return self.ep.*.unauthorized(r), - .AuthOK => self.ep.*.get(r), + .AuthFailed => callHandlerIfExist("unauthorized", self.ep, r), + .AuthOK => callHandlerIfExist("get", self.ep, r), .Handled => {}, }; } @@ -258,8 +271,8 @@ pub fn Authenticating(EndpointType: type, Authenticator: type) type { /// Authenticates POST requests using the Authenticator. pub fn post(self: *AuthenticatingEndpoint, r: zap.Request) anyerror!void { try switch (self.authenticator.authenticateRequest(&r)) { - .AuthFailed => return self.ep.*.unauthorized(r), - .AuthOK => self.ep.*.post(r), + .AuthFailed => callHandlerIfExist("unauthorized", self.ep, r), + .AuthOK => callHandlerIfExist("post", self.ep, r), .Handled => {}, }; } @@ -267,8 +280,8 @@ pub fn Authenticating(EndpointType: type, Authenticator: type) type { /// Authenticates PUT requests using the Authenticator. pub fn put(self: *AuthenticatingEndpoint, r: zap.Request) anyerror!void { try switch (self.authenticator.authenticateRequest(&r)) { - .AuthFailed => return self.ep.*.unauthorized(r), - .AuthOK => self.ep.*.put(r), + .AuthFailed => callHandlerIfExist("unauthorized", self.ep, r), + .AuthOK => callHandlerIfExist("put", self.ep, r), .Handled => {}, }; } @@ -276,8 +289,8 @@ pub fn Authenticating(EndpointType: type, Authenticator: type) type { /// Authenticates DELETE requests using the Authenticator. pub fn delete(self: *AuthenticatingEndpoint, r: zap.Request) anyerror!void { try switch (self.authenticator.authenticateRequest(&r)) { - .AuthFailed => return self.ep.*.unauthorized(r), - .AuthOK => self.ep.*.delete(r), + .AuthFailed => callHandlerIfExist("unauthorized", self.ep, r), + .AuthOK => callHandlerIfExist("delete", self.ep, r), .Handled => {}, }; } @@ -285,8 +298,8 @@ pub fn Authenticating(EndpointType: type, Authenticator: type) type { /// Authenticates PATCH requests using the Authenticator. pub fn patch(self: *AuthenticatingEndpoint, r: zap.Request) anyerror!void { try switch (self.authenticator.authenticateRequest(&r)) { - .AuthFailed => return self.ep.*.unauthorized(r), - .AuthOK => self.ep.*.patch(r), + .AuthFailed => callHandlerIfExist("unauthorized", self.ep, r), + .AuthOK => callHandlerIfExist("patch", self.ep, r), .Handled => {}, }; } @@ -294,17 +307,17 @@ pub fn Authenticating(EndpointType: type, Authenticator: type) type { /// Authenticates OPTIONS requests using the Authenticator. pub fn options(self: *AuthenticatingEndpoint, r: zap.Request) anyerror!void { try switch (self.authenticator.authenticateRequest(&r)) { - .AuthFailed => return self.ep.*.unauthorized(r), - .AuthOK => self.ep.*.put(r), + .AuthFailed => callHandlerIfExist("unauthorized", self.ep, r), + .AuthOK => callHandlerIfExist("options", self.ep, r), .Handled => {}, }; } - + /// Authenticates HEAD requests using the Authenticator. pub fn head(self: *AuthenticatingEndpoint, r: zap.Request) anyerror!void { try switch (self.authenticator.authenticateRequest(&r)) { - .AuthFailed => return self.ep.*.unauthorized(r), - .AuthOK => self.ep.*.head(r), + .AuthFailed => callHandlerIfExist("unauthorized", self.ep, r), + .AuthOK => callHandlerIfExist("head", self.ep, r), .Handled => {}, }; } diff --git a/src/middleware.zig b/src/middleware.zig index 2dcfd43..4782894 100644 --- a/src/middleware.zig +++ b/src/middleware.zig @@ -1,5 +1,6 @@ const std = @import("std"); const zap = @import("zap.zig"); +const callHandlerIfExist = @import("endpoint.zig").callHandlerIfExist; /// Your middleware components need to contain a handler. /// @@ -102,16 +103,16 @@ pub fn EndpointHandler(comptime HandlerType: anytype, comptime EndpointType: any if (!self.options.checkPath or std.mem.startsWith(u8, r.path orelse "", self.endpoint.path)) { - switch (r.methodAsEnum()) { - .GET => try self.endpoint.*.get(r), - .POST => try self.endpoint.*.post(r), - .PUT => try self.endpoint.*.put(r), - .DELETE => try self.endpoint.*.delete(r), - .PATCH => try self.endpoint.*.patch(r), - .OPTIONS => try self.endpoint.*.options(r), - .HEAD => try self.endpoint.*.head(r), + try switch (r.methodAsEnum()) { + .GET => callHandlerIfExist("get", self.endpoint, r), + .POST => callHandlerIfExist("post", self.endpoint, r), + .PUT => callHandlerIfExist("put", self.endpoint, r), + .DELETE => callHandlerIfExist("delete", self.endpoint, r), + .PATCH => callHandlerIfExist("patch", self.endpoint, r), + .OPTIONS => callHandlerIfExist("options", self.endpoint, r), + .HEAD => callHandlerIfExist("head", self.endpoint, r), else => {}, - } + }; } // if the request was handled by the endpoint, we may break the chain here diff --git a/src/tests/test_auth.zig b/src/tests/test_auth.zig index 14c7ead..40c3326 100644 --- a/src/tests/test_auth.zig +++ b/src/tests/test_auth.zig @@ -165,12 +165,6 @@ pub const Endpoint = struct { std.time.sleep(1 * std.time.ns_per_s); zap.stop(); } - pub fn post(_: *Endpoint, _: zap.Request) !void {} - pub fn put(_: *Endpoint, _: zap.Request) !void {} - pub fn delete(_: *Endpoint, _: zap.Request) !void {} - pub fn patch(_: *Endpoint, _: zap.Request) !void {} - pub fn options(_: *Endpoint, _: zap.Request) !void {} - pub fn head(_: *Endpoint, _: zap.Request) !void {} }; // // end of http client code