kemal icon indicating copy to clipboard operation
kemal copied to clipboard

Router Conditions

Open MuhammetDilmac opened this issue 7 years ago • 8 comments

Maybe support some conditions on route definitions.

Like sinatra; http://www.sinatrarb.com/intro.html#conditions

Example;

get "/", host_name: "api.example.com" do
  "api.example.com"
end

MuhammetDilmac avatar Sep 25 '17 15:09 MuhammetDilmac

Not sure about this, an alternative and more decoupled of doing this would be using a middleware.

class SubdomainHandler
  only ["/"]

  def call(env)
    if only_match?(env)
    ..... # your_logic
    end
    call_next(env)
  end
end

add_handler SubdomainHandler.new

get "/" do |env|
  "api.example.com"
end

sdogruyol avatar Sep 25 '17 15:09 sdogruyol

The problem with the middleware solution is it's completely invisible to the developer working on the route. I think middleware makes more sense when more general, perhaps adding content-json headers to everything with a '/api/*' route.

For a single route, express js has route specific middleware, which you could attach as another parameter within the route declaration. The 2nd parameter can actually take an array of middlewares.

eg

var is_admin = function (req, res, next) {
  //do something
  next()
})

var is_over18 = function (req, res, next) {
  //do something else
  next()
})

app.get('/example', [is_admin, is_over18], function (req, res) {
  res.send("You're and admin and over 18")
})

This is much more declarative, anyone looking at the route can see whats happening. In kemal we could replicate this with procs?

def id_admin(ctx)
  # return if ok, or throw
end

def is_over18(ctx)
  # return if ok, or throw
end

get "/example",[->is_admin(Ctx),->is_over18(Ctx)] do |ctx|
  "You're and admin and over 18"
end

Or with standard middleware handlers

class Is_Admin
  include HTTP::Handler
  def call(context)
    # do something
    call_next(context)
  end
end

class Is_Over_18
  include HTTP::Handler
  def call(context)
    # do something
    call_next(context)
  end
end

get "/example",[Is_Admin.new,Is_Over_18.new] do |ctx|
  "You're and admin and over 18"
end

WDYT?

crisward avatar Oct 04 '17 18:10 crisward

I have some ideas about middlewares but it's still in flux. How about creating a new issue for this proposal (RFC) @crisward ?

sdogruyol avatar Oct 05 '17 14:10 sdogruyol

Ok. Any preference on the above. On reflection I think I prefer 2 (standard middleware handlers), especially as you could use existing middleware like this.

The definition is more verbose, but most middleware is going to contain a handful of lines of code anyway.

crisward avatar Oct 05 '17 14:10 crisward

@crisward let's consider both, however my preference tends to be in favor for options 2

sdogruyol avatar Oct 05 '17 15:10 sdogruyol

Been a while since last comment on this but what would your thoughts be on giving routes an optional config hash/namedtuple. My reasoning is there doesn't seem to be a way to nicely handle route specific options, besides using a Handler with an only [] macro. That is totally doable but is a little bit of a pain imo as that array could get pretty big after a while.

Was thinking this could work like:

  get "/user/:id", {resolve: User} do
    "Hello World!"
  end

  # Wouldn't trigger resolve handler nor auth handler
  get "/settings/:id", do
    "Hello World!"
  end

  post "/user/:id", {auth: true, requirements: [Is_Admin.new,Is_Over_18.new]} do
    "Hello World!"
  end
  class ResolveHandler < Kemal::Handler
    def call(context)
      if context.config && context.config[:resolve]
        # Query User model for record with given id
        # Set given record to context (or env.set?) as well maybe so could be accessible within route.
      end
      call_next context
    end
  end
  class AuthHandler < Kemal::Handler
    def call(context)
      if context.config && context.config[:authed]
        # Do your auth logic here
      end
      call_next context
    end
  end

Edit overall i think this adds quite a bit of flexibility as the same handler can be used for multiple routes, but the config can slightly change the logic of each handler, reducing the amount of handlers required.

Blacksmoke16 avatar Jul 04 '18 19:07 Blacksmoke16

I wouldn't put too much logic in the routing layer, that's what handlers are for.

straight-shoota avatar Jul 04 '18 20:07 straight-shoota

The main logic would still live in the handler. The config by itself doesn't do anything unless a handler is setup to utilize it. Just adds some added flexibility without having to keep an array up to date or a handler specific to one senario.

Blacksmoke16 avatar Jul 04 '18 21:07 Blacksmoke16