Skip to content

Commit

Permalink
Merge pull request #5 from go-andiamo/get-vars
Browse files Browse the repository at this point in the history
Get vars
  • Loading branch information
marrow16 authored Jul 1, 2023
2 parents 08fbf77 + a75a069 commit fece0f4
Show file tree
Hide file tree
Showing 5 changed files with 260 additions and 6 deletions.
22 changes: 22 additions & 0 deletions path_part.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,28 @@ func (pt *pathPart) pathFrom(tracker *positionsTracker) (string, error) {
return `/` + pb.String(), nil
}

func (pt *pathPart) getVars(vars []PathVar, namePosns map[string]int) []PathVar {
if !pt.fixed && len(pt.subParts) > 0 {
for _, sp := range pt.subParts {
vars = sp.getVars(vars, namePosns)
}
} else if !pt.fixed {
if pt.name == "" {
vars = append(vars, PathVar{
Position: len(vars),
})
} else {
vars = append(vars, PathVar{
Name: pt.name,
NamedPosition: namePosns[pt.name],
Position: len(vars),
})
namePosns[pt.name] = namePosns[pt.name] + 1
}
}
return vars
}

type positionsTracker struct {
vars PathVars
varPosition int
Expand Down
15 changes: 15 additions & 0 deletions path_vars.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,21 @@ func newPathVars(varsType PathVarsType) PathVars {
}
}

func PathVarsFromMap(m map[string]interface{}) PathVars {
result := newPathVars(Names)
for k, v := range m {
switch av := v.(type) {
case []interface{}:
for _, sv := range av {
_ = result.AddNamedValue(k, sv)
}
default:
_ = result.AddNamedValue(k, av)
}
}
return result
}

func (pvs *pathVars) GetPositional(position int) (string, bool) {
if position < 0 && (len(pvs.all)+position) >= 0 {
return getValueIf(pvs.all[len(pvs.all)+position].Value)
Expand Down
36 changes: 36 additions & 0 deletions path_vars_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,3 +212,39 @@ func TestPathVars_Get_Named(t *testing.T) {
require.True(t, ok)
require.Equal(t, "c", v)
}

func TestPathVarsFromMap(t *testing.T) {
m := map[string]interface{}{
"foo": "a",
"bar": "b",
}
args := PathVarsFromMap(m)
require.Equal(t, 2, args.Len())

v, ok := args.GetNamed("foo", 0)
require.True(t, ok)
require.Equal(t, "a", v)
v, ok = args.GetNamed("bar", 0)
require.True(t, ok)
require.Equal(t, "b", v)

m = map[string]interface{}{
"foo": "a",
"bar": []interface{}{"b", "c", "d"},
}
args = PathVarsFromMap(m)
require.Equal(t, 4, args.Len())

v, ok = args.GetNamed("foo", 0)
require.True(t, ok)
require.Equal(t, "a", v)
v, ok = args.GetNamed("bar", 0)
require.True(t, ok)
require.Equal(t, "b", v)
v, ok = args.GetNamed("bar", 1)
require.True(t, ok)
require.Equal(t, "c", v)
v, ok = args.GetNamed("bar", 2)
require.True(t, ok)
require.Equal(t, "d", v)
}
42 changes: 36 additions & 6 deletions template.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/go-andiamo/splitter"
"io"
"net/http"
"net/url"
"strings"
)

Expand Down Expand Up @@ -51,6 +52,9 @@ type Template interface {
// Matches checks whether the specified path matches the template -
// and if a successful match, returns the extracted path vars
Matches(path string, options ...interface{}) (PathVars, bool)
// MatchesUrl checks whether the specified URL path matches the template -
// and if successful match, returns the extracted path vars
MatchesUrl(u url.URL, options ...interface{}) (PathVars, bool)
// MatchesRequest checks whether the specified request matches the template -
// and if a successful match, returns the extracted path vars
MatchesRequest(req *http.Request, options ...interface{}) (PathVars, bool)
Expand All @@ -60,6 +64,8 @@ type Template interface {
ResolveTo(vars PathVars) (Template, error)
// VarsType returns the path vars type (Positions or Names)
VarsType() PathVarsType
// Vars returns the path vars of the template
Vars() []PathVar
// OriginalTemplate returns the original (or generated) path template string
OriginalTemplate() string
}
Expand Down Expand Up @@ -137,6 +143,26 @@ func (t *template) RequestFrom(method string, vars PathVars, body io.Reader, opt
// Matches checks whether the specified path matches the template -
// and if a successful match, returns the extracted path vars
func (t *template) Matches(path string, options ...interface{}) (PathVars, bool) {
u, err := url.Parse(path)
if err != nil {
return nil, false
}
return t.matches(u.Path, options...)
}

// MatchesUrl checks whether the specified URL path matches the template -
// and if successful match, returns the extracted path vars
func (t *template) MatchesUrl(u url.URL, options ...interface{}) (PathVars, bool) {
return t.matches(u.Path, options...)
}

// MatchesRequest checks whether the specified request matches the template -
// and if a successful match, returns the extracted path vars
func (t *template) MatchesRequest(req *http.Request, options ...interface{}) (PathVars, bool) {
return t.matches(req.URL.Path, options...)
}

func (t *template) matches(path string, options ...interface{}) (PathVars, bool) {
pts, err := matchPathSplitter.Split(path)
if err != nil || len(pts) != len(t.pathParts) {
return nil, false
Expand All @@ -153,12 +179,6 @@ func (t *template) Matches(path string, options ...interface{}) (PathVars, bool)
return result, ok
}

// MatchesRequest checks whether the specified request matches the template -
// and if a successful match, returns the extracted path vars
func (t *template) MatchesRequest(req *http.Request, options ...interface{}) (PathVars, bool) {
return t.Matches(req.URL.Path, options...)
}

// Sub generates a new template with added sub-path
func (t *template) Sub(path string, options ...interface{}) (Template, error) {
add, err := NewTemplate(path, options...)
Expand Down Expand Up @@ -281,6 +301,16 @@ func (t *template) VarsType() PathVarsType {
return Names
}

// Vars returns the path vars of the template
func (t *template) Vars() []PathVar {
result := make([]PathVar, 0, len(t.pathParts))
namePosns := map[string]int{}
for _, p := range t.pathParts {
result = p.getVars(result, namePosns)
}
return result
}

// OriginalTemplate returns the original (or generated) path template string
func (t *template) OriginalTemplate() string {
return t.originalTemplate
Expand Down
151 changes: 151 additions & 0 deletions template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"github.com/stretchr/testify/require"
"net/http"
"net/url"
"regexp"
"testing"
)
Expand Down Expand Up @@ -39,6 +40,79 @@ func TestMatchString(t *testing.T) {
require.Equal(t, "bbb", v)
}

func TestMatchWithUrlString(t *testing.T) {
tmp, err := NewTemplate("/foo/{foo}/bar/{bar}/{bar}")
require.NoError(t, err)
require.NotNil(t, tmp)

_, ok := tmp.Matches("https://my.org/foo")
require.False(t, ok)

args, ok := tmp.Matches("https://my.org/foo/fooey/bar/aaa/bbb")
require.True(t, ok)
v, ok := args.GetPositional(0)
require.True(t, ok)
require.Equal(t, "fooey", v)
v, ok = args.GetPositional(1)
require.True(t, ok)
require.Equal(t, "aaa", v)
v, ok = args.GetPositional(2)
require.True(t, ok)
require.Equal(t, "bbb", v)
_, ok = args.GetPositional(3)
require.False(t, ok)
v, ok = args.GetNamedFirst("foo")
require.True(t, ok)
require.Equal(t, "fooey", v)
v, ok = args.GetNamedFirst("bar")
require.True(t, ok)
require.Equal(t, "aaa", v)
v, ok = args.GetNamedLast("bar")
require.True(t, ok)
require.Equal(t, "bbb", v)

// bad url...
const badUrl = "://my.org"
_, ok = tmp.Matches(badUrl)
require.False(t, ok)
_, err = url.Parse(badUrl)
require.Error(t, err)
}

func TestMatchWithUrl(t *testing.T) {
tmp, err := NewTemplate("/foo/{foo}/bar/{bar}/{bar}")
require.NoError(t, err)
require.NotNil(t, tmp)

u, _ := url.Parse("https://my.org/foo")
_, ok := tmp.MatchesUrl(*u)
require.False(t, ok)

u, _ = url.Parse("https://my.org/foo/fooey/bar/aaa/bbb")
args, ok := tmp.MatchesUrl(*u)
require.True(t, ok)
v, ok := args.GetPositional(0)
require.True(t, ok)
require.Equal(t, "fooey", v)
v, ok = args.GetPositional(1)
require.True(t, ok)
require.Equal(t, "aaa", v)
v, ok = args.GetPositional(2)
require.True(t, ok)
require.Equal(t, "bbb", v)
_, ok = args.GetPositional(3)
require.False(t, ok)
v, ok = args.GetNamedFirst("foo")
require.True(t, ok)
require.Equal(t, "fooey", v)
v, ok = args.GetNamedFirst("bar")
require.True(t, ok)
require.Equal(t, "aaa", v)
v, ok = args.GetNamedLast("bar")
require.True(t, ok)
require.Equal(t, "bbb", v)
}

func TestMatchPositional(t *testing.T) {
tmp, err := NewTemplate("/foo/?/bar/?/?")
require.NoError(t, err)
Expand Down Expand Up @@ -456,6 +530,83 @@ func TestTemplate_MergeOptions(t *testing.T) {
}
}

func TestTemplate_Vars(t *testing.T) {
testCases := []struct {
path string
expect []PathVar
}{
{
path: "/foo/{fooid}",
expect: []PathVar{
{
Name: "fooid",
NamedPosition: 0,
Position: 0,
},
},
},
{
path: "/foo/{fooid}/foo/{fooid: [a-z]*}",
expect: []PathVar{
{
Name: "fooid",
NamedPosition: 0,
Position: 0,
},
{
Name: "fooid",
NamedPosition: 1,
Position: 1,
},
},
},
{
path: "/foo/{foo1}-{foo2}/bar/{bar}-{bar}",
expect: []PathVar{
{
Name: "foo1",
Position: 0,
},
{
Name: "foo2",
Position: 1,
},
{
Name: "bar",
Position: 2,
},
{
Name: "bar",
NamedPosition: 1,
Position: 3,
},
},
},
{
path: "foo/?/bar/?",
expect: []PathVar{
{
Position: 0,
},
{
Position: 1,
},
},
},
}
for i, tc := range testCases {
t.Run(fmt.Sprintf("[%d]", i+1), func(t *testing.T) {
tmp, err := NewTemplate(tc.path)
require.NoError(t, err)
vars := tmp.Vars()
require.Equal(t, len(tc.expect), len(vars))
for vi, v := range vars {
require.Equal(t, tc.expect[vi], v)
}
})
}
}

type uuidChecker struct {
}

Expand Down

0 comments on commit fece0f4

Please sign in to comment.