Skip to content

Instantly share code, notes, and snippets.

@abdivasiyev
Created April 1, 2024 08:57
Show Gist options
  • Save abdivasiyev/a74e5a24d05ac3f4d6f66ce991ac598e to your computer and use it in GitHub Desktop.
Save abdivasiyev/a74e5a24d05ac3f4d6f66ce991ac598e to your computer and use it in GitHub Desktop.
package http
import (
"context"
"encoding/json"
"mime/multipart"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"github.com/rs/zerolog"
)
type (
Request interface {
PathParams(r *http.Request, getter func(*http.Request, string) string) error
Headers(getter func(string) string) error
QueryParams(getter func(string) string) error
Files(getter func(string) (multipart.File, *multipart.FileHeader, error)) error
FormValues(getter func(string) string) error
Validate() error
}
RequestPointer[T any] interface {
*T
Request
}
Response interface {
Payload() any
}
EmptyRequest struct{}
EmptyResponse struct{}
BaseResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Payload any `json:"payload"`
}
Handler[T any, Req RequestPointer[T], Res Response] struct {
// custom dependencies
logger *zerolog.Logger
}
)
func New[T any, Req RequestPointer[T], Res Response](logger *zerolog.Logger) Handler[T, Req, Res] {
return Handler[T, Req, Res]{
logger: logger,
}
}
func (h Handler[T, Req, Res]) reply(w http.ResponseWriter, result BaseResponse) error {
w.WriteHeader(result.Code)
return json.NewEncoder(w).Encode(result)
}
func (h Handler[T, Req, Res]) Handle(handler func(context.Context, Req) (Res, error)) http.HandlerFunc {
var (
logger = h.logger.With().Str("request_id", uuid.NewString()).Logger()
)
return func(w http.ResponseWriter, r *http.Request) {
var (
request Req = new(T)
result = Success
err error
)
defer func(result *BaseResponse, err error) {
if err != nil {
logger.Error().Err(err).Msg("failed to handle request")
if err = h.reply(w, result.From(err)); err != nil {
logger.Error().Err(err).Msg("failed to reply to request")
}
return
}
if err = h.reply(w, *result); err != nil {
logger.Error().Err(err).Msg("failed to reply to request")
}
}(&result, err)
err = request.PathParams(r, chi.URLParam)
if err != nil {
return
}
err = request.QueryParams(r.URL.Query().Get)
if err != nil {
return
}
err = request.Headers(r.Header.Get)
if err != nil {
return
}
switch contentType := r.Header.Get("Content-Type"); contentType {
case "application/json":
err = json.NewDecoder(r.Body).Decode(&request)
if err != nil {
logger.Error().Err(err).Msg("failed to decode request")
result = result.From(err)
if err = h.reply(w, result); err != nil {
logger.Error().Err(err).Msg("failed to reply to request")
}
return
}
case "application/x-www-form-urlencoded":
err = request.FormValues(r.FormValue)
if err != nil {
logger.Error().Err(err).Msg("failed to set form values")
result = result.From(err)
if err = h.reply(w, result); err != nil {
logger.Error().Err(err).Msg("failed to reply to request")
}
return
}
case "multipart/form-data":
err = r.ParseMultipartForm(32 << 20)
if err != nil {
logger.Error().Err(err).Msg("failed to parse multipart form")
result = result.From(err)
if err = h.reply(w, result); err != nil {
logger.Error().Err(err).Msg("failed to reply to request")
}
return
}
err = request.Files(r.FormFile)
if err != nil {
logger.Error().Err(err).Msg("failed to set form files")
result = result.From(err)
if err = h.reply(w, result); err != nil {
logger.Error().Err(err).Msg("failed to reply to request")
}
return
}
err = request.FormValues(r.FormValue)
if err != nil {
logger.Error().Err(err).Msg("failed to set form values")
result = result.From(err)
if err = h.reply(w, result); err != nil {
logger.Error().Err(err).Msg("failed to reply to request")
}
return
}
}
err = request.Validate()
if err != nil {
return
}
logger.Info().Str("path", r.URL.Path).Str("method", r.Method).Any("request", request).Send()
result.Payload, err = handler(r.Context(), request)
if err != nil {
return
}
logger.Info().Str("path", r.URL.Path).Str("method", r.Method).Any("result", result).Send()
}
}
func (request *EmptyRequest) PathParams(r *http.Request, getter func(*http.Request, string) string) error {
//TODO: set path params to struct
return nil
}
func (request *EmptyRequest) Headers(getter func(string) string) error {
//TODO: set headers to struct
return nil
}
func (request *EmptyRequest) QueryParams(getter func(string) string) error {
//TODO: set query params to struct
return nil
}
func (request *EmptyRequest) Files(getter func(string) (multipart.File, *multipart.FileHeader, error)) error {
//TODO: set files to struct
return nil
}
func (request *EmptyRequest) FormValues(getter func(string) string) error {
//TODO: set form values to struct
return nil
}
func (request *EmptyRequest) Validate() error {
//TODO: validate struct
return nil
}
func (response EmptyResponse) Payload() any {
//TODO: return response to frontend
return response
}
func newResponse(code int, message string) BaseResponse {
return BaseResponse{
Code: code,
Message: message,
Payload: nil,
}
}
func (b BaseResponse) From(err error) BaseResponse {
// TODO: map custom errors to base response
return b
}
var (
Success = newResponse(http.StatusOK, "OK")
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment