From 9b244a3fe8398cc8471f1bce7ef2ae6706865cb8 Mon Sep 17 00:00:00 2001 From: Guillaume Poirier-Morency Date: Sat, 9 Jan 2016 13:38:05 -0500 Subject: [PATCH] [WIP] Provide a routing context instead of a stack Each context consist of a mapping of 'string' to 'Value?' with a possible parent context. It is the best compromise between a mapping for the parameters and a routing stack. Keys are resolved recursively in the context tree. Remove 'Request.params', it's not needed anymore. Update tests, documentation and examples. --- docs/application.rst | 4 +- docs/redirection-and-error.rst | 8 +- docs/router.rst | 59 +++-------- examples/app/app.vala | 50 +++++----- examples/fastcgi/app.vala | 4 +- examples/memcached/app.vala | 12 +-- examples/scgi/app.vala | 2 +- src/valum-context.vala | 83 ++++++++++++++++ src/valum-route.vala | 11 +- src/valum-router.vala | 33 +++--- src/valum-server-sent-events.vala | 8 +- src/valum.vala | 34 +++---- src/vsgi-request.vala | 10 -- tests/cgi-test.vala | 1 - tests/fastcgi-test.vala | 1 - tests/negociate-test.vala | 6 +- tests/route-test.vala | 160 ++++++++++++------------------ tests/router-test.vala | 47 +++++---- tests/soup-test.vala | 1 - tests/tests.vala | 2 +- 20 files changed, 270 insertions(+), 266 deletions(-) create mode 100644 src/valum-context.vala diff --git a/docs/application.rst b/docs/application.rst index 6d407220b..7b0f4c3b4 100644 --- a/docs/application.rst +++ b/docs/application.rst @@ -38,7 +38,7 @@ a :doc:`route` instance. .. code:: vala - app.get ("", (req, res, next, stack) => { + app.get ("", (req, res, next, context) => { res.body.write_all ("Hello world!".data, null); }); @@ -48,7 +48,7 @@ processing. The callback, named handler, receives four arguments: - a :doc:`vsgi/request` that describes a resource being requested - a :doc:`vsgi/response` that correspond to that resource - a ``next`` continuation to `keep routing` -- a routing ``stack`` to retrieve and store states from previous and for +- a routing ``context`` to retrieve and store states from previous and for following handlers Serving the application diff --git a/docs/redirection-and-error.rst b/docs/redirection-and-error.rst index f5b5bca55..e08815a1c 100644 --- a/docs/redirection-and-error.rst +++ b/docs/redirection-and-error.rst @@ -130,11 +130,11 @@ the :doc:`router` can handle them properly. throw new ClientError.NOT_FOUND (""); }); -During status handling, the error message will be pushed on the routing stack -as a ``string``. +During status handling, the error message will be pushed on the routing context +as a ``string`` to the key ``message``. .. code:: vala - app.status (404, (req, res, next, stack) => { - var message = stack.pop_tail ().get_string (); + app.status (404, (req, res, next, context) => { + var message = context["message"].get_string (); }); diff --git a/docs/router.rst b/docs/router.rst index d4da378bb..7f321047c 100644 --- a/docs/router.rst +++ b/docs/router.rst @@ -25,29 +25,21 @@ It can be performed automatically with ``Router.use``: next (req, res); }); -Routing stack -------------- +Routing context +--------------- During the routing, states can obtained from a previous handler or passed to -the next one using the routing stack. The stack is a simple `GLib.Queue`_ that -can be accessed from its head or tail. - -.. warning:: - - The queue tail is used to perform stack operations with ``push_tail`` and - ``pop_tail``. - -.. _GLib.Queue: http://valadoc.org/#!api=glib-2.0/GLib.Queue +the next one using the routing context. .. code:: vala - app.get ("", (req, res, next, stack) => { - stack.push_tail ("some value"); + app.get ("", (req, res, next, context) => { + context["some key"] = "some value"; next (req, res); }); - app.get ("", (req, res, next, stack) => { - var some_value = stack.pop_tail ().get_string (); + app.get ("", (req, res, next, context) => { + var some_value = context["some key"]; // or context.parent["some key"] }); HTTP methods @@ -151,14 +143,14 @@ Thrown status code can be handled by a ``HandlerCallback`` pretty much like how typically matched requests are being handled. The received :doc:`vsgi/request` and :doc:`vsgi/response` object are in the -same state they were when the status was thrown. The error message is stacked -and available in the ``HandlerCallback`` last parameter. +same state they were when the status was thrown. The error message is bound to +the key ``message`` in the routing context. .. code:: vala - app.status (Soup.Status.NOT_FOUND, (req, res, next, stack) => { + app.status (Soup.Status.NOT_FOUND, (req, res, next, context) => { // produce a 404 page... - var message = stack.pop_tail ().get_string (); + var message = context["message"].get_string (); }); Similarly to conventional request handling, the ``next`` continuation can be @@ -257,7 +249,7 @@ application. a maximum inter-operability with other frameworks based on VSGI. The following example delegates all ``GET`` requests to another router which -will process in isolation with its own routing stack. +will process in isolation with its own routing context. .. code:: vala @@ -301,25 +293,6 @@ Filters // res is transparently gzipped }) -Stacked states -~~~~~~~~~~~~~~ - -Additionally, states can be passed to the next handler in the queue by pushing -them in a stack. - -.. code:: vala - - app.get ("", (req, res, next, stack) => { - message ("pre"); - stack.push_tail (new Object ()); // propagate the state - next (req, res); - }); - - app.get ("", (req, res, next, stack) => { - // perform an operation with the provided state - var obj = stack.pop_tail (); - }); - Sequence -------- @@ -386,8 +359,8 @@ responses designed for non-human client. }); }); - app.status (Status.NOT_ACCEPTABLE, (req, res, next, stack) => { - res.body.write_all ("

%s

".printf (stack.pop_tail ().get_string ()).data, null); + app.status (Status.NOT_ACCEPTABLE, (req, res, next, context) => { + res.body.write_all ("

%s

".printf (context["message"].get_string ()).data, null); }); Middleware @@ -482,8 +455,8 @@ attached to a :doc:`route`, the processing will happen in a ``NextCallback``. .. code:: vala - app.get ("home", (req, res, next, stack) => { + app.get ("home", (req, res, next, context) => { compress (req, res, (req, res) => { res.body.write_all ("Hello world!".data, null); - }, stack); + }, new Context.with_parent (context)); }); diff --git a/examples/app/app.vala b/examples/app/app.vala index 74f75bb37..b254013a3 100644 --- a/examples/app/app.vala +++ b/examples/app/app.vala @@ -82,14 +82,14 @@ app.get ("cookies", (req, res) => { }); app.scope ("cookie", (inner) => { - inner.get ("", (req, res) => { + inner.get ("", (req, res, next, context) => { foreach (var cookie in VSGI.Cookies.from_request (req)) - if (cookie.name == req.params["name"]) + if (cookie.name == context["name"].get_string ()) res.body.write_all ("%s\n".printf (cookie.value).data, null); }); - inner.post ("", (req, res) => { - var cookie = new Soup.Cookie (req.params["name"], req.flatten_utf8 (), "0.0.0.0", "/", 60); + inner.post ("", (req, res, next, context) => { + var cookie = new Soup.Cookie (context["name"].get_string (), req.flatten_utf8 (), "0.0.0.0", "/", 60); res.headers.append ("Set-Cookie", cookie.to_set_cookie_header ()); }); }); @@ -140,13 +140,13 @@ app.method (VSGI.Request.GET, "custom-method", (req, res) => { res.body.write_all (req.method.data, null); }); -app.get ("hello/", (req, res) => { - res.body.write_all ("hello %s!".printf (req.params["id"]).data, null); +app.get ("hello/", (req, res, next, context) => { + res.body.write_all ("hello %s!".printf (context["id"].get_string ()).data, null); }); -app.get ("users//", (req, res) => { - var id = req.params["id"]; - var test = req.params["action"]; +app.get ("users//", (req, res, next, context) => { + var id = context["id"].get_string (); + var test = context["action"].get_string (); var writer = new DataOutputStream (res.body); writer.put_string (@"id\t=> $id\n"); @@ -155,8 +155,8 @@ app.get ("users//", (req, res) => { app.types["permutations"] = /abc|acb|bac|bca|cab|cba/; -app.get ("custom-route-type/", (req, res) => { - res.body.write_all (req.params["p"].data, null); +app.get ("custom-route-type/", (req, res, next, context) => { + res.body.write_all (context["p"].get_string ().data, null); }); app.regex (VSGI.Request.GET, /custom-regular-expression/, (req, res) => { @@ -193,8 +193,8 @@ app.get ("not-found", (req, res) => { var api = new Router (); -api.get ("repository/", (req, res) => { - var name = req.params["name"]; +api.get ("repository/", (req, res, next, context) => { + var name = context["name"].get_string (); res.body.write_all (name.data, null); }); @@ -209,17 +209,17 @@ app.get ("next", (req, res) => { res.body.write_all ("Matched by the next route in the queue.".data, null); }); -app.get ("state", (req, res, next, stack) => { - stack.push_tail ("I have been passed!"); +app.get ("state", (req, res, next, context) => { + context["state"] = "I have been passed!"; next (req, res); }); -app.get ("state", (req, res, next, stack) => { - res.body.write_all (stack.pop_tail ().get_string ().data, null); +app.get ("state", (req, res, next, context) => { + res.body.write_all (context["state"].get_string ().data, null); }); // Ctpl template rendering -app.get ("ctpl//", (req, res) => { +app.get ("ctpl//", (req, res, next, context) => { var tpl = new View.from_string ("""

hello {foo}

hello {bar}

@@ -230,8 +230,8 @@ app.get ("ctpl//", (req, res) => { """); - tpl.push_string ("foo", req.params["foo"]); - tpl.push_string ("bar", req.params["bar"]); + tpl.push_string ("foo", context["foo"].get_string ()); + tpl.push_string ("bar", context["bar"].get_string ()); tpl.push_strings ("strings", {"a", "b", "c"}); tpl.push_int ("int", 1); @@ -240,9 +240,9 @@ app.get ("ctpl//", (req, res) => { }); // serve static resource using a path route parameter -app.get ("static/.", (req, res) => { - var resource = req.params["resource"]; - var type = req.params["type"]; +app.get ("static/.", (req, res, next, context) => { + var resource = context["resource"].get_string (); + var type = context["type"].get_string (); var path = "/static/%s.%s".printf (resource, type); bool uncertain; @@ -279,9 +279,9 @@ app.get ("server-sent-events", stream_events ((req, send) => { }); })); -app.status (Soup.Status.NOT_FOUND, (req, res, next, stack) => { +app.status (Soup.Status.NOT_FOUND, (req, res, next, context) => { var template = new View.from_stream (resources_open_stream ("/templates/404.html", ResourceLookupFlags.NONE)); - template.environment.push_string ("message", stack.pop_tail ().get_string ()); + template.environment.push_string ("message", context["message"].get_string ()); res.status = Soup.Status.NOT_FOUND; HashTable @params; res.headers.get_content_type (out @params); diff --git a/examples/fastcgi/app.vala b/examples/fastcgi/app.vala index 27b43f020..1399d7d6b 100644 --- a/examples/fastcgi/app.vala +++ b/examples/fastcgi/app.vala @@ -26,8 +26,8 @@ public static int main (string[] args) { res.body.write_all ("Hello world!".data, null); }); - app.get ("random/", (req, res) => { - var size = int.parse (req.params["size"]); + app.get ("random/", (req, res, next, context) => { + var size = int.parse (context["size"].get_string ()); var writer = new DataOutputStream (res.body); for (; size > 0; size--) { diff --git a/examples/memcached/app.vala b/examples/memcached/app.vala index 70d42865a..acd7a1e5a 100644 --- a/examples/memcached/app.vala +++ b/examples/memcached/app.vala @@ -21,8 +21,8 @@ using VSGI.Soup; var app = new Router (); var memcached = new Memcached.Context.from_configuration ("--SERVER=localhost".data); -app.get ("", (req, res, next, stack) => { - var key = stack.pop_tail ().get_string (); +app.get ("", (req, res, next, context) => { + var key = context["key"].get_string (); uint32 flags; Memcached.ReturnCode error; @@ -39,8 +39,8 @@ app.get ("", (req, res, next, stack) => { } }); -app.put ("", (req, res, next, stack) => { - var key = stack.pop_tail ().get_string (); +app.put ("", (req, res, next, context) => { + var key = context["key"].get_string (); var buffer = new MemoryOutputStream (null, realloc, free); @@ -59,8 +59,8 @@ app.put ("", (req, res, next, stack) => { } }); -app.delete ("", (req, res, next, stack) => { - var key = stack.pop_tail ().get_string (); +app.delete ("", (req, res, next, context) => { + var key = context["key"].get_string (); var error = memcached.delete (key.data, 0); diff --git a/examples/scgi/app.vala b/examples/scgi/app.vala index c0e8ef5cc..149ec989e 100644 --- a/examples/scgi/app.vala +++ b/examples/scgi/app.vala @@ -32,4 +32,4 @@ app.get ("async", (req, res) => { res.body.write_all_async.begin ("Hello world!".data, Priority.DEFAULT, null); }); -new Server ("org.valum.example.SCGI", app.handle).run (); +new Server ("org.valum.example.SCGI", app.handle).run ({"app", "--port", "3003"}); diff --git a/src/valum-context.vala b/src/valum-context.vala new file mode 100644 index 000000000..d54f97468 --- /dev/null +++ b/src/valum-context.vala @@ -0,0 +1,83 @@ +/* + * This file is part of Valum. + * + * Valum is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * Valum is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Valum. If not, see . + */ + +using GLib; + +/** + * Routing context that stores various states for middleware interaction. + * + * @since 0.3 + */ +public class Valum.Context : Object { + + /** + * Internal mapping of states. + */ + private HashTable states = new HashTable (str_hash, str_equal); + + /** + * Parent's context from which missing keys are resolved. + * + * @since 0.3 + */ + public Context? parent { construct; get; default = null; } + + /** + * Create a new root context. + * + * @since 0.3 + */ + public Context () { + + } + + /** + * Create a new child context. + * + * @since 0.3 + */ + public Context.with_parent (Context parent) { + Object (parent: parent); + } + + /** + * Obtain a key from this context or its parent if it's not found. + * + * @since 0.3 + */ + public new Value? @get (string key) { + return states[key] ?? (parent == null ? null : parent[key]); + } + + /** + * Set a key in this context. + * + * @since 0.3 + */ + public new void @set (string key, Value? @value) { + states[key] = @value; + } + + /** + * Lookup if this context or its parent has a key. + * + * @since 0.3 + */ + public bool contains (string key) { + return states.contains (key) || (parent != null && parent.contains (key)); + } +} diff --git a/src/valum-route.vala b/src/valum-route.vala index c5d2363f4..cc3d128e2 100644 --- a/src/valum-route.vala +++ b/src/valum-route.vala @@ -106,17 +106,14 @@ namespace Valum { // regex are optimized automatically :) var prepared_regex = new Regex (pattern.str, RegexCompileFlags.OPTIMIZE); - this (router, method, (req, stack) => { + this (router, method, (req, context) => { MatchInfo match_info; if (prepared_regex.match (req.uri.get_path (), 0, out match_info)) { if (captures.length () > 0) { - // populate the request parameters - var p = new HashTable (str_hash, str_equal); + // populate the context parameters foreach (var capture in captures) { - p[capture] = match_info.fetch_named (capture); - stack.push_tail (match_info.fetch_named (capture)); + context[capture] = match_info.fetch_named (capture); } - req.params = p; } return true; } @@ -190,7 +187,7 @@ namespace Valum { public Route then (owned HandlerCallback handler) { return this.router.matcher (this.method, (req, stack) => { // since the same matcher is shared, we preserve the stack intact - return match (req, new Queue ()); + return match (req, new Context ()); }, (owned) handler); } } diff --git a/src/valum-router.vala b/src/valum-router.vala index 7d7ddb42e..780c0c4e0 100644 --- a/src/valum-router.vala +++ b/src/valum-router.vala @@ -272,26 +272,23 @@ namespace Valum { private bool perform_routing (List routes, Request req, Response res, - Queue stack) throws Informational, + Context context) throws Informational, Success, Redirection, ClientError, ServerError, Error { - var tmp_stack = new Queue (); for (unowned List node = routes; node != null; node = node.next) { - tmp_stack.clear (); - if ((node.data.method == null || node.data.method == req.method) && node.data.match (req, tmp_stack)) { - // commit the stack pushes - while (!tmp_stack.is_empty ()) - stack.push_tail (tmp_stack.pop_head ()); + var local_context = new Context.with_parent (context); + if ((node.data.method == null || node.data.method == req.method) && + node.data.match (req, local_context)) { node.data.fire (req, res, (req, res) => { // keep routing if there are more routes to explore if (node.next != null) - if (perform_routing (node.next, req, res, stack)) + if (perform_routing (node.next, req, res, local_context)) return; throw new ClientError.NOT_FOUND ("The request URI %s was not found.", req.uri.to_string (true)); - }, stack); + }, local_context); return true; } } @@ -322,15 +319,15 @@ namespace Valum { err is ServerError) ? err.code : 500; /* - * Only the error message is pushed on the stack, the handler - * should assume that the status code is the one for which it - * has been registered. + * Only the error message is pushed on the routing context, the + * handler should assume that the status code is the one for + * which it has been registered. */ if (this.status_handlers.contains (status_code)) { - var stack = new Queue (); - stack.push_tail (err.message); + var context = new Context (); + context["message"] = err.message; try { - if (this.perform_routing (this.status_handlers[status_code].head, req, res, stack)) + if (this.perform_routing (this.status_handlers[status_code].head, req, res, context)) return; // handled! } catch (Error err) { // feed the error back in the invocation @@ -444,10 +441,10 @@ namespace Valum { // initial invocation this.invoke (req, res, () => { - var stack = new Queue (); + var context = new Context (); // ensure at least one route has been declared with that method - if (this.perform_routing (this.routes.head, req, res, stack)) + if (this.perform_routing (this.routes.head, req, res, context)) return; // something matched // find routes from other methods matching this request @@ -460,7 +457,7 @@ namespace Valum { #else !string.joinv (",", allowed).contains (route.method) && #endif - route.match (req, stack)) { + route.match (req, new Context ())) { allowed += route.method; } } diff --git a/src/valum-server-sent-events.vala b/src/valum-server-sent-events.vala index 1abc227f2..4c3062152 100644 --- a/src/valum-server-sent-events.vala +++ b/src/valum-server-sent-events.vala @@ -56,13 +56,13 @@ namespace Valum.ServerSentEvents { * * @param request request this is responding to * @param send_event send a SSE message - * @param stack + * @param context * * @throws GLib.Error */ public delegate void EventStreamCallback (Request request, owned SendEventCallback send_event, - Queue stack) throws Error; + Context context) throws Error; /** * Middleware that create a context for sending Server-Sent Events. @@ -83,7 +83,7 @@ namespace Valum.ServerSentEvents { * @param context context for sending events */ public HandlerCallback stream_events (owned EventStreamCallback context) { - return (req, res, next, stack) => { + return (req, res, next, _context) => { HashTable @params; res.headers.get_content_type (out @params); res.headers.set_content_type ("text/event-stream", @params); @@ -111,7 +111,7 @@ namespace Valum.ServerSentEvents { res.body.write_all (message.str.data, null); res.body.flush (); - }, stack); + }, _context); } catch (Error err) { warning (err.message); } diff --git a/src/valum.vala b/src/valum.vala index 9f8636b08..2c0216a02 100644 --- a/src/valum.vala +++ b/src/valum.vala @@ -35,17 +35,14 @@ namespace Valum { public delegate void LoaderCallback (Router router); /** - * Match the request and populate the {@link VSGI.Request.params}. - * - * It is important for a matcher to populate the - * {@link VSGI.Request.params} only if it matches the request. + * Match the request and populate the initial {@link Valum.Context}. * * @since 0.1 * - * @param req request being matched - * @param initial_stack destination for the initial routing stack + * @param req request being matched + * @param context initial context */ - public delegate bool MatcherCallback (Request req, Queue initial_stack); + public delegate bool MatcherCallback (Request req, Context context); /** * Produce a matching middleware that negates the provided middleware. @@ -91,21 +88,22 @@ namespace Valum { * @throws ServerError trigger a 5xx server error * @throws Error any other error which will be handled as a {@link Valum.ServerError.INTERNAL} * - * @param req request being handled - * @param res response to send back to the requester - * @param next keep routing - * @param stack routing stack as altered by the preceeding next invocation - * or initialized by the first {@link Valum.MatcherCallback} + * @param req request being handled + * @param res response to send back to the requester + * @param next keep routing + * @param context routing context which parent is the context of the + * preceeding 'next' invocation or initialized by the + * first {@link Valum.MatcherCallback} */ public delegate void HandlerCallback (Request req, Response res, NextCallback next, - Queue stack) throws Informational, - Success, - Redirection, - ClientError, - ServerError, - Error; + Context context) throws Informational, + Success, + Redirection, + ClientError, + ServerError, + Error; /** * Continuation passed in a {@link Valum.HandlerCallback} to *keep routing* diff --git a/src/vsgi-request.vala b/src/vsgi-request.vala index ecdda5742..b05d99597 100644 --- a/src/vsgi-request.vala +++ b/src/vsgi-request.vala @@ -61,16 +61,6 @@ namespace VSGI { */ public const string[] METHODS = {OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT, PATCH}; - /** - * Parameters for the request. - * - * These should be extracted from the URI path. - * - * @since 0.0.1 - */ - [Deprecated (since = "0.2")] - public HashTable? @params { get; set; default = null; } - /** * Connection containing raw streams. * diff --git a/tests/cgi-test.vala b/tests/cgi-test.vala index 071afc48c..7bc098efb 100644 --- a/tests/cgi-test.vala +++ b/tests/cgi-test.vala @@ -43,7 +43,6 @@ public static void test_vsgi_cgi_request () { assert (request.query.contains ("a")); assert ("b" == request.query["a"]); assert (3003 == request.uri.get_port ()); - assert (null == request.params); assert ("example.com" == request.headers.get_one ("Host")); assert (connection.input_stream == request.body); } diff --git a/tests/fastcgi-test.vala b/tests/fastcgi-test.vala index 183817ea5..04b2ab798 100644 --- a/tests/fastcgi-test.vala +++ b/tests/fastcgi-test.vala @@ -38,7 +38,6 @@ public static void test_vsgi_fastcgi_request () { assert ("0.0.0.0" == request.uri.get_host ()); assert (3003 == request.uri.get_port ()); assert (null == request.query); - assert (null == request.params); assert ("example.com" == request.headers.get_one ("Host")); assert (connection.input_stream == request.body); } diff --git a/tests/negociate-test.vala b/tests/negociate-test.vala index bfe5b2f0f..860eccfcf 100644 --- a/tests/negociate-test.vala +++ b/tests/negociate-test.vala @@ -9,9 +9,9 @@ public void test_negociate () { req.headers.append ("Accept", "text/html; q=0.9, text/xml; q=0"); - assert (negociate ("Accept", "text/html") (req, null)); - assert (!negociate ("Accept", "text/xml") (req, null)); - assert (!negociate ("Accept-Encoding", "utf-8") (req, null)); + assert (negociate ("Accept", "text/html") (req, new Context ())); + assert (!negociate ("Accept", "text/xml") (req, new Context ())); + assert (!negociate ("Accept-Encoding", "utf-8") (req, new Context ())); } diff --git a/tests/route-test.vala b/tests/route-test.vala index 1b517b0cf..acfbc7f91 100644 --- a/tests/route-test.vala +++ b/tests/route-test.vala @@ -22,12 +22,12 @@ using VSGI.Test; * @since 0.1 */ public void test_route () { - var router = new Router (); - var route = new Route (router, "GET", (req) => { return true; }, (req, res) => {}); - var req = new Request.with_uri (new Soup.URI ("http://localhost/5")); - var stack = new Queue (); + var router = new Router (); + var route = new Route (router, "GET", (req) => { return true; }, (req, res) => {}); + var req = new Request.with_uri (new Soup.URI ("http://localhost/5")); + var context = new Context (); - assert (route.match (req, stack)); + assert (route.match (req, context)); assert (router == route.router); } @@ -35,75 +35,64 @@ public void test_route () { * @since 0.1 */ public void test_route_from_rule () { - var route = new Route.from_rule (new Router (), "GET", "", (req, res) => {}); + var route = new Route.from_rule (new Router (), "GET", "", (req, res) => {}); var request = new Request.with_uri (new Soup.URI ("http://localhost/5")); - var stack = new Queue (); + var context = new Context (); - assert (route.match (request, stack)); - assert (request.params != null); - assert (request.params["id"] == "5"); - assert ("5" == stack.pop_tail ().get_string ()); + assert (route.match (request, context)); + assert ("5" == context["id"].get_string ()); } /** * @since 0.1 */ public void test_route_from_rule_null () { - var route = new Route.from_rule (new Router (), "GET", null, (req, res) => {}); + var route = new Route.from_rule (new Router (), "GET", null, (req, res) => {}); var request = new Request.with_uri (new Soup.URI ("http://localhost/5")); - var stack = new Queue (); + var context = new Context (); - assert (route.match (request, stack)); - assert (request.params != null); - assert (request.params.contains ("path")); - assert ("5" == request.params["path"]); - assert ("5" == stack.pop_tail ().get_string ()); + assert (route.match (request, context)); + assert (context.contains ("path")); + assert ("5" == context["path"].get_string ()); } /** * @since 0.1 */ public static void test_route_from_rule_null_matches_empty_path () { - var route = new Route.from_rule (new Router (), "GET", null, (req, res) => {}); + var route = new Route.from_rule (new Router (), "GET", null, (req, res) => {}); var request = new Request.with_uri (new Soup.URI ("http://localhost/")); - var stack = new Queue (); + var context = new Context (); - assert (route.match (request, stack)); - assert ("" == request.params["path"]); - assert ("" == stack.pop_tail ().get_string ()); + assert (route.match (request, context)); + assert ("" == context["path"].get_string ()); } /** * @since 0.1 */ public void test_route_from_rule_any () { - var route = new Route.from_rule (new Router (), "GET", "", (req, res) => {}); + var route = new Route.from_rule (new Router (), "GET", "", (req, res) => {}); var request = new Request.with_uri (new Soup.URI ("http://localhost/5")); - var stack = new Queue (); + var context = new Context (); - assert (route.match (request, stack)); + assert (route.match (request, context)); - assert (request.params != null); - assert (request.params["id"] == "5"); - assert ("5" == stack.pop_tail ().get_string ()); + assert ("5" == context["id"].get_string ()); } /** * @since 0.1 */ public void test_route_from_rule_without_captures () { - var route = new Route.from_rule (new Router (), "GET", "", (req, res) => {}); - var req = new Request.with_uri (new Soup.URI ("http://localhost/")); - var stack = new Queue (); - - assert (req.params == null); + var route = new Route.from_rule (new Router (), "GET", "", (req, res) => {}); + var req = new Request.with_uri (new Soup.URI ("http://localhost/")); + var context = new Context (); - var matches = route.match (req, stack); + var matches = route.match (req, context); // ensure params are still null if there is no captures assert (matches); - assert (req.params == null); - assert (stack.is_empty ()); } /** @@ -117,43 +106,31 @@ public void test_route_from_rule_undefined_type () { * @since 0.1 */ public void test_route_from_regex () { - var route = new Route.from_regex (new Router (), "GET", /(?\d+)/, (req, res) => {}); - var req = new Request.with_uri (new Soup.URI ("http://localhost/5")); - var stack = new Queue (); - - assert (req.params == null); + var route = new Route.from_regex (new Router (), "GET", /(?\d+)/, (req, res) => {}); + var req = new Request.with_uri (new Soup.URI ("http://localhost/5")); + var context = new Context (); - var matches = route.match (req, stack); + var matches = route.match (req, context); assert (matches); - assert (req.params != null); - assert (req.params["id"] == "5"); - assert ("5" == stack.pop_tail ().get_string ()); + assert ("5" == context["id"].get_string ()); } /** * @since 0.2 */ public void test_route_from_regex_multiple_captures () { - var route = new Route.from_regex (new Router (), "GET", /(?\w+)\/(?\d+)/, (req, res) => {}); - var req = new Request.with_uri (new Soup.URI ("http://localhost/user/5")); - var stack = new Queue (); + var route = new Route.from_regex (new Router (), "GET", /(?\w+)\/(?\d+)/, (req, res) => {}); + var req = new Request.with_uri (new Soup.URI ("http://localhost/user/5")); + var context = new Context (); - assert (req.params == null); - - var matches = route.match (req, stack); - - assert (matches); - assert (req.params != null); + assert (route.match (req, context)); - assert ("action" in req.params); - assert ("id" in req.params); + assert ("action" in context); + assert ("id" in context); - assert ("user" == req.params["action"]); - assert ("5" == req.params["id"]); - - assert ("5" == stack.pop_tail ().get_string ()); - assert ("user" == stack.pop_tail ().get_string ()); + assert ("user" == context["action"].get_string ()); + assert ("5" == context["id"].get_string ()); } /** @@ -164,66 +141,54 @@ public void test_route_from_regex_scoped () { router.scopes.push_tail ("test"); - var route = new Route.from_regex (router, "GET", /(?\d+)/, (req, res) => {}); - var req = new Request.with_uri (new Soup.URI ("http://localhost/test/5")); - var stack = new Queue (); - - assert (req.params == null); + var route = new Route.from_regex (router, "GET", /(?\d+)/, (req, res) => {}); + var req = new Request.with_uri (new Soup.URI ("http://localhost/test/5")); + var context = new Context (); - var matches = route.match (req, stack); + assert (route.match (req, context)); - assert (matches); - assert (req.params != null); - assert (req.params["id"] == "5"); - assert ("5" == stack.pop_tail ().get_string ()); + assert ("5" == context["id"].get_string ()); } /** * @since 0.1 */ public void test_route_from_regex_without_captures () { - var route = new Route.from_regex (new Router (), "GET", /.*/, (req, res) => {}); - var req = new Request.with_uri (new Soup.URI ("http://localhost/")); - var stack = new Queue (); + var route = new Route.from_regex (new Router (), "GET", /.*/, (req, res) => {}); + var req = new Request.with_uri (new Soup.URI ("http://localhost/")); + var context = new Context (); - var matches = route.match (req, stack); + var matches = route.match (req, context); // ensure params are still null if there is no captures - assert (route.match (req, stack)); - assert (req.params == null); - assert (stack.is_empty ()); + assert (route.match (req, context)); } /** * @since 0.1 */ public void test_route_match () { - var route = new Route.from_rule (new Router (), "GET", "", (req, res) => {}); - var req = new Request.with_uri (new Soup.URI ("http://localhost/5")); - var stack = new Queue (); + var route = new Route.from_rule (new Router (), "GET", "", (req, res) => {}); + var req = new Request.with_uri (new Soup.URI ("http://localhost/5")); + var context = new Context (); - assert (req.params == null); - - var matches = route.match (req, stack); + var matches = route.match (req, context); assert (matches); - assert (req.params != null); - assert (req.params.contains ("id")); - assert ("5" == stack.pop_tail ().get_string ()); + assert (context.contains ("id")); + assert ("5" == context["id"].get_string ()); } /** * @since 0.1 */ public void test_route_match_not_matching () { - var route = new Route.from_rule (new Router (), "GET", "", (req, res) => {}); - var req = new Request.with_uri (new Soup.URI ("http://localhost/home")); - var stack = new Queue (); + var route = new Route.from_rule (new Router (), "GET", "", (req, res) => {}); + var req = new Request.with_uri (new Soup.URI ("http://localhost/home")); + var context = new Context (); // no match and params remains null - assert (route.match (req, stack) == false); - assert (req.params == null); - assert (stack.is_empty ()); + assert (route.match (req, context) == false); } /** @@ -234,9 +199,10 @@ public void test_route_fire () { var route = new Route.from_rule (new Router (), "GET", "", (req, res) => { setted = true; }); - var req = new Request.with_uri (new Soup.URI ("http://localhost/home")); - var res = new Response (req); - var stack = new Queue (); + + var req = new Request.with_uri (new Soup.URI ("http://localhost/home")); + var res = new Response (req); + var context = new Context (); assert (setted == false); diff --git a/tests/router-test.vala b/tests/router-test.vala index ec52444fb..59a7d4cab 100644 --- a/tests/router-test.vala +++ b/tests/router-test.vala @@ -660,8 +660,8 @@ public static void test_router_next_propagate_state () { var router = new Router (); var state = new Object (); - router.get ("", (req, res, next, stack) => { - stack.push_tail (state); + router.get ("", (req, res, next, context) => { + context["state"] = state; next (req, res); }); @@ -669,9 +669,9 @@ public static void test_router_next_propagate_state () { next (req, res); }); - router.get ("", (req, res, next, st) => { + router.get ("", (req, res, next, context) => { res.status = 413; - assert (st.pop_tail () == state); + assert (state == context["state"]); }); var request = new Request.with_uri (new Soup.URI ("http://localhost/")); @@ -689,19 +689,22 @@ public static void test_router_next_replace_propagated_state () { var router = new Router (); var state = new Object (); - router.get ("", (req, res, next, stack) => { - stack.push_tail (state); + router.get ("", (req, res, next, context) => { + context["state"] = state; next (req, res); }); - router.get ("", (req, res, next, stack) => { - assert (state == stack.pop_tail ()); + router.get ("", (req, res, next, context) => { + assert (state == context["state"]); + context["state"] = "something really different"; next (req, res); }); - router.get ("", (req, res, next, stack) => { + router.get ("", (req, res, next, context) => { res.status = 413; - assert (stack.is_empty ()); + assert (context["state"].holds (typeof (string))); + assert (context.parent["state"].holds (typeof (string))); + assert (context.parent.parent["state"].holds (typeof (Object))); }); var request = new Request.with_uri (new Soup.URI ("http://localhost/")); @@ -715,8 +718,8 @@ public static void test_router_next_replace_propagated_state () { public static void test_router_status_propagates_error_message () { var router = new Router (); - router.status (404, (req, res, next, stack) => { - var message = stack.pop_tail (); + router.status (404, (req, res, next, context) => { + var message = context["message"]; res.status = 418; assert ("The request URI / was not found." == message.get_string ()); }); @@ -780,13 +783,13 @@ public static void test_router_invoke_propagate_state () { var router = new Router (); var message = "test"; - router.get ("", (req, res, next, stack) => { - stack.push_tail (message); + router.get ("", (req, res, next, context) => { + context["message"] = message; router.invoke (req, res, next); }); - router.get ("", (req, res, next, stack) => { - assert (message == stack.pop_tail ().get_string ()); + router.get ("", (req, res, next, context) => { + assert (message == context["message"].get_string ()); throw new ClientError.IM_A_TEAPOT ("this is insane!"); }); @@ -827,18 +830,18 @@ public void test_router_then () { /** * @since 0.2.2 */ -public void test_router_then_preserve_matching_stack () { +public void test_router_then_preserve_matching_context () { var router = new Router (); var reached = false; - router.get ("", (req, res, next, stack) => { - stack.push_tail ("test"); + router.get ("", (req, res, next, context) => { + context["test"] = "test"; next (req, res); - }).then ((req, res, next, stack) => { + }).then ((req, res, next, context) => { reached = true; - assert ("test" == stack.pop_tail ().get_string ()); - assert ("5" == stack.pop_tail ().get_string ()); + assert ("test" == context["test"].get_string ()); + assert ("5" == context["id"].get_string ()); }); var req = new Request.with_uri (new Soup.URI ("http://localhost/5")); diff --git a/tests/soup-test.vala b/tests/soup-test.vala index 4251e40ee..0ac838f19 100644 --- a/tests/soup-test.vala +++ b/tests/soup-test.vala @@ -32,7 +32,6 @@ public static void test_vsgi_soup_request () { assert ("0.0.0.0" == request.uri.get_host ()); assert (3003 == request.uri.get_port ()); assert (null == request.query); - assert (null == request.params); assert (message.request_headers == request.headers); } diff --git a/tests/tests.vala b/tests/tests.vala index cd3f5c0d2..69c6e5924 100644 --- a/tests/tests.vala +++ b/tests/tests.vala @@ -75,7 +75,7 @@ public int main (string[] args) { Test.add_func ("/router/invoke/propagate_state", test_router_invoke_propagate_state); Test.add_func ("/router/then", test_router_then); - Test.add_func ("/router/then/preserve_matching_stack", test_router_then_preserve_matching_stack); + Test.add_func ("/router/then/preserve_matching_context", test_router_then_preserve_matching_context); Test.add_func ("/router/error", test_router_error);