Skip to content

Commit

Permalink
handler improvements (#240)
Browse files Browse the repository at this point in the history
* added not found handler to mux

* add HandleNotFound into interface

* removed redundant handle prefixes in mux

* fix panic

* remove NotFound method from Router & export Mux type

* add not found handler to handler example

* add docs

---------

Co-authored-by: TopiSenpai <[email protected]>
  • Loading branch information
Thunder33345 and topi314 authored Feb 16, 2023
1 parent 12bf0cc commit 604780d
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 43 deletions.
24 changes: 15 additions & 9 deletions _examples/handler/example.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/disgoorg/disgo"
"github.com/disgoorg/disgo/bot"
"github.com/disgoorg/disgo/discord"
"github.com/disgoorg/disgo/events"
"github.com/disgoorg/disgo/handler"
"github.com/disgoorg/disgo/handler/middleware"
)
Expand Down Expand Up @@ -72,18 +73,19 @@ func main() {
r.Group(func(r handler.Router) {
r.Use(middleware.Print("group1"))
r.Route("/test", func(r handler.Router) {
r.HandleCommand("/sub2", handleContent("/test/sub2"))
r.Command("/sub2", handleContent("/test/sub2"))
r.Route("/{group}", func(r handler.Router) {
r.HandleCommand("/sub", handleVariableContent)
r.Command("/sub", handleVariableContent)
})
})
})
r.Group(func(r handler.Router) {
r.Use(middleware.Print("group2"))
r.HandleCommand("/ping", handlePing)
r.HandleCommand("/ping2", handleContent("pong2"))
r.HandleComponent("button1/{data}", handleComponent)
r.Command("/ping", handlePing)
r.Command("/ping2", handleContent("pong2"))
r.Component("button1/{data}", handleComponent)
})
r.NotFound(handleNotFound)

client, err := disgo.New(token,
bot.WithDefaultGateway(),
Expand Down Expand Up @@ -111,17 +113,17 @@ func main() {
}

func handleContent(content string) handler.CommandHandler {
return func(client bot.Client, event *handler.CommandEvent) error {
return func(event *handler.CommandEvent) error {
return event.CreateMessage(discord.MessageCreate{Content: content})
}
}

func handleVariableContent(client bot.Client, event *handler.CommandEvent) error {
func handleVariableContent(event *handler.CommandEvent) error {
group := event.Variables["group"]
return event.CreateMessage(discord.MessageCreate{Content: "group: " + group})
}

func handlePing(client bot.Client, event *handler.CommandEvent) error {
func handlePing(event *handler.CommandEvent) error {
return event.CreateMessage(discord.MessageCreate{
Content: "pong",
Components: []discord.ContainerComponent{
Expand All @@ -132,7 +134,11 @@ func handlePing(client bot.Client, event *handler.CommandEvent) error {
})
}

func handleComponent(client bot.Client, event *handler.ComponentEvent) error {
func handleComponent(event *handler.ComponentEvent) error {
data := event.Variables["data"]
return event.CreateMessage(discord.MessageCreate{Content: "component: " + data})
}

func handleNotFound(event *events.InteractionCreate) error {
return event.Respond(discord.InteractionResponseTypeCreateMessage, discord.MessageCreate{Content: "not found"})
}
19 changes: 18 additions & 1 deletion handler/handler.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
// Package handler provides a way to handle interactions like application commands, autocomplete, buttons, select menus & modals with a simple interface.
//
// The handler package is inspired by the go-chi/chi http router.
// Each interaction has a path which is either the command name (starting with /) or the custom id. According to this path all interactions are routed to the correct handler.
// Slash Commands can have subcommands, which are nested paths. For example /test/subcommand1 or /test/subcommandgroup/subcommand.
//
// The handler also supports variables in its path which is especially useful for subcommands, components and modals.
// Variables are defined by curly braces like {variable} and can be accessed in the handler via the Variables map.
//
// You can also register middlewares, which are executed before the handler is called. Middlewares can be used to check permissions, validate input or do other things.
// Middlewares can also be attached to sub-routers, which is useful if you want to have a middleware for all subcommands of a command as an example.
// A middleware does not care which interaction type it is, it is just executed before the handler and has the following signature:
// type Middleware func(next func(e *events.InteractionCreate)) func(e *events.InteractionCreate)
//
// The handler iterates over all routes until it finds the fist matching route. If no route matches, the handler will call the NotFoundHandler.
// The NotFoundHandler can be set via the `NotFound` method on the *Mux. If no NotFoundHandler is set nothing will happen.

package handler

import (
Expand Down Expand Up @@ -25,7 +42,7 @@ func (h *handlerHolder[T]) Match(path string, t discord.InteractionType) bool {
if strings.HasPrefix(part, "{") && strings.HasSuffix(part, "}") {
continue
}
if part != parts[i] {
if len(parts) <= i || part != parts[i] {
return false
}
}
Expand Down
68 changes: 46 additions & 22 deletions handler/mux.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,29 @@ import (
"github.com/disgoorg/disgo/events"
)

func New() Router {
return &mux{}
// New returns a new Router.
func New() *Mux {
return &Mux{}
}

func newRouter(pattern string, middlewares []Middleware, routes []Route) *mux {
return &mux{
func newRouter(pattern string, middlewares []Middleware, routes []Route) *Mux {
return &Mux{
pattern: pattern,
middlewares: middlewares,
routes: routes,
}
}

type mux struct {
pattern string
middlewares []Middleware
routes []Route
// Mux is a basic Router implementation.
type Mux struct {
pattern string
middlewares []Middleware
routes []Route
notFoundHandler NotFoundHandler
}

func (r *mux) OnEvent(event bot.Event) {
// OnEvent is called when a new event is received.
func (r *Mux) OnEvent(event bot.Event) {
e, ok := event.(*events.InteractionCreate)
if !ok {
return
Expand All @@ -53,7 +57,8 @@ func (r *mux) OnEvent(event bot.Event) {
}
}

func (r *mux) Match(path string, t discord.InteractionType) bool {
// Match returns true if the given path matches the Route.
func (r *Mux) Match(path string, t discord.InteractionType) bool {
if r.pattern != "" {
parts := splitPath(path)
patternParts := splitPath(r.pattern)
Expand All @@ -63,7 +68,7 @@ func (r *mux) Match(path string, t discord.InteractionType) bool {
if strings.HasPrefix(part, "{") && strings.HasSuffix(part, "}") {
continue
}
if part != parts[i] {
if len(parts) <= i || part != parts[i] {
return false
}
}
Expand All @@ -77,7 +82,8 @@ func (r *mux) Match(path string, t discord.InteractionType) bool {
return false
}

func (r *mux) Handle(path string, variables map[string]string, e *events.InteractionCreate) error {
// Handle handles the given interaction event.
func (r *Mux) Handle(path string, variables map[string]string, e *events.InteractionCreate) error {
path = parseVariables(path, r.pattern, variables)
middlewares := func(event *events.InteractionCreate) {}
for i := len(r.middlewares) - 1; i >= 0; i-- {
Expand All @@ -90,44 +96,53 @@ func (r *mux) Handle(path string, variables map[string]string, e *events.Interac
return route.Handle(path, variables, e)
}
}
if r.notFoundHandler != nil {
return r.notFoundHandler(e)
}
return nil
}

func (r *mux) Use(middlewares ...Middleware) {
// Use adds the given middlewares to the current Router.
func (r *Mux) Use(middlewares ...Middleware) {
r.middlewares = append(r.middlewares, middlewares...)
}

func (r *mux) With(middlewares ...Middleware) Router {
// With returns a new Router with the given middlewares.
func (r *Mux) With(middlewares ...Middleware) Router {
return newRouter("", middlewares, nil)
}

func (r *mux) Group(fn func(router Router)) {
// Group creates a new Router and adds it to the current Router.
func (r *Mux) Group(fn func(router Router)) {
router := New()
fn(router)
r.handle(router)
}

func (r *mux) Route(pattern string, fn func(r Router)) Router {
// Route creates a new sub-router with the given pattern and adds it to the current Router.
func (r *Mux) Route(pattern string, fn func(r Router)) Router {
checkPattern(pattern)
router := newRouter(pattern, nil, nil)
fn(router)
r.handle(router)
return router
}

func (r *mux) Mount(pattern string, router Router) {
// Mount mounts the given router with the given pattern to the current Router.
func (r *Mux) Mount(pattern string, router Router) {
if pattern == "" {
r.handle(router)
return
}
r.handle(newRouter(pattern, nil, []Route{router}))
}

func (r *mux) handle(route Route) {
func (r *Mux) handle(route Route) {
r.routes = append(r.routes, route)
}

func (r *mux) HandleCommand(pattern string, h CommandHandler) {
// Command registers the given CommandHandler to the current Router.
func (r *Mux) Command(pattern string, h CommandHandler) {
checkPattern(pattern)
r.handle(&handlerHolder[CommandHandler]{
pattern: pattern,
Expand All @@ -136,7 +151,8 @@ func (r *mux) HandleCommand(pattern string, h CommandHandler) {
})
}

func (r *mux) HandleAutocomplete(pattern string, h AutocompleteHandler) {
// Autocomplete registers the given AutocompleteHandler to the current Router.
func (r *Mux) Autocomplete(pattern string, h AutocompleteHandler) {
checkPattern(pattern)
r.handle(&handlerHolder[AutocompleteHandler]{
pattern: pattern,
Expand All @@ -145,7 +161,8 @@ func (r *mux) HandleAutocomplete(pattern string, h AutocompleteHandler) {
})
}

func (r *mux) HandleComponent(pattern string, h ComponentHandler) {
// Component registers the given ComponentHandler to the current Router.
func (r *Mux) Component(pattern string, h ComponentHandler) {
checkPatternEmpty(pattern)
r.handle(&handlerHolder[ComponentHandler]{
pattern: pattern,
Expand All @@ -154,7 +171,8 @@ func (r *mux) HandleComponent(pattern string, h ComponentHandler) {
})
}

func (r *mux) HandleModal(pattern string, h ModalHandler) {
// Modal registers the given ModalHandler to the current Router.
func (r *Mux) Modal(pattern string, h ModalHandler) {
checkPatternEmpty(pattern)
r.handle(&handlerHolder[ModalHandler]{
pattern: pattern,
Expand All @@ -163,6 +181,12 @@ func (r *mux) HandleModal(pattern string, h ModalHandler) {
})
}

// NotFound sets the NotFoundHandler for this router.
// This handler only works for the root router and will be ignored for sub routers.
func (r *Mux) NotFound(h NotFoundHandler) {
r.notFoundHandler = h
}

func checkPatternEmpty(pattern string) {
if pattern == "" {
panic("pattern must not be empty")
Expand Down
29 changes: 18 additions & 11 deletions handler/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,18 @@ type (
AutocompleteHandler func(e *AutocompleteEvent) error
ComponentHandler func(e *ComponentEvent) error
ModalHandler func(e *ModalEvent) error
NotFoundHandler func(event *events.InteractionCreate) error
)

var (
_ Route = (*mux)(nil)
_ Route = (*Mux)(nil)
_ Route = (*handlerHolder[CommandHandler])(nil)
_ Route = (*handlerHolder[AutocompleteHandler])(nil)
_ Route = (*handlerHolder[ComponentHandler])(nil)
_ Route = (*handlerHolder[ModalHandler])(nil)
)

// Route is a basic interface for a route in a Router.
type Route interface {
// Match returns true if the given path matches the Route.
Match(path string, t discord.InteractionType) bool
Expand All @@ -26,14 +31,16 @@ type Route interface {
Handle(path string, variables map[string]string, e *events.InteractionCreate) error
}

// Router provides with the core routing functionality.
// It is used to register handlers and middlewares and sub-routers.
type Router interface {
bot.EventListener
Route

// Use adds the given middlewares to the current Router
// Use adds the given middlewares to the current Router.
Use(middlewares ...Middleware)

// With returns a new Router with the given middlewares
// With returns a new Router with the given middlewares.
With(middlewares ...Middleware) Router

// Group creates a new Router and adds it to the current Router.
Expand All @@ -45,15 +52,15 @@ type Router interface {
// Mount mounts the given router with the given pattern to the current Router.
Mount(pattern string, r Router)

// HandleCommand registers the given CommandHandler to the current Router.
HandleCommand(pattern string, h CommandHandler)
// Command registers the given CommandHandler to the current Router.
Command(pattern string, h CommandHandler)

// HandleAutocomplete registers the given AutocompleteHandler to the current Router.
HandleAutocomplete(pattern string, h AutocompleteHandler)
// Autocomplete registers the given AutocompleteHandler to the current Router.
Autocomplete(pattern string, h AutocompleteHandler)

// HandleComponent registers the given ComponentHandler to the current Router.
HandleComponent(pattern string, h ComponentHandler)
// Component registers the given ComponentHandler to the current Router.
Component(pattern string, h ComponentHandler)

// HandleModal registers the given ModalHandler to the current Router.
HandleModal(pattern string, h ModalHandler)
// Modal registers the given ModalHandler to the current Router.
Modal(pattern string, h ModalHandler)
}

0 comments on commit 604780d

Please sign in to comment.