github.com/System-Glitch/goyave/v2@v2.10.3-0.20200819142921-51011e75d504/route.go (about)

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