Skip to content

Commit

Permalink
Merge pull request #66 from calvinmclean/feature/extra-helper
Browse files Browse the repository at this point in the history
Add extra helper function
  • Loading branch information
calvinmclean committed May 18, 2024
2 parents 79b3b80 + d432f14 commit 61d5822
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 11 deletions.
4 changes: 4 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,10 @@ func (c *Client[T]) MakeGenericRequest(req *http.Request, target any) (*Response
}
result.Body = string(body)

if target == nil {
return result, nil
}

err = json.Unmarshal(body, target)
if err != nil {
return nil, fmt.Errorf("error decoding response body %q: %w", string(body), err)
Expand Down
6 changes: 3 additions & 3 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ func NewContextWithLogger(ctx context.Context, logger *slog.Logger) context.Cont

// GetRequestBodyFromContext gets an API resource from the request context. It can only be used in
// URL paths that include the resource ID
func (a *API[T]) GetRequestBodyFromContext(ctx context.Context) T {
func GetRequestBodyFromContext[T any](ctx context.Context) (T, bool) {
value, ok := ctx.Value(requestBodyCtxKey).(T)
if !ok {
return *new(T)
return *new(T), false
}
return value
return value, true
}

// NewContextWithRequestBody stores the API resource in the context
Expand Down
31 changes: 25 additions & 6 deletions helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,8 @@ func (a *API[T]) GetRequestedResourceAndDoMiddleware(do func(*http.Request, T) (
// ReadRequestBodyAndDo is a wrapper that handles decoding the request body into the resource type and rendering a response
func (a *API[T]) ReadRequestBodyAndDo(do func(*http.Request, T) (T, *ErrResponse)) http.HandlerFunc {
return Handler(func(w http.ResponseWriter, r *http.Request) render.Renderer {
logger := GetLoggerFromContext(r.Context())

resource, httpErr := a.GetFromRequest(r)
if httpErr != nil {
logger.Error("invalid request to create resource", "error", httpErr.Error())
return httpErr
}

Expand All @@ -143,14 +140,36 @@ func (a *API[T]) ReadRequestBodyAndDo(do func(*http.Request, T) (T, *ErrResponse
})
}

// ReadRequestBodyAndDo is a helper function that can be used without an API to handle a request
func ReadRequestBodyAndDo[T RendererBinder](do func(*http.Request, T) (T, *ErrResponse), instance func() T) http.HandlerFunc {
return Handler(func(w http.ResponseWriter, r *http.Request) render.Renderer {
resource, httpErr := GetFromRequest(r, instance)
if httpErr != nil {
return httpErr
}

resp, httpErr := do(r, resource)
if httpErr != nil {
return httpErr
}

return resp
})
}

// GetFromRequest will read the API's resource type from the request body or request context
func (a *API[T]) GetFromRequest(r *http.Request) (T, *ErrResponse) {
resource := a.GetRequestBodyFromContext(r.Context())
if resource != *new(T) {
return GetFromRequest(r, a.instance)
}

// GetFromRequest will read a resource type from the request body or request context
func GetFromRequest[T RendererBinder](r *http.Request, instance func() T) (T, *ErrResponse) {
resource, ok := GetRequestBodyFromContext[T](r.Context())
if ok {
return resource, nil
}

resource = a.instance()
resource = instance()
err := render.Bind(r, resource)
if err != nil {
return *new(T), ErrInvalidRequest(err)
Expand Down
10 changes: 8 additions & 2 deletions resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,21 @@ import (
// It enables HTTP request/response handling and getting resources by ID
type Resource interface {
comparable

RendererBinder

GetID() string
}

// RendererBinder just combines render.Renderer and render.Binder
type RendererBinder interface {
// Renderer is used to control the output behavior when creating a response.
// Use this for any after-request logic or response modifications
render.Renderer

// Binder is used to control the input behavior, after decoding the request.
// Use it for input validation or additional modification of the resource using request headers or other params
render.Binder

GetID() string
}

// Patcher is used to optionally-enable PATCH endpoint. Since the library cannot generically modify resources without using
Expand Down

0 comments on commit 61d5822

Please sign in to comment.