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
45 changes: 44 additions & 1 deletion binder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,50 @@

package okapi

import "testing"
import (
"errors"
"net/http"
"testing"
)

type User struct {
Name string `json:"name" required:"true"`
}

func TestContext_Bind(t *testing.T) {
o := Default()

o.Get("/", func(c Context) error {
return c.XML(http.StatusOK, books)
})
o.Get("/hello", func(c Context) error {
return c.Text(http.StatusOK, "Hello World!")
})
o.Post("/hello", func(c Context) error {
user := User{}
if err := c.Bind(&user); err != nil {
return c.AbortBadRequest("Bad requests")
}
return c.JSON(http.StatusCreated, user)
})
o.Put("/hello", func(c Context) error {
user := User{}
if err := c.B(&user); err != nil {
return c.AbortBadRequest("Bad requests")
}
return c.JSON(http.StatusCreated, user)
})
o.Get("/hello", func(c Context) error {
return c.JSON(http.StatusOK, books)
})
go func() {
if err := o.Start(); err != nil && !errors.Is(err, http.ErrServerClosed) {
t.Errorf("Server failed to start: %v", err)
}
}()
defer o.Stop()
waitForServer()
assertStatus(t, "GET", "http://localhost:8080", nil, nil, "", http.StatusOK)
assertStatus(t, "POST", "http://localhost:8080/hello", nil, nil, "", http.StatusBadRequest)

}
12 changes: 6 additions & 6 deletions constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ const (
TRACE = http.MethodTrace
)
const banner = `
___ _ _
/ _ \| | ____ _ _ _(_)
| | | | |/ / _` + "`" + ` | '_| |
| |_| | < (_| | | | |
\___/|_|\_\__,_|_| |_|
🦒 Okapi Web Framework`
___ _ _
/ _ \| | ____ _ _ __ (_)
| | | | |/ / _` + "`" + ` | '_ \| |
| |_| | < (_| | |_) | |
\___/|_|\_\__,_| .__/|_|
🦒 Okapi Web |_| Framework`
4 changes: 0 additions & 4 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ type ErrorResponse struct {
Message string `json:"message"`
Details string `json:"details,omitempty"`
Timestamp time.Time `json:"timestamp"`
Path string `json:"path,omitempty"`
}

// ValidationError represents validation error details
Expand Down Expand Up @@ -78,7 +77,6 @@ func (c *Context) AbortWithError(code int, msg string, err error) error {
Message: msg,
Details: details,
Timestamp: time.Now(),
Path: c.Request.URL.Path,
})
}

Expand All @@ -94,7 +92,6 @@ func (c *Context) AbortWithStatus(code int, message string) error {
Message: http.StatusText(code),
Details: message,
Timestamp: time.Now(),
Path: c.Request.URL.Path,
})
}

Expand Down Expand Up @@ -333,7 +330,6 @@ func (c *Context) AbortValidationErrors(errors []ValidationError, msg ...string)
Code: http.StatusUnprocessableEntity,
Message: message,
Timestamp: time.Now(),
Path: c.Request.URL.Path,
},
Errors: errors,
})
Expand Down
10 changes: 6 additions & 4 deletions group.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@ func (g *Group) Trace(path string, h HandleFunc, opts ...RouteOption) *Route {
return g.handle(TRACE, path, h, opts...)
}

// Connect registers a CONNECT route within the group with the given path and handler.
func (g *Group) Connect(path string, h HandleFunc, opts ...RouteOption) *Route {
return g.handle(CONNECT, path, h, opts...)
}

// Group creates a nested subgroup with an additional path segment and optional middlewares.
// The new group inherits all middlewares from its parent group.
func (g *Group) Group(path string, middlewares ...Middleware) *Group {
Expand Down Expand Up @@ -180,10 +185,7 @@ func (g *Group) HandleStd(method, path string, h func(http.ResponseWriter, *http
// HandleHTTP registers a standard http.Handler and wraps it with the group's middleware chain.
func (g *Group) HandleHTTP(method, path string, h http.Handler, opts ...RouteOption) {
// Convert standard handler to HandleFunc
converted := func(c Context) error {
h.ServeHTTP(c.Response, c.Request)
return nil
}
converted := g.okapi.wrapHTTPHandler(h)
// Apply group middleware
for i := len(g.middlewares) - 1; i >= 0; i-- {
converted = g.middlewares[i](converted)
Expand Down
4 changes: 4 additions & 0 deletions groupe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ func TestGroup(t *testing.T) {
api.Delete("hello", helloHandler)
api.Options("hello", helloHandler)
api.Head("hello", helloHandler)
api.Trace("hello", helloHandler)
api.Connect("hello", helloHandler)

api.Get("/group", func(c Context) error {
slog.Info("Calling route", "path", c.Request.URL.Path)
Expand All @@ -92,6 +94,8 @@ func TestGroup(t *testing.T) {
assertStatus(t, "PATCH", "http://localhost:8080/api/hello", nil, nil, "", http.StatusOK)
assertStatus(t, "DELETE", "http://localhost:8080/api/hello", nil, nil, "", http.StatusOK)
assertStatus(t, "OPTIONS", "http://localhost:8080/api/hello", nil, nil, "", http.StatusOK)
assertStatus(t, "TRACE", "http://localhost:8080/api/hello", nil, nil, "", http.StatusOK)
assertStatus(t, "CONNECT", "http://localhost:8080/api/hello", nil, nil, "", http.StatusOK)
assertStatus(t, "HEAD", "http://localhost:8080/api/hello", nil, nil, "", http.StatusOK)
assertStatus(t, "GET", "http://localhost:8080/api/standard-http", nil, nil, "", http.StatusNotFound)
}
Expand Down
7 changes: 6 additions & 1 deletion middlewares.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
// Algo Expected signing algorithm (e.g., "RS256", "HS256"). Optional.
Algo string
// TokenLookup Where to extract the token from (e.g., "header:Authorization", "query:token", "cookie:jwt").
// default: (header:Authorization)
TokenLookup string
// ContextKey where validated token claims will be stored (e.g., "user").
ContextKey string
Expand Down Expand Up @@ -204,7 +205,11 @@

// extractToken pulls the token from header, query or cookie
func (jwtAuth JWTAuth) extractToken(c Context) (string, error) {
parts := strings.Split(jwtAuth.TokenLookup, ":")
tokenLookup := jwtAuth.TokenLookup
if tokenLookup == "" {
tokenLookup = "header:Authorization"
}

Check warning on line 211 in middlewares.go

View check run for this annotation

Codecov / codecov/patch

middlewares.go#L210-L211

Added lines #L210 - L211 were not covered by tests
parts := strings.Split(tokenLookup, ":")
if len(parts) != 2 {
return "", errors.New("invalid token lookup config")
}
Expand Down
51 changes: 51 additions & 0 deletions middlewares_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,58 @@ func TestBasicAuth(t *testing.T) {
assertStatus(t, "GET", "http://localhost:8080/protected", nil, nil, "", http.StatusUnauthorized)
assertStatus(t, "GET", "http://localhost:8080/protected", headers, nil, "", http.StatusOK)
}
func TestStdMiddleware(t *testing.T) {
o := Default()
o.UseMiddleware(func(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
slog.Info("Hello Go standard HTTP middleware function")
handler.ServeHTTP(w, r)
})

})
o.Get("/", func(c Context) error {
return c.JSON(http.StatusOK, M{"hello": "world"})
})
api := o.Group("/api")
api.UseMiddleware(func(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
slog.Info("Hello Go standard HTTP Group middleware function")

handler.ServeHTTP(w, r)
})
})
api.Get("/", func(c Context) error {
return c.JSON(http.StatusOK, M{"hello": "world"})
})
o.Handle("GET", "hello", func(c Context) error {
return c.JSON(http.StatusOK, M{"hello": "world"})
})
o.HandleStd("POST", "hello", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusCreated)
_, err := w.Write([]byte("hello world"))
if err != nil {
return
}
})

slog.Info("Route count", "count", len(o.Routes()))
slog.Info("Middleware count", "count", len(o.Middlewares()))
// Start server in background
go func() {
if err := o.Start(); err != nil && !errors.Is(err, http.ErrServerClosed) {
t.Errorf("Server failed to start: %v", err)
return
}
}()
defer o.Stop()

waitForServer()

assertStatus(t, "GET", "http://localhost:8080/", nil, nil, "", http.StatusOK)
assertStatus(t, "GET", "http://localhost:8080/api/", nil, nil, "", http.StatusOK)
assertStatus(t, "GET", "http://localhost:8080/hello", nil, nil, "", http.StatusOK)
assertStatus(t, "POST", "http://localhost:8080/hello", nil, nil, "", http.StatusCreated)
}
func mustGenerateToken(t *testing.T, secret []byte) string {
t.Helper()
claims := jwt.MapClaims{
Expand Down
Loading