Skip to content

Commit 99f0c38

Browse files
authored
Merge pull request #162 from duhnnie/feature/addtrailingslash
2 parents f1fb607 + 2a657ad commit 99f0c38

File tree

5 files changed

+150
-1
lines changed

5 files changed

+150
-1
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ lint:
4141
## modernize: ♻️ Check for outdated patterns
4242
.PHONY: modernize
4343
modernize:
44-
go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -test=false ./...
44+
go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -fix -test=false ./...
4545

4646
## test: 🚦 Execute all tests
4747
.PHONY: test

bytes.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,10 @@ func ToUpperBytes(b []byte) []byte {
6363

6464
return b
6565
}
66+
67+
// AddTrailingSlashBytes appends a trailing '/' to b if it does not already end with one.
68+
// If the input already ends with '/', the original slice is returned.
69+
// A new slice is returned when a '/' is appended. The original slice is never modified.
70+
func AddTrailingSlashBytes(b []byte) []byte {
71+
return UnsafeBytes(AddTrailingSlashString(UnsafeString(b)))
72+
}

bytes_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,76 @@ func Test_ToUpperBytes_Edge(t *testing.T) {
105105
})
106106
}
107107
}
108+
109+
func Test_AddTrailingSlashBytes(t *testing.T) {
110+
t.Parallel()
111+
112+
testCases := []struct {
113+
name string
114+
in []byte
115+
want []byte
116+
}{
117+
{name: "empty", in: []byte(""), want: []byte("/")},
118+
{name: "slash-only", in: []byte("/"), want: []byte("/")},
119+
{name: "short-no-slash", in: []byte("abc"), want: []byte("abc/")},
120+
{name: "short-with-slash", in: []byte("abc/"), want: []byte("abc/")},
121+
{name: "path-no-slash", in: []byte("/api/v1/users"), want: []byte("/api/v1/users/")},
122+
{name: "path-with-slash", in: []byte("/api/v1/users/"), want: []byte("/api/v1/users/")},
123+
{name: "double-slash", in: []byte("abc//"), want: []byte("abc//")},
124+
{name: "unicode", in: []byte("/日本語"), want: []byte("/日本語/")},
125+
}
126+
127+
for _, tc := range testCases {
128+
t.Run(tc.name, func(t *testing.T) {
129+
t.Parallel()
130+
result := AddTrailingSlashBytes(tc.in)
131+
require.Equal(t, tc.want, result)
132+
})
133+
}
134+
}
135+
136+
func Test_AddTrailingSlashBytes_NoMutation(t *testing.T) {
137+
t.Parallel()
138+
139+
original := []byte("test")
140+
originalCopy := make([]byte, len(original))
141+
copy(originalCopy, original)
142+
143+
_ = AddTrailingSlashBytes(original)
144+
145+
require.Equal(t, originalCopy, original, "original slice should not be mutated")
146+
}
147+
148+
func Test_AddTrailingSlashBytes_ReturnsSame(t *testing.T) {
149+
t.Parallel()
150+
151+
input := []byte("test/")
152+
result := AddTrailingSlashBytes(input)
153+
require.Equal(t, input, result)
154+
require.Same(t, &input[0], &result[0], "should return same slice instance")
155+
}
156+
157+
func Benchmark_AddTrailingSlashBytes(b *testing.B) {
158+
cases := []struct {
159+
name string
160+
input []byte
161+
}{
162+
{name: "empty", input: []byte("")},
163+
{name: "slash-only", input: []byte("/")},
164+
{name: "short-no-slash", input: []byte("abc")},
165+
{name: "short-with-slash", input: []byte("abc/")},
166+
{name: "path-no-slash", input: []byte("/api/v1/users")},
167+
{name: "path-with-slash", input: []byte("/api/v1/users/")},
168+
}
169+
170+
for _, tc := range cases {
171+
b.Run(tc.name, func(b *testing.B) {
172+
b.ReportAllocs()
173+
var res []byte
174+
for n := 0; n < b.N; n++ {
175+
res = AddTrailingSlashBytes(tc.input)
176+
}
177+
_ = res
178+
})
179+
}
180+
}

strings.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,20 @@ func ToUpper(b string) string {
4848

4949
return b
5050
}
51+
52+
// AddTrailingSlashString appends a trailing '/' to s if it does not already end with one.
53+
// If the input already ends with '/', the original string is returned.
54+
// A new string is returned only when a '/' needs to be appended.
55+
func AddTrailingSlashString(s string) string {
56+
n := len(s)
57+
if n == 0 {
58+
return "/"
59+
}
60+
if s[n-1] == '/' {
61+
return s
62+
}
63+
buf := make([]byte, n+1)
64+
copy(buf, s)
65+
buf[n] = '/'
66+
return UnsafeString(buf)
67+
}

strings_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,3 +193,55 @@ func Benchmark_ToLower(b *testing.B) {
193193
})
194194
}
195195
}
196+
197+
func Test_AddTrailingSlashString(t *testing.T) {
198+
t.Parallel()
199+
200+
testCases := []struct {
201+
name string
202+
in string
203+
want string
204+
}{
205+
{name: "empty", in: "", want: "/"},
206+
{name: "slash-only", in: "/", want: "/"},
207+
{name: "short-no-slash", in: "abc", want: "abc/"},
208+
{name: "short-with-slash", in: "abc/", want: "abc/"},
209+
{name: "path-no-slash", in: "/api/v1/users", want: "/api/v1/users/"},
210+
{name: "path-with-slash", in: "/api/v1/users/", want: "/api/v1/users/"},
211+
{name: "double-slash", in: "abc//", want: "abc//"},
212+
{name: "unicode", in: "/日本語", want: "/日本語/"},
213+
}
214+
215+
for _, tc := range testCases {
216+
t.Run(tc.name, func(t *testing.T) {
217+
t.Parallel()
218+
result := AddTrailingSlashString(tc.in)
219+
require.Equal(t, tc.want, result)
220+
})
221+
}
222+
}
223+
224+
func Benchmark_AddTrailingSlashString(b *testing.B) {
225+
cases := []struct {
226+
name string
227+
input string
228+
}{
229+
{name: "empty", input: ""},
230+
{name: "slash-only", input: "/"},
231+
{name: "short-no-slash", input: "abc"},
232+
{name: "short-with-slash", input: "abc/"},
233+
{name: "path-no-slash", input: "/api/v1/users"},
234+
{name: "path-with-slash", input: "/api/v1/users/"},
235+
}
236+
237+
for _, tc := range cases {
238+
b.Run(tc.name, func(b *testing.B) {
239+
b.ReportAllocs()
240+
var res string
241+
for n := 0; n < b.N; n++ {
242+
res = AddTrailingSlashString(tc.input)
243+
}
244+
_ = res
245+
})
246+
}
247+
}

0 commit comments

Comments
 (0)