-
-
Save benhoyt/98b08cf79d0fe659b5700c52667c8742 to your computer and use it in GitHub Desktop.
Yuri Vishnevsky tweaks to my match() router but simpler
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
From Yuri Vishnevsky on Gophers Slack: | |
Hi Ben! I read your great post on Go routing. Based on | |
the ideas there I wound up implementing my own router for a small | |
site I'm building, which combines a few ideas from your post.Instead | |
of passing a format string followed by pieces as in match | |
("foo/+/baz", &bar) I decided to inline the pieces, so the match | |
arguments read as the path does: match("foo", &bar, "baz"). I'm | |
curious if you have any thoughts, since this seems quite nice to me, | |
and has a simple implementation. No worries if you're too busy to | |
think about this right now; I just thought you might find it | |
interesting. | |
*/ | |
package main | |
import ( | |
"fmt" | |
"strconv" | |
"strings" | |
) | |
// match reports whether `path` matches the given `pieces` | |
// and assigns pointer pieces. A piece can be a string, | |
// *string, or *int64. This function matches pieces greedily | |
// and may assign pieces even when the path does not match. | |
// Note: Does not normalize paths with path.Clean. | |
// Note: Consecutive string path components need to be matched | |
// with separate strings, since this always splits on /. | |
func match(path string, pieces ...interface{}) bool { | |
// Remove the initial "/" prefix | |
if strings.HasPrefix(path, "/") { | |
path = path[1:] | |
} | |
var head string | |
for i, piece := range pieces { | |
// Shift the next path component into `head` | |
head, path = nextComponent(path) | |
// Match pieces based on their type | |
switch p := piece.(type) { | |
case string: | |
// Match a specific string | |
if p != head { | |
return false | |
} | |
case *string: | |
// Match any string, including the empty string | |
*p = head | |
case *int64: | |
// Match any 64-bit integer, including negative integers | |
n, err := strconv.ParseInt(head, 10, 64) | |
if err != nil { | |
return false | |
} | |
*p = n | |
default: | |
panic(fmt.Sprintf("each piece must be a string, *string, or *int64. Got %T", piece)) | |
} | |
// If the path is fully consumed, we're done if pieces are also fully consumed | |
if path == "" { | |
return i == len(pieces)-1 | |
} | |
} | |
// Pieces are consumed; return true if the path is too. | |
return path == "" | |
} | |
// Accepts a path without leading slash and returns two strings: | |
// its first component and the rest without leading slash | |
func nextComponent(path string) (head, tail string) { | |
i := strings.IndexByte(path, '/') | |
if i == -1 { | |
return path, "" | |
} | |
return path[:i], path[i+1:] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment