Compare commits

...

2 Commits

Author SHA1 Message Date
andrewlalis 458ed2d75f Added registerHandlers method.
Build and Test Module / build-and-test (push) Successful in 12s Details
2026-01-12 16:36:52 -05:00
andrewlalis c1756fa648 Upgraded dependencies. 2026-01-12 14:37:01 -05:00
4 changed files with 125 additions and 3 deletions

View File

@ -6,7 +6,7 @@
"dependencies": { "dependencies": {
"handy-http-primitives": "~>1.8", "handy-http-primitives": "~>1.8",
"path-matcher": "~>1.2.0", "path-matcher": "~>1.2.0",
"slf4d": "~>4.1.1" "slf4d": "~>4.0"
}, },
"description": "Library for some common, handy request handlers that you're likely to need in web projects.", "description": "Library for some common, handy request handlers that you're likely to need in web projects.",
"license": "CC0", "license": "CC0",

View File

@ -1,9 +1,9 @@
{ {
"fileVersion": 1, "fileVersion": 1,
"versions": { "versions": {
"handy-http-primitives": "1.8.0", "handy-http-primitives": "1.8.1",
"path-matcher": "1.2.0", "path-matcher": "1.2.0",
"slf4d": "4.1.1", "slf4d": "4.2.0",
"streams": "3.6.0" "streams": "3.6.0"
} }
} }

View File

@ -204,6 +204,35 @@ class PathHandler : HttpRequestHandler {
} }
} }
/**
* Adds mappings to this path handler which correspond to functions defined
* in the given symbol which have been annotated with the `@PathMapping`
* attribute.
*/
void registerHandlers(alias symbol)() {
static assert(
__traits(isModule, symbol),
"PathHandler.registerHandlers can only be called with a module."
);
import std.conv: to;
HttpRequestHandler handlerRef;
static foreach (i, mem; __traits(allMembers, symbol)) {
static if (isValidHandlerRegistrationTarget!(__traits(getMember, symbol, mem))) {
mixin("alias target" ~ i.to!string ~ " = __traits(getMember, symbol, mem);");
static foreach (attr; __traits(getAttributes, mixin("target" ~ i.to!string))) {
static if (is(typeof(attr) == PathMapping)) {
debugF!"Registered handler: %s -> %s"(
attr,
__traits(fullyQualifiedName, mixin("target" ~ i.to!string))
);
handlerRef = HttpRequestHandler.of(mixin("&target" ~ i.to!string));
this.addMapping(attr.method, attr.pattern, handlerRef);
}
}
}
}
}
/** /**
* Finds the handler to use to handle a given request, using our list of * Finds the handler to use to handle a given request, using our list of
* pre-configured mappings. * pre-configured mappings.
@ -235,6 +264,40 @@ class PathHandler : HttpRequestHandler {
} }
} }
/**
* A user-defined attribute that, when added to a function, allows that
* function to be registered automatically by a path handler when you call
* its `registerHandlers` method on the module containing the function.
*/
struct PathMapping {
/**
* The HTTP method that the mapping accepts.
*/
HttpMethod method;
/**
* The path pattern for the mapping.
*/
string pattern;
}
private bool isValidHandlerRegistrationTarget(alias symbol)() {
import std.traits;
static if (isFunction!(symbol)) {
alias params = Parameters!(symbol);
static if (params.length == 2) {
alias paramStorageClasses = ParameterStorageClassTuple!(symbol);
return is(params[0] == ServerHttpRequest) && is(params[1] == ServerHttpResponse) &&
paramStorageClasses[0] == ParameterStorageClass.ref_ &&
paramStorageClasses[1] == ParameterStorageClass.ref_ &&
is(ReturnType!(symbol) == void);
} else {
return false;
}
} else {
return false;
}
}
// Test PathHandler.setNotFoundHandler // Test PathHandler.setNotFoundHandler
unittest { unittest {
import std.exception; import std.exception;
@ -296,3 +359,40 @@ unittest {
auto result9 = generateHandledData(HttpMethod.POST, "/api/do-something"); auto result9 = generateHandledData(HttpMethod.POST, "/api/do-something");
assert(result9.response.status == HttpStatus.OK); assert(result9.response.status == HttpStatus.OK);
} }
// Test registerHandlers
unittest {
import handy_http_handlers.path_handler_sample_module;
class C {}
PathHandler ph = new PathHandler();
ph.registerHandlers!(handy_http_handlers.path_handler_sample_module);
// Verify that the handlers defined in the module were actually added.
ServerHttpRequest req1 = ServerHttpRequestBuilder()
.withMethod("GET")
.withUrl("/h1")
.build();
ServerHttpResponse resp1 = ServerHttpResponseBuilder().build();
ph.handle(req1, resp1);
assert(resp1.status == HttpStatus.OK);
ServerHttpRequest req2 = ServerHttpRequestBuilder()
.withMethod("POST")
.withUrl("/h2")
.build();
ServerHttpResponse resp2 = ServerHttpResponseBuilder().build();
ph.handle(req2, resp2);
assert(resp2.status == HttpStatus.OK);
// Verify that other stuff returns a 404.
ServerHttpRequest req3 = ServerHttpRequestBuilder()
.withMethod("GET")
.withUrl("/h2")
.build();
ServerHttpResponse resp3 = ServerHttpResponseBuilder().build();
ph.handle(req3, resp3);
assert(resp3.status == HttpStatus.NOT_FOUND);
}

View File

@ -0,0 +1,22 @@
/**
* This module defines some path mapping functions to help test the path
* handler's function for registering annotated functions.
*/
module handy_http_handlers.path_handler_sample_module;
version(unittest) {
import handy_http_primitives;
import handy_http_handlers.path_handler;
@PathMapping(HttpMethod.GET, "/h1")
void h1(ref ServerHttpRequest request, ref ServerHttpResponse response) {
response.status = HttpStatus.OK;
}
@PathMapping(HttpMethod.POST, "/h2")
void h2(ref ServerHttpRequest request, ref ServerHttpResponse response) {
response.status = HttpStatus.OK;
}
}