Skip to content

Commit fe59b9d

Browse files
committed
fix: remove trailing slash when joining empty path segments and disable path validation when group prefix is defined
1 parent ea4820a commit fe59b9d

File tree

4 files changed

+58
-32
lines changed

4 files changed

+58
-32
lines changed

middlewares_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ func TestStdMiddleware(t *testing.T) {
323323
waitForServer()
324324

325325
assertStatus(t, "GET", "http://localhost:8080/", nil, nil, "", http.StatusOK)
326-
assertStatus(t, "GET", "http://localhost:8080/api/", nil, nil, "", http.StatusOK)
326+
assertStatus(t, "GET", "http://localhost:8080/api", nil, nil, "", http.StatusOK)
327327
assertStatus(t, "GET", "http://localhost:8080/api/hello", nil, nil, "", http.StatusOK)
328328
assertStatus(t, "GET", "http://localhost:8080/hello", nil, nil, "", http.StatusOK)
329329
assertStatus(t, "POST", "http://localhost:8080/hello", nil, nil, "", http.StatusCreated)

okapi_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,14 @@ func (bc *BookController) Routes() []RouteDefinition {
532532
coreGroup := &Group{Prefix: "/core", Tags: []string{"CoreGroup"}}
533533
return []RouteDefinition{
534534
{
535+
Method: http.MethodGet,
536+
Path: "",
537+
OperationId: "Get",
538+
Handler: bc.GetBooks,
539+
Group: coreGroup,
540+
Request: nil,
541+
Response: &Book{},
542+
}, {
535543
Method: http.MethodGet,
536544
Path: "/books",
537545
OperationId: "GetBooks",

route.go

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
package okapi
2626

2727
import (
28+
"fmt"
2829
"strings"
2930
)
3031

@@ -120,41 +121,48 @@ type RouteDefinition struct {
120121
//
121122
// Example:
122123
//
123-
// routes := []RouteDefinition{
124-
// {
125-
// Method: "GET",
126-
// Path: "/example",
127-
// Handler: exampleHandler,
128-
// Options: []okapi.RouteOption{
129-
// okapi.DocSummary("Example GET request"),
130-
// },
131-
// Group: &okapi.Group{Prefix: "/api/v1", Tags: []string{"Example"}},
132-
// },
133-
// {
134-
// Method: "POST",
135-
// Path: "/example",
136-
// Handler: exampleHandler,
124+
// routes := []okapi.RouteDefinition{
125+
// {
126+
// Method: "GET",
127+
// Path: "/example",
128+
// Handler: exampleHandler,
129+
// OperationId: "get-example",
130+
// Summary: "Example GET request",
131+
// Request: nil,
132+
// Response: &ExampleResponse{},
133+
// Group: &okapi.Group{Prefix: "/api/v1", Tags: []string{"Example"}},
134+
// },
135+
// {
136+
// Method: "POST",
137+
// Path: "/example",
138+
// Handler: exampleHandler,
137139
// Middlewares: []okapi.Middleware{customMiddleware}
138-
// Options: []okapi.RouteOption{
139-
// okapi.DocSummary("Example POST request"),
140-
// },
141-
// Security: Security: []map[string][]string{
142-
// {
143-
// "bearerAuth": {},
144-
// },
140+
// Options: []okapi.RouteOption{
141+
// okapi.DocSummary("Example POST request"),
142+
// okapi.Request(&ExampleRequest{}),
143+
// okapi.Response(&ExampleResponse{}),
144+
// },
145+
// Security: Security: []map[string][]string{
146+
// {
147+
// "bearerAuth": {},
145148
// },
146-
// },
149+
// },
150+
// },
147151
// }
152+
//
148153
// // Create a new Okapi instance
149154
// app := okapi.New()
150155
// okapi.RegisterRoutes(app, routes)
151156
func RegisterRoutes(o *Okapi, routes []RouteDefinition) {
152157
for _, r := range routes {
153-
if r.Path == "" || r.Method == "" {
154-
panic("invalid route definition: path and method must ve specified")
158+
if r.Path == "" && r.Group == nil {
159+
panic("okapi: invalid route definition — either Path or Group must be specified")
160+
}
161+
if r.Method == "" {
162+
panic(fmt.Sprintf("okapi: invalid route definition — missing HTTP method for path=%q", r.Path))
155163
}
156164
if r.Handler == nil {
157-
panic("invalid route definition: handler must be specified")
165+
panic(fmt.Sprintf("okapi: invalid route definition — missing handler for method=%q path=%q", r.Method, r.Path))
158166
}
159167
group := r.Group
160168
for _, mid := range r.Middlewares {
@@ -183,7 +191,7 @@ func RegisterRoutes(o *Okapi, routes []RouteDefinition) {
183191
case methodConnect:
184192
o.Connect(r.Path, r.Handler, r.Options...)
185193
default:
186-
panic("unsupported method: " + r.Method)
194+
panic(fmt.Sprintf("okapi: unsupported HTTP method %q for path=%q", r.Method, r.Path))
187195
}
188196
continue
189197
}
@@ -210,7 +218,7 @@ func RegisterRoutes(o *Okapi, routes []RouteDefinition) {
210218
case methodConnect:
211219
group.Connect(r.Path, r.Handler, r.Options...)
212220
default:
213-
panic("unsupported method: " + r.Method)
221+
panic(fmt.Sprintf("okapi: unsupported HTTP method %q for path=%q", r.Method, r.Path))
214222
}
215223
}
216224
}

util.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,21 @@ func ValidateAddr(addr string) bool {
135135
}
136136

137137
func joinPaths(basePath, path string) string {
138-
// Ensure both segments have exactly one slash between them
139-
joined := strings.TrimRight(basePath, "/") + "/" + strings.TrimLeft(path, "/")
138+
// Trim slashes from both parts
139+
basePath = strings.TrimRight(basePath, "/")
140+
path = strings.TrimLeft(path, "/")
141+
142+
// If path is empty, return basePath as is
143+
if path == "" {
144+
// Ensure leading slash
145+
if !strings.HasPrefix(basePath, "/") {
146+
return "/" + basePath
147+
}
148+
return basePath
149+
}
140150

141-
// Normalize any remaining double slashes
142-
joined = strings.ReplaceAll(joined, "//", "/")
151+
// Join with a single slash
152+
joined := basePath + "/" + path
143153

144154
// Ensure leading slash
145155
if !strings.HasPrefix(joined, "/") {

0 commit comments

Comments
 (0)