...
Tawesoft Logo

Source file src/tawesoft.co.uk/go/router/public.go

Documentation: src/tawesoft.co.uk/go/router/public.go

     1  package router
     2  
     3  import (
     4      "fmt"
     5      "net/http"
     6      "regexp"
     7      "strings"
     8  )
     9  
    10  // Router is a general purpose router of a method or verb (like HTTP "GET") and
    11  // path (like "/foo/bar") to a tree of Route objects.
    12  type Router struct {
    13      // DefaultMethods is the comma-separated list of methods to use if a route
    14      // has none specified e.g. HTTP "GET, POST".
    15      //
    16      // router.New() sets this to only "GET" by default.
    17      DefaultMethods string
    18      
    19      root *Route
    20      routesByName map[string]*Route
    21      parents map[*Route]*Route
    22  }
    23  
    24  // Route describes a mapping between methods (like HTTP "GET") and path
    25  // component (like "foo" in "/foo/bar") to a user-supplied handler value.
    26  //
    27  // Each route may contain child routes which map to subsequent path components.
    28  // For example a route may match "foo" in "/foo/bar", and a child route could
    29  // match "bar".
    30  //
    31  // A path such as "/user/123/profile" resolves into the components ["", "user",
    32  // "123", "profile"]. A path such as "/user/123/profile/" resolves into the
    33  // components ["", "user", "123", "profile", ""]. As such, the root Route
    34  // and folder-index Routes will normally be configured to match the empty
    35  // string "".
    36  //
    37  // A route can be Final, in which case it matches all remaining path components
    38  // at once.
    39  type Route struct {
    40      // Name (optional) is a way of uniquely identifying a specific route. The
    41      // name must be unique to the whole router tree.
    42      Name string
    43      
    44      // Key (optional) is a way of uniquely identifying a path parameter (e.g.
    45      // the path "/user/123/profile" might have a parameter for the user ID).
    46      //
    47      // This key is used to query the value of the path parameter by name in a
    48      // Match result.
    49      //
    50      // All keys must be unique to a route and its parents, but does not have to
    51      // be unique across the entire router tree.
    52      Key string
    53      
    54      // Pattern (optional) is a way of matching against a path component. This
    55      // may be a literal string e.g. "user", or or a compiled regular expression
    56      // (https://golang.org/pkg/regexp/) such as regexp.MustCompile(`$\d{1,5}^`)
    57      // (this matches one to five ASCII digits).
    58      //
    59      // Note that the regexp implementation provided by regexp is guaranteed to
    60      // run in time linear in the size of the input so it is generally not
    61      // essential to limit the length of the path component. Path components are
    62      // limited to 255 characters regardless.
    63      //
    64      // If the pattern is nil or an empty string, the route matches an empty
    65      // path component. This is useful for the root node, or index pages for
    66      // folders.
    67      //
    68      // As a special case, the string "*" matches everything. This is useful for
    69      // a per-directory catch-all.
    70      //
    71      // Patterns are matched in the following order first, then in order of
    72      // sequence defined:
    73      //
    74      // 1 Empty path (Pattern is nil or empty string; Final has no effect)
    75      // 2 Final Exact matches (Pattern is string and Final is true)
    76      // 3 Exact matches (Pattern is string and Final is false)
    77      // 4 Final Regex matches (Pattern is Regexp and Final is true)
    78      // 5 Regex matches (Pattern is Regexp and Final is false)
    79      // 6 Wildcard (string "*" and Final is false)
    80      // 7 Final Wildcard (string "*" and Final is true)
    81      Pattern interface{}
    82      
    83      // Methods (optional) is a comma-separated string of methods or verbs
    84      // accepted by the route e.g. HTTP "GET, POST". If left empty, implies
    85      // only the Router's DefaultMethods.
    86      //
    87      // Note that "OPTIONS" is not handled automatically - this is up to you.
    88      Methods string
    89      
    90      // Handler (optional) is the caller-supplied information attached to a
    91      // route e.g. a HTTP Handler or any custom value.
    92      //
    93      // It is up to the caller to do something with the resulting Handler -
    94      // nothing is called automatically for you.
    95      Handler interface{}
    96      
    97      // Children (optional) are any child Routes for subsequent path components.
    98      // For example, the path "/foo/bar" decomposes into the path components
    99      // ["", "foo", "bar"], and can be matched by a route with pattern "",
   100      // child route with pattern "foo", and grandchild route "bar".
   101      Children []Route
   102      
   103      // Final (optional; default false), if true, indicates that the pattern
   104      // should be matched against the entire remaining path, not just the
   105      // current path component.
   106      //
   107      // For example, a Route might have a pattern to accept arbitrary
   108      // files in subdirectories, like "static/assets/foo.png". It would be
   109      // Final with a Pattern value of something like
   110      // regexp.MustCompile(`(\w+/)*\w+\.\w`)
   111      Final bool
   112  }
   113  
   114  // Match is the result of a routing query - may be nil if no match found
   115  type Match struct {
   116      // Route is the route picked - never nil
   117      Route *Route
   118      
   119      // Router is the router that was matched on
   120      Router *Router
   121      
   122      // mapping of route keys => path parameters;
   123      // this may be nil: use Value() or Values()
   124      params map[string]string
   125      
   126      // mapping of route keys => submatches of paath parameters, like
   127      // `(*Regexp) FindStringSubmatch`
   128      // from https://golang.org/pkg/regexp/#Regexp.FindStringSubmatch
   129      // this may be nil: use Submatch() or Submatches()
   130      subparams map[string][]string
   131  }
   132  
   133  // Values returns a non-nil mapping of the route keys => path parameters
   134  func (match Match) Values() map[string]string {
   135      if match.params == nil { match.params = make(map[string]string) }
   136      return match.params
   137  }
   138  
   139  // Value returns a parsed path parameter identified by a Route Key.
   140  //
   141  // For example, you might create a tree of routes such that the path
   142  // "/user/123/profile" captures the path component "123" and associates it
   143  // with a "user-id" route.Key.
   144  func (match Match) Value(key string) string {
   145      if match.params == nil { return "" }
   146      return match.params[key]
   147  }
   148  
   149  // Submatches returns a non-nil mapping of the route keys => submatches of path
   150  // parameters.
   151  func (match Match) Submatches() map[string][]string {
   152      if match.subparams == nil { match.subparams = make(map[string][]string) }
   153      return match.subparams
   154  }
   155  
   156  // Submatch returns a submatch of a parsed path parameter identified by a
   157  // Route Key and
   158  //
   159  // For example, you might create a tree of routes such that the path
   160  // "/user/123.json" captures the path component "123.json" and associates it
   161  // with a "user-id" route.Key. For a pattern like "(\d+)(\.(\w+))?", the submatches for that key would be
   162  // ["123.json", "123", ".json", "json"], so index 1 gives "123".
   163  func (match Match) Submatch(key string, index int) string {
   164      if match.subparams == nil { return "" }
   165      args := match.subparams[key]
   166      if args == nil { return "" }
   167      if index >= len(args) { return "" }
   168      return args[index]
   169  }
   170  
   171  // Creates a new router with DefaultMethods set to GET
   172  func New(root Route) (*Router, error) {
   173      router := &Router{}
   174      
   175      router.DefaultMethods = "GET"
   176      
   177      // we take a copy of the routes rather than just hold a pointer because
   178      // we modify the route Children by sorting them, and we return
   179      
   180      router.root = &root
   181      sortRoutes(router.root.Children) // note, must be done first!
   182      
   183      routesByName, err := scanRouteNames(router.root)
   184      if err != nil { return nil, err }
   185      router.routesByName = routesByName
   186      
   187      // router.methods = scanMethods(router.root)
   188      router.parents = scanParents(router.root)
   189      
   190      return router, nil
   191  }
   192  
   193  // Matches a HTTP request to a route. See Match
   194  func (router *Router) MatchHttpRequest(r *http.Request) *Match {
   195      return router.Match(r.Method, r.URL.Path)
   196  }
   197  
   198  // Match attempts to match a method (e.g. a HTTP method like "GET") and path (e.g. "/foo/bar") to a route in
   199  // a router's tree of routes. In the event that there is no match, returns nil.
   200  func (router *Router) Match(method string, path string) *Match {
   201      var params map[string]string
   202      var subparams map[string][]string
   203      
   204      route := router.match(method, path, router.root, &params, &subparams)
   205      if route == nil { return nil }
   206      
   207      return &Match{route, router, params, subparams}
   208  }
   209  
   210  // Parent returns the parent Route of a Route in the router's tree of Routes. May be nil (i.e. at the root).
   211  func (router *Router) Parent(route *Route) *Route {
   212      return router.parents[route]
   213  }
   214  
   215  // Format creates a URL for a route - the opposite of routing. Any
   216  // regexp.Regexp patterns are replaced using each arg in sequence.
   217  //
   218  // WARNING: The return value from this function may be controlled by the
   219  // User Agent. Escape it as necessary.
   220  func (router *Router) Format(route *Route, args ... string) (string, error) {
   221      components := make([]string, 0)
   222      current := route
   223      index := len(args) - 1
   224      
   225      for current != nil {
   226          
   227          switch v := current.Pattern.(type) {
   228              case nil:
   229                  components = append(components, "")
   230              case string:
   231                  components = append(components, v)
   232              case *regexp.Regexp:
   233                  if index < 0 {
   234                      return "", fmt.Errorf("not enough arguments to format path")
   235                  }
   236                  components = append(components, args[index])
   237                  index--
   238              default:
   239                  panic(fmt.Errorf("invalid Route Pattern type %T", v))
   240          }
   241          
   242          current = router.Parent(current)
   243      }
   244      
   245      reverseStringList(components...)
   246      
   247      return strings.Join(components, "/"), nil
   248  }
   249  
   250  // MustFormat is like Format, but panics on error.
   251  func (router *Router) MustFormat(route *Route, args ... string) string {
   252      result, err := router.Format(route, args...)
   253      if err != nil {
   254          panic(fmt.Errorf("unable to format a route: %v", err))
   255      }
   256      return result
   257  }
   258  
   259  // Format creates a URL for a route - the opposite of routing. Any
   260  // regexp.Regexp patterns are replaced using each the route Key to lookup
   261  // a value in the the mapping argument. Pass in Match Values() to have
   262  // this automatically filled based on the parsed request.
   263  //
   264  // WARNING: The return value from this function may be controlled by the
   265  // User Agent. Escape it as necessary.
   266  func (router *Router) FormatMap(route *Route, mapping map[string]string) (string, error) {
   267      components := make([]string, 0)
   268      current := route
   269      
   270      for current != nil {
   271          
   272          switch v := current.Pattern.(type) {
   273              case nil:
   274                  components = append(components, "")
   275              case string:
   276                  components = append(components, v)
   277              case *regexp.Regexp:
   278                  components = append(components, mapping[current.Key])
   279              default:
   280                  panic(fmt.Errorf("invalid Route Pattern type %T", v))
   281          }
   282          
   283          current = router.Parent(current)
   284      }
   285      
   286      reverseStringList(components...)
   287      
   288      return strings.Join(components, "/"), nil
   289  }
   290  
   291  // MustFormatMap is like FormatMap, but panics on error.
   292  func (router *Router) MustFormatMap(route *Route, mapping map[string]string) string {
   293      result, err := router.FormatMap(route, mapping)
   294      if err != nil {
   295          panic(fmt.Errorf("unable to format a route: %v", err))
   296      }
   297      return result
   298  }
   299  
   300  // Named returns a named Route where `name == route.Name`. Nil if not found.
   301  func (router *Router) Named(name string) *Route {
   302      return router.routesByName[name]
   303  }
   304  
   305  // MustNamed is like Named, but panics if not found.
   306  func (router *Router) MustNamed(name string) *Route {
   307      route := router.routesByName[name]
   308      if route == nil {
   309          panic(fmt.Errorf("named route %q not found", name))
   310      }
   311      return route
   312  }
   313  

View as plain text