Compare commits
1 Commits
hwsauth/v0
...
hws/v0.3.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f49063432 |
@@ -51,6 +51,12 @@ func main() {
|
||||
Method: hws.MethodGET,
|
||||
Handler: http.HandlerFunc(getUserHandler),
|
||||
},
|
||||
{
|
||||
// Single route handling multiple HTTP methods
|
||||
Path: "/api/resource",
|
||||
Methods: []hws.Method{hws.MethodGET, hws.MethodPOST, hws.MethodPUT},
|
||||
Handler: http.HandlerFunc(resourceHandler),
|
||||
},
|
||||
}
|
||||
|
||||
// Add routes and middleware
|
||||
@@ -73,6 +79,18 @@ func getUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
w.Write([]byte("User ID: " + id))
|
||||
}
|
||||
|
||||
func resourceHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Handle GET, POST, and PUT for the same path
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
w.Write([]byte("Getting resource"))
|
||||
case "POST":
|
||||
w.Write([]byte("Creating resource"))
|
||||
case "PUT":
|
||||
w.Write([]byte("Updating resource"))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
12
hws/doc.go
12
hws/doc.go
@@ -74,6 +74,18 @@
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// A single route can handle multiple HTTP methods using the Methods field:
|
||||
//
|
||||
// routes := []hws.Route{
|
||||
// {
|
||||
// Path: "/api/resource",
|
||||
// Methods: []hws.Method{hws.MethodGET, hws.MethodPOST, hws.MethodPUT},
|
||||
// Handler: http.HandlerFunc(resourceHandler),
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// Note: The Methods field takes precedence over Method if both are provided.
|
||||
//
|
||||
// Path parameters can be accessed using r.PathValue():
|
||||
//
|
||||
// func getUser(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -4,11 +4,15 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
)
|
||||
|
||||
type Route struct {
|
||||
Path string // Absolute path to the requested resource
|
||||
Method Method // HTTP Method
|
||||
// Methods is an optional slice of Methods to use, if more than one can use the same handler.
|
||||
// Will take precedence over the Method field if provided
|
||||
Methods []Method
|
||||
Handler http.Handler // Handler to use for the request
|
||||
}
|
||||
|
||||
@@ -28,22 +32,34 @@ const (
|
||||
|
||||
// Server.AddRoutes registers the page handlers for the server.
|
||||
// At least one route must be provided.
|
||||
// If any route patterns (path + method) are defined multiple times, the first
|
||||
// instance will be added and any additional conflicts will be discarded.
|
||||
func (server *Server) AddRoutes(routes ...Route) error {
|
||||
if len(routes) == 0 {
|
||||
return errors.New("No routes provided")
|
||||
}
|
||||
patterns := []string{}
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("GET /healthz", func(http.ResponseWriter, *http.Request) {})
|
||||
for _, route := range routes {
|
||||
if !validMethod(route.Method) {
|
||||
return fmt.Errorf("Invalid method %s for path %s", route.Method, route.Path)
|
||||
if len(route.Methods) == 0 {
|
||||
route.Methods = []Method{route.Method}
|
||||
}
|
||||
for _, method := range route.Methods {
|
||||
if !validMethod(method) {
|
||||
return fmt.Errorf("Invalid method %s for path %s", method, route.Path)
|
||||
}
|
||||
if route.Handler == nil {
|
||||
return fmt.Errorf("No handler provided for %s %s", route.Method, route.Path)
|
||||
return fmt.Errorf("No handler provided for %s %s", method, route.Path)
|
||||
}
|
||||
pattern := fmt.Sprintf("%s %s", route.Method, route.Path)
|
||||
pattern := fmt.Sprintf("%s %s", method, route.Path)
|
||||
if slices.Contains(patterns, pattern) {
|
||||
continue
|
||||
}
|
||||
patterns = append(patterns, pattern)
|
||||
mux.Handle(pattern, route.Handler)
|
||||
}
|
||||
}
|
||||
|
||||
server.server.Handler = mux
|
||||
server.routes = true
|
||||
|
||||
@@ -122,6 +122,111 @@ func Test_AddRoutes(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_AddRoutes_MultipleMethods(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
t.Run("Single route with multiple methods", func(t *testing.T) {
|
||||
server := createTestServer(t, &buf)
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(r.Method + " response"))
|
||||
})
|
||||
err := server.AddRoutes(hws.Route{
|
||||
Path: "/api/resource",
|
||||
Methods: []hws.Method{hws.MethodGET, hws.MethodPOST, hws.MethodPUT},
|
||||
Handler: handler,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test GET request
|
||||
req := httptest.NewRequest("GET", "/api/resource", nil)
|
||||
rr := httptest.NewRecorder()
|
||||
server.Handler().ServeHTTP(rr, req)
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
assert.Equal(t, "GET response", rr.Body.String())
|
||||
|
||||
// Test POST request
|
||||
req = httptest.NewRequest("POST", "/api/resource", nil)
|
||||
rr = httptest.NewRecorder()
|
||||
server.Handler().ServeHTTP(rr, req)
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
assert.Equal(t, "POST response", rr.Body.String())
|
||||
|
||||
// Test PUT request
|
||||
req = httptest.NewRequest("PUT", "/api/resource", nil)
|
||||
rr = httptest.NewRecorder()
|
||||
server.Handler().ServeHTTP(rr, req)
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
assert.Equal(t, "PUT response", rr.Body.String())
|
||||
})
|
||||
|
||||
t.Run("Methods field takes precedence over Method field", func(t *testing.T) {
|
||||
server := createTestServer(t, &buf)
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
err := server.AddRoutes(hws.Route{
|
||||
Path: "/test",
|
||||
Method: hws.MethodGET, // This should be ignored
|
||||
Methods: []hws.Method{hws.MethodPOST, hws.MethodPUT},
|
||||
Handler: handler,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// GET should not work (Method field ignored)
|
||||
req := httptest.NewRequest("GET", "/test", nil)
|
||||
rr := httptest.NewRecorder()
|
||||
server.Handler().ServeHTTP(rr, req)
|
||||
assert.Equal(t, http.StatusMethodNotAllowed, rr.Code)
|
||||
|
||||
// POST should work (from Methods field)
|
||||
req = httptest.NewRequest("POST", "/test", nil)
|
||||
rr = httptest.NewRecorder()
|
||||
server.Handler().ServeHTTP(rr, req)
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
|
||||
// PUT should work (from Methods field)
|
||||
req = httptest.NewRequest("PUT", "/test", nil)
|
||||
rr = httptest.NewRecorder()
|
||||
server.Handler().ServeHTTP(rr, req)
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
})
|
||||
|
||||
t.Run("Invalid method in Methods slice", func(t *testing.T) {
|
||||
server := createTestServer(t, &buf)
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
err := server.AddRoutes(hws.Route{
|
||||
Path: "/test",
|
||||
Methods: []hws.Method{hws.MethodGET, hws.Method("INVALID")},
|
||||
Handler: handler,
|
||||
})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "Invalid method")
|
||||
})
|
||||
|
||||
t.Run("Empty Methods slice falls back to Method field", func(t *testing.T) {
|
||||
server := createTestServer(t, &buf)
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
err := server.AddRoutes(hws.Route{
|
||||
Path: "/test",
|
||||
Method: hws.MethodGET,
|
||||
Methods: []hws.Method{}, // Empty slice
|
||||
Handler: handler,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// GET should work (from Method field)
|
||||
req := httptest.NewRequest("GET", "/test", nil)
|
||||
rr := httptest.NewRecorder()
|
||||
server.Handler().ServeHTTP(rr, req)
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Routes_EndToEnd(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
server := createTestServer(t, &buf)
|
||||
|
||||
Reference in New Issue
Block a user