Add function to get unescaped elements for a path segment (#422)
* Function and tests for returned unescaped elements * Regression test and fix for empty segment bug If a raw segment had no elements that had length > 0 or just didn't have any elements it would still create a segment, throwing everything else off. Explicitly test for that now.
This commit is contained in:
parent
5fe9cc51aa
commit
e50728c0d6
@ -88,7 +88,7 @@ func newPath(segments [][]string) Base {
|
|||||||
res := Base{segmentIdx: make([]int, 0, len(segments))}
|
res := Base{segmentIdx: make([]int, 0, len(segments))}
|
||||||
idx := 0
|
idx := 0
|
||||||
for _, s := range segments {
|
for _, s := range segments {
|
||||||
res.segmentIdx = append(res.segmentIdx, idx)
|
sIdx := idx
|
||||||
|
|
||||||
for _, e := range s {
|
for _, e := range s {
|
||||||
if len(e) == 0 {
|
if len(e) == 0 {
|
||||||
@ -98,6 +98,10 @@ func newPath(segments [][]string) Base {
|
|||||||
res.elements = append(res.elements, escapeElement(e))
|
res.elements = append(res.elements, escapeElement(e))
|
||||||
idx++
|
idx++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if sIdx != idx {
|
||||||
|
res.segmentIdx = append(res.segmentIdx, sIdx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res
|
return res
|
||||||
@ -155,9 +159,23 @@ func (b Base) segment(n int) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// unescapedSegmentElements returns the unescaped version of the elements that
|
// unescapedSegmentElements returns the unescaped version of the elements that
|
||||||
// comprise the requested segment.
|
// comprise the requested segment. Path segment indices are 0-based.
|
||||||
func (p Base) unescapedSegmentElements(n int) []string {
|
func (b Base) unescapedSegmentElements(n int) []string {
|
||||||
return nil
|
var elements []string
|
||||||
|
|
||||||
|
if n == len(b.segmentIdx)-1 {
|
||||||
|
elements = b.elements[b.segmentIdx[n]:]
|
||||||
|
} else {
|
||||||
|
elements = b.elements[b.segmentIdx[n]:b.segmentIdx[n+1]]
|
||||||
|
}
|
||||||
|
|
||||||
|
res := make([]string, 0, len(elements))
|
||||||
|
|
||||||
|
for _, e := range elements {
|
||||||
|
res = append(res, unescape(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransformedSegments returns a slice of the path segments where each segments
|
// TransformedSegments returns a slice of the path segments where each segments
|
||||||
@ -167,6 +185,9 @@ func (b Base) TransformedSegments() []string {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// escapeElement takes a single path element and escapes all characters that
|
||||||
|
// require an escape sequence. If there are no characters that need escaping,
|
||||||
|
// the input is returned unchanged.
|
||||||
func escapeElement(element string) string {
|
func escapeElement(element string) string {
|
||||||
escapeIdx := make([]int, 0)
|
escapeIdx := make([]int, 0)
|
||||||
|
|
||||||
@ -196,6 +217,32 @@ func escapeElement(element string) string {
|
|||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// unescape returns the given element and converts it into a "raw"
|
||||||
|
// element that does not have escape characters before characters that need
|
||||||
|
// escaping. Using this function on segments that contain escaped path
|
||||||
|
// separators will result in an ambiguous or incorrect segment.
|
||||||
|
func unescape(element string) string {
|
||||||
|
b := strings.Builder{}
|
||||||
|
|
||||||
|
startIdx := 0
|
||||||
|
prevWasEscape := false
|
||||||
|
for i, c := range element {
|
||||||
|
if c != escapeCharacter || prevWasEscape {
|
||||||
|
prevWasEscape = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is an escape character, remove it from the output.
|
||||||
|
b.WriteString(element[startIdx:i])
|
||||||
|
startIdx = i + 1
|
||||||
|
prevWasEscape = true
|
||||||
|
}
|
||||||
|
|
||||||
|
b.WriteString(element[startIdx:])
|
||||||
|
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
// validateSegments takes a slice of segments and ensures that escaped
|
// validateSegments takes a slice of segments and ensures that escaped
|
||||||
// sequences match the set of characters that need escaping and that there
|
// sequences match the set of characters that need escaping and that there
|
||||||
// aren't hanging escape characters at the end of a segment.
|
// aren't hanging escape characters at the end of a segment.
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package path
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -10,6 +11,141 @@ import (
|
|||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var basicInputs = []struct {
|
||||||
|
name string
|
||||||
|
input [][]string
|
||||||
|
expectedString string
|
||||||
|
expectedEscapedSegments []string
|
||||||
|
expectedUnescapedElements [][]string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "SimplePath",
|
||||||
|
input: [][]string{
|
||||||
|
{`this`},
|
||||||
|
{`is`},
|
||||||
|
{`a`},
|
||||||
|
{`path`},
|
||||||
|
},
|
||||||
|
expectedString: "this/is/a/path",
|
||||||
|
expectedEscapedSegments: []string{
|
||||||
|
`this`,
|
||||||
|
`is`,
|
||||||
|
`a`,
|
||||||
|
`path`,
|
||||||
|
},
|
||||||
|
expectedUnescapedElements: [][]string{
|
||||||
|
{`this`},
|
||||||
|
{`is`},
|
||||||
|
{`a`},
|
||||||
|
{`path`},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "EscapeSeparator",
|
||||||
|
input: [][]string{
|
||||||
|
{`this`},
|
||||||
|
{`is/a`},
|
||||||
|
{`path`},
|
||||||
|
},
|
||||||
|
expectedString: `this/is\/a/path`,
|
||||||
|
expectedEscapedSegments: []string{
|
||||||
|
`this`,
|
||||||
|
`is\/a`,
|
||||||
|
`path`,
|
||||||
|
},
|
||||||
|
expectedUnescapedElements: [][]string{
|
||||||
|
{`this`},
|
||||||
|
{`is/a`},
|
||||||
|
{`path`},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "EscapeEscapeChar",
|
||||||
|
input: [][]string{
|
||||||
|
{`this`},
|
||||||
|
{`is\`},
|
||||||
|
{`a`},
|
||||||
|
{`path`},
|
||||||
|
},
|
||||||
|
expectedString: `this/is\\/a/path`,
|
||||||
|
expectedEscapedSegments: []string{
|
||||||
|
`this`,
|
||||||
|
`is\\`,
|
||||||
|
`a`,
|
||||||
|
`path`,
|
||||||
|
},
|
||||||
|
expectedUnescapedElements: [][]string{
|
||||||
|
{`this`},
|
||||||
|
{`is\`},
|
||||||
|
{`a`},
|
||||||
|
{`path`},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "EscapeEscapeAndSeparator",
|
||||||
|
input: [][]string{
|
||||||
|
{`this`},
|
||||||
|
{`is\/a`},
|
||||||
|
{`path`},
|
||||||
|
},
|
||||||
|
expectedString: `this/is\\\/a/path`,
|
||||||
|
expectedEscapedSegments: []string{
|
||||||
|
`this`,
|
||||||
|
`is\\\/a`,
|
||||||
|
`path`,
|
||||||
|
},
|
||||||
|
expectedUnescapedElements: [][]string{
|
||||||
|
{`this`},
|
||||||
|
{`is\/a`},
|
||||||
|
{`path`},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "SeparatorAtEndOfElement",
|
||||||
|
input: [][]string{
|
||||||
|
{`this`},
|
||||||
|
{`is/`},
|
||||||
|
{`a`},
|
||||||
|
{`path`},
|
||||||
|
},
|
||||||
|
expectedString: `this/is\//a/path`,
|
||||||
|
expectedEscapedSegments: []string{
|
||||||
|
`this`,
|
||||||
|
`is\/`,
|
||||||
|
`a`,
|
||||||
|
`path`,
|
||||||
|
},
|
||||||
|
expectedUnescapedElements: [][]string{
|
||||||
|
{`this`},
|
||||||
|
{`is/`},
|
||||||
|
{`a`},
|
||||||
|
{`path`},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "SeparatorAtEndOfPath",
|
||||||
|
input: [][]string{
|
||||||
|
{`this`},
|
||||||
|
{`is`},
|
||||||
|
{`a`},
|
||||||
|
{`path/`},
|
||||||
|
},
|
||||||
|
expectedString: `this/is/a/path\/`,
|
||||||
|
expectedEscapedSegments: []string{
|
||||||
|
`this`,
|
||||||
|
`is`,
|
||||||
|
`a`,
|
||||||
|
`path\/`,
|
||||||
|
},
|
||||||
|
expectedUnescapedElements: [][]string{
|
||||||
|
{`this`},
|
||||||
|
{`is`},
|
||||||
|
{`a`},
|
||||||
|
{`path/`},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
type PathUnitSuite struct {
|
type PathUnitSuite struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
}
|
}
|
||||||
@ -19,49 +155,33 @@ func TestPathUnitSuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *PathUnitSuite) TestPathEscapingAndSegments() {
|
func (suite *PathUnitSuite) TestPathEscapingAndSegments() {
|
||||||
|
for _, test := range basicInputs {
|
||||||
|
suite.T().Run(test.name, func(t *testing.T) {
|
||||||
|
p := newPath(test.input)
|
||||||
|
assert.Equal(t, test.expectedString, p.String())
|
||||||
|
|
||||||
|
for i, s := range test.expectedEscapedSegments {
|
||||||
|
segment := ""
|
||||||
|
assert.NotPanics(t, func() {
|
||||||
|
segment = p.segment(i)
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, s, segment)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Panics(t, func() {
|
||||||
|
_ = p.segment(len(test.input))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *PathUnitSuite) TestPathEscapingAndSegments_EmpytElements() {
|
||||||
table := []struct {
|
table := []struct {
|
||||||
name string
|
name string
|
||||||
input [][]string
|
input [][]string
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{
|
|
||||||
name: "SimplePath",
|
|
||||||
input: [][]string{
|
|
||||||
{`this`},
|
|
||||||
{`is`},
|
|
||||||
{`a`},
|
|
||||||
{`path`},
|
|
||||||
},
|
|
||||||
expected: "this/is/a/path",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "EscapeSeparator",
|
|
||||||
input: [][]string{
|
|
||||||
{`this`},
|
|
||||||
{`is/a`},
|
|
||||||
{`path`},
|
|
||||||
},
|
|
||||||
expected: `this/is\/a/path`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "EscapeEscapeChar",
|
|
||||||
input: [][]string{
|
|
||||||
{`this`},
|
|
||||||
{`is\`},
|
|
||||||
{`a`},
|
|
||||||
{`path`},
|
|
||||||
},
|
|
||||||
expected: `this/is\\/a/path`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "EscapeEscapeAndSeparator",
|
|
||||||
input: [][]string{
|
|
||||||
{`this`},
|
|
||||||
{`is\/a`},
|
|
||||||
{`path`},
|
|
||||||
},
|
|
||||||
expected: `this/is\\\/a/path`,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "EmptyInternalElement",
|
name: "EmptyInternalElement",
|
||||||
input: [][]string{
|
input: [][]string{
|
||||||
@ -74,36 +194,43 @@ func (suite *PathUnitSuite) TestPathEscapingAndSegments() {
|
|||||||
expected: "this/is/a/path",
|
expected: "this/is/a/path",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "SeparatorAtEndOfElement",
|
name: "EmptyInternalElement2",
|
||||||
input: [][]string{
|
|
||||||
{`this`},
|
|
||||||
{`is/`},
|
|
||||||
{`a`},
|
|
||||||
{`path`},
|
|
||||||
},
|
|
||||||
expected: `this/is\//a/path`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "SeparatorAtEndOfPath",
|
|
||||||
input: [][]string{
|
input: [][]string{
|
||||||
{`this`},
|
{`this`},
|
||||||
{`is`},
|
{`is`},
|
||||||
|
{"", "", ""},
|
||||||
{`a`},
|
{`a`},
|
||||||
{`path/`},
|
{`path`},
|
||||||
},
|
},
|
||||||
expected: `this/is/a/path\/`,
|
expected: "this/is/a/path",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "EmptyInternalElement3",
|
||||||
|
input: [][]string{
|
||||||
|
{`this`},
|
||||||
|
{`is`},
|
||||||
|
{},
|
||||||
|
{`a`},
|
||||||
|
{`path`},
|
||||||
|
},
|
||||||
|
expected: "this/is/a/path",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
suite.T().Run(test.name, func(t *testing.T) {
|
suite.T().Run(test.name, func(t *testing.T) {
|
||||||
p := newPath(test.input)
|
p := newPath(test.input)
|
||||||
assert.Equal(t, test.expected, p.String())
|
|
||||||
|
|
||||||
|
idx := 0
|
||||||
for i := 0; i < len(test.input); i++ {
|
for i := 0; i < len(test.input); i++ {
|
||||||
|
if i == 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
assert.NotPanics(t, func() {
|
assert.NotPanics(t, func() {
|
||||||
_ = p.segment(i)
|
_ = p.segment(idx)
|
||||||
})
|
})
|
||||||
|
idx++
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Panics(t, func() {
|
assert.Panics(t, func() {
|
||||||
@ -113,6 +240,27 @@ func (suite *PathUnitSuite) TestPathEscapingAndSegments() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *PathUnitSuite) TestUnescapedSegmentElements() {
|
||||||
|
for _, test := range basicInputs {
|
||||||
|
suite.T().Run(test.name, func(t *testing.T) {
|
||||||
|
p := newPath(test.input)
|
||||||
|
|
||||||
|
for i, s := range test.expectedUnescapedElements {
|
||||||
|
elements := []string{}
|
||||||
|
require.NotPanics(t, func() {
|
||||||
|
elements = p.unescapedSegmentElements(i)
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.True(t, reflect.DeepEqual(s, elements))
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Panics(t, func() {
|
||||||
|
_ = p.unescapedSegmentElements(len(test.input))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (suite *PathUnitSuite) TestPathSplitsEscapedPath() {
|
func (suite *PathUnitSuite) TestPathSplitsEscapedPath() {
|
||||||
table := []struct {
|
table := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user