urlpath

package module
v0.0.0-...-7ccc79b Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 24, 2020 License: MIT Imports: 1 Imported by: 20

README

urlpath GoDoc Badge CI Badge

urlpath is a Golang library for matching paths against a template, or constructing paths using a template. It's meant for applications that take in REST-like URL paths, and need to validate and extract data from those paths.

This is easiest explained with an example:

import "github.com/ucarion/urlpath"

var getBookPath = urlpath.New("/shelves/:shelf/books/:book")

func main() {
  inputPath := "/shelves/foo/books/bar"
  match, ok := getBookPath.Match(inputPath)
  if !ok {
    // handle the input not being valid
    return
  }

  // Output:
  //
  // foo
  // bar
  fmt.Println(match.Params["shelf"])
  fmt.Println(match.Params["book"])
}

One slightly fancier feature is support for trailing segments, like if you have a path that ends with a filename. For example, a GitHub-like API might need to deal with paths like:

/ucarion/urlpath/blob/master/src/foo/bar/baz.go

You can do this with a path that ends with "*". This works like:

path := urlpath.New("/:user/:repo/blob/:branch/*")

match, ok := path.Match("/ucarion/urlpath/blob/master/src/foo/bar/baz.go")
fmt.Println(match.Params["user"])   // ucarion
fmt.Println(match.Params["repo"])   // urlpath
fmt.Println(match.Params["branch"]) // master
fmt.Println(match.Trailing)         // src/foo/bar/baz.go

Additionally, you can call Build to construct a path from a template:

path := urlpath.New("/:user/:repo/blob/:branch/*")

res, ok := path.Build(urlpath.Match{
  Params: map[string]string{
    "user": "ucarion",
    "repo": "urlpath",
    "branch": "master",
  },
  Trailing: "src/foo/bar/baz.go",
})

fmt.Println(res) // /ucarion/urlpath/blob/master/src/foo/bar/baz.go

How it works

urlpath operates on the basis of "segments", which is basically the result of splitting a path by slashes. When you call urlpath.New, each of the segments in the input is treated as either:

  • A parameterized segment, like :user. All segments starting with : are considered parameterized. Any corresponding segment in the input (even the empty string!) will be satisfactory, and will be sent to Params in the outputted Match. For example, data corresponding to :user would go in Params["user"].
  • An exact-match segment, like users. Only segments exactly equal to users will be satisfactory.
  • A "trailing" segment, *. This is only treated specially when it's the last segment -- otherwise, it's just a usual exact-match segment. Any leftover data in the input, after all previous segments were satisfied, goes into Trailing in the outputted Match.

Performance

Although performance wasn't the top priority for this library, urlpath does typically perform better than an equivalent regular expression. In other words, this:

path := urlpath.New("/test/:foo/bar/:baz")
matches := path.Match(...)

Will usually perform better than this:

r := regexp.MustCompile("/test/(?P<foo>[^/]+)/bar/(?P<baz>[^/]+)")
matches := r.FindStringSubmatch(...)

The results of go test -benchmem -bench .:

goos: darwin
goarch: amd64
pkg: github.com/ucarion/urlpath
BenchmarkMatch/without_trailing_segments/urlpath-8 	 1436247	       819 ns/op	     784 B/op	      10 allocs/op
BenchmarkMatch/without_trailing_segments/regex-8   	  693924	      1816 ns/op	     338 B/op	      10 allocs/op
BenchmarkMatch/with_trailing_segments/urlpath-8    	 1454750	       818 ns/op	     784 B/op	      10 allocs/op
BenchmarkMatch/with_trailing_segments/regex-8      	  592644	      2365 ns/op	     225 B/op	       8 allocs/op

Do your own benchmarking if performance matters a lot to you. See BenchmarkMatch in urlpath_test.go for the code that gives these results.

Documentation

Overview

Package urlpath matches paths against a template. It's meant for applications that take in REST-like URL paths, and need to validate and extract data from those paths.

See New for documentation of the syntax for creating paths. See Match for how to validate and parse an inputted path.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Match

type Match struct {
	// The segments in the input corresponding to parameterized segments in Path.
	Params map[string]string

	// The trailing segments from the input. Note that the leading slash from the
	// trailing segments is not included, since it's implied.
	//
	// An exception to this leading slash rule is made if the Path was constructed
	// as New("*"), in which case Trailing will be identical to the inputted
	// string.
	Trailing string
}

Match represents the data extracted by matching an input against a Path.

To construct instances of Match, see the Match method on Path.

Example
package main

import (
	"fmt"

	"github.com/ucarion/urlpath"
)

func main() {
	path := urlpath.New("users/:id/files/*")
	match, ok := path.Match("users/123/files/foo/bar/baz.txt")
	fmt.Println(ok)
	fmt.Println(match.Params)
	fmt.Println(match.Trailing)
}
Output:


true
map[id:123]
foo/bar/baz.txt

type Path

type Path struct {
	// A sequence of constraints on what valid segments must look like.
	Segments []Segment

	// Whether additional, trailing segments after Segments are acceptable.
	Trailing bool
}

Path is a representation of a sequence of segments.

To construct instances of Path, see New.

func New

func New(path string) Path

New constructs a new Path from its human-readable string representation.

The syntax for paths looks something like the following:

/shelves/:shelf/books/:book

This would match inputs like:

/shelves/foo/books/bar
/shelves/123/books/456
/shelves/123/books/
/shelves//books/456
/shelves//books/

But not any of the following:

/shelves/foo/books
/shelves/foo/books/bar/
/shelves/foo/books/bar/pages/baz
/SHELVES/foo/books/bar
shelves/foo/books/bar

Optionally, a path can allow for "trailing" segments in the input. This is done using a segment simply named "*". For example, this path:

/users/:user/files/*

Would match inputs like:

/users/foo/files/
/users/foo/files/foo/bar/baz.txt
/users/foo/files////

But not:

/users/foo
/users/foo/files

The asterisk syntax for trailing segments only takes effect on the last segment. If an asterisk appears in any other segment, it carries no special meaning.

In more formal terms, the string representation of a path is a sequence of segments separated by slashes. Segments starting with colon (":") are treated as "parameter" segments (see Match).

If the final segment is just the character asterisk ("*"), it is treated as an indication that the path accepts trailing segments, and not included in the Segments of the return value. Instead, Trailing in the return value is marked as true.

Example
package main

import (
	"fmt"

	"github.com/ucarion/urlpath"
)

func main() {
	path := urlpath.New("users/:id/files/*")
	fmt.Println(path.Segments[0].Const)
	fmt.Println(path.Segments[1].Param)
	fmt.Println(path.Segments[2].Const)
	fmt.Println(path.Trailing)
}
Output:


users
id
files
true

func (*Path) Build

func (p *Path) Build(m Match) (string, bool)

Build is the inverse of Match. Given parameter and trailing segment information, Build returns a string which satifies this information.

The second parameter indicates whether the inputted match has the parameters the path specifies. If any of the parameters in the path are not found in the provided Match's Params, then false is returned.

Example
package main

import (
	"fmt"

	"github.com/ucarion/urlpath"
)

func main() {
	path := urlpath.New("users/:id/files/*")
	built, ok := path.Build(urlpath.Match{
		Params:   map[string]string{"id": "123"},
		Trailing: "foo/bar/baz.txt",
	})
	fmt.Println(ok)
	fmt.Println(built)
}
Output:


true
users/123/files/foo/bar/baz.txt

func (*Path) Match

func (p *Path) Match(s string) (Match, bool)

Match checks if the input string satisfies a Path's constraints, and returns parameter and trailing segment information.

The second return value indicates whether the inputted string matched the path. The first return value is meaningful only if the match was successful.

If the match was a success, all parameterized segments in Path have a corresponding entry in the Params of Match. If the path allows for trailing segments in the input, these will be in Trailing.

type Segment

type Segment struct {
	// Whether this segment is parameterized.
	IsParam bool

	// The name of the parameter this segment will be mapped to.
	Param string

	// The constant value the segment is expected to take.
	Const string
}

Segment is a constraint on a single segment in a path.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL