Simplify path struct (#647)
* Rewrite basic path logic to be simpler Make basic path logic deal only with path elements instead of elements and segments. Upper-layer logic can deal with elements. Base path logic does not require a complete resource path as would be seen by kopia, it just manages splitting/joining/escaping path elements. Will have transformers to go from a basic path to a resource specific path in a follow up. Remove upper-layer logic for now to reduce load while reviewing as it also changed slightly. Will be re-added in a follow up
This commit is contained in:
parent
c4e9046870
commit
f24ad6ccbd
@ -1,110 +0,0 @@
|
|||||||
package path
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
emailCategory = "email"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ Path = &ExchangeMail{}
|
|
||||||
|
|
||||||
type ExchangeMail struct {
|
|
||||||
Base
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewExchangeEmailPath creates and returns a new ExchangeEmailPath struct after
|
|
||||||
// verifying the path is properly escaped and contains information for the
|
|
||||||
// required segments. The provided segments and folder elements should not be
|
|
||||||
// escaped prior to calling this.
|
|
||||||
func NewExchangeMail(
|
|
||||||
tenant string,
|
|
||||||
user string,
|
|
||||||
folder []string,
|
|
||||||
item string,
|
|
||||||
) (*ExchangeMail, error) {
|
|
||||||
tmpFolder := strings.Join(folder, "")
|
|
||||||
if err := validateExchangeMailSegments(tenant, user, tmpFolder, item); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
p := newPath([][]string{
|
|
||||||
{tenant},
|
|
||||||
{emailCategory},
|
|
||||||
{user},
|
|
||||||
folder,
|
|
||||||
{item},
|
|
||||||
})
|
|
||||||
|
|
||||||
return &ExchangeMail{p}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewExchangeMailFromEscapedSegments takes a series of already escaped segments
|
|
||||||
// representing the tenant, user, folder, and item validates them and returns a
|
|
||||||
// *ExchangeMail. The caller is expected to concatenate of all folders
|
|
||||||
// into a single string like `some/subfolder/structure`. Any special characters
|
|
||||||
// in the folder path need to be escaped.
|
|
||||||
func NewExchangeMailFromEscapedSegments(tenant, user, folder, item string) (*ExchangeMail, error) {
|
|
||||||
if err := validateExchangeMailSegments(tenant, user, folder, item); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
p, err := newPathFromEscapedSegments([]string{tenant, emailCategory, user, folder, item})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ExchangeMail{p}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateExchangeMailSegments(tenant, user, folder, item string) error {
|
|
||||||
if len(tenant) == 0 {
|
|
||||||
return errors.Wrap(errMissingSegment, "tenant")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(user) == 0 {
|
|
||||||
return errors.Wrap(errMissingSegment, "user")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(folder) == 0 {
|
|
||||||
return errors.Wrap(errMissingSegment, "mail folder")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(item) == 0 {
|
|
||||||
return errors.Wrap(errMissingSegment, "mail item")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tenant returns the tenant ID for the referenced email resource.
|
|
||||||
func (emp ExchangeMail) Tenant() string {
|
|
||||||
return emp.segment(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cateory returns an identifier noting this is a path for an email resource.
|
|
||||||
func (emp ExchangeMail) Category() string {
|
|
||||||
return emp.segment(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// User returns the user ID for the referenced email resource.
|
|
||||||
func (emp ExchangeMail) User() string {
|
|
||||||
return emp.segment(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Folder returns the folder segment for the referenced email resource.
|
|
||||||
func (emp ExchangeMail) Folder() string {
|
|
||||||
return emp.segment(3)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (emp ExchangeMail) FolderElements() []string {
|
|
||||||
return emp.unescapedSegmentElements(3)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mail returns the email ID for the referenced email resource.
|
|
||||||
func (emp ExchangeMail) Item() string {
|
|
||||||
return emp.segment(4)
|
|
||||||
}
|
|
||||||
@ -1,162 +0,0 @@
|
|||||||
package path_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"github.com/stretchr/testify/suite"
|
|
||||||
|
|
||||||
"github.com/alcionai/corso/internal/path"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
tenant = "aTenant"
|
|
||||||
user = "aUser"
|
|
||||||
item = "anItem"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Purposely doesn't have characters that need escaping so it can be easily
|
|
||||||
// computed using strings.Join().
|
|
||||||
folder = []string{"some", "folder", "path"}
|
|
||||||
|
|
||||||
missingInfo = []struct {
|
|
||||||
name string
|
|
||||||
tenant string
|
|
||||||
user string
|
|
||||||
folder []string
|
|
||||||
item string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "NoTenant",
|
|
||||||
tenant: "",
|
|
||||||
user: user,
|
|
||||||
folder: folder,
|
|
||||||
item: item,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "NoUser",
|
|
||||||
tenant: tenant,
|
|
||||||
user: "",
|
|
||||||
folder: folder,
|
|
||||||
item: item,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "NoFolder",
|
|
||||||
tenant: "",
|
|
||||||
user: user,
|
|
||||||
folder: nil,
|
|
||||||
item: item,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "EmptyFolder",
|
|
||||||
tenant: "",
|
|
||||||
user: user,
|
|
||||||
folder: []string{"", ""},
|
|
||||||
item: item,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "NoItem",
|
|
||||||
tenant: tenant,
|
|
||||||
user: user,
|
|
||||||
folder: folder,
|
|
||||||
item: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
type ExchangeMailUnitSuite struct {
|
|
||||||
suite.Suite
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExchangeMailUnitSuite(t *testing.T) {
|
|
||||||
suite.Run(t, new(ExchangeMailUnitSuite))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (suite *ExchangeMailUnitSuite) TestMissingInfoErrors() {
|
|
||||||
for _, test := range missingInfo {
|
|
||||||
suite.T().Run(test.name, func(t *testing.T) {
|
|
||||||
_, err := path.NewExchangeMail(
|
|
||||||
test.tenant, test.user, test.folder, test.item)
|
|
||||||
assert.Error(t, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (suite *ExchangeMailUnitSuite) TestMissingInfoWithSegmentsErrors() {
|
|
||||||
for _, test := range missingInfo {
|
|
||||||
suite.T().Run(test.name, func(t *testing.T) {
|
|
||||||
folders := strings.Join(test.folder, "")
|
|
||||||
|
|
||||||
_, err := path.NewExchangeMailFromEscapedSegments(
|
|
||||||
test.tenant, test.user, folders, test.item)
|
|
||||||
assert.Error(t, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some simple escaping examples. Don't want to duplicate everything that is in
|
|
||||||
// the regular path.Base tests.
|
|
||||||
func (suite *ExchangeMailUnitSuite) TestNewExchangeMailFromRaw() {
|
|
||||||
t := suite.T()
|
|
||||||
localItem := `an\item`
|
|
||||||
|
|
||||||
em, err := path.NewExchangeMail(tenant, user, folder, localItem)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, `an\\item`, em.Item())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (suite *ExchangeMailUnitSuite) TestNewExchangeMailFromEscaped() {
|
|
||||||
t := suite.T()
|
|
||||||
localItem := `an\\item`
|
|
||||||
localFolder := strings.Join(folder, "/")
|
|
||||||
|
|
||||||
em, err := path.NewExchangeMailFromEscapedSegments(tenant, user, localFolder, localItem)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, localItem, em.Item())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (suite *ExchangeMailUnitSuite) TestNewExchangeMailFromEscaped_Errors() {
|
|
||||||
t := suite.T()
|
|
||||||
localItem := `an\item`
|
|
||||||
localFolder := strings.Join(folder, "/")
|
|
||||||
|
|
||||||
_, err := path.NewExchangeMailFromEscapedSegments(tenant, user, localFolder, localItem)
|
|
||||||
assert.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
type PopulatedExchangeMailUnitSuite struct {
|
|
||||||
suite.Suite
|
|
||||||
em *path.ExchangeMail
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPopulatedExchangeMailUnitSuite(t *testing.T) {
|
|
||||||
suite.Run(t, new(PopulatedExchangeMailUnitSuite))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (suite *PopulatedExchangeMailUnitSuite) SetupTest() {
|
|
||||||
em, err := path.NewExchangeMail(tenant, user, folder, item)
|
|
||||||
require.NoError(suite.T(), err)
|
|
||||||
|
|
||||||
suite.em = em
|
|
||||||
}
|
|
||||||
|
|
||||||
func (suite *PopulatedExchangeMailUnitSuite) TestGetTenant() {
|
|
||||||
assert.Equal(suite.T(), tenant, suite.em.Tenant())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (suite *PopulatedExchangeMailUnitSuite) TestGetUser() {
|
|
||||||
assert.Equal(suite.T(), user, suite.em.User())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (suite *PopulatedExchangeMailUnitSuite) TestGetFolder() {
|
|
||||||
assert.Equal(suite.T(), strings.Join(folder, "/"), suite.em.Folder())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (suite *PopulatedExchangeMailUnitSuite) TestGetItem() {
|
|
||||||
assert.Equal(suite.T(), item, suite.em.Item())
|
|
||||||
}
|
|
||||||
@ -51,8 +51,6 @@ var charactersToEscape = map[rune]struct{}{
|
|||||||
escapeCharacter: {},
|
escapeCharacter: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
var errMissingSegment = errors.New("missing required path segment")
|
|
||||||
|
|
||||||
// TODO(ashmrtn): Getting the category should either be through type-switches or
|
// TODO(ashmrtn): Getting the category should either be through type-switches or
|
||||||
// through a function, but if it's a function it should re-use existing enums
|
// through a function, but if it's a function it should re-use existing enums
|
||||||
// for resource types.
|
// for resource types.
|
||||||
@ -67,121 +65,90 @@ type Path interface {
|
|||||||
Item() string
|
Item() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Base struct {
|
// Builder is a simple path representation that only tracks path elements. It
|
||||||
// Escaped path elements.
|
// can join, escape, and unescape elements. Higher-level packages are expected
|
||||||
|
// to wrap this struct to build resource-speicific contexts (e.x. an
|
||||||
|
// ExchangeMailPath).
|
||||||
|
// Resource-specific paths allow access to more information like segments in the
|
||||||
|
// path. Builders that are turned into resource paths later on do not need to
|
||||||
|
// manually add prefixes for items that normally appear in the data layer (ex.
|
||||||
|
// tenant ID, service, user ID, etc).
|
||||||
|
type Builder struct {
|
||||||
|
// Unescaped version of elements.
|
||||||
elements []string
|
elements []string
|
||||||
// Contains starting index in elements of each segment.
|
|
||||||
segmentIdx []int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// newPath takes a path that is broken into segments and elements in the segment
|
// UnescapeAndAppend creates a copy of this Builder and adds one or more already
|
||||||
// and returns a Base. Each element in the input will get escaped.
|
// escaped path elements to the end of the new Builder. Elements are added in
|
||||||
// Example: [this, is\, a, path] will transform into [this, is\\, a, path].
|
// the order they are passed.
|
||||||
func newPath(segments [][]string) Base {
|
func (pb Builder) UnescapeAndAppend(elements ...string) (*Builder, error) {
|
||||||
if len(segments) == 0 {
|
res := &Builder{elements: make([]string, 0, len(pb.elements))}
|
||||||
return Base{}
|
copy(res.elements, pb.elements)
|
||||||
|
|
||||||
|
if err := res.appendElements(true, elements); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
res := Base{segmentIdx: make([]int, 0, len(segments))}
|
return res, nil
|
||||||
idx := 0
|
}
|
||||||
for _, s := range segments {
|
|
||||||
sIdx := idx
|
|
||||||
|
|
||||||
for _, e := range s {
|
// Append creates a copy of this Builder and adds the given elements them to the
|
||||||
if len(e) == 0 {
|
// end of the new Builder. Elements are added in the order they are passed.
|
||||||
continue
|
func (pb Builder) Append(elements ...string) *Builder {
|
||||||
}
|
res := &Builder{elements: make([]string, len(pb.elements))}
|
||||||
|
copy(res.elements, pb.elements)
|
||||||
|
|
||||||
res.elements = append(res.elements, escapeElement(e))
|
// Unescaped elements can't fail validation.
|
||||||
idx++
|
//nolint:errcheck
|
||||||
}
|
res.appendElements(false, elements)
|
||||||
|
|
||||||
if sIdx != idx {
|
|
||||||
res.segmentIdx = append(res.segmentIdx, sIdx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPathFromEscapedSegments takes already escaped segments of a path, verifies
|
func (pb *Builder) appendElements(escaped bool, elements []string) error {
|
||||||
// the segments are escaped properly, and returns a new Base struct. If there is
|
for _, e := range elements {
|
||||||
// an unescaped trailing '/' it is removed. This function is safe to use with
|
if len(e) == 0 {
|
||||||
// escaped user input where each chunk is a segment. For example, the input
|
|
||||||
// [this, is\//a, path] will produce:
|
|
||||||
// segments: [this, is\//a, path]
|
|
||||||
// elements: [this, is\/, a, path].
|
|
||||||
func newPathFromEscapedSegments(segments []string) (Base, error) {
|
|
||||||
b := Base{}
|
|
||||||
|
|
||||||
if err := validateSegments(segments); err != nil {
|
|
||||||
return b, errors.Wrap(err, "validating escaped path")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make a copy of the input so we don't modify the original slice.
|
|
||||||
tmpSegments := make([]string, len(segments))
|
|
||||||
copy(tmpSegments, segments)
|
|
||||||
tmpSegments[len(tmpSegments)-1] = trimTrailingSlash(tmpSegments[len(tmpSegments)-1])
|
|
||||||
|
|
||||||
for _, s := range tmpSegments {
|
|
||||||
newElems := split(s)
|
|
||||||
|
|
||||||
if len(newElems) == 0 {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
b.segmentIdx = append(b.segmentIdx, len(b.elements))
|
tmp := e
|
||||||
b.elements = append(b.elements, newElems...)
|
|
||||||
|
if escaped {
|
||||||
|
tmp = trimTrailingSlash(tmp)
|
||||||
|
// If tmp was just the path separator then it will be empty now.
|
||||||
|
if len(tmp) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateEscapedElement(tmp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp = unescape(tmp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pb.elements = append(pb.elements, tmp)
|
||||||
}
|
}
|
||||||
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a string that contains all path segments joined
|
|
||||||
// together. Elements of the path that need escaping will be escaped.
|
|
||||||
func (b Base) String() string {
|
|
||||||
return join(b.elements)
|
|
||||||
}
|
|
||||||
|
|
||||||
// segment returns the nth segment of the path. Path segment indices are
|
|
||||||
// 0-based. As this function is used exclusively by wrappers of path, it does no
|
|
||||||
// bounds checking. Callers are expected to have validated the number of
|
|
||||||
// segments when making the path.
|
|
||||||
func (b Base) segment(n int) string {
|
|
||||||
if n == len(b.segmentIdx)-1 {
|
|
||||||
return join(b.elements[b.segmentIdx[n]:])
|
|
||||||
}
|
|
||||||
|
|
||||||
return join(b.elements[b.segmentIdx[n]:b.segmentIdx[n+1]])
|
|
||||||
}
|
|
||||||
|
|
||||||
// unescapedSegmentElements returns the unescaped version of the elements that
|
|
||||||
// comprise the requested segment. Path segment indices are 0-based.
|
|
||||||
func (b Base) unescapedSegmentElements(n int) []string {
|
|
||||||
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
|
|
||||||
// has also been transformed such that it contains no characters outside the set
|
|
||||||
// of acceptable file system path characters.
|
|
||||||
func (b Base) TransformedSegments() []string {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String returns a string that contains all path elements joined together.
|
||||||
|
// Elements of the path that need escaping are escaped.
|
||||||
|
func (pb Builder) String() string {
|
||||||
|
escaped := make([]string, 0, len(pb.elements))
|
||||||
|
|
||||||
|
for _, e := range pb.elements {
|
||||||
|
escaped = append(escaped, escapeElement(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
return join(escaped)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pb Builder) join(start, end int) string {
|
||||||
|
return join(pb.elements[start:end])
|
||||||
|
}
|
||||||
|
|
||||||
// escapeElement takes a single path element and escapes all characters that
|
// escapeElement takes a single path element and escapes all characters that
|
||||||
// require an escape sequence. If there are no characters that need escaping,
|
// require an escape sequence. If there are no characters that need escaping,
|
||||||
// the input is returned unchanged.
|
// the input is returned unchanged.
|
||||||
@ -198,13 +165,14 @@ func escapeElement(element string) string {
|
|||||||
return element
|
return element
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startIdx := 0
|
||||||
b := strings.Builder{}
|
b := strings.Builder{}
|
||||||
b.Grow(len(element) + len(escapeIdx))
|
b.Grow(len(element) + len(escapeIdx))
|
||||||
startIdx := 0
|
|
||||||
|
|
||||||
for _, idx := range escapeIdx {
|
for _, idx := range escapeIdx {
|
||||||
b.WriteString(element[startIdx:idx])
|
b.WriteString(element[startIdx:idx])
|
||||||
b.WriteRune(escapeCharacter)
|
b.WriteRune(escapeCharacter)
|
||||||
|
|
||||||
startIdx = idx
|
startIdx = idx
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,9 +188,9 @@ func escapeElement(element string) string {
|
|||||||
// separators will result in an ambiguous or incorrect segment.
|
// separators will result in an ambiguous or incorrect segment.
|
||||||
func unescape(element string) string {
|
func unescape(element string) string {
|
||||||
b := strings.Builder{}
|
b := strings.Builder{}
|
||||||
|
|
||||||
startIdx := 0
|
startIdx := 0
|
||||||
prevWasEscape := false
|
prevWasEscape := false
|
||||||
|
|
||||||
for i, c := range element {
|
for i, c := range element {
|
||||||
if c != escapeCharacter || prevWasEscape {
|
if c != escapeCharacter || prevWasEscape {
|
||||||
prevWasEscape = false
|
prevWasEscape = false
|
||||||
@ -240,33 +208,37 @@ func unescape(element string) string {
|
|||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateSegments takes a slice of segments and ensures that escaped
|
// validateEscapedElement takes an escaped element that has had trailing
|
||||||
// sequences match the set of characters that need escaping and that there
|
// separators trimmed and ensures that no characters requiring escaping are
|
||||||
// aren't hanging escape characters at the end of a segment.
|
// unescaped and that no escape characters are combined with characters that
|
||||||
func validateSegments(segments []string) error {
|
// don't need escaping.
|
||||||
for _, segment := range segments {
|
func validateEscapedElement(element string) error {
|
||||||
prevWasEscape := false
|
prevWasEscape := false
|
||||||
|
|
||||||
for _, c := range segment {
|
for _, c := range element {
|
||||||
switch prevWasEscape {
|
switch prevWasEscape {
|
||||||
case true:
|
case true:
|
||||||
prevWasEscape = false
|
prevWasEscape = false
|
||||||
|
|
||||||
if _, ok := charactersToEscape[c]; !ok {
|
if _, ok := charactersToEscape[c]; !ok {
|
||||||
return errors.Errorf(
|
return errors.Errorf(
|
||||||
"bad escape sequence in path: '%c%c'", escapeCharacter, c)
|
"bad escape sequence in path: '%c%c'", escapeCharacter, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
case false:
|
case false:
|
||||||
if c == escapeCharacter {
|
if c == escapeCharacter {
|
||||||
prevWasEscape = true
|
prevWasEscape = true
|
||||||
}
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := charactersToEscape[c]; ok {
|
||||||
|
return errors.Errorf("unescaped '%c' in path", c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if prevWasEscape {
|
if prevWasEscape {
|
||||||
return errors.New("trailing escape character in segment")
|
return errors.New("trailing escape character")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -284,6 +256,7 @@ func trimTrailingSlash(element string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
numSlashes := 0
|
numSlashes := 0
|
||||||
|
|
||||||
for i := lastIdx - 1; i >= 0; i-- {
|
for i := lastIdx - 1; i >= 0; i-- {
|
||||||
if element[i] != escapeCharacter {
|
if element[i] != escapeCharacter {
|
||||||
break
|
break
|
||||||
@ -306,52 +279,3 @@ func join(elements []string) string {
|
|||||||
// '\' according to the escaping rules.
|
// '\' according to the escaping rules.
|
||||||
return strings.Join(elements, string(pathSeparator))
|
return strings.Join(elements, string(pathSeparator))
|
||||||
}
|
}
|
||||||
|
|
||||||
// split returns a slice of path elements for the given segment when the segment
|
|
||||||
// is split on the path separator according to the escaping rules.
|
|
||||||
func split(segment string) []string {
|
|
||||||
res := make([]string, 0)
|
|
||||||
numEscapes := 0
|
|
||||||
startIdx := 0
|
|
||||||
// Start with true to ignore leading separator.
|
|
||||||
prevWasSeparator := true
|
|
||||||
|
|
||||||
for i, c := range segment {
|
|
||||||
if c == escapeCharacter {
|
|
||||||
numEscapes++
|
|
||||||
prevWasSeparator = false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if c != pathSeparator {
|
|
||||||
prevWasSeparator = false
|
|
||||||
numEscapes = 0
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remaining is just path separator handling.
|
|
||||||
if numEscapes%2 != 0 {
|
|
||||||
// This is an escaped separator.
|
|
||||||
prevWasSeparator = false
|
|
||||||
numEscapes = 0
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore leading separator characters and don't add elements that would
|
|
||||||
// be empty.
|
|
||||||
if !prevWasSeparator {
|
|
||||||
res = append(res, segment[startIdx:i])
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't want to include the path separator in the result.
|
|
||||||
startIdx = i + 1
|
|
||||||
prevWasSeparator = true
|
|
||||||
numEscapes = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the final segment because the loop above won't catch it. There should
|
|
||||||
// be no trailing separator character, but do a bounds check to be safe.
|
|
||||||
res = append(res, segment[startIdx:])
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|||||||
@ -2,7 +2,6 @@ package path
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -11,138 +10,185 @@ import (
|
|||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
)
|
)
|
||||||
|
|
||||||
var basicInputs = []struct {
|
type testData struct {
|
||||||
name string
|
name string
|
||||||
input [][]string
|
input []string
|
||||||
expectedString string
|
expectedString string
|
||||||
expectedEscapedSegments []string
|
}
|
||||||
expectedUnescapedElements [][]string
|
|
||||||
}{
|
// Test cases that are the same with and without escaping by the
|
||||||
|
// system-under-test.
|
||||||
|
var genericCases = []testData{
|
||||||
{
|
{
|
||||||
name: "SimplePath",
|
name: "SimplePath",
|
||||||
input: [][]string{
|
input: []string{
|
||||||
{`this`},
|
|
||||||
{`is`},
|
|
||||||
{`a`},
|
|
||||||
{`path`},
|
|
||||||
},
|
|
||||||
expectedString: "this/is/a/path",
|
|
||||||
expectedEscapedSegments: []string{
|
|
||||||
`this`,
|
`this`,
|
||||||
`is`,
|
`is`,
|
||||||
`a`,
|
`a`,
|
||||||
`path`,
|
`path`,
|
||||||
},
|
},
|
||||||
expectedUnescapedElements: [][]string{
|
expectedString: "this/is/a/path",
|
||||||
{`this`},
|
|
||||||
{`is`},
|
|
||||||
{`a`},
|
|
||||||
{`path`},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "EmptyElement",
|
||||||
|
input: []string{
|
||||||
|
`this`,
|
||||||
|
`is`,
|
||||||
|
``,
|
||||||
|
`a`,
|
||||||
|
`path`,
|
||||||
|
},
|
||||||
|
expectedString: `this/is/a/path`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "EmptyInput",
|
||||||
|
expectedString: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inputs that should be escaped.
|
||||||
|
var basicUnescapedInputs = []testData{
|
||||||
{
|
{
|
||||||
name: "EscapeSeparator",
|
name: "EscapeSeparator",
|
||||||
input: [][]string{
|
input: []string{
|
||||||
{`this`},
|
`this`,
|
||||||
{`is/a`},
|
`is/a`,
|
||||||
{`path`},
|
`path`,
|
||||||
},
|
},
|
||||||
expectedString: `this/is\/a/path`,
|
expectedString: `this/is\/a/path`,
|
||||||
expectedEscapedSegments: []string{
|
},
|
||||||
|
{
|
||||||
|
name: "EscapeEscapeChar",
|
||||||
|
input: []string{
|
||||||
|
`this`,
|
||||||
|
`is\`,
|
||||||
|
`a`,
|
||||||
|
`path`,
|
||||||
|
},
|
||||||
|
expectedString: `this/is\\/a/path`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "EscapeEscapeAndSeparator",
|
||||||
|
input: []string{
|
||||||
`this`,
|
`this`,
|
||||||
`is\/a`,
|
`is\/a`,
|
||||||
`path`,
|
`path`,
|
||||||
},
|
},
|
||||||
expectedUnescapedElements: [][]string{
|
expectedString: `this/is\\\/a/path`,
|
||||||
{`this`},
|
|
||||||
{`is/a`},
|
|
||||||
{`path`},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "EscapeEscapeChar",
|
name: "SeparatorAtEndOfElement",
|
||||||
input: [][]string{
|
input: []string{
|
||||||
{`this`},
|
`this`,
|
||||||
{`is\`},
|
`is/`,
|
||||||
{`a`},
|
`a`,
|
||||||
{`path`},
|
`path`,
|
||||||
},
|
},
|
||||||
expectedString: `this/is\\/a/path`,
|
expectedString: `this/is\//a/path`,
|
||||||
expectedEscapedSegments: []string{
|
},
|
||||||
|
{
|
||||||
|
name: "SeparatorAtEndOfPath",
|
||||||
|
input: []string{
|
||||||
|
`this`,
|
||||||
|
`is`,
|
||||||
|
`a`,
|
||||||
|
`path/`,
|
||||||
|
},
|
||||||
|
expectedString: `this/is/a/path\/`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inputs that are already escaped.
|
||||||
|
var basicEscapedInputs = []testData{
|
||||||
|
{
|
||||||
|
name: "EscapedSeparator",
|
||||||
|
input: []string{
|
||||||
|
`this`,
|
||||||
|
`is\/a`,
|
||||||
|
`path`,
|
||||||
|
},
|
||||||
|
expectedString: `this/is\/a/path`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "EscapedEscapeChar",
|
||||||
|
input: []string{
|
||||||
`this`,
|
`this`,
|
||||||
`is\\`,
|
`is\\`,
|
||||||
`a`,
|
`a`,
|
||||||
`path`,
|
`path`,
|
||||||
},
|
},
|
||||||
expectedUnescapedElements: [][]string{
|
expectedString: `this/is\\/a/path`,
|
||||||
{`this`},
|
|
||||||
{`is\`},
|
|
||||||
{`a`},
|
|
||||||
{`path`},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "EscapeEscapeAndSeparator",
|
name: "EscapedEscapeAndSeparator",
|
||||||
input: [][]string{
|
input: []string{
|
||||||
{`this`},
|
|
||||||
{`is\/a`},
|
|
||||||
{`path`},
|
|
||||||
},
|
|
||||||
expectedString: `this/is\\\/a/path`,
|
|
||||||
expectedEscapedSegments: []string{
|
|
||||||
`this`,
|
`this`,
|
||||||
`is\\\/a`,
|
`is\\\/a`,
|
||||||
`path`,
|
`path`,
|
||||||
},
|
},
|
||||||
expectedUnescapedElements: [][]string{
|
expectedString: `this/is\\\/a/path`,
|
||||||
{`this`},
|
|
||||||
{`is\/a`},
|
|
||||||
{`path`},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "SeparatorAtEndOfElement",
|
name: "EscapedSeparatorAtEndOfElement",
|
||||||
input: [][]string{
|
input: []string{
|
||||||
{`this`},
|
|
||||||
{`is/`},
|
|
||||||
{`a`},
|
|
||||||
{`path`},
|
|
||||||
},
|
|
||||||
expectedString: `this/is\//a/path`,
|
|
||||||
expectedEscapedSegments: []string{
|
|
||||||
`this`,
|
`this`,
|
||||||
`is\/`,
|
`is\/`,
|
||||||
`a`,
|
`a`,
|
||||||
`path`,
|
`path`,
|
||||||
},
|
},
|
||||||
expectedUnescapedElements: [][]string{
|
expectedString: `this/is\//a/path`,
|
||||||
{`this`},
|
|
||||||
{`is/`},
|
|
||||||
{`a`},
|
|
||||||
{`path`},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "SeparatorAtEndOfPath",
|
name: "EscapedSeparatorAtEndOfPath",
|
||||||
input: [][]string{
|
input: []string{
|
||||||
{`this`},
|
|
||||||
{`is`},
|
|
||||||
{`a`},
|
|
||||||
{`path/`},
|
|
||||||
},
|
|
||||||
expectedString: `this/is/a/path\/`,
|
|
||||||
expectedEscapedSegments: []string{
|
|
||||||
`this`,
|
`this`,
|
||||||
`is`,
|
`is`,
|
||||||
`a`,
|
`a`,
|
||||||
`path\/`,
|
`path\/`,
|
||||||
},
|
},
|
||||||
expectedUnescapedElements: [][]string{
|
expectedString: `this/is/a/path\/`,
|
||||||
{`this`},
|
},
|
||||||
{`is`},
|
{
|
||||||
{`a`},
|
name: "ElementOfSeparator",
|
||||||
{`path/`},
|
input: []string{
|
||||||
|
`this`,
|
||||||
|
`is`,
|
||||||
|
`/`,
|
||||||
|
`a`,
|
||||||
|
`path`,
|
||||||
},
|
},
|
||||||
|
expectedString: `this/is/a/path`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TrailingElementSeparator",
|
||||||
|
input: []string{
|
||||||
|
`this`,
|
||||||
|
`is`,
|
||||||
|
`a/`,
|
||||||
|
`path`,
|
||||||
|
},
|
||||||
|
expectedString: `this/is/a/path`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TrailingSeparatorAtEnd",
|
||||||
|
input: []string{
|
||||||
|
`this`,
|
||||||
|
`is`,
|
||||||
|
`a`,
|
||||||
|
`path/`,
|
||||||
|
},
|
||||||
|
expectedString: `this/is/a/path`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TrailingSeparatorWithEmptyElementAtEnd",
|
||||||
|
input: []string{
|
||||||
|
`this`,
|
||||||
|
`is`,
|
||||||
|
`a`,
|
||||||
|
`path/`,
|
||||||
|
``,
|
||||||
|
},
|
||||||
|
expectedString: `this/is/a/path`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,214 +200,38 @@ func TestPathUnitSuite(t *testing.T) {
|
|||||||
suite.Run(t, new(PathUnitSuite))
|
suite.Run(t, new(PathUnitSuite))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *PathUnitSuite) TestPathEscapingAndSegments() {
|
func (suite *PathUnitSuite) TestAppend() {
|
||||||
for _, test := range basicInputs {
|
table := append(append([]testData{}, genericCases...), basicUnescapedInputs...)
|
||||||
|
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 := Builder{}.Append(test.input...)
|
||||||
assert.Equal(t, test.expectedString, p.String())
|
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() {
|
func (suite *PathUnitSuite) TestUnescapeAndAppend() {
|
||||||
table := []struct {
|
table := append(append([]testData{}, genericCases...), basicEscapedInputs...)
|
||||||
name string
|
|
||||||
input [][]string
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "EmptyInternalElement",
|
|
||||||
input: [][]string{
|
|
||||||
{`this`},
|
|
||||||
{`is`},
|
|
||||||
{""},
|
|
||||||
{`a`},
|
|
||||||
{`path`},
|
|
||||||
},
|
|
||||||
expected: "this/is/a/path",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "EmptyInternalElement2",
|
|
||||||
input: [][]string{
|
|
||||||
{`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, err := Builder{}.UnescapeAndAppend(test.input...)
|
||||||
|
|
||||||
idx := 0
|
|
||||||
for i := 0; i < len(test.input); i++ {
|
|
||||||
if i == 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.NotPanics(t, func() {
|
|
||||||
_ = p.segment(idx)
|
|
||||||
})
|
|
||||||
idx++
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Panics(t, func() {
|
|
||||||
_ = p.segment(len(test.input))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
|
||||||
table := []struct {
|
|
||||||
name string
|
|
||||||
input []string
|
|
||||||
expected string
|
|
||||||
expectedSegments []string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "SimplePath",
|
|
||||||
input: []string{`this`, `is/a`, `path`},
|
|
||||||
expected: "this/is/a/path",
|
|
||||||
expectedSegments: []string{`this`, `is/a`, `path`},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "EscapeSeparator",
|
|
||||||
input: []string{`this`, `is\/a`, `path`},
|
|
||||||
expected: `this/is\/a/path`,
|
|
||||||
expectedSegments: []string{`this`, `is\/a`, `path`},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "EscapeEscapeChar",
|
|
||||||
input: []string{`this`, `is\\/a`, `path`},
|
|
||||||
expected: `this/is\\/a/path`,
|
|
||||||
expectedSegments: []string{`this`, `is\\/a`, `path`},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "EmptyInternalElement",
|
|
||||||
input: []string{`this`, `is//a`, `path`},
|
|
||||||
expected: "this/is/a/path",
|
|
||||||
expectedSegments: []string{`this`, `is/a`, `path`},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "SeparatorAtEndOfElement",
|
|
||||||
input: []string{`this`, `is\//a`, `path`},
|
|
||||||
expected: `this/is\//a/path`,
|
|
||||||
expectedSegments: []string{`this`, `is\//a`, `path`},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "SeparatorAtEndOfPath",
|
|
||||||
input: []string{`this`, `is/a`, `path\/`},
|
|
||||||
expected: `this/is/a/path\/`,
|
|
||||||
expectedSegments: []string{`this`, `is/a`, `path\/`},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "TrailingSeparator",
|
|
||||||
input: []string{`this`, `is/a`, `path/`},
|
|
||||||
expected: `this/is/a/path`,
|
|
||||||
expectedSegments: []string{`this`, `is/a`, `path`},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "TrailingSeparator2",
|
|
||||||
input: []string{`this`, `is/a`, `path\\\\/`},
|
|
||||||
expected: `this/is/a/path\\\\`,
|
|
||||||
expectedSegments: []string{`this`, `is/a`, `path\\\\`},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "ManyEscapesNotSeparator",
|
|
||||||
input: []string{`this`, `is\\\\/a`, `path/`},
|
|
||||||
expected: `this/is\\\\/a/path`,
|
|
||||||
expectedSegments: []string{`this`, `is\\\\/a`, `path`},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "ManyEscapesAndSeparator",
|
|
||||||
input: []string{`this`, `is\\\/a`, `path`},
|
|
||||||
expected: `this/is\\\/a/path`,
|
|
||||||
expectedSegments: []string{`this`, `is\\\/a`, `path`},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range table {
|
|
||||||
suite.T().Run(test.name, func(t *testing.T) {
|
|
||||||
p, err := newPathFromEscapedSegments(test.input)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, test.expected, p.String())
|
|
||||||
|
|
||||||
for i, s := range test.expectedSegments {
|
assert.Equal(t, test.expectedString, p.String())
|
||||||
segment := ""
|
|
||||||
require.NotPanics(t, func() {
|
|
||||||
segment = p.segment(i)
|
|
||||||
})
|
|
||||||
|
|
||||||
assert.Equal(t, s, segment)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *PathUnitSuite) TestEscapedFailure() {
|
func (suite *PathUnitSuite) TestEscapedFailure() {
|
||||||
target := "i_s/a"
|
target := "i_s"
|
||||||
|
|
||||||
for c := range charactersToEscape {
|
for c := range charactersToEscape {
|
||||||
if c == pathSeparator {
|
suite.T().Run(fmt.Sprintf("Unescaped-%c", c), func(t *testing.T) {
|
||||||
// Extra path separators in the path will just lead to more segments, not
|
tmp := strings.ReplaceAll(target, "_", string(c))
|
||||||
// a validation error.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
tmp := strings.ReplaceAll(target, "_", string(c))
|
_, err := Builder{}.UnescapeAndAppend("this", tmp, "path")
|
||||||
basePath := []string{"this", tmp, "path"}
|
assert.Error(t, err, "path with unescaped %s did not error", string(c))
|
||||||
_, err := newPathFromEscapedSegments(basePath)
|
})
|
||||||
assert.Error(suite.T(), err, "path with unescaped %s did not error", string(c))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,16 +240,18 @@ func (suite *PathUnitSuite) TestBadEscapeSequenceErrors() {
|
|||||||
notEscapes := []rune{'a', 'b', '#', '%'}
|
notEscapes := []rune{'a', 'b', '#', '%'}
|
||||||
|
|
||||||
for _, c := range notEscapes {
|
for _, c := range notEscapes {
|
||||||
tmp := strings.ReplaceAll(target, "_", string(c))
|
suite.T().Run(fmt.Sprintf("Escaped-%c", c), func(t *testing.T) {
|
||||||
basePath := []string{"this", tmp, "path"}
|
tmp := strings.ReplaceAll(target, "_", string(c))
|
||||||
_, err := newPathFromEscapedSegments(basePath)
|
|
||||||
assert.Error(
|
_, err := Builder{}.UnescapeAndAppend("this", tmp, "path")
|
||||||
suite.T(),
|
assert.Error(
|
||||||
err,
|
t,
|
||||||
"path with bad escape sequence %c%c did not error",
|
err,
|
||||||
escapeCharacter,
|
"path with bad escape sequence %c%c did not error",
|
||||||
c,
|
escapeCharacter,
|
||||||
)
|
c,
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -387,13 +259,17 @@ func (suite *PathUnitSuite) TestTrailingEscapeChar() {
|
|||||||
base := []string{"this", "is", "a", "path"}
|
base := []string{"this", "is", "a", "path"}
|
||||||
|
|
||||||
for i := 0; i < len(base); i++ {
|
for i := 0; i < len(base); i++ {
|
||||||
suite.T().Run(fmt.Sprintf("Segment%v", i), func(t *testing.T) {
|
suite.T().Run(fmt.Sprintf("Element%v", i), func(t *testing.T) {
|
||||||
path := make([]string, len(base))
|
path := make([]string, len(base))
|
||||||
copy(path, base)
|
copy(path, base)
|
||||||
path[i] = path[i] + string(escapeCharacter)
|
path[i] = path[i] + string(escapeCharacter)
|
||||||
|
|
||||||
_, err := newPathFromEscapedSegments(path)
|
_, err := Builder{}.UnescapeAndAppend(path...)
|
||||||
assert.Error(suite.T(), err)
|
assert.Error(
|
||||||
|
t,
|
||||||
|
err,
|
||||||
|
"path with trailing escape character did not error",
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user