github.com/fedir/buffalo@v0.11.1/router.go (about)

     1  package buffalo
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"os"
     7  	"path"
     8  	"reflect"
     9  	"sort"
    10  	"strings"
    11  
    12  	"github.com/markbates/inflect"
    13  	"github.com/pkg/errors"
    14  )
    15  
    16  // GET maps an HTTP "GET" request to the path and the specified handler.
    17  func (a *App) GET(p string, h Handler) *RouteInfo {
    18  	return a.addRoute("GET", p, h)
    19  }
    20  
    21  // POST maps an HTTP "POST" request to the path and the specified handler.
    22  func (a *App) POST(p string, h Handler) *RouteInfo {
    23  	return a.addRoute("POST", p, h)
    24  }
    25  
    26  // PUT maps an HTTP "PUT" request to the path and the specified handler.
    27  func (a *App) PUT(p string, h Handler) *RouteInfo {
    28  	return a.addRoute("PUT", p, h)
    29  }
    30  
    31  // DELETE maps an HTTP "DELETE" request to the path and the specified handler.
    32  func (a *App) DELETE(p string, h Handler) *RouteInfo {
    33  	return a.addRoute("DELETE", p, h)
    34  }
    35  
    36  // HEAD maps an HTTP "HEAD" request to the path and the specified handler.
    37  func (a *App) HEAD(p string, h Handler) *RouteInfo {
    38  	return a.addRoute("HEAD", p, h)
    39  }
    40  
    41  // OPTIONS maps an HTTP "OPTIONS" request to the path and the specified handler.
    42  func (a *App) OPTIONS(p string, h Handler) *RouteInfo {
    43  	return a.addRoute("OPTIONS", p, h)
    44  }
    45  
    46  // PATCH maps an HTTP "PATCH" request to the path and the specified handler.
    47  func (a *App) PATCH(p string, h Handler) *RouteInfo {
    48  	return a.addRoute("PATCH", p, h)
    49  }
    50  
    51  // Redirect from one URL to another URL. Only works for "GET" requests.
    52  func (a *App) Redirect(status int, from, to string) *RouteInfo {
    53  	return a.GET(from, func(c Context) error {
    54  		return c.Redirect(status, to)
    55  	})
    56  }
    57  
    58  // Mount mounts a http.Handler (or Buffalo app) and passes through all requests to it.
    59  //
    60  //	func muxer() http.Handler {
    61  //		f := func(res http.ResponseWriter, req *http.Request) {
    62  //			fmt.Fprintf(res, "%s - %s", req.Method, req.URL.String())
    63  //		}
    64  //		mux := mux.NewRouter()
    65  //		mux.HandleFunc("/foo", f).Methods("GET")
    66  //		mux.HandleFunc("/bar", f).Methods("POST")
    67  //		mux.HandleFunc("/baz/baz", f).Methods("DELETE")
    68  //		return mux
    69  //	}
    70  //
    71  //	a.Mount("/admin", muxer())
    72  //
    73  //	$ curl -X DELETE http://localhost:3000/admin/baz/baz
    74  func (a *App) Mount(p string, h http.Handler) {
    75  	prefix := path.Join(a.Prefix, p)
    76  	path := path.Join(p, "{path:.+}")
    77  	a.ANY(path, WrapHandler(http.StripPrefix(prefix, h)))
    78  }
    79  
    80  // ServeFiles maps an path to a directory on disk to serve static files.
    81  // Useful for JavaScript, images, CSS, etc...
    82  /*
    83  	a.ServeFiles("/assets", http.Dir("path/to/assets"))
    84  */
    85  func (a *App) ServeFiles(p string, root http.FileSystem) {
    86  	path := path.Join(a.Prefix, p)
    87  	a.router.PathPrefix(path).Handler(http.StripPrefix(path, a.fileServer(root)))
    88  }
    89  
    90  func (a *App) fileServer(fs http.FileSystem) http.Handler {
    91  	fsh := http.FileServer(fs)
    92  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    93  		_, err := fs.Open(path.Clean(r.URL.Path))
    94  		if os.IsNotExist(err) {
    95  			eh := a.ErrorHandlers.Get(404)
    96  			eh(404, errors.Errorf("could not find %s", r.URL.Path), a.newContext(RouteInfo{}, w, r))
    97  			return
    98  		}
    99  		fsh.ServeHTTP(w, r)
   100  	})
   101  }
   102  
   103  // Resource maps an implementation of the Resource interface
   104  // to the appropriate RESTful mappings. Resource returns the *App
   105  // associated with this group of mappings so you can set middleware, etc...
   106  // on that group, just as if you had used the a.Group functionality.
   107  /*
   108  	a.Resource("/users", &UsersResource{})
   109  
   110  	// Is equal to this:
   111  
   112  	ur := &UsersResource{}
   113  	g := a.Group("/users")
   114  	g.GET("/", ur.List) // GET /users => ur.List
   115  	g.GET("/new", ur.New) // GET /users/new => ur.New
   116  	g.GET("/{user_id}", ur.Show) // GET /users/{user_id} => ur.Show
   117  	g.GET("/{user_id}/edit", ur.Edit) // GET /users/{user_id}/edit => ur.Edit
   118  	g.POST("/", ur.Create) // POST /users => ur.Create
   119  	g.PUT("/{user_id}", ur.Update) PUT /users/{user_id} => ur.Update
   120  	g.DELETE("/{user_id}", ur.Destroy) DELETE /users/{user_id} => ur.Destroy
   121  */
   122  func (a *App) Resource(p string, r Resource) *App {
   123  	g := a.Group(p)
   124  	p = "/"
   125  
   126  	rv := reflect.ValueOf(r)
   127  	if rv.Kind() == reflect.Ptr {
   128  		rv = rv.Elem()
   129  	}
   130  
   131  	rt := rv.Type()
   132  	rname := fmt.Sprintf("%s.%s", rt.PkgPath(), rt.Name()) + ".%s"
   133  
   134  	name := strings.Replace(rt.Name(), "Resource", "", 1)
   135  	paramName := inflect.Singularize(inflect.Underscore(name))
   136  
   137  	spath := path.Join(p, fmt.Sprintf("{%s_id}", paramName))
   138  	setFuncKey(r.List, fmt.Sprintf(rname, "List"))
   139  	g.GET(p, r.List)
   140  	setFuncKey(r.New, fmt.Sprintf(rname, "New"))
   141  	g.GET(path.Join(p, "new"), r.New)
   142  	setFuncKey(r.Show, fmt.Sprintf(rname, "Show"))
   143  	g.GET(path.Join(spath), r.Show)
   144  	setFuncKey(r.Edit, fmt.Sprintf(rname, "Edit"))
   145  	g.GET(path.Join(spath, "edit"), r.Edit)
   146  	setFuncKey(r.Create, fmt.Sprintf(rname, "Create"))
   147  	g.POST(p, r.Create)
   148  	setFuncKey(r.Update, fmt.Sprintf(rname, "Update"))
   149  	g.PUT(path.Join(spath), r.Update)
   150  	setFuncKey(r.Destroy, fmt.Sprintf(rname, "Destroy"))
   151  	g.DELETE(path.Join(spath), r.Destroy)
   152  	g.Prefix = path.Join(g.Prefix, spath)
   153  	return g
   154  }
   155  
   156  // ANY accepts a request across any HTTP method for the specified path
   157  // and routes it to the specified Handler.
   158  func (a *App) ANY(p string, h Handler) {
   159  	a.GET(p, h)
   160  	a.POST(p, h)
   161  	a.PUT(p, h)
   162  	a.PATCH(p, h)
   163  	a.HEAD(p, h)
   164  	a.OPTIONS(p, h)
   165  	a.DELETE(p, h)
   166  }
   167  
   168  // Group creates a new `*App` that inherits from it's parent `*App`.
   169  // This is useful for creating groups of end-points that need to share
   170  // common functionality, like middleware.
   171  /*
   172  	g := a.Group("/api/v1")
   173  	g.Use(AuthorizeAPIMiddleware)
   174  	g.GET("/users, APIUsersHandler)
   175  	g.GET("/users/:user_id, APIUserShowHandler)
   176  */
   177  func (a *App) Group(groupPath string) *App {
   178  	g := New(a.Options)
   179  	g.Prefix = path.Join(a.Prefix, groupPath)
   180  	g.Name = g.Prefix
   181  
   182  	g.router = a.router
   183  	g.Middleware = a.Middleware.clone()
   184  	g.ErrorHandlers = a.ErrorHandlers
   185  	g.root = a
   186  	if a.root != nil {
   187  		g.root = a.root
   188  	}
   189  	a.children = append(a.children, g)
   190  	return g
   191  }
   192  
   193  func (a *App) addRoute(method string, url string, h Handler) *RouteInfo {
   194  	a.moot.Lock()
   195  	defer a.moot.Unlock()
   196  
   197  	url = path.Join(a.Prefix, url)
   198  	name := a.buildRouteName(url)
   199  
   200  	hs := funcKey(h)
   201  	r := &RouteInfo{
   202  		Method:      method,
   203  		Path:        url,
   204  		HandlerName: hs,
   205  		Handler:     h,
   206  		App:         a,
   207  		Aliases:     []string{},
   208  	}
   209  
   210  	r.MuxRoute = a.router.Handle(url, r).Methods(method)
   211  	r.Name(name)
   212  
   213  	routes := a.Routes()
   214  	routes = append(routes, r)
   215  	sort.Sort(routes)
   216  
   217  	if a.root != nil {
   218  		a.root.routes = routes
   219  	} else {
   220  		a.routes = routes
   221  	}
   222  
   223  	return r
   224  }
   225  
   226  //buildRouteName builds a route based on the path passed.
   227  func (a *App) buildRouteName(p string) string {
   228  	if p == "/" || p == "" {
   229  		return "root"
   230  	}
   231  
   232  	resultParts := []string{}
   233  	parts := strings.Split(p, "/")
   234  
   235  	for index, part := range parts {
   236  
   237  		if strings.Contains(part, "{") || part == "" {
   238  			continue
   239  		}
   240  
   241  		shouldSingularize := (len(parts) > index+1) && strings.Contains(parts[index+1], "{")
   242  		if shouldSingularize {
   243  			part = inflect.Singularize(part)
   244  		}
   245  
   246  		if parts[index] == "new" || parts[index] == "edit" {
   247  			resultParts = append([]string{part}, resultParts...)
   248  			continue
   249  		}
   250  
   251  		if index > 0 && strings.Contains(parts[index-1], "}") {
   252  			resultParts = append(resultParts, part)
   253  			continue
   254  		}
   255  
   256  		resultParts = append(resultParts, part)
   257  	}
   258  
   259  	if len(resultParts) == 0 {
   260  		return "unnamed"
   261  	}
   262  
   263  	underscore := strings.TrimSpace(strings.Join(resultParts, "_"))
   264  	return inflect.CamelizeDownFirst(underscore)
   265  }