goyave.dev/goyave/v4@v4.4.11/route.go (about)

     1  package goyave
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"strings"
     7  
     8  	"goyave.dev/goyave/v4/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  	parameterizable
    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.parameterizable.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.parameterizable.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  	return BaseURL() + r.BuildURI(parameters...)
   114  }
   115  
   116  // BuildURI build a full URI pointing to this route. The returned
   117  // string doesn't include the protocol and domain. (e.g. "/user/login")
   118  // Panics if the amount of parameters doesn't match the amount of
   119  // actual parameters for this route.
   120  func (r *Route) BuildURI(parameters ...string) string {
   121  	fullURI, fullParameters := r.GetFullURIAndParameters()
   122  
   123  	if len(parameters) != len(fullParameters) {
   124  		panic(fmt.Errorf("BuildURI: route has %d parameters, %d given", len(fullParameters), len(parameters)))
   125  	}
   126  
   127  	var builder strings.Builder
   128  	builder.Grow(len(fullURI))
   129  
   130  	idxs, _ := r.braceIndices(fullURI)
   131  	length := len(idxs)
   132  	end := 0
   133  	currentParam := 0
   134  	for i := 0; i < length; i += 2 {
   135  		raw := fullURI[end:idxs[i]]
   136  		end = idxs[i+1]
   137  		builder.WriteString(raw)
   138  		builder.WriteString(parameters[currentParam])
   139  		currentParam++
   140  		end++ // Skip closing braces
   141  	}
   142  	builder.WriteString(fullURI[end:])
   143  
   144  	return builder.String()
   145  }
   146  
   147  // GetName get the name of this route.
   148  func (r *Route) GetName() string {
   149  	return r.name
   150  }
   151  
   152  // GetURI get the URI of this route.
   153  // The returned URI is relative to the parent router of this route, it is NOT
   154  // the full path to this route.
   155  //
   156  // Note that this URI may contain route parameters in their définition format.
   157  // Use the request's URI if you want to see the URI as it was requested by the client.
   158  func (r *Route) GetURI() string {
   159  	return r.uri
   160  }
   161  
   162  // GetFullURI get the full URI of this route.
   163  //
   164  // Note that this URI may contain route parameters in their définition format.
   165  // Use the request's URI if you want to see the URI as it was requested by the client.
   166  func (r *Route) GetFullURI() string {
   167  	router := r.parent
   168  	segments := make([]string, 0, 3)
   169  	segments = append(segments, r.uri)
   170  
   171  	for router != nil {
   172  		segments = append(segments, router.prefix)
   173  		router = router.parent
   174  	}
   175  
   176  	// Revert segements
   177  	for i := len(segments)/2 - 1; i >= 0; i-- {
   178  		opp := len(segments) - 1 - i
   179  		segments[i], segments[opp] = segments[opp], segments[i]
   180  	}
   181  
   182  	return strings.Join(segments, "")
   183  }
   184  
   185  // GetMethods returns the methods the route matches against.
   186  func (r *Route) GetMethods() []string {
   187  	cpy := make([]string, len(r.methods))
   188  	copy(cpy, r.methods)
   189  	return cpy
   190  }
   191  
   192  // GetHandler returns the Handler associated with this route.
   193  func (r *Route) GetHandler() Handler {
   194  	return r.handler
   195  }
   196  
   197  // GetValidationRules returns the validation rules associated with this route.
   198  func (r *Route) GetValidationRules() *validation.Rules {
   199  	return r.validationRules
   200  }
   201  
   202  // GetFullURIAndParameters get the full uri and parameters for this route and all its parent routers.
   203  func (r *Route) GetFullURIAndParameters() (string, []string) {
   204  	router := r.parent
   205  	segments := make([]string, 0, 3)
   206  	segments = append(segments, r.uri)
   207  
   208  	parameters := make([]string, 0, len(r.parameters))
   209  	for i := len(r.parameters) - 1; i >= 0; i-- {
   210  		parameters = append(parameters, r.parameters[i])
   211  	}
   212  
   213  	for router != nil {
   214  		segments = append(segments, router.prefix)
   215  		for i := len(router.parameters) - 1; i >= 0; i-- {
   216  			parameters = append(parameters, router.parameters[i])
   217  		}
   218  		router = router.parent
   219  	}
   220  
   221  	// Revert segements
   222  	for i := len(segments)/2 - 1; i >= 0; i-- {
   223  		opp := len(segments) - 1 - i
   224  		segments[i], segments[opp] = segments[opp], segments[i]
   225  	}
   226  
   227  	// Revert parameters
   228  	for i := len(parameters)/2 - 1; i >= 0; i-- {
   229  		opp := len(parameters) - 1 - i
   230  		parameters[i], parameters[opp] = parameters[opp], parameters[i]
   231  	}
   232  
   233  	return strings.Join(segments, ""), parameters
   234  }