Skip to content

Commit

Permalink
feat: wip on rehydrate function
Browse files Browse the repository at this point in the history
  • Loading branch information
tigerwill90 committed Oct 11, 2024
1 parent ee48785 commit a25e768
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 5 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,9 @@ if errors.Is(err, fox.ErrRouteConflict) {
```

#### Named parameters
A route can be defined using placeholder (e.g `{name}`). The matching segment are recorder into the `fox.Params` slice accessible
via `fox.Context`. The `Param` and `Get` methods are helpers to retrieve the value using the placeholder name.
A route can be defined using placeholder (e.g `{name}`). The matching segment are recorder into `fox.Param` accessible
via `fox.Context`. `fox.Context.Params` provide an iterator to range over `fox.Param` and `fox.Context.Param` allow
to retrieve directly the value of a parameter using the placeholder name.

````
Pattern /avengers/{name}
Expand Down Expand Up @@ -168,14 +169,13 @@ GET /fs/*{filepath} #3 => match /fs/avengers/ironman.txt

#### Warning about context
The `fox.Context` instance is freed once the request handler function returns to optimize resource allocation.
If you need to retain `fox.Context` or `fox.Params` beyond the scope of the handler, use the `Clone` methods.
If you need to retain `fox.Context` beyond the scope of the handler, use the `fox.Context.Clone` methods.
````go
func Hello(c fox.Context) {
cc := c.Clone()
// cp := c.Params().Clone()
go func() {
time.Sleep(2 * time.Second)
log.Println(cc.Param("name")) // Safe
log.Println(c.Param("name")) // Safe
}()
_ = c.String(http.StatusOK, "Hello %s\n", c.Param("name"))
}
Expand Down
123 changes: 123 additions & 0 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ type Context interface {
Tree() *Tree
// Fox returns the Router instance.
Fox() *Router
// Rehydrate try to rehydrate the Context with the providing ResponseWriter, http.Request and route which is then suitable
// to use for calling the handler of the provided route. The state of the context is only updated if it succeed.
// TODO improve documentation
Rehydrate(w ResponseWriter, r *http.Request, route *Route) bool
}

// cTx holds request-related information and allows interaction with the ResponseWriter.
Expand Down Expand Up @@ -124,6 +128,125 @@ func (c *cTx) Reset(w ResponseWriter, r *http.Request) {
*c.params = (*c.params)[:0]
}

func (c *cTx) Rehydrate(w ResponseWriter, r *http.Request, route *Route) bool {

Check warning on line 131 in context.go

View check run for this annotation

Codecov / codecov/patch

context.go#L131

Added line #L131 was not covered by tests

target := r.URL.Path
if len(r.URL.RawPath) > 0 {

Check warning on line 134 in context.go

View check run for this annotation

Codecov / codecov/patch

context.go#L133-L134

Added lines #L133 - L134 were not covered by tests
// Using RawPath to prevent unintended match (e.g. /search/a%2Fb/1)
target = r.URL.RawPath

Check warning on line 136 in context.go

View check run for this annotation

Codecov / codecov/patch

context.go#L136

Added line #L136 was not covered by tests
}

// This was a static route, too easy.
if target == route.path {
c.req = r
c.w = w
c.tsr = false
c.cachedQuery = nil
c.route = route
c.scope = RouteHandler
*c.params = (*c.params)[:0]

Check warning on line 147 in context.go

View check run for this annotation

Codecov / codecov/patch

context.go#L140-L147

Added lines #L140 - L147 were not covered by tests

return true

Check warning on line 149 in context.go

View check run for this annotation

Codecov / codecov/patch

context.go#L149

Added line #L149 was not covered by tests
}

var params Params
maxParams := c.tree.maxParams.Load()
if len(*c.params) == 0 {
params = *c.params
} else if len(*c.tsrParams) == 0 {
params = *c.tsrParams
} else if maxParams < 10 {
params = make(Params, 0, 10) // stack allocation
} else {
params = make(Params, 0, maxParams)

Check warning on line 161 in context.go

View check run for this annotation

Codecov / codecov/patch

context.go#L152-L161

Added lines #L152 - L161 were not covered by tests
}
_ = params

Check warning on line 163 in context.go

View check run for this annotation

Codecov / codecov/patch

context.go#L163

Added line #L163 was not covered by tests

return true

Check warning on line 165 in context.go

View check run for this annotation

Codecov / codecov/patch

context.go#L165

Added line #L165 was not covered by tests
}

func hydrateParams(path string, route string, params *Params) bool {
// Note that we assume that this is a valid route (validated with parseRoute).
rLen := len(route)
pLen := len(path)
var i, j int
state := stateDefault

OUTER:
for i < rLen && j < pLen {
switch state {
case stateParam:

ri := string(route[i])
_ = ri
pj := string(path[j])
_ = pj

startPath := j
idx := strings.IndexByte(path[j:], slashDelim)
if idx > 0 {
j += idx
} else if idx < 0 {
j += len(path[j:])
} else {

Check warning on line 191 in context.go

View check run for this annotation

Codecov / codecov/patch

context.go#L190-L191

Added lines #L190 - L191 were not covered by tests
// segment is empty
return false

Check warning on line 193 in context.go

View check run for this annotation

Codecov / codecov/patch

context.go#L193

Added line #L193 was not covered by tests
}

startRoute := i
idx = strings.IndexByte(route[i:], slashDelim)
if idx >= 0 {
i += idx
} else {
i += len(route[i:])

Check warning on line 201 in context.go

View check run for this annotation

Codecov / codecov/patch

context.go#L201

Added line #L201 was not covered by tests
}

*params = append(*params, Param{
Key: route[startRoute : i-1],
Value: path[startPath:j],
})

state = stateDefault

default:

ri := string(route[i])
_ = ri
pj := string(path[j])
_ = pj

if route[i] == path[j] {
i++
j++
continue
}

if route[i] == '{' {
i++
state = stateParam
continue
}

if route[i] == '*' {
state = stateCatchAll
break OUTER
}

return false

Check warning on line 235 in context.go

View check run for this annotation

Codecov / codecov/patch

context.go#L235

Added line #L235 was not covered by tests
}
}

if state == stateCatchAll || route[i] == '*' {
*params = append(*params, Param{
Key: route[i+2 : rLen-1],
Value: path[j:],
})
return true
}

return i == rLen && j == pLen

Check warning on line 247 in context.go

View check run for this annotation

Codecov / codecov/patch

context.go#L247

Added line #L247 was not covered by tests
}

// reset resets the Context to its initial state, attaching the provided http.ResponseWriter and http.Request.
// Caution: always pass the original http.ResponseWriter to this method, not the ResponseWriter itself, to
// avoid wrapping the ResponseWriter within itself. Use wisely!
Expand Down
8 changes: 8 additions & 0 deletions context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package fox

import (
"bytes"
"fmt"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"io"
Expand Down Expand Up @@ -490,3 +491,10 @@ func TestWrapH(t *testing.T) {
})
}
}

func TestHydrateParams(t *testing.T) {

params := make(Params, 0)
fmt.Println(hydrateParams("/foo/ab:1/baz/123/y/bo/lo", "/foo/ab:{bar}/baz/{x}/{y}/*{zo}", &params))
fmt.Println(params)
}
6 changes: 6 additions & 0 deletions fox_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1574,6 +1574,12 @@ func TestParseRoute(t *testing.T) {
wantErr: ErrInvalidRoute,
wantN: -1,
},
{
name: "unexpected character in param",
path: "/foo/{*bar}",
wantErr: ErrInvalidRoute,
wantN: -1,
},
{
name: "in flight catch-all after param in one route segment",
path: "/foo/{bar}*{baz}",
Expand Down

0 comments on commit a25e768

Please sign in to comment.