command: make the type a string, for easier equality test
This commit is contained in:
@@ -16,14 +16,12 @@ var _ fmt.Stringer = (*Command)(nil)
|
|||||||
// by one or more slash-separated Segments of lowercase characters.
|
// by one or more slash-separated Segments of lowercase characters.
|
||||||
//
|
//
|
||||||
// [Command]: https://github.com/ucan-wg/spec#command
|
// [Command]: https://github.com/ucan-wg/spec#command
|
||||||
type Command struct {
|
type Command string
|
||||||
segments []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a validated command from the provided list of segment strings.
|
// New creates a validated command from the provided list of segment strings.
|
||||||
// An error is returned if an invalid Command would be formed
|
// An error is returned if an invalid Command would be formed
|
||||||
func New(segments ...string) Command {
|
func New(segments ...string) Command {
|
||||||
return Command{segments: segments}
|
return Top().Join(segments...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse verifies that the provided string contains the required
|
// Parse verifies that the provided string contains the required
|
||||||
@@ -33,20 +31,20 @@ func New(segments ...string) Command {
|
|||||||
// [segment structure]: https://github.com/ucan-wg/spec#segment-structure
|
// [segment structure]: https://github.com/ucan-wg/spec#segment-structure
|
||||||
func Parse(s string) (Command, error) {
|
func Parse(s string) (Command, error) {
|
||||||
if !strings.HasPrefix(s, "/") {
|
if !strings.HasPrefix(s, "/") {
|
||||||
return Command{}, ErrRequiresLeadingSlash
|
return "", ErrRequiresLeadingSlash
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(s) > 1 && strings.HasSuffix(s, "/") {
|
if len(s) > 1 && strings.HasSuffix(s, "/") {
|
||||||
return Command{}, ErrDisallowsTrailingSlash
|
return "", ErrDisallowsTrailingSlash
|
||||||
}
|
}
|
||||||
|
|
||||||
if s != strings.ToLower(s) {
|
if s != strings.ToLower(s) {
|
||||||
return Command{}, ErrRequiresLowercase
|
return "", ErrRequiresLowercase
|
||||||
}
|
}
|
||||||
|
|
||||||
// The leading slash will result in the first element from strings.Split
|
// The leading slash will result in the first element from strings.Split
|
||||||
// being an empty string which is removed as strings.Join will ignore it.
|
// being an empty string which is removed as strings.Join will ignore it.
|
||||||
return Command{strings.Split(s, "/")[1:]}, nil
|
return Command(s), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustParse is the same as Parse, but panic() if the parsing fail.
|
// MustParse is the same as Parse, but panic() if the parsing fail.
|
||||||
@@ -58,14 +56,14 @@ func MustParse(s string) Command {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// [Top] is the most powerful capability.
|
// Top is the most powerful capability.
|
||||||
//
|
//
|
||||||
// This function returns a Command that is a wildcard and therefore represents the
|
// This function returns a Command that is a wildcard and therefore represents the
|
||||||
// most powerful ability. As such, it should be handled with care and used sparingly.
|
// most powerful ability. As such, it should be handled with care and used sparingly.
|
||||||
//
|
//
|
||||||
// [Top]: https://github.com/ucan-wg/spec#-aka-top
|
// [Top]: https://github.com/ucan-wg/spec#-aka-top
|
||||||
func Top() Command {
|
func Top() Command {
|
||||||
return New()
|
return Command(separator)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsValid returns true if the provided string is a valid UCAN command.
|
// IsValid returns true if the provided string is a valid UCAN command.
|
||||||
@@ -77,17 +75,34 @@ func IsValid(s string) bool {
|
|||||||
// Join appends segments to the end of this command using the required
|
// Join appends segments to the end of this command using the required
|
||||||
// segment separator.
|
// segment separator.
|
||||||
func (c Command) Join(segments ...string) Command {
|
func (c Command) Join(segments ...string) Command {
|
||||||
return Command{append(c.segments, segments...)}
|
size := 0
|
||||||
|
for _, s := range segments {
|
||||||
|
size += len(s)
|
||||||
|
}
|
||||||
|
if size == 0 {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
buf := make([]byte, 0, len(c)+size+len(segments))
|
||||||
|
buf = append(buf, []byte(c)...)
|
||||||
|
for _, s := range segments {
|
||||||
|
if s != "" {
|
||||||
|
if len(buf) > 1 {
|
||||||
|
buf = append(buf, separator...)
|
||||||
|
}
|
||||||
|
buf = append(buf, []byte(s)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Command(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Segments returns the ordered segments that comprise the Command as a
|
// Segments returns the ordered segments that comprise the Command as a
|
||||||
// slice of strings.
|
// slice of strings.
|
||||||
func (c Command) Segments() []string {
|
func (c Command) Segments() []string {
|
||||||
return c.segments
|
return strings.Split(string(c), separator)
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns the composed representation the command. This is also
|
// String returns the composed representation the command. This is also
|
||||||
// the required wire representation (before IPLD encoding occurs.)
|
// the required wire representation (before IPLD encoding occurs.)
|
||||||
func (c Command) String() string {
|
func (c Command) String() string {
|
||||||
return "/" + strings.Join(c.segments, "/")
|
return string(c)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,73 +13,66 @@ func TestTop(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIsValidCommand(t *testing.T) {
|
func TestIsValidCommand(t *testing.T) {
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
t.Run("succeeds when", func(t *testing.T) {
|
t.Run("succeeds when", func(t *testing.T) {
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
for _, testcase := range validTestcases(t) {
|
for _, testcase := range validTestcases(t) {
|
||||||
testcase := testcase
|
|
||||||
|
|
||||||
t.Run(testcase.name, func(t *testing.T) {
|
t.Run(testcase.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
require.True(t, command.IsValid(testcase.inp))
|
require.True(t, command.IsValid(testcase.inp))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("fails when", func(t *testing.T) {
|
t.Run("fails when", func(t *testing.T) {
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
for _, testcase := range invalidTestcases(t) {
|
for _, testcase := range invalidTestcases(t) {
|
||||||
testcase := testcase
|
|
||||||
|
|
||||||
t.Run(testcase.name, func(t *testing.T) {
|
t.Run(testcase.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
require.False(t, command.IsValid(testcase.inp))
|
require.False(t, command.IsValid(testcase.inp))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
require.Equal(t, command.Top(), command.New())
|
||||||
|
require.Equal(t, "/foo", command.New("foo").String())
|
||||||
|
require.Equal(t, "/foo/bar", command.New("foo", "bar").String())
|
||||||
|
require.Equal(t, "/foo/bar/baz", command.New("foo", "bar/baz").String())
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseCommand(t *testing.T) {
|
func TestParseCommand(t *testing.T) {
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
t.Run("succeeds when", func(t *testing.T) {
|
t.Run("succeeds when", func(t *testing.T) {
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
for _, testcase := range validTestcases(t) {
|
for _, testcase := range validTestcases(t) {
|
||||||
testcase := testcase
|
|
||||||
|
|
||||||
t.Run(testcase.name, func(t *testing.T) {
|
t.Run(testcase.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
cmd, err := command.Parse("/elem0/elem1/elem2")
|
cmd, err := command.Parse("/elem0/elem1/elem2")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, cmd)
|
require.NotEmpty(t, cmd)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("fails when", func(t *testing.T) {
|
t.Run("fails when", func(t *testing.T) {
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
for _, testcase := range invalidTestcases(t) {
|
for _, testcase := range invalidTestcases(t) {
|
||||||
testcase := testcase
|
|
||||||
|
|
||||||
t.Run(testcase.name, func(t *testing.T) {
|
t.Run(testcase.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
cmd, err := command.Parse(testcase.inp)
|
cmd, err := command.Parse(testcase.inp)
|
||||||
require.ErrorIs(t, err, testcase.err)
|
require.ErrorIs(t, err, testcase.err)
|
||||||
require.Equal(t, command.Command{}, cmd)
|
require.Zero(t, cmd)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEquality(t *testing.T) {
|
||||||
|
require.True(t, command.MustParse("/foo/bar/baz") == command.MustParse("/foo/bar/baz"))
|
||||||
|
require.False(t, command.MustParse("/foo/bar/baz") == command.MustParse("/foo/bar/bazz"))
|
||||||
|
require.False(t, command.MustParse("/foo/bar") == command.MustParse("/foo/bar/baz"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJoin(t *testing.T) {
|
||||||
|
require.Equal(t, "/foo", command.Top().Join("foo").String())
|
||||||
|
require.Equal(t, "/foo/bar", command.Top().Join("foo/bar").String())
|
||||||
|
require.Equal(t, "/foo/bar", command.Top().Join("foo", "bar").String())
|
||||||
|
require.Equal(t, "/faz/boz/foo/bar", command.MustParse("/faz/boz").Join("foo/bar").String())
|
||||||
|
require.Equal(t, "/faz/boz/foo/bar", command.MustParse("/faz/boz").Join("foo", "bar").String())
|
||||||
|
}
|
||||||
|
|
||||||
type testcase struct {
|
type testcase struct {
|
||||||
name string
|
name string
|
||||||
inp string
|
inp string
|
||||||
|
|||||||
Reference in New Issue
Block a user