Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions amaro.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,45 @@ func (a *App) HEAD(path string, handler Handler, middlewares ...Middleware) erro
return a.router.Add(http.MethodHead, path, handler, middlewares...)
}

// Any registers a route that matches all standard HTTP methods.
func (a *App) Any(path string, handler Handler, middlewares ...Middleware) error {
methods := []string{
http.MethodGet,
http.MethodPost,
http.MethodPut,
http.MethodDelete,
http.MethodPatch,
http.MethodOptions,
http.MethodHead,
}
for _, method := range methods {
if err := a.Add(method, path, handler, middlewares...); err != nil {
return err
}
}
return nil
}

// Mount registers an http.Handler (e.g., grpc-gateway mux) at the specified path prefix.
// It registers the handler for all standard HTTP methods for the exact path and all subpaths.
func (a *App) Mount(path string, handler http.Handler) error {
h := WrapHTTPHandler(handler)

// Exact match
if err := a.Any(path, h); err != nil {
return err
}

// Wildcard match for subpaths
wildcardPath := path
if !strings.HasSuffix(wildcardPath, "/") {
wildcardPath += "/"
}
wildcardPath += "*filepath"

return a.Any(wildcardPath, h)
}

// Add registers a new route with the specified method, path, handler, and middlewares.
func (a *App) Add(method, path string, handler Handler, middlewares ...Middleware) error {
return a.router.Add(method, path, handler, middlewares...)
Expand Down Expand Up @@ -259,3 +298,11 @@ func Compile(handler Handler, middlewares ...Middleware) Handler {
}
return handler
}

// WrapHTTPHandler converts a standard http.Handler to an amaro.Handler.
func WrapHTTPHandler(h http.Handler) Handler {
return func(c *Context) error {
h.ServeHTTP(c.Writer, c.Request)
return nil
}
}
18 changes: 18 additions & 0 deletions binding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,21 @@ func TestBindErrorOnNonPointer(t *testing.T) {
t.Errorf("Expected 'non-nil pointer' error, got: %v", err)
}
}

func TestBindQueryFlag(t *testing.T) {
type SectorQuery struct {
Sector bool `query:"Sector"`
}
req := httptest.NewRequest("GET", "/?Sector", nil)
w := httptest.NewRecorder()
c := NewContext(w, req)

var q SectorQuery
if err := c.BindQuery(&q); err != nil {
t.Fatalf("BindQuery failed: %v", err)
}

if !q.Sector {
t.Errorf("Expected Sector to be true, got false")
}
}
4 changes: 3 additions & 1 deletion context.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,9 @@ func setField(val reflect.Value, inputs []string) error {
}

case reflect.Bool:
if b, err := strconv.ParseBool(input); err == nil {
if input == "" {
val.SetBool(true)
} else if b, err := strconv.ParseBool(input); err == nil {
val.SetBool(b)
} else {
return err
Expand Down
39 changes: 39 additions & 0 deletions group.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,45 @@ func (g *Group) HEAD(path string, handler Handler, middlewares ...Middleware) er
return g.Add(http.MethodHead, path, handler, middlewares...)
}

// Any registers a route that matches all standard HTTP methods.
func (g *Group) Any(path string, handler Handler, middlewares ...Middleware) error {
methods := []string{
http.MethodGet,
http.MethodPost,
http.MethodPut,
http.MethodDelete,
http.MethodPatch,
http.MethodOptions,
http.MethodHead,
}
for _, method := range methods {
if err := g.Add(method, path, handler, middlewares...); err != nil {
return err
}
}
return nil
}

// Mount registers an http.Handler (e.g., grpc-gateway mux) at the specified path prefix within the group.
// It registers the handler for all standard HTTP methods for the exact path and all subpaths.
func (g *Group) Mount(path string, handler http.Handler) error {
h := WrapHTTPHandler(handler)

// Exact match
if err := g.Any(path, h); err != nil {
return err
}

// Wildcard match for subpaths
wildcardPath := path
if !strings.HasSuffix(wildcardPath, "/") {
wildcardPath += "/"
}
wildcardPath += "*filepath"

return g.Any(wildcardPath, h)
}

func (g *Group) Group(prefix string) *Group {
return NewGroup(g.prefix+prefix, g.router)
}
Expand Down
88 changes: 88 additions & 0 deletions grpc_gateway_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package amaro_test

import (
"fmt"
"net/http"
"net/http/httptest"
"testing"

"github.com/buildwithgo/amaro"
"github.com/buildwithgo/amaro/routers"
)

func TestMount(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "foo")
})
mux.HandleFunc("/bar/baz", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "barbaz")
})

app := amaro.New(amaro.WithRouter(routers.NewTrieRouter()))

// Mount the mux at /gateway
// Use StripPrefix so mux sees paths starting from /foo, /bar/baz
app.Mount("/gateway", http.StripPrefix("/gateway", mux))

t.Run("Exact Match", func(t *testing.T) {
req := httptest.NewRequest("GET", "/gateway/foo", nil)
w := app.Test(req)
if w.Body.String() != "foo" {
t.Errorf("Expected 'foo', got '%s'", w.Body.String())
}
})

t.Run("Deep Match", func(t *testing.T) {
req := httptest.NewRequest("POST", "/gateway/bar/baz", nil)
w := app.Test(req)
if w.Body.String() != "barbaz" {
t.Errorf("Expected 'barbaz', got '%s'", w.Body.String())
}
})

t.Run("Method Support", func(t *testing.T) {
req := httptest.NewRequest("PUT", "/gateway/foo", nil)
w := app.Test(req)
if w.Body.String() != "foo" {
t.Errorf("Expected 'foo', got '%s'", w.Body.String())
}
})
}

func TestGroupMount(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/v1/echo", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "echo")
})

app := amaro.New(amaro.WithRouter(routers.NewTrieRouter()))
api := app.Group("/api")

// Mount at /api -> so it handles /api/v1/echo
// Strip /api so mux sees /v1/echo
api.Mount("/", http.StripPrefix("/api", mux))

req := httptest.NewRequest("GET", "/api/v1/echo", nil)
w := app.Test(req)
if w.Body.String() != "echo" {
t.Errorf("Expected 'echo', got '%s'", w.Body.String())
}
}

func TestAny(t *testing.T) {
app := amaro.New(amaro.WithRouter(routers.NewTrieRouter()))

app.Any("/all", func(c *amaro.Context) error {
return c.String(200, c.Request.Method)
})

methods := []string{"GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"}
for _, m := range methods {
req := httptest.NewRequest(m, "/all", nil)
w := app.Test(req)
if w.Body.String() != m {
t.Errorf("Expected %s, got %s", m, w.Body.String())
}
}
}