Skip to content

Instantly share code, notes, and snippets.

@benhoyt
Created October 23, 2021 05:44
Show Gist options
  • Save benhoyt/98b08cf79d0fe659b5700c52667c8742 to your computer and use it in GitHub Desktop.
Save benhoyt/98b08cf79d0fe659b5700c52667c8742 to your computer and use it in GitHub Desktop.
Yuri Vishnevsky tweaks to my match() router but simpler
/*
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