Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Path vars #4

Merged
merged 1 commit into from
Nov 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions headers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package urit

import (
"errors"
)

type HeadersOption interface {
GetHeaders() (map[string]string, error)
}

type Headers interface {
HeadersOption
Set(key string, value interface{}) Headers
Get(key string) (interface{}, bool)
Has(key string) bool
Del(key string) Headers
Clone() Headers
}

func NewHeaders(namesAndValues ...interface{}) (Headers, error) {
if len(namesAndValues)%2 != 0 {
return nil, errors.New("must be a value for each name")
}
result := &headers{
entries: map[string]interface{}{},
}
for i := 0; i < len(namesAndValues)-1; i += 2 {
if k, ok := namesAndValues[i].(string); ok {
result.entries[k] = namesAndValues[i+1]
} else {
return nil, errors.New("name must be a string")
}
}
return result, nil
}

type headers struct {
entries map[string]interface{}
}

func (h *headers) GetHeaders() (map[string]string, error) {
result := map[string]string{}
for k, v := range h.entries {
if str, err := getValue(v); err == nil {
result[k] = str
} else {
return result, err
}
}
return result, nil
}

func (h *headers) Set(key string, value interface{}) Headers {
h.entries[key] = value
return h
}

func (h *headers) Get(key string) (interface{}, bool) {
v, ok := h.entries[key]
return v, ok
}

func (h *headers) Has(key string) bool {
_, ok := h.entries[key]
return ok
}

func (h *headers) Del(key string) Headers {
delete(h.entries, key)
return h
}

func (h *headers) Clone() Headers {
result := &headers{
entries: map[string]interface{}{},
}
for k, v := range h.entries {
result.entries[k] = v
}
return result
}
105 changes: 105 additions & 0 deletions headers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package urit

import (
"github.com/stretchr/testify/require"
"testing"
)

func TestNewHeaders(t *testing.T) {
h, err := NewHeaders()
require.NoError(t, err)
require.NotNil(t, h)
rh, ok := h.(*headers)
require.True(t, ok)
require.Equal(t, 0, len(rh.entries))

h, err = NewHeaders("foo", 1.23)
require.NoError(t, err)
require.NotNil(t, h)
rh, ok = h.(*headers)
require.True(t, ok)
require.Equal(t, 1, len(rh.entries))
require.Equal(t, 1.23, rh.entries["foo"])
}

func TestNewHeadersErrors(t *testing.T) {
_, err := NewHeaders("foo") // must be an even number!
require.Error(t, err)
require.Equal(t, `must be a value for each name`, err.Error())

_, err = NewHeaders(true, false) // first must be a string!
require.Error(t, err)
require.Equal(t, `name must be a string`, err.Error())
}

func TestHeaders_GetHeaders(t *testing.T) {
h, err := NewHeaders()
require.NoError(t, err)
hds, err := h.GetHeaders()
require.NoError(t, err)
require.Equal(t, 0, len(hds))

h, err = NewHeaders("foo", 1.23)
require.NoError(t, err)
hds, err = h.GetHeaders()
require.NoError(t, err)
require.Equal(t, 1, len(hds))
require.Equal(t, "1.23", hds["foo"])

h, err = NewHeaders("foo", nil)
require.NoError(t, err)
_, err = h.GetHeaders()
require.Error(t, err)
require.Equal(t, `unknown value type`, err.Error())

h, err = NewHeaders("foo", func() {
// this does not yield a string
})
require.NoError(t, err)
_, err = h.GetHeaders()
require.Error(t, err)
require.Equal(t, `unknown value type`, err.Error())
}

func TestHeaders_GetSet(t *testing.T) {
h, err := NewHeaders("foo", 1)
require.NoError(t, err)
v, ok := h.Get("foo")
require.True(t, ok)
require.Equal(t, 1, v)
h.Set("foo", 2)
v, ok = h.Get("foo")
require.True(t, ok)
require.Equal(t, 2, v)
_, ok = h.Get("bar")
require.False(t, ok)
}

func TestHeaders_HasDel(t *testing.T) {
h, err := NewHeaders("foo", 1)
require.NoError(t, err)
require.True(t, h.Has("foo"))
h.Del("foo")
require.False(t, h.Has("foo"))
require.False(t, h.Has("bar"))
}

func TestHeaders_Clone(t *testing.T) {
h1, err := NewHeaders("foo", 1)
require.NoError(t, err)
require.True(t, h1.Has("foo"))
require.False(t, h1.Has("bar"))
h2 := h1.Clone()
require.True(t, h2.Has("foo"))
require.False(t, h2.Has("bar"))
h2.Del("foo")
require.True(t, h1.Has("foo"))
require.False(t, h1.Has("bar"))
require.False(t, h2.Has("foo"))
require.False(t, h2.Has("bar"))
h2.Set("bar", 2)
require.True(t, h1.Has("foo"))
require.False(t, h1.Has("bar"))
require.False(t, h2.Has("foo"))
require.True(t, h2.Has("bar"))
}
23 changes: 23 additions & 0 deletions host.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package urit

type HostOption interface {
GetAddress() string
}

type Host interface {
HostOption
}

func NewHost(address string) Host {
return &host{
address: address,
}
}

type host struct {
address string
}

func (h *host) GetAddress() string {
return h.address
}
15 changes: 15 additions & 0 deletions host_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package urit

import (
"github.com/stretchr/testify/require"
"testing"
)

func TestNewHost(t *testing.T) {
h := NewHost(`www.example.com`)
require.NotNil(t, h)
rh, ok := h.(*host)
require.True(t, ok)
require.Equal(t, `www.example.com`, rh.address)
require.Equal(t, `www.example.com`, h.GetAddress())
}
15 changes: 15 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ type VarMatchOption interface {

var (
_CaseInsensitiveFixed = &caseInsensitiveFixed{}
_PathRegexCheck = &pathRegexChecker{}
)
var (
CaseInsensitiveFixed = _CaseInsensitiveFixed // is a FixedMatchOption that can be used with templates to allow case-insensitive fixed path parts
PathRegexCheck = _PathRegexCheck // is a VarMatchOption that can be used with Template.PathFrom or Template.RequestFrom to check that vars passed in match regexes for the path part
)

type fixedMatchOptions []FixedMatchOption
Expand Down Expand Up @@ -64,3 +66,16 @@ type caseInsensitiveFixed struct{}
func (o *caseInsensitiveFixed) Match(value string, expected string, pathPos int, vars PathVars) bool {
return value == expected || strings.EqualFold(value, expected)
}

type pathRegexChecker struct{}

func (o *pathRegexChecker) Applicable(value string, position int, name string, rx *regexp.Regexp, rxs string, pathPos int, vars PathVars) bool {
return rx != nil
}

func (o *pathRegexChecker) Match(value string, position int, name string, rx *regexp.Regexp, rxs string, pathPos int, vars PathVars) (string, bool) {
if rx != nil && !rx.MatchString(value) {
return value, false
}
return value, true
}
41 changes: 34 additions & 7 deletions path_part.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package urit

import (
"errors"
"fmt"
"regexp"
"strconv"
Expand Down Expand Up @@ -150,27 +151,53 @@ func (pt *pathPart) pathFrom(tracker *positionsTracker) (string, error) {

type positionsTracker struct {
vars PathVars
position int
varPosition int
pathPosition int
namedPositions map[string]int
varMatches varMatchOptions
}

func (tr *positionsTracker) getVar(pt *pathPart) (string, error) {
if tr.vars.VarsType() == Positions {
if str, ok := tr.vars.GetPositional(tr.position); ok {
tr.position++
useVars := tr.vars
if useVars == nil {
useVars = Positional()
}
var err error
if useVars.VarsType() == Positions {
if str, ok := useVars.GetPositional(tr.varPosition); ok {
tr.varPosition++
return str, nil
}
return "", fmt.Errorf("no var for position %d", tr.position+1)
return "", fmt.Errorf("no var for varPosition %d", tr.varPosition+1)
} else {
np := tr.namedPositions[pt.name]
if str, ok := tr.vars.GetNamed(pt.name, np); ok {
if str, ok := useVars.GetNamed(pt.name, np); ok {
str, err = tr.checkVar(str, pt, tr.varPosition, tr.pathPosition)
if err != nil {
return "", err
}
tr.namedPositions[pt.name] = np + 1
tr.varPosition++
return str, nil
} else if np == 0 {
return "", fmt.Errorf("no var for '%s'", pt.name)
}
return "", fmt.Errorf("no var for '%s' (position %d)", pt.name, np+1)
return "", fmt.Errorf("no var for '%s' (varPosition %d)", pt.name, np+1)
}
}

func (tr *positionsTracker) checkVar(s string, pt *pathPart, pos int, pathPos int) (result string, err error) {
result = s
for _, ck := range tr.varMatches {
if ck.Applicable(result, pos, pt.name, pt.regexp, pt.orgRegexp, pathPos, tr.vars) {
if altS, ok := ck.Match(result, pos, pt.name, pt.regexp, pt.orgRegexp, pathPos, tr.vars); ok {
result = altS
} else {
err = errors.New("no match path var")
}
}
}
return
}

func addRegexHeadAndTail(rx string) string {
Expand Down
Loading