valum-framework / valum

Web micro-framework written in Vala
https://valum-framework.readthedocs.io/en/latest/
GNU Lesser General Public License v3.0
225 stars 23 forks source link

Problems with basepath #223

Closed devnix closed 1 year ago

devnix commented 5 years ago

Hi again!

I'm trying to build the skeleton of my app and I'm having a bad time with subroutes. I've tried several ways to implement it, and the basepath options seems the most modular approach.

I don't have any clue why any of my routes are matching. My current code is the following:

using Valum;
using Valum.ContentNegotiation;
using VSGI;

namespace ValaBB
{
    public class Application : Object
    {
        public static int main (string[] args)
        {
            var app = new Router();

            app.use(basic());
            app.use(accept("text/html"));

            app.use (basepath ("/", new ValaBB.Controller.IndexController().handle));
            app.use (basepath ("/admin", new ValaBB.Controller.AdminIndexController().handle));

            return Server.@new("http", handler: app).run(args);
        }
    }
}
using Valum;
using VSGI;

namespace ValaBB.Controller
{
    class IndexController : Router
    {
        construct {
            get ("/", index);
            get ("/test", test);
        }

        int counter = 0;

        public bool index(Request request, Response response, NextCallback next, Context context) throws Error
        {
            return response.expand_utf8("Counter: %s".printf(this.counter++.to_string()));
        }

        public bool test(Request request, Response response, NextCallback next, Context context) throws Error
        {
            return response.expand_utf8("Test: %s".printf(this.counter++.to_string()));
        }
    }
}
using Valum;
using VSGI;

namespace ValaBB.Controller
{
    class AdminIndexController : Router
    {
        construct
        {
            rule(Method.GET | Method.POST, "/", index);
            rule(Method.GET | Method.POST, "/test", index);
        }

        int counter = 0;

        public bool index(Request request, Response response, NextCallback next, Context context) throws Error
        {
            return response.expand_utf8("Counter: %s".printf(this.counter++.to_string()));
        }
    }
}

By the way, thank you so much for the framework and your time! I'm impatient to build something to give back to the community :-)

arteymix commented 5 years ago

The basepath for your index controller should be an empty string, otherwise it will match // in the controller.

Does the admin endpoints work as expected?

devnix commented 5 years ago

Ok, now I've registered the routes like this:

app.use(basepath("/admin", new ValaBB.Controller.Admin.IndexController().handle));
app.use(basepath("", new ValaBB.Controller.IndexController().handle));

and the both routers are working. But if I register the routes like this (which for my mind it has more sense, specially if the application grows), the admin router does not work:

app.use(basepath("", new ValaBB.Controller.IndexController().handle));
app.use(basepath("/admin", new ValaBB.Controller.Admin.IndexController().handle));

Is it the expected way to work? I really find it kinda confusing. I would expect to write something like this:

app.use(basepath("", new ValaBB.Controller.IndexController().handle));
app.use(basepath("/preferences", new ValaBB.Controller.PreferencesController().handle));
app.use(basepath("/user", new ValaBB.Controller.UserController().handle));
app.use(basepath("/category", new ValaBB.Controller.CategoryController().handle));
app.use(basepath("/thread", new ValaBB.Controller.ThreadController().handle));
app.use(basepath("/admin", new ValaBB.Controller.Admin.IndexController().handle));
app.use(basepath("/admin/user", new ValaBB.Controller.Admin.UserController().handle));
app.use(basepath("/admin/category", new ValaBB.Controller.Admin.CategoryController().handle));
app.use(basepath("/admin/thread", new ValaBB.Controller.Admin.ThreadController().handle));

and I would be forced to write somethink similar to this:

app.use(basepath("/admin/user", new ValaBB.Controller.Admin.UserController().handle));
app.use(basepath("/admin/category", new ValaBB.Controller.Admin.CategoryController().handle));
app.use(basepath("/admin/thread", new ValaBB.Controller.Admin.ThreadController().handle));
app.use(basepath("/admin", new ValaBB.Controller.Admin.IndexController().handle));
app.use(basepath("/preferences", new ValaBB.Controller.PreferencesController().handle));
app.use(basepath("/user", new ValaBB.Controller.UserController().handle));
app.use(basepath("/category", new ValaBB.Controller.CategoryController().handle));
app.use(basepath("/thread", new ValaBB.Controller.ThreadController().handle));
app.use(basepath("", new ValaBB.Controller.IndexController().handle));
arteymix commented 5 years ago

In basepath, unmatched route should result in a call to next by the forward callback (here the Router.handle), but it only implements the VSGI.Handler interface which does not have the capability of forwarding requests.

You could fix this with:

app.use (basepath ("", (req, res, next, ctx) => {
    if (!index_router.handle (req, res)) { // false is returned here if the request has not been "handled"
       return next (req, res, next, ctx);
    } else {
       return true;
   }
});

Basepath could be modified to adopt this behaviour, which would be more natural to work with callback that does not implement the middleware signature.

arteymix commented 5 years ago

The alternative would be to bind those index routes directly on the root router.

In the future, I wanted rework Router to work with classes implementing the Middleware interface instead of callbacks and having Middleware-wrapped callbacks, which would allow for a fully asynchronous routing.

Have a look at Template-GLib! I think it has gotten very good now since it's part of Builder and well maintained.