Skip to content

Commit

Permalink
Basic slug-route matching
Browse files Browse the repository at this point in the history
  • Loading branch information
mariomenjr committed Apr 25, 2022
1 parent 3d1cd8d commit 4b37849
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 30 deletions.
3 changes: 2 additions & 1 deletion handlr.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ type Handlr struct {

// Registers Routers and ListenAndServer over Handlr.mux
func (h *Handlr) Start(portNumber int) error {
h.router.regiterRoutesAndHandler(h.mux)
h.mux.Handle("/", &h.router) // Binds mux to routes

return h.listenAndServe(portNumber)
}

Expand Down
72 changes: 54 additions & 18 deletions router.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package handlr

import (
"log"
"net/http"
"path"
"strings"
)

// A router instance will house paths and handlers.
Expand All @@ -14,41 +15,76 @@ type Router struct {
path string
parent *Router
children []*Router
handler *func(w http.ResponseWriter, r *http.Request)
handler *ActionHandler
}

// Allows Route registration.
// You don't program behavior through this method.
func (r *Router) Route(path string, routeHandler func(r *Router)) {
router := &Router{path: path, parent: r}
func (rt *Router) Route(path string, routeHandler RouteHandler) {
router := &Router{path: path, parent: rt}
routeHandler(router)

r.children = append(r.children, router)
rt.children = append(rt.children, router)
}

// Allows Handler registration which gives you the ability
// to tie a behavior to a path.
// i.e. Get a record from database by hiting URL:
// http://example.org/get/record?id=1
func (r *Router) Handler(path string, actionHandler func(w http.ResponseWriter, r *http.Request)) {
router := &Router{path: path, parent: r, handler: &actionHandler}
func (rt *Router) Handler(path string, actionHandler ActionHandler) {
router := &Router{path: path, parent: rt, handler: &actionHandler}

r.children = append(r.children, router)
rt.children = append(rt.children, router)
}

// Recursively register handlers for paths.
// An error will be thrown if the same path is registered twice, no ServeMux
// instance is provided, or mux.HandleFunc throws an error itself.
func (r *Router) regiterRoutesAndHandler(mux *http.ServeMux) {
if mux == nil {
log.Fatal("router: No *http.ServeMux instance provided for registering routes and handlers.")
func (rt *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if handler := rt.findHandler(r); handler != nil {
(*handler)(w, r)
return
}
http.NotFound(w, r)
}

// It attempts to produce Router's endpoint path
// based on parent and children routes.
func (rt *Router) buildPath() string {
if rt.parent == nil {
return rt.path
}
return path.Join(rt.parent.buildPath(), trimSlash(rt.path))
}

// Recursively find a handler to the request
func (rt *Router) findHandler(r *http.Request) *ActionHandler {
for _, v := range rt.children {
if v.handler == nil {
continue
}

for _, v := range r.children {
if v.handler != nil {
mux.HandleFunc(v.buildPath(), *v.handler)
if v.isMatch(r) {
return v.handler
}

v.regiterRoutesAndHandler(mux)
v.findHandler(r)
}
return nil
}

// Asserts if Router path matches URL from Request
func (rt *Router) isMatch(r *http.Request) bool {
rp := strings.Split(trimSlash(rt.buildPath()), "/")
ep := strings.Split(trimSlash(r.URL.Path), "/")

if len(rp) == len(ep) {
m := true
for i := 0; i < len(rp); i++ {
rs := strings.ToLower(rp[i])
es := strings.ToLower(ep[i])

// Very basic slug matching. Regex later.
m = m && rs == es || (rs[0] == ':' && es != "")
}
return m
}
return false
}
6 changes: 6 additions & 0 deletions types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package handlr

import "net/http"

type ActionHandler func(w http.ResponseWriter, r *http.Request)
type RouteHandler func(r *Router)
11 changes: 0 additions & 11 deletions utils.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,9 @@
package handlr

import (
"path"
"strings"
)

// Given a Router instance and attempts to
// produce its endpoint path based on parent
// and children routes.
func (r *Router) buildPath() string {
if r.parent == nil {
return r.path
}
return path.Join(r.parent.buildPath(), trimSlash(r.path))
}

// Trims slashes from a string.
func trimSlash(endpoint string) string {
return strings.Trim(endpoint, "/")
Expand Down

0 comments on commit 4b37849

Please sign in to comment.