Skip to content
Open
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
23 changes: 20 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@

## Agent Instructions

> **Note for the AI agent:** Before running the startup script, check for a `vendor` directory. If it exists, skip fetching modules; otherwise proceed. This vendor-check logic is for the agent’s decision-making and is not part of the user-facing script.
> **Note for the AI agent:** Before running the startup script, verify that all dependencies and CLI tools are already available. If a `vendor/` directory exists **or** `go list -m -mod=readonly all` succeeds (indicating modules are cached), you may skip the entire startup script—including all `go mod` commands and `go install` steps. Otherwise run the script as listed below.

---

## Startup script
## Startup script (reference only – do not run)

- Fetch dependencies:

```bash
go get ./...
go mod tidy && go mod download && go mod vendor
```

- Install CLI tools referenced in Makefile:
Expand All @@ -25,3 +25,20 @@
go install github.com/dkorunic/betteralign/cmd/betteralign@latest # struct alignment
go mod tidy # clean up go.mod & go.sum
```

## Makefile commands

Use `make help` to list all available commands. Common targets include:

- **audit**: run `go mod verify`, `go vet`, and `govulncheck` for quality checks.
- **benchmark**: run benchmarks with `go test`.
- **coverage**: generate a coverage report.
- **format**: apply formatting using `gofumpt`.
- **lint**: execute `golangci-lint`.
- **test**: run the test suite with `gotestsum`.
- **longtest**: run the test suite 15 times with shuffling enabled.
- **tidy**: clean and tidy dependencies.
- **betteralign**: optimize struct field alignment.
- **generate**: run `go generate` after installing msgp and ifacemaker.

These targets can be invoked via `make <target>` as needed during development and testing.
10 changes: 5 additions & 5 deletions app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,7 @@ func Test_App_Use_CaseSensitive(t *testing.T) {
require.NoError(t, err, "app.Test(req)")
require.Equal(t, StatusNotFound, resp.StatusCode, "Status code")

// right letters in the requrested route -> 200
// right letters in the requested route -> 200
resp, err = app.Test(httptest.NewRequest(MethodGet, "/abc", nil))
require.NoError(t, err, "app.Test(req)")
require.Equal(t, StatusOK, resp.StatusCode, "Status code")
Expand Down Expand Up @@ -565,7 +565,7 @@ func Test_App_Not_Use_StrictRouting(t *testing.T) {
require.NoError(t, err, "app.Test(req)")
require.Equal(t, StatusOK, resp.StatusCode, "Status code")

// right path in the requrested route -> 200
// right path in the requested route -> 200
resp, err = app.Test(httptest.NewRequest(MethodGet, "/abc", nil))
require.NoError(t, err, "app.Test(req)")
require.Equal(t, StatusOK, resp.StatusCode, "Status code")
Expand All @@ -575,7 +575,7 @@ func Test_App_Not_Use_StrictRouting(t *testing.T) {
require.NoError(t, err, "app.Test(req)")
require.Equal(t, StatusOK, resp.StatusCode, "Status code")

// right path with group in the requrested route -> 200
// right path with group in the requested route -> 200
resp, err = app.Test(httptest.NewRequest(MethodGet, "/foo/", nil))
require.NoError(t, err, "app.Test(req)")
require.Equal(t, StatusOK, resp.StatusCode, "Status code")
Expand Down Expand Up @@ -645,7 +645,7 @@ func Test_App_Use_StrictRouting(t *testing.T) {
require.NoError(t, err, "app.Test(req)")
require.Equal(t, StatusNotFound, resp.StatusCode, "Status code")

// right path in the requrested route -> 200
// right path in the requested route -> 200
resp, err = app.Test(httptest.NewRequest(MethodGet, "/abc", nil))
require.NoError(t, err, "app.Test(req)")
require.Equal(t, StatusOK, resp.StatusCode, "Status code")
Expand All @@ -655,7 +655,7 @@ func Test_App_Use_StrictRouting(t *testing.T) {
require.NoError(t, err, "app.Test(req)")
require.Equal(t, StatusNotFound, resp.StatusCode, "Status code")

// right path with group in the requrested route -> 200
// right path with group in the requested route -> 200
resp, err = app.Test(httptest.NewRequest(MethodGet, "/foo/", nil))
require.NoError(t, err, "app.Test(req)")
require.Equal(t, StatusOK, resp.StatusCode, "Status code")
Expand Down
93 changes: 65 additions & 28 deletions ctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,14 @@ const (
maxDetectionPaths = 3
)

var (
_ io.Writer = (*DefaultCtx)(nil) // Compile-time check
_ context.Context = (*DefaultCtx)(nil) // Compile-time check
)

// The contextKey type is unexported to prevent collisions with context keys defined in
// other packages.
type contextKey int

// userContextKey define the key name for storing context.Context in *fasthttp.RequestCtx
const userContextKey contextKey = 0 // __local_user_context__
type contextKey int //nolint:unused // need for future (nolintlint)

// DefaultCtx is the default implementation of the Ctx interface
// generation tool `go install github.com/vburenin/ifacemaker@975a95966976eeb2d4365a7fb236e274c54da64c`
Expand Down Expand Up @@ -391,23 +393,6 @@ func (c *DefaultCtx) RequestCtx() *fasthttp.RequestCtx {
return c.fasthttp
}

// Context returns a context implementation that was set by
// user earlier or returns a non-nil, empty context,if it was not set earlier.
func (c *DefaultCtx) Context() context.Context {
ctx, ok := c.fasthttp.UserValue(userContextKey).(context.Context)
if !ok {
ctx = context.Background()
c.SetContext(ctx)
}

return ctx
}

// SetContext sets a context implementation by user.
func (c *DefaultCtx) SetContext(ctx context.Context) {
c.fasthttp.SetUserValue(userContextKey, ctx)
}

// Cookie sets a cookie by passing a cookie struct.
func (c *DefaultCtx) Cookie(cookie *Cookie) {
fcookie := fasthttp.AcquireCookie()
Expand Down Expand Up @@ -444,6 +429,28 @@ func (c *DefaultCtx) Cookie(cookie *Cookie) {
fasthttp.ReleaseCookie(fcookie)
}

// Deadline returns the time when work done on behalf of this context
// should be canceled. Deadline returns ok==false when no deadline is
// set. Successive calls to Deadline return the same results.
//
// Due to current limitations in how fasthttp works, Deadline operates as a nop.
// See: https://github.com/valyala/fasthttp/issues/965#issuecomment-777268945
func (*DefaultCtx) Deadline() (time.Time, bool) {
return time.Time{}, false
}

// Done returns a channel that's closed when work done on behalf of this
// context should be canceled. Done may return nil if this context can
// never be canceled. Successive calls to Done return the same value.
// The close of the Done channel may happen asynchronously,
// after the cancel function returns.
//
// Due to current limitations in how fasthttp works, Done operates as a nop.
// See: https://github.com/valyala/fasthttp/issues/965#issuecomment-777268945
func (*DefaultCtx) Done() <-chan struct{} {
return nil
}

// Cookies are used for getting a cookie value by key.
// Defaults to the empty string "" if the cookie doesn't exist.
// If a default value is given, it will return that value if the cookie doesn't exist.
Expand All @@ -468,6 +475,18 @@ func (c *DefaultCtx) Download(file string, filename ...string) error {
return c.SendFile(file)
}

// If Done is not yet closed, Err returns nil.
// If Done is closed, Err returns a non-nil error explaining why:
// context.DeadlineExceeded if the context's deadline passed,
// or context.Canceled if the context was canceled for some other reason.
// After Err returns a non-nil error, successive calls to Err return the same error.
//
// Due to current limitations in how fasthttp works, Err operates as a nop.
// See: https://github.com/valyala/fasthttp/issues/965#issuecomment-777268945
func (*DefaultCtx) Err() error {
return nil
}

// Request return the *fasthttp.Request object
// This allows you to use all fasthttp request methods
// https://godoc.org/github.com/valyala/fasthttp#Request
Expand Down Expand Up @@ -644,9 +663,14 @@ func (c *DefaultCtx) Get(key string, defaultValue ...string) string {

// GetReqHeader returns the HTTP request header specified by filed.
// This function is generic and can handle different headers type values.
// If the generic type cannot be matched to a supported type, the function
// returns the default value (if provided) or the zero value of type V.
func GetReqHeader[V GenericType](c Ctx, key string, defaultValue ...V) V {
var v V
return genericParseType[V](c.App().getString(c.Request().Header.Peek(key)), v, defaultValue...)
v, err := genericParseType[V](c.App().getString(c.Request().Header.Peek(key)))
if err != nil && len(defaultValue) > 0 {
return defaultValue[0]
}
return v
}

// GetRespHeader returns the HTTP response header specified by field.
Expand Down Expand Up @@ -1103,6 +1127,8 @@ func (c *DefaultCtx) Params(key string, defaultValue ...string) string {

// Params is used to get the route parameters.
// This function is generic and can handle different route parameters type values.
// If the generic type cannot be matched to a supported type, the function
// returns the default value (if provided) or the zero value of type V.
//
// Example:
//
Expand All @@ -1115,8 +1141,11 @@ func (c *DefaultCtx) Params(key string, defaultValue ...string) string {
// http://example.com/id/:number -> http://example.com/id/john
// Params[int](c, "number", 0) -> returns 0 because can't parse 'john' as integer.
func Params[V GenericType](c Ctx, key string, defaultValue ...V) V {
var v V
return genericParseType(c.Params(key), v, defaultValue...)
v, err := genericParseType[V](c.Params(key))
if err != nil && len(defaultValue) > 0 {
return defaultValue[0]
}
return v
}

// Path returns the path part of the request URL.
Expand Down Expand Up @@ -1238,10 +1267,12 @@ func (c *DefaultCtx) Queries() map[string]string {
// age := Query[int](c, "age") // Returns 8
// unknown := Query[string](c, "unknown", "default") // Returns "default" since the query parameter "unknown" is not found
func Query[V GenericType](c Ctx, key string, defaultValue ...V) V {
var v V
q := c.App().getString(c.RequestCtx().QueryArgs().Peek(key))

return genericParseType[V](q, v, defaultValue...)
v, err := genericParseType[V](q)
if err != nil && len(defaultValue) > 0 {
return defaultValue[0]
}
return v
}

// Range returns a struct containing the type and a slice of ranges.
Expand Down Expand Up @@ -1804,6 +1835,12 @@ func (c *DefaultCtx) Vary(fields ...string) {
c.Append(HeaderVary, fields...)
}

// Value makes it possible to retrieve values (Locals) under keys scoped to the request
// and therefore available to all following routes that match the request.
func (c *DefaultCtx) Value(key any) any {
return c.fasthttp.UserValue(key)
}

// Write appends p into response body.
func (c *DefaultCtx) Write(p []byte) (int, error) {
c.fasthttp.Response.AppendBody(p)
Expand Down
26 changes: 20 additions & 6 deletions ctx_interface_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading