├── .gitignore ├── LICENSE ├── README.md ├── reverse.go └── reverse_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | 3 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 4 | *.o 5 | *.a 6 | *.so 7 | 8 | # Folders 9 | _obj 10 | _test 11 | 12 | # Architecture specific extensions/prefixes 13 | *.[568vq] 14 | [568vq].out 15 | 16 | *.cgo1.go 17 | *.cgo2.c 18 | _cgo_defun.c 19 | _cgo_gotypes.go 20 | _cgo_export.* 21 | 22 | _testmain.go 23 | 24 | *.exe 25 | *.test 26 | *.prof 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017-present, Alexey Khalyapin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # reverse 2 | Go (golang) URL reverse 3 | 4 | Simple URL reverse package. It's useful for templates. You can get a URL by a name and params and not depend on URL structure. 5 | 6 | It fits to any router. All it does is just stores urls by a name and replace params when you retrieve a URL. 7 | To use it you have to add a URL with a name, raw URL with placeholders (params) and a list of these params. 8 | 9 | ```go 10 | // To set a URL and return raw URL use: 11 | reverse.Add("UrlName", "/url_path/:param1/:param2", ":param1", ":param2") 12 | // OUT: "/url_path/:param1/:param2" 13 | 14 | // To set a URL with group (subrouter) prefix and return URL without prefix use: 15 | reverse.AddGr("UrlName", "/url_path", "/:param1/:param2", ":param1", ":param2") 16 | // OUT: "/:param1/:param2" 17 | 18 | // Note, that these funcs panic if errors. Instead you can use Urls.Add() and Urls.Reverse() 19 | // that return errors. Or you can make your own wrapper for them. 20 | 21 | // To retrieve a URL by name with given params use: 22 | reverse.Rev("UrlName", "value1", "value2") 23 | // OUT: "/url_path/value1/value2" 24 | 25 | // Get raw url by name 26 | reverse.Get("UrlName") 27 | // OUT: "/url_path/:param1/:param2" 28 | 29 | // Get all url as map[string]string 30 | reverse.GetAllUrls() 31 | 32 | // Get all urls params as map[string][]string 33 | reverse.GetAllParams() 34 | 35 | ``` 36 | 37 | Example for Gin router (https://github.com/gin-gonic/gin): 38 | 39 | ```go 40 | func main() { 41 | router := gin.Default() 42 | 43 | // URL: "/" 44 | // To fetch the url use: reverse.Rev("home") 45 | router.GET(reverse.Add("home", "/"), indexEndpoint) 46 | 47 | // URL: "/get/123" 48 | // With param: c.Param("id") 49 | // To fetch the URL use: reverse.Rev("get_url", "123") 50 | router.GET(reverse.Add("get_url", "/get/:id"), getUrlEndpoint) 51 | 52 | 53 | // Simple group: v1 (each URL starts with /v1 prefix) 54 | groupName := "/v1" 55 | v1 := router.Group(groupName) 56 | { 57 | // URL: "/v1" 58 | // To fetch the URL use: reverse.Rev("v1_root") 59 | v1.GET(reverse.AddGr("v1_root", groupName, ""), v1RootEndpoint) 60 | 61 | // URL: "v1/read/cat123/id456" 62 | // With params (c.Param): catId, articleId 63 | // To fetch the URL use: reverse.Rev("v1_read", "123", "456") 64 | v1.GET(reverse.AddGr("v1_read", groupName, "/read/cat:catId/id:articleId", ":catId", ":articleId"), readEndpoint) 65 | 66 | // URL: /v1/login 67 | // To fetch the URL use: reverse.Rev("v1_login") 68 | v1.GET(reverse.AddGr("v1_login", groupName, "/login"), loginGetEndpoint) 69 | 70 | } 71 | 72 | router.Run(":8080") 73 | } 74 | 75 | ``` 76 | 77 | Example using Goji router: 78 | 79 | ```go 80 | package main 81 | 82 | import ( 83 | "fmt" 84 | "net/http" 85 | "github.com/alehano/reverse" 86 | "github.com/zenazn/goji" 87 | "github.com/zenazn/goji/web" 88 | ) 89 | 90 | func hello(c web.C, w http.ResponseWriter, r *http.Request) { 91 | // We can get reversed URL by it's name and a list of params: 92 | // reverse.Rev("UrlName", "value1", "value2") 93 | 94 | fmt.Fprintf(w, "Hello, %s", reverse.Rev("HelloUrl", c.URLParams["name"])) 95 | } 96 | 97 | func main() { 98 | // Set a URL and Params and return raw URL to a router 99 | // reverse.Add("UrlName", "/url_path/:param1/:param2", ":param1", ":param2") 100 | 101 | goji.Get(reverse.Add("HelloUrl", "/hello/:name", ":name"), hello) 102 | 103 | // In regexp instead of: re := regexp.MustCompile("^/comment/(?P\\d+)$") 104 | re := regexp.MustCompile(reverse.Add("DeleteCommentUrl", "^/comment/(?P\\d+)$", "(?P\\d+)$")) 105 | goji.Delete(re, deleteComment) 106 | 107 | goji.Serve() 108 | } 109 | ``` 110 | 111 | Example for Gorilla Mux 112 | 113 | ```go 114 | 115 | // Original set: r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) 116 | r.HandleFunc(reverse.Add("ArticleCatUrl", "/articles/{category}/{id:[0-9]+}", "{category}", "{id:[0-9]+}"), ArticleHandler) 117 | 118 | // So, if we want to retrieve URL "/articles/news/123", we call: 119 | fmt.Println( reverse.Rev("ArticleCatUrl", "news", "123") ) 120 | 121 | ``` 122 | 123 | 124 | Example subrouters for [Chi](https://github.com/go-chi/chi) router: 125 | 126 | ```go 127 | // Original code 128 | r.Route("/articles", func(r chi.Router) { 129 | r.Get("/", listArticles) 130 | r.Route("/{articleID}", func(r chi.Router) { 131 | r.Get("/", getArticle) 132 | }) 133 | }) 134 | 135 | // With reverse package 136 | r.Route("/articles", func(r chi.Router) { 137 | r.Get(reverse.AddGr("list_articles", "/articles", "/"), listArticles) 138 | r.Route("/{articleID}", func(r chi.Router) { 139 | r.Get(reverse.AddGr("get_article", "/articles/{articleID}", "/", "{articleID}"), getArticle) 140 | }) 141 | }) 142 | 143 | // Get a reverse URL: 144 | reverse.Rev("get_article", "123") 145 | // Output: /articles/123/ 146 | 147 | // One more example (without tailing slashes) 148 | r.Route(reverse.Add("admin.index", "/admin"), func(r chi.Router) { 149 | r.Get("/", index) 150 | 151 | r.Route(reverse.AddGr("admin.login", "/admin", "/login"), func(r chi.Router) { 152 | r.Get("/", login) 153 | r.Post("/", loginPost) 154 | }) 155 | }) 156 | 157 | ``` 158 | -------------------------------------------------------------------------------- /reverse.go: -------------------------------------------------------------------------------- 1 | package reverse 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | var Urls *urlStore 10 | 11 | func init() { 12 | Urls = &urlStore{store: make(map[string]url)} 13 | } 14 | 15 | func Clear() { 16 | for k := range Urls.store { 17 | delete(Urls.store, k) 18 | } 19 | } 20 | 21 | // Adds url to store 22 | func Add(urlName, urlAddr string, params ...string) string { 23 | return Urls.MustAdd(urlName, urlAddr, params...) 24 | } 25 | 26 | // Adds url with concat group, but returns just urlAddr 27 | func AddGr(urlName, group, urlAddr string, params ...string) string { 28 | return Urls.MustAddGr(urlName, group, urlAddr, params...) 29 | } 30 | 31 | // Reverse url by name 32 | func Rev(urlName string, params ...string) string { 33 | return Urls.MustReverse(urlName, params...) 34 | } 35 | 36 | // Gets raw url by name 37 | func Get(urlName string) string { 38 | return Urls.Get(urlName) 39 | } 40 | 41 | // Gets saved all urls 42 | func GetAllUrls() map[string]string { 43 | out := map[string]string{} 44 | for key, value := range Urls.store { 45 | out[key] = value.url 46 | } 47 | return out 48 | } 49 | 50 | // Gets all params 51 | func GetAllParams() map[string][]string { 52 | out := map[string][]string{} 53 | for key, value := range Urls.store { 54 | out[key] = value.params 55 | } 56 | return out 57 | } 58 | 59 | type url struct { 60 | url string 61 | params []string 62 | } 63 | 64 | type urlStore struct { 65 | store map[string]url 66 | } 67 | 68 | // Adds a Url to the Store 69 | func (us *urlStore) Add(urlName, urlAddr string, params ...string) (string, error) { 70 | return us.AddGr(urlName, "", urlAddr, params...) 71 | } 72 | 73 | // Adds a Url and panics if error 74 | func (us urlStore) MustAdd(urlName, urlAddr string, params ...string) string { 75 | addr, err := us.Add(urlName, urlAddr, params...) 76 | if err != nil { 77 | panic(err) 78 | } 79 | return addr 80 | } 81 | 82 | // Adds with group prefix 83 | func (us *urlStore) AddGr(urlName, group, urlAddr string, params ...string) (string, error) { 84 | if _, ok := us.store[urlName]; ok { 85 | return "", errors.New("Url already exists. Try to use .Get() method.") 86 | } 87 | 88 | tmpUrl := url{group + urlAddr, params} 89 | us.store[urlName] = tmpUrl 90 | return urlAddr, nil 91 | } 92 | 93 | // Adds a Url with group prefix 94 | func (us urlStore) MustAddGr(urlName, group, urlAddr string, params ...string) string { 95 | addr, err := us.AddGr(urlName, group, urlAddr, params...) 96 | if err != nil { 97 | panic(err) 98 | } 99 | return addr 100 | } 101 | 102 | // Gets raw url string 103 | func (us urlStore) Get(urlName string) string { 104 | return us.store[urlName].url 105 | } 106 | 107 | // Gets reversed url 108 | func (us urlStore) Reverse(urlName string, params ...string) (string, error) { 109 | if len(params) != len(us.store[urlName].params) { 110 | return "", errors.New("Bad Url Reverse: mismatch params for URL: "+ urlName) 111 | } 112 | res := us.store[urlName].url 113 | for i, val := range params { 114 | res = strings.Replace(res, us.store[urlName].params[i], val, 1) 115 | } 116 | return res, nil 117 | } 118 | 119 | // Gets reversed url and panics if error 120 | func (us urlStore) MustReverse(urlName string, params ...string) string { 121 | res, err := us.Reverse(urlName, params...) 122 | if err != nil { 123 | panic(err) 124 | } 125 | return res 126 | } 127 | 128 | // Reverse url, but returns empty string in case of error 129 | func (us urlStore) Rev(urlName string, params ...string) string { 130 | return us.MustReverse(urlName, params...) 131 | } 132 | 133 | func (us urlStore) Sting() string { 134 | return fmt.Sprint(us.store) 135 | } 136 | 137 | // For testing 138 | func (us urlStore) getParam(urlName string, num int) string { 139 | return us.store[urlName].params[num] 140 | } 141 | 142 | -------------------------------------------------------------------------------- /reverse_test.go: -------------------------------------------------------------------------------- 1 | package reverse 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestReverse(t *testing.T) { 9 | showError := func(info string) { 10 | t.Error(fmt.Sprintf("Error: %s. urlStore: %s", info, Urls)) 11 | } 12 | 13 | if Urls.MustAdd("firstUrl", "/first") != "/first" { 14 | showError("0") 15 | } 16 | 17 | if Urls.MustAdd("helloUrl", "/hello/:p1:p2", "1", "2") != "/hello/:p1:p2" { 18 | showError("0-1") 19 | } 20 | 21 | Urls.MustAdd("secondUrl", "/second/:param/:param2", ":param", ":param2") 22 | 23 | // re := regexp.MustCompile("^/comment/(?P\d+)$") 24 | Urls.MustAdd("thirdUrl", "/comment/:p1", ":p1") 25 | 26 | if Urls.getParam("helloUrl", 1) != "2" { 27 | showError("1") 28 | } 29 | 30 | if Urls.Get("helloUrl") != "/hello/:p1:p2" { 31 | showError("2") 32 | } 33 | 34 | if Urls.getParam("secondUrl", 0) != ":param" { 35 | showError("3") 36 | } 37 | 38 | if Urls.MustReverse("firstUrl") != "/first" { 39 | showError("4") 40 | } 41 | 42 | if Urls.MustReverse("secondUrl", "123", "ABC") != "/second/123/ABC" { 43 | showError("5") 44 | } 45 | 46 | if Urls.MustReverse("thirdUrl", "123") != "/comment/123" { 47 | t.Error(Urls.Reverse("thirdUrl", "123")) 48 | showError("6") 49 | } 50 | 51 | Clear() 52 | if len(Urls.store) != 0 { 53 | showError("7") 54 | } 55 | 56 | } 57 | --------------------------------------------------------------------------------