Bläddra i källkod

Router module, initial import.

Bozhin Zafirov 5 timmar sedan
incheckning
a946535ce4
6 ändrade filer med 183 tillägg och 0 borttagningar
  1. 24 0
      .gitignore
  2. 53 0
      README.md
  3. 17 0
      constructor.go
  4. 10 0
      errors.go
  5. 3 0
      go.mod
  6. 76 0
      route.go

+ 24 - 0
.gitignore

@@ -0,0 +1,24 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof

+ 53 - 0
README.md

@@ -0,0 +1,53 @@
+# router
+
+A golang library for HTTP routing. Once initialized, router is immutable and thread-safe.
+
+
+## Usage:
+
+First, import the router:
+
+	import "go.deck17.com/router"
+
+
+Then make a router instance:
+
+	urlRouter, err := router.NewRouter(
+		map[string]string{
+			"page.index": "/",
+			"page.about": "/about",
+			"page.edit":  "/object/{name}/edit",
+		},
+	)
+
+
+To initialize endpoints with http library:
+
+
+	pattern := func(key string) string {
+		path, err := urlRouter.Pattern(key)
+		if err != nil {
+            panic(err)
+        }
+        return path
+	}
+
+	http.HandlerFunc(pattern("page.index"), handleIndex)
+    http.HandlerFunc(pattern("page.edit"), handleEdit)
+
+
+To use the handler inside Go code:
+
+	route := func(key string, args ...any) string {
+		path, err := urlRouter.Route(key, args...)
+		if err != nil {
+			panic(err)
+		}
+		return path
+	}
+    
+    http.Redirect(w, route("page.edit", "object-1"), http.StatusSeeOther)
+
+
+It is also possible to use the router inside templates. For this to work, it is necessary to implement a custom template function
+either similar to "route" or just as a wrapper to "route". 

+ 17 - 0
constructor.go

@@ -0,0 +1,17 @@
+package router
+
+/* NewRouter initializes a new router instance */
+func NewRouter(urls map[string]string) (*Router, error) {
+	/* copy elements from urls */
+	router := &Router{
+		urlMap: make(map[string]routeElement),
+	}
+	/* pre-initialize routes */
+	for key, path := range urls {
+		if err := router.addRoute(key, path); err != nil {
+			return nil, err
+		}
+	}
+	/* return new Router object */
+	return router, nil
+}

+ 10 - 0
errors.go

@@ -0,0 +1,10 @@
+package router
+
+import "errors"
+
+var (
+	ErrMissingKey    = errors.New("route: missing key")
+	ErrArgMismatch   = errors.New("route: arguments mismatch")
+	ErrBrokenPattern = errors.New("route: broken pattern")
+	ErrKeyExists     = errors.New("route: key already exists")
+)

+ 3 - 0
go.mod

@@ -0,0 +1,3 @@
+module go.deck17.com/router
+
+go 1.24.4

+ 76 - 0
route.go

@@ -0,0 +1,76 @@
+package router
+
+import (
+	"fmt"
+	"strings"
+)
+
+/* routeElement contains a pre-compiled route pattern */
+type routeElement struct {
+	originalPath string
+	parsedPath   string
+	parsedArgs   int
+}
+
+/* Router object is safe for concurrent use after construction */
+type Router struct {
+	urlMap map[string]routeElement
+}
+
+/* addRoute adds or sets routing element in urlMap */
+func (r *Router) addRoute(key string, path string) error {
+	if _, ok := r.urlMap[key]; ok {
+		return ErrKeyExists
+	}
+	/* pre-compile path and replace path values with %v */
+	pathBuffer := strings.Builder{}
+	pathBuffer.Grow(len(path))
+	placeholderCount := 0
+	for idx := 0; idx < len(path); {
+		/* look for open bracket */
+		if path[idx] == '{' {
+			/* look for corresponding closing bracket */
+			closing := strings.IndexByte(path[idx:], '}')
+			if closing == -1 {
+				/* broken pattern, fail */
+				return ErrBrokenPattern
+			}
+			pathBuffer.WriteString("%v")
+			placeholderCount++
+			idx += closing + 1
+			continue
+		}
+		pathBuffer.WriteByte(path[idx])
+		idx++
+	}
+	/* save pre-compiled path */
+	r.urlMap[key] = routeElement{
+		originalPath: path,
+		parsedPath:   pathBuffer.String(),
+		parsedArgs:   placeholderCount,
+	}
+	return nil
+
+}
+
+/* Route returns path  route by key */
+func (r *Router) Route(key string, args ...any) (string, error) {
+	/* lookup path in the database */
+	element, ok := r.urlMap[key]
+	if !ok {
+		return "", ErrMissingKey
+	}
+	/* return path unchanged if there are no arguments */
+	if len(args) != element.parsedArgs {
+		return "", ErrArgMismatch
+	}
+	return fmt.Sprintf(element.parsedPath, args...), nil
+}
+
+/* Pattern returns original path */
+func (r *Router) Pattern(key string) (string, error) {
+	if element, ok := r.urlMap[key]; ok {
+		return element.originalPath, nil
+	}
+	return "", ErrMissingKey
+}