diff --git a/source/handy_http_handlers/path_handler.d b/source/handy_http_handlers/path_handler.d index 330e69e..8613e36 100644 --- a/source/handy_http_handlers/path_handler.d +++ b/source/handy_http_handlers/path_handler.d @@ -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 * 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 unittest { import std.exception; @@ -296,3 +359,40 @@ unittest { auto result9 = generateHandledData(HttpMethod.POST, "/api/do-something"); 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); + +} diff --git a/source/handy_http_handlers/path_handler_sample_module.d b/source/handy_http_handlers/path_handler_sample_module.d new file mode 100644 index 0000000..1ad0431 --- /dev/null +++ b/source/handy_http_handlers/path_handler_sample_module.d @@ -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; +} + +} \ No newline at end of file