introduce experimental adapter pattern for hdlrs

This commit is contained in:
Hayden 2023-03-04 22:41:12 -09:00
parent b82fbb01f5
commit b0a9c510ad
No known key found for this signature in database
GPG key ID: 17CF79474E257545
5 changed files with 153 additions and 2 deletions

View file

@ -5,7 +5,8 @@ import (
"errors"
)
type UnauthorizedError struct{}
type UnauthorizedError struct {
}
func (err *UnauthorizedError) Error() string {
return "unauthorized"
@ -28,7 +29,7 @@ func (err *InvalidRouteKeyError) Error() string {
return "invalid route key: " + err.key
}
func NewInvalidRouteKeyError(key string) error {
func NewRouteKeyError(key string) error {
return &InvalidRouteKeyError{key}
}

View file

@ -0,0 +1,95 @@
// Package adapters provides functions to adapt functions to the server.Handler interface.
package adapters
import (
"context"
"net/http"
"github.com/google/uuid"
"github.com/hay-kot/homebox/backend/pkgs/server"
)
type AdapterFunc[T any, Y any] func(context.Context, T) (Y, error)
type IDFunc[T any, Y any] func(context.Context, uuid.UUID, T) (Y, error)
// Query is a server.Handler that decodes a query from the request and calls the provided function.
func Query[T any, Y any](f AdapterFunc[T, Y], ok int) server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
q, err := decodeQuery[T](r)
if err != nil {
return err
}
res, err := f(r.Context(), q)
if err != nil {
return err
}
return server.Respond(w, ok, res)
}
}
// QueryID is a server.Handler that decodes a query and an ID from the request and calls the provided function.
func QueryID[T any, Y any](param string, f IDFunc[T, Y], ok int) server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
ID, err := routeUUID(r, param)
if err != nil {
return err
}
q, err := decodeQuery[T](r)
if err != nil {
return err
}
res, err := f(r.Context(), ID, q)
if err != nil {
return err
}
return server.Respond(w, ok, res)
}
}
// Action is a function that adapts a function to the server.Handler interface.
// It decodes the request body into a value of type T and passes it to the function f.
// The function f is expected to return a value of type Y and an error.
//
// Note: Action differs from Query in that it decodes the request body.
func Action[T any, Y any](f AdapterFunc[T, Y], ok int) server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
v, err := decode[T](r)
if err != nil {
return err
}
res, err := f(r.Context(), v)
if err != nil {
return err
}
return server.Respond(w, ok, res)
}
}
// ActionID functions the same as Action, but it also decodes a UUID from the URL path.
func ActionID[T any, Y any](param string, f IDFunc[T, Y], ok int) server.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
ID, err := routeUUID(r, param)
if err != nil {
return err
}
v, err := decode[T](r)
if err != nil {
return err
}
res, err := f(r.Context(), ID, v)
if err != nil {
return err
}
return server.Respond(w, ok, res)
}
}

View file

@ -0,0 +1,52 @@
package adapters
import (
"net/http"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"github.com/gorilla/schema"
"github.com/hay-kot/homebox/backend/internal/sys/validate"
"github.com/hay-kot/homebox/backend/pkgs/server"
)
var queryDecoder = schema.NewDecoder()
func decodeQuery[T any](r *http.Request) (T, error) {
var v T
err := queryDecoder.Decode(&v, r.URL.Query())
if err != nil {
return v, err
}
err = validate.Check(v)
if err != nil {
return v, err
}
return v, nil
}
func decode[T any](r *http.Request) (T, error) {
var v T
err := server.Decode(r, &v)
if err != nil {
return v, err
}
err = validate.Check(v)
if err != nil {
return v, err
}
return v, nil
}
func routeUUID(r *http.Request, key string) (uuid.UUID, error) {
ID, err := uuid.Parse(chi.URLParam(r, key))
if err != nil {
return uuid.Nil, validate.NewRouteKeyError(key)
}
return ID, nil
}