mirror of
https://github.com/zigzap/zap.git
synced 2025-10-20 15:14:08 +00:00
WIP endpoint: don't care about not implemented methods
This commit is contained in:
parent
e17107db3b
commit
cc1ee787fc
2 changed files with 108 additions and 129 deletions
217
src/endpoint.zig
217
src/endpoint.zig
|
@ -77,117 +77,14 @@ const Request = zap.Request;
|
||||||
const ListenerSettings = zap.HttpListenerSettings;
|
const ListenerSettings = zap.HttpListenerSettings;
|
||||||
const HttpListener = zap.HttpListener;
|
const HttpListener = zap.HttpListener;
|
||||||
|
|
||||||
const ImplementedMethods = struct {
|
|
||||||
get: bool = false,
|
|
||||||
head: bool = false,
|
|
||||||
post: bool = false,
|
|
||||||
put: bool = false,
|
|
||||||
delete: bool = false,
|
|
||||||
patch: bool = false,
|
|
||||||
options: bool = false,
|
|
||||||
custom_method: bool = false,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn checkEndpointType(T: type) ImplementedMethods {
|
|
||||||
var implemented_methods: ImplementedMethods = .{};
|
|
||||||
|
|
||||||
if (@hasField(T, "path")) {
|
|
||||||
if (@FieldType(T, "path") != []const u8) {
|
|
||||||
@compileError(@typeName(@FieldType(T, "path")) ++ " has wrong type, expected: []const u8");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
@compileError(@typeName(T) ++ " has no path field");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (@hasField(T, "error_strategy")) {
|
|
||||||
if (@FieldType(T, "error_strategy") != ErrorStrategy) {
|
|
||||||
@compileError(@typeName(@FieldType(T, "error_strategy")) ++ " has wrong type, expected: zap.Endpoint.ErrorStrategy");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
@compileError(@typeName(T) ++ " has no error_strategy field");
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: use field names of ImplementedMethods
|
|
||||||
const methods_to_check = [_][]const u8{
|
|
||||||
"get",
|
|
||||||
"head",
|
|
||||||
"post",
|
|
||||||
"put",
|
|
||||||
"delete",
|
|
||||||
"patch",
|
|
||||||
"options",
|
|
||||||
"custom_method",
|
|
||||||
};
|
|
||||||
|
|
||||||
const params_to_check = [_]type{
|
|
||||||
*T,
|
|
||||||
Request,
|
|
||||||
};
|
|
||||||
|
|
||||||
inline for (methods_to_check) |method| {
|
|
||||||
if (@hasDecl(T, method)) {
|
|
||||||
const Method = @TypeOf(@field(T, method));
|
|
||||||
const method_info = @typeInfo(Method);
|
|
||||||
if (method_info != .@"fn") {
|
|
||||||
@compileError("Expected `" ++ @typeName(T) ++ "." ++ method ++ "` to be a request handler method, got: " ++ @typeName(Method));
|
|
||||||
}
|
|
||||||
|
|
||||||
// now check parameters
|
|
||||||
const params = method_info.@"fn".params;
|
|
||||||
if (params.len != params_to_check.len) {
|
|
||||||
@compileError(std.fmt.comptimePrint(
|
|
||||||
"Expected method `{s}.{s}` to have {d} parameters, got {d}",
|
|
||||||
.{
|
|
||||||
@typeName(T),
|
|
||||||
method,
|
|
||||||
params_to_check.len,
|
|
||||||
params.len,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
inline for (params_to_check, 0..) |param_type_expected, i| {
|
|
||||||
if (params[i].type.? != param_type_expected) {
|
|
||||||
@compileError(std.fmt.comptimePrint(
|
|
||||||
"Expected parameter {d} of method {s}.{s} to be {s}, got {s}",
|
|
||||||
.{
|
|
||||||
i + 1,
|
|
||||||
@typeName(T),
|
|
||||||
method,
|
|
||||||
@typeName(param_type_expected),
|
|
||||||
@typeName(params[i].type.?),
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check return type
|
|
||||||
const ret_type = method_info.@"fn".return_type.?;
|
|
||||||
const ret_info = @typeInfo(ret_type);
|
|
||||||
if (ret_info != .error_union) {
|
|
||||||
@compileError("Expected return type of method `" ++ @typeName(T) ++ "." ++ method ++ "` to be !void, got: " ++ @typeName(ret_type));
|
|
||||||
}
|
|
||||||
if (ret_info.error_union.payload != void) {
|
|
||||||
@compileError("Expected return type of method `" ++ @typeName(T) ++ "." ++ method ++ "` to be !void, got: !" ++ @typeName(ret_info.error_union.payload));
|
|
||||||
}
|
|
||||||
@field(implemented_methods, method) = true;
|
|
||||||
} else {
|
|
||||||
@compileError(@typeName(T) ++ " has no method named `" ++ method ++ "`");
|
|
||||||
// TODO: shall we warn?
|
|
||||||
// No, we should provide a default implementation that calls
|
|
||||||
// "unhandled request" callback, and if that's not defined, log the
|
|
||||||
// request as being unhandled.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return implemented_methods;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const Binder = struct {
|
pub const Binder = struct {
|
||||||
pub const Interface = struct {
|
pub const Interface = struct {
|
||||||
call: *const fn (*Interface, zap.Request) anyerror!void = undefined,
|
pub const CallBack = *const fn (*Interface, zap.Request) anyerror!void;
|
||||||
|
pub const MethodCallbacks = [std.meta.fields(zap.http.Method).len]CallBack;
|
||||||
|
|
||||||
|
methods: MethodCallbacks,
|
||||||
path: []const u8,
|
path: []const u8,
|
||||||
destroy: *const fn (*Interface, std.mem.Allocator) void = undefined,
|
destroy: *const fn (*Interface, std.mem.Allocator) void = undefined,
|
||||||
implemented_methods: ImplementedMethods = undefined,
|
|
||||||
};
|
};
|
||||||
pub fn Bind(ArbitraryEndpoint: type) type {
|
pub fn Bind(ArbitraryEndpoint: type) type {
|
||||||
return struct {
|
return struct {
|
||||||
|
@ -211,17 +108,13 @@ pub const Binder = struct {
|
||||||
try self.onRequest(r);
|
try self.onRequest(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn onUnimplementedInterface(_: *Interface, _: zap.Request) !void {
|
||||||
|
return error.NotImplemented;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn onRequest(self: *Bound, r: zap.Request) !void {
|
pub fn onRequest(self: *Bound, r: zap.Request) !void {
|
||||||
const ret = switch (r.methodAsEnum()) {
|
const method_index = @intFromEnum(r.methodAsEnum());
|
||||||
.GET => self.endpoint.*.get(r),
|
const ret = self.interface.methods[method_index](self, r);
|
||||||
.HEAD => self.endpoint.*.head(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),
|
|
||||||
.UNKNOWN => self.endpoint.*.custom_method(r),
|
|
||||||
};
|
|
||||||
if (ret) {
|
if (ret) {
|
||||||
// handled without error
|
// handled without error
|
||||||
} else |err| {
|
} else |err| {
|
||||||
|
@ -232,19 +125,105 @@ pub const Binder = struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn setupEndpoint() Interface.MethodCallbacks {
|
||||||
|
const T = ArbitraryEndpoint;
|
||||||
|
if (@hasField(T, "path")) {
|
||||||
|
if (@FieldType(T, "path") != []const u8) {
|
||||||
|
@compileError(@typeName(@FieldType(T, "path")) ++ " has wrong type, expected: []const u8");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
@compileError(@typeName(T) ++ " has no path field");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@hasField(T, "error_strategy")) {
|
||||||
|
if (@FieldType(T, "error_strategy") != ErrorStrategy) {
|
||||||
|
@compileError(@typeName(@FieldType(T, "error_strategy")) ++ " has wrong type, expected: zap.Endpoint.ErrorStrategy");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
@compileError(@typeName(T) ++ " has no error_strategy field");
|
||||||
|
}
|
||||||
|
|
||||||
|
const params_to_check = [_]type{
|
||||||
|
*T,
|
||||||
|
Request,
|
||||||
|
};
|
||||||
|
|
||||||
|
comptime {
|
||||||
|
var method_callbacks: Interface.MethodCallbacks = undefined;
|
||||||
|
for (std.meta.tags(zap.http.Method)) |http_method| {
|
||||||
|
const method: []const u8 = if (http_method != .unknown) @tagName(http_method) else "custom_method";
|
||||||
|
if (@hasDecl(T, method)) {
|
||||||
|
const Method = @TypeOf(@field(T, method));
|
||||||
|
const method_info = @typeInfo(Method);
|
||||||
|
if (method_info != .@"fn") {
|
||||||
|
@compileError("Expected `" ++ @typeName(T) ++ "." ++ method ++ "` to be a request handler method, got: " ++ @typeName(Method));
|
||||||
|
}
|
||||||
|
|
||||||
|
// now check parameters
|
||||||
|
const params = method_info.@"fn".params;
|
||||||
|
if (params.len != params_to_check.len) {
|
||||||
|
@compileError(std.fmt.comptimePrint(
|
||||||
|
"Expected method `{s}.{s}` to have {d} parameters, got {d}",
|
||||||
|
.{
|
||||||
|
@typeName(T),
|
||||||
|
method,
|
||||||
|
params_to_check.len,
|
||||||
|
params.len,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (params_to_check, 0..) |param_type_expected, i| {
|
||||||
|
if (params[i].type.? != param_type_expected) {
|
||||||
|
@compileError(std.fmt.comptimePrint(
|
||||||
|
"Expected parameter {d} of method {s}.{s} to be {s}, got {s}",
|
||||||
|
.{
|
||||||
|
i + 1,
|
||||||
|
@typeName(T),
|
||||||
|
method,
|
||||||
|
@typeName(param_type_expected),
|
||||||
|
@typeName(params[i].type.?),
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check return type
|
||||||
|
const ret_type = method_info.@"fn".return_type.?;
|
||||||
|
const ret_info = @typeInfo(ret_type);
|
||||||
|
if (ret_info != .error_union) {
|
||||||
|
@compileError("Expected return type of method `" ++ @typeName(T) ++ "." ++ method ++ "` to be !void, got: " ++ @typeName(ret_type));
|
||||||
|
}
|
||||||
|
if (ret_info.error_union.payload != void) {
|
||||||
|
@compileError("Expected return type of method `" ++ @typeName(T) ++ "." ++ method ++ "` to be !void, got: !" ++ @typeName(ret_info.error_union.payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
method_callbacks[@intFromEnum(http_method)] = Bound.onRequestInterface;
|
||||||
|
} else {
|
||||||
|
@compileError(@typeName(T) ++ " has no method named `" ++ method ++ "`");
|
||||||
|
// TODO: shall we warn?
|
||||||
|
// No, we should provide a default implementation that calls
|
||||||
|
// "unhandled request" callback, and if that's not defined, log the
|
||||||
|
// request as being unhandled.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return method_callbacks;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(ArbitraryEndpoint: type, value: *ArbitraryEndpoint) Binder.Bind(ArbitraryEndpoint) {
|
pub fn init(ArbitraryEndpoint: type, value: *ArbitraryEndpoint) Binder.Bind(ArbitraryEndpoint) {
|
||||||
const implemented_methods = checkEndpointType(ArbitraryEndpoint);
|
|
||||||
const BoundEp = Binder.Bind(ArbitraryEndpoint);
|
const BoundEp = Binder.Bind(ArbitraryEndpoint);
|
||||||
|
const methods = BoundEp.setupEndpoint();
|
||||||
return .{
|
return .{
|
||||||
.endpoint = value,
|
.endpoint = value,
|
||||||
.interface = .{
|
.interface = .{
|
||||||
.path = value.path,
|
.path = value.path,
|
||||||
.call = BoundEp.onRequestInterface,
|
.call = BoundEp.onRequestInterface,
|
||||||
.destroy = BoundEp.destroy,
|
.destroy = BoundEp.destroy,
|
||||||
.implemented_methods = implemented_methods,
|
.implemented_methods = methods,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
20
src/http.zig
20
src/http.zig
|
@ -131,21 +131,21 @@ pub const StatusCode = enum(u16) {
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Method = enum {
|
pub const Method = enum {
|
||||||
GET,
|
get,
|
||||||
HEAD,
|
head,
|
||||||
POST,
|
post,
|
||||||
PUT,
|
put,
|
||||||
DELETE,
|
delete,
|
||||||
PATCH,
|
patch,
|
||||||
OPTIONS,
|
options,
|
||||||
UNKNOWN,
|
unknown,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn methodToEnum(method: ?[]const u8) Method {
|
pub fn methodToEnum(method: ?[]const u8) Method {
|
||||||
{
|
{
|
||||||
if (method) |m| {
|
if (method) |m| {
|
||||||
return std.meta.stringToEnum(Method, m) orelse .UNKNOWN;
|
return std.meta.stringToEnum(Method, m) orelse .unknown;
|
||||||
}
|
}
|
||||||
return Method.UNKNOWN;
|
return .unknown;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue