github.com/System-Glitch/goyave/v3@v3.6.1-0.20210226143142-ac2fe42ee80e/route.go (about)

     1  package goyave
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"strings"
     7  
     8  	"github.com/System-Glitch/goyave/v3/validation"
     9  )
    10  
    11  // Route stores information for matching and serving.
    12  type Route struct {
    13  	name            string
    14  	uri             string
    15  	methods         []string
    16  	parent          *Router
    17  	handler         Handler
    18  	validationRules *validation.Rules
    19  	middlewareHolder
    20  	parametrizeable
    21  }
    22  
    23  var _ routeMatcher = (*Route)(nil) // implements routeMatcher
    24  
    25  // newRoute create a new route without any settings except its handler.
    26  // This is used to generate a fake route for the Method Not Allowed and Not Found handlers.
    27  // This route has the core middleware enabled and can be used without a parent router.
    28  // Thus, custom status handlers can use language and body.
    29  func newRoute(handler Handler) *Route {
    30  	return &Route{
    31  		handler: handler,
    32  		middlewareHolder: middlewareHolder{
    33  			middleware: []Middleware{recoveryMiddleware, parseRequestMiddleware, languageMiddleware},
    34  		},
    35  	}
    36  }
    37  
    38  func (r *Route) match(req *http.Request, match *routeMatch) bool {
    39  	if params := r.parametrizeable.regex.FindStringSubmatch(match.currentPath); params != nil {
    40  		if r.checkMethod(req.Method) {
    41  			if len(params) > 1 {
    42  				match.mergeParams(r.makeParameters(params))
    43  			}
    44  			match.route = r
    45  			return true
    46  		}
    47  		match.err = errMatchMethodNotAllowed
    48  		return false
    49  	}
    50  
    51  	if match.err == nil {
    52  		// Don't override error if already set.
    53  		// Not nil error means it's either already errMatchNotFound
    54  		// or it's errMatchMethodNotAllowed, implying that a route has
    55  		// already been matched but with wrong method.
    56  		match.err = errMatchNotFound
    57  	}
    58  	return false
    59  }
    60  
    61  func (r *Route) checkMethod(method string) bool {
    62  	for _, m := range r.methods {
    63  		if m == method {
    64  			return true
    65  		}
    66  	}
    67  	return false
    68  }
    69  
    70  func (r *Route) makeParameters(match []string) map[string]string {
    71  	return r.parametrizeable.makeParameters(match, r.parameters)
    72  }
    73  
    74  // Name set the name of the route.
    75  // Panics if a route with the same name already exists.
    76  // Returns itself.
    77  func (r *Route) Name(name string) *Route {
    78  	if r.name != "" {
    79  		panic(fmt.Errorf("Route name is already set"))
    80  	}
    81  
    82  	if _, ok := r.parent.namedRoutes[name]; ok {
    83  		panic(fmt.Errorf("Route %q already exists", name))
    84  	}
    85  
    86  	r.name = name
    87  	r.parent.namedRoutes[name] = r
    88  	return r
    89  }
    90  
    91  // Validate adds validation rules to this route. If the user-submitted data
    92  // doesn't pass validation, the user will receive an error and messages explaining
    93  // what is wrong.
    94  //
    95  // Returns itself.
    96  func (r *Route) Validate(validationRules validation.Ruler) *Route {
    97  	r.validationRules = validationRules.AsRules()
    98  	return r
    99  }
   100  
   101  // Middleware register middleware for this route only.
   102  //
   103  // Returns itself.
   104  func (r *Route) Middleware(middleware ...Middleware) *Route {
   105  	r.middleware = middleware
   106  	return r
   107  }
   108  
   109  // BuildURL build a full URL pointing to this route.
   110  // Panics if the amount of parameters doesn't match the amount of
   111  // actual parameters for this route.
   112  func (r *Route) BuildURL(parameters ...string) string {
   113  	fullURI, fullParameters := r.getFullParameters()
   114  
   115  	if len(parameters) != len(fullParameters) {
   116  		panic(fmt.Errorf("BuildURL: route has %d parameters, %d given", len(fullParameters), len(parameters)))
   117  	}
   118  
   119  	address := BaseURL()
   120  
   121  	var builder strings.Builder
   122  	builder.Grow(len(fullURI) + len(address))
   123  
   124  	builder.WriteString(address)
   125  
   126  	idxs, _ := r.braceIndices(fullURI)
   127  	length := len(idxs)
   128  	end := 0
   129  	currentParam := 0
   130  	for i := 0; i < length; i += 2 {
   131  		raw := fullURI[end:idxs[i]]
   132  		end = idxs[i+1]
   133  		builder.WriteString(raw)
   134  		builder.WriteString(parameters[currentParam])
   135  		currentParam++
   136  		end++ // Skip closing braces
   137  	}
   138  	builder.WriteString(fullURI[end:])
   139  
   140  	return builder.String()
   141  }
   142  
   143  // GetName get the name of this route.
   144  func (r *Route) GetName() string {
   145  	return r.name
   146  }
   147  
   148  // GetURI get the URI of this route.
   149  // The returned URI is relative to the parent router of this route, it is NOT
   150  // the full path to this route.
   151  //
   152  // Note that this URI may contain route parameters in their définition format.
   153  // Use the request's URI if you want to see the URI as it was requested by the client.
   154  func (r *Route) GetURI() string {
   155  	return r.uri
   156  }
   157  
   158  // GetFullURI get the full URI of this route.
   159  //
   160  // Note that this URI may contain route parameters in their définition format.
   161  // Use the request's URI if you want to see the URI as it was requested by the client.
   162  func (r *Route) GetFullURI() string {
   163  	router := r.parent
   164  	segments := make([]string, 0, 3)
   165  	segments = append(segments, r.uri)
   166  
   167  	for router != nil {
   168  		segments = append(segments, router.prefix)
   169  		router = router.parent
   170  	}
   171  
   172  	// Revert segements
   173  	for i := len(segments)/2 - 1; i >= 0; i-- {
   174  		opp := len(segments) - 1 - i
   175  		segments[i], segments[opp] = segments[opp], segments[i]
   176  	}
   177  
   178  	return strings.Join(segments, "")
   179  }
   180  
   181  // GetMethods returns the methods the route matches against.
   182  func (r *Route) GetMethods() []string {
   183  	cpy := make([]string, len(r.methods))
   184  	copy(cpy, r.methods)
   185  	return cpy
   186  }
   187  
   188  // getFullParameters get the full uri and parameters for this route and all its parent routers.
   189  func (r *Route) getFullParameters() (string, []string) {
   190  	router := r.parent
   191  	segments := make([]string, 0, 3)
   192  	segments = append(segments, r.uri)
   193  
   194  	parameters := make([]string, 0, len(r.parameters))
   195  	for i := len(r.parameters) - 1; i >= 0; i-- {
   196  		parameters = append(parameters, r.parameters[i])
   197  	}
   198  
   199  	for router != nil {
   200  		segments = append(segments, router.prefix)
   201  		for i := len(router.parameters) - 1; i >= 0; i-- {
   202  			parameters = append(parameters, router.parameters[i])
   203  		}
   204  		router = router.parent
   205  	}
   206  
   207  	// Revert segements
   208  	for i := len(segments)/2 - 1; i >= 0; i-- {
   209  		opp := len(segments) - 1 - i
   210  		segments[i], segments[opp] = segments[opp], segments[i]
   211  	}
   212  
   213  	// Revert parameters
   214  	for i := len(parameters)/2 - 1; i >= 0; i-- {
   215  		opp := len(parameters) - 1 - i
   216  		parameters[i], parameters[opp] = parameters[opp], parameters[i]
   217  	}
   218  
   219  	return strings.Join(segments, ""), parameters
   220  }