forked from mirrors/homebox
introduce experimental adapter pattern for hdlrs
This commit is contained in:
parent
b82fbb01f5
commit
b0a9c510ad
5 changed files with 153 additions and 2 deletions
|
@ -11,6 +11,7 @@ require (
|
|||
github.com/go-playground/validator/v10 v10.11.2
|
||||
github.com/gocarina/gocsv v0.0.0-20230219202803-bcce7dc8d0bb
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/schema v1.2.0
|
||||
github.com/mattn/go-sqlite3 v1.14.16
|
||||
github.com/rs/zerolog v1.29.0
|
||||
github.com/stretchr/testify v1.8.2
|
||||
|
|
|
@ -398,6 +398,8 @@ github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqE
|
|||
github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY=
|
||||
github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
|
||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||
github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc=
|
||||
github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/hashicorp/consul/api v1.15.3/go.mod h1:/g/qgcoBcEXALCNZgRRisyTW0nY86++L0KbeAMXYCeY=
|
||||
|
|
|
@ -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}
|
||||
}
|
||||
|
||||
|
|
95
backend/internal/web/adapters/adapters.go
Normal file
95
backend/internal/web/adapters/adapters.go
Normal 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)
|
||||
}
|
||||
}
|
52
backend/internal/web/adapters/decoders.go
Normal file
52
backend/internal/web/adapters/decoders.go
Normal 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
|
||||
}
|
Loading…
Reference in a new issue