hndlor

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 9, 2025 License: MIT Imports: 18 Imported by: 0

README

Hndlor (a-k-a Handler)

Push Status

HTTP mux library with utility methods for easy api development

Download
go get -u github.com/OpenRunic/hndlor
Example
// login credentials struct
type Credentials struct {
	Username string
	Password string
}

// create primary router
r := hndlor.Router()

// attach middlewares
r.Use(hndlor.Logger(log.Writer()), hndlor.PrepareMux())

// create auth sub router
rAuth := hndlor.SubRouter("/auth")
rAuth.Handle(
  // route pattern
  "POST /login",

  // http.Handler
  hndlor.New(

    // automatically injected into callback
    func(creds Credentials) (hndlor.JSON, error) {
      return hndlor.JSON{
        "username": creds.Username,
        "password": creds.Password,
      }, nil
    },

    // injectable arguments [hndlor.Value]
    hndlor.Struct[Credentials](),
  ),
)

// mount the auth router
rAuth.MountTo(r)

// print routes info
hndlor.WriteStats(
  r.Mux(), // *http.ServeMux
  log.Writer(), // io.Writer

  // *hndlor.WalkConfig for nested printing since nested mux cannot be accessed
  hndlor.NewWalkConfig().Set(rAuth.Path, rAuth.Mux()),
)

// start server
if err := http.ListenAndServe(":8080", r); err != nil {
  fmt.Printf("failed to start server: %s", err.Error())
}
Middlewares
// Default Middleware: Logger
hndlor.Logger(io.Writer)

// Default Middleware: PrepareMux
// Parses request and caches body if required
hndlor.PrepareMux(io.Writer)

// Simple middleware that prints message before every request
r.Use(hndlor.M(func(w http.ResponseWriter, r *http.Request, next http.Handler) {
  println("new request!")
  next.ServeHTTP(w, r)
}))

// Middleware that responds with error on fail
r.Use(hndlor.MM(func(w http.ResponseWriter, r *http.Request, next http.Handler) error {
  return errors.New("some validation failed")
}))

// Custom timeout middleware with option(s)
func Timeout(t time.Duration) hndlor.NextHandler {
  return hndlor.M(func(w http.ResponseWriter, r *http.Request, next http.Handler) {
    ctx, cancel := context.WithTimeout(r.Context(), t)
    defer cancel()

    next.ServeHTTP(w, r.WithContext(ctx))
  })
}

// use the timeout middleware
r.Use(Timeout(2 * time.Second))
Router

Router provides [hndlor.MuxRouter] and exposes methods identitical to [http.Handle] and [http.HandleFunc]

r := hndlor.Router()

sub := hndlor.SubRouter("/nested")

sub.MountTo(r)
Handler
// handler that panics on invalid callback signature
hn := hndlor.New(

  // resolved values automatically injected into callback
  func(v1 string, v2 int, v3 string) (hndlor.JSON, error) {
    return hndlor.JSON{}, nil
  },

  // value resolvers: refer to `Values` section
  valueResolver1[string],
  valueResolver2[int],
  valueResolver3[string],
)

// handler with custom writer logic
hn := hndlor.New(
  func(w http.ResponseWriter, v1 string) {
    hndlor.WriteData(w, hndlor.JSON{
      "value": v1,
    })
  },
  hndlor.HTTPResponseWriter(),
  valueResolver1[string],
)

// custom callback for value resolve fail
hn.OnFail(func(hndlor.ValueResolver, error) error)
Values
// value resolver from http GET
vr := hndlor.Get[string]("q")

// value resolver from http Body
vr := hndlor.Body[string]("first_name")

// value resolver from url path parameters
vr := hndlor.Path[int]("id")

// value resolver from request header
vr := hndlor.Header[string]("X-Api-Token").As("token")

// value resolver from resolved context data
vr := hndlor.Context[string]("gatewayToken").Optional()

// value resolver from custom reader
vr := hndlor.Reader(func(_ http.ResponseWriter, _ *http.Request) (string, error) {
  return "user-001-uid", nil
}).As("uid")

// value resolver to struct from defined source and validate
vr := hndlor.Struct[Credentials]().As("credentials").
  Validate(func(r *http.Request, tlc Credentials) error {
    if len(tlc.Username) > 0 {
      return nil
    }
    return errors.New("unable to resolve login credentials")
  })

// collect multiple values at once as [hndlor.JSON]
values, err := hndlor.Values(http.ResponseWriter, *http.Request,
  vr1,
  vr2,
  vr3,
  ...,
  vrN,
)

// collect multiple values as struct
var creds Credentials
err := hndlor.ValuesAs(http.ResponseWriter, *http.Request, &creds,
  vr1,
  vr2,
)

// resolve single value
q, err := vr.Resolve(http.ResponseWriter, *http.Request)
Utility
// get request address [net.Addr]
addr := hndlor.RequestAddr(*http.Request)

// converts from one struct type to other
var data T
err := hndlor.StructToStruct(map[string]any{
  "user": "admin",
}, &data)

// [hndlor.JSON] for reference
type JSON map[string]any

// write [hndlor.JSON] to io.Writer | http.ResponseWriter
hndlor.WriteData(data)

// write error to io.Writer | http.ResponseWriter
hndlor.WriteError(error)

// write error message
hndlor.WriteErrorMessage("authentication failed...")

// Custom Context Value is stored as [hndlor.JSON] with key
// hndlor.ContextValueDefault

// write custom context value
req := hndlor.PatchValue(*http.Request, "gatewayToken", "0x010")

// write multiple context value
req := hndlor.PatchMap(*http.Request, hndlor.JSON)

// read custom context value
val, err := hndlor.GetData[T](*http.Request, key, fallbackValue)

// read all custom context values saved as [hndlor.JSON]
val, err := hndlor.GetAllData(*http.Request)

// get all cached [hndlor.JSON] body data from request
bodyJSON := hndlor.BodyJSON(*http.Request)

// get single body value from cached body data
username, ok := hndlor.BodyRead(*http.Request, "username")

// make struct from cached body data
var creds Credentials
err := hndlor.BodyReadStruct(*http.Request, &creds)

// create error returns [hndlor.ResponseError]
err := hndlor.Error("error message")
err := hndlor.Errorf("error message: %s", name)

Support

You can file an Issue.

Contribute

To contrib to this project, you can open a PR or an issue.

Documentation

Index

Constants

View Source
const ContentTypeJSON = "application/json"

ContentType of json

View Source
const ContentTypeMultipart = "multipart/form-data"

ContentType of multipart

View Source
const ContentTypeURLEncoded = "application/x-www-form-urlencoded"

ContentType of url encoded form

Variables

This section is empty.

Functions

func BodyRead

func BodyRead(r *http.Request, key string) (any, bool)

BodyRead reads value from request body

func BodyReadStruct

func BodyReadStruct[T any](r *http.Request, data T) error

BodyReadStruct reads values from request body as struct

func GetCaller

func GetCaller(skip int) (string, int, bool)

GetCaller retrieves the runtime.Caller info

func GetData

func GetData[T any](r *http.Request, key string, fb T) (T, error)

GetData retrieves specific key from saved default context data

func HasBody

func HasBody(r *http.Request) bool

HasBody checks if request has body

func LogError

func LogError(w io.Writer, err error)

LogError prints log to io.Writer

func Patch

func Patch(r *http.Request, key any, value any) *http.Request

Patch updates the request context with new key/value data

func PatchMap

func PatchMap(r *http.Request, value JSON) *http.Request

PatchValue writes JSON to default context data

func PatchValue

func PatchValue(r *http.Request, key string, value any) *http.Request

PatchValue writes key/value to default context data

func PrepareBody

func PrepareBody(r *http.Request) (*http.Request, error)

PrepareBody parses any body request

func ReadFields

func ReadFields(tp reflect.Type) []string

ReadFields retrieves all exported fields for the struct

func ReadValue

func ReadValue[T any](tp reflect.Type, value any, fb T) (T, error)

ReadValue reads value as provided type or returns [error]

func RequestAddr

func RequestAddr(r *http.Request) net.Addr

RequestAddr retrieves the requesting address info

func StructToStruct

func StructToStruct(src any, data any) error

StructToStruct convert struct type

func ValuesAs

func ValuesAs(w http.ResponseWriter, r *http.Request, data any, values ...ValueResolver) error

ValuesAs collects values and maps it to struct

func Walk

func Walk(mux *http.ServeMux, cb WalkCallback, configs ...*WalkConfig)

Walk parses the *http.ServeMux using reflection to collect list of routes via callback WalkCallback

Route collection from nested *http.ServeMux isn't supported unless details are provided via WalkConfig

func WriteData

func WriteData(w io.Writer, data JSON) error

WriteData writes JSON to io.Writer

func WriteError

func WriteError(w io.Writer, err error) error

WriteError writes [error] to io.Writer and tries to use AsExportableResponse when available

func WriteErrorMessage

func WriteErrorMessage(w io.Writer, err string) error

WriteError writes error message to io.Writer

func WriteMessage added in v0.1.0

func WriteMessage(w io.Writer, msg string) error

WriteMessage writes message to io.Writer

func WriteStats

func WriteStats(mux *http.ServeMux, w io.Writer, configs ...*WalkConfig)

WriteStats logs info of defined routes to io.Writer

Types

type AsExportableResponse

type AsExportableResponse interface {

	// ResponseStatus returns the http status code
	ResponseStatus() int

	// ResponseJSON exports the error as json data
	ResponseJSON() JSON
}

AsExportableResponse defines interface to export data for response

type AsLoggable

type AsLoggable interface {
	Log(io.Writer)
}

AsLoggable defines interface to check if struct is loggable

type ContextValue

type ContextValue int

ContextValue defines custom type for context.Context key

const (
	ContextValueDefault ContextValue = iota // default context key for data
	ContextValueJSON
)

type Handler

type Handler struct {
	Err error
	// contains filtered or unexported fields
}

Handler defines struct for callback

func New

func New(cb any, values ...ValueResolver) *Handler

New creates Handler for handling response and panics on invalid func signature

Example: reads query string 'name' and passes as func argument

mux.Handle("GET /hello", hndlor.New(func(name string) (hndlor.JSON, error) {
	return hndlor.JSON{
		"hello": name,
	}, nil
}, hndlor.Get[string]("name")))

func (*Handler) Invalidate

func (h *Handler) Invalidate() error

Invalidate verifies the provided function with requested values

func (*Handler) OnFail

func (h *Handler) OnFail(cb ValueFailHandler) *Handler

OnFail defines callback when value resolve fails

func (Handler) ServeHTTP

func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request)

func (*Handler) Values

func (h *Handler) Values(w http.ResponseWriter, r *http.Request) ([]reflect.Value, error)

Values resolves the dynamic handler values

type JSON

type JSON map[string]any

JSON represents json data format

func BodyJSON

func BodyJSON(r *http.Request) JSON

BodyJSON reads the loaded json data from request context

func GetAllData

func GetAllData(r *http.Request) JSON

GetAllData retrieves saved JSON saved in default context data

func Values

func Values(w http.ResponseWriter, r *http.Request, values ...ValueResolver) (JSON, error)

Values collects all provided values from *http.Request

type Middleware

type Middleware func(w http.ResponseWriter, r *http.Request, next http.Handler)

Middleware defines default function signature

type MiddlewareWithError

type MiddlewareWithError func(w http.ResponseWriter, r *http.Request, next http.Handler) error

MiddlewareWithError defines function signature with support for error capturing/response

type MountableMux

type MountableMux interface {
	Handle(string, http.Handler)
}

MountableMux defines an interface to verify if it can handle requests

type MuxRouter

type MuxRouter struct {
	Path        string
	Middlewares []NextHandler
	// contains filtered or unexported fields
}

MuxRouter defines a helper router

func Router

func Router() *MuxRouter

Router creates new parent router instance

func SubRouter

func SubRouter(path string) *MuxRouter

SubRouter creates new router instance with path

func (*MuxRouter) Handle

func (g *MuxRouter) Handle(pattern string, handler http.Handler)

Handle adds new request handler http.Handler

func (*MuxRouter) HandleFunc

func (g *MuxRouter) HandleFunc(pattern string, handler http.HandlerFunc)

HandleFunc adds new request handler func http.HandlerFunc

func (MuxRouter) MountTo

func (g MuxRouter) MountTo(target MountableMux)

MountTo attaches MuxRouter to parent MountableMux

func (*MuxRouter) Mux

func (g *MuxRouter) Mux() *http.ServeMux

Get internal mux router *http.ServeMux

func (MuxRouter) ServeHTTP

func (g MuxRouter) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServerHTTP server the response

func (*MuxRouter) Use

func (g *MuxRouter) Use(hns ...NextHandler) *MuxRouter

Use adds NextHandler as middlewares for all routes

type NextHandler

type NextHandler func(next http.Handler) http.Handler

NextHandler defines function signature for next handler

func Chain

func Chain(mds ...NextHandler) NextHandler

Chain accepts multiple NextHandler and builds new NextHandler as middleware

func Logger

func Logger(lw any) NextHandler

Logger middleware builds handler to log every requests received

func M

func M(fn Middleware) NextHandler

M creates a middleware wrapper around the handler

func MM

MM creates a middleware wrapper around the handler with support for writing error response

func PrepareMux

func PrepareMux() NextHandler

PrepareMux middleware parses request to create cache data as needed

type ResponseError

type ResponseError struct {
	// contains filtered or unexported fields
}

ResponseError defines struct with error message and extras

func Error

func Error(msg string) *ResponseError

Error creates error with message only

func Errorf

func Errorf(format string, a ...any) *ResponseError

Errorf creates error with message and formatting options

func (*ResponseError) AsJSON

func (e *ResponseError) AsJSON() JSON

AsJSON generates json data for export

func (*ResponseError) Caller

func (e *ResponseError) Caller(skip int) *ResponseError

Caller reads caller info to error

func (*ResponseError) Client

func (e *ResponseError) Client() *ResponseError

Client marks error as client side error

func (*ResponseError) ClientMessage

func (e *ResponseError) ClientMessage(m string) *ResponseError

ClientMessage sets error message for exported server error

func (ResponseError) Error

func (e ResponseError) Error() string

func (*ResponseError) ErrorCode

func (e *ResponseError) ErrorCode(c string) *ResponseError

ErrorCode updates primary error code

func (*ResponseError) Extras

func (e *ResponseError) Extras(d JSON) *ResponseError

Data updates the extra data for response

func (ResponseError) Log

func (e ResponseError) Log(w io.Writer)

func (*ResponseError) Message

func (e *ResponseError) Message() string

Message returns underlying error message

func (*ResponseError) Path

func (e *ResponseError) Path(p string) *ResponseError

Path updates the request path info

func (*ResponseError) Reason

func (e *ResponseError) Reason(r string) *ResponseError

Reason updates the reason of error

func (ResponseError) ResponseJSON

func (e ResponseError) ResponseJSON() JSON

func (ResponseError) ResponseStatus

func (e ResponseError) ResponseStatus() int

func (*ResponseError) Server

func (e *ResponseError) Server() *ResponseError

Server marks error as server error

func (*ResponseError) Status

func (e *ResponseError) Status(c int) *ResponseError

Status updates the status code for response

type RouteStat

type RouteStat struct {
	Str    string
	Path   string
	Method string
	Host   string
	Loc    string
	Group  bool
	Prefix string
}

RouteStat defines struct for collected route info

func WalkCollect

func WalkCollect(mux *http.ServeMux, configs ...*WalkConfig) []RouteStat

WalkCollect walks through mux to generate route stats

func (RouteStat) String

func (s RouteStat) String() string

type Value

type Value[T any] struct {
	// contains filtered or unexported fields
}

Value[T any] defines struct for resolving value from sources

func Body

func Body[T any](field string) *Value[T]

Body defines value resolver from request body

func Context

func Context[T any](key string) *Value[T]

Context defines value resolver from default data on request context

func Get

func Get[T any](field string) *Value[T]

Get defines value resolver from query string

func HTTPContext added in v0.1.0

func HTTPContext() *Value[context.Context]

HTTPContext defines value resolver to access request's context.Context

func HTTPRequest added in v0.1.0

func HTTPRequest() *Value[*http.Request]

HTTPRequest defines value resolver to access *http.Request

func HTTPResponseWriter added in v0.1.0

func HTTPResponseWriter() *Value[http.ResponseWriter]

HTTPResponseWriter defines value resolver to access http.ResponseWriter

func Header[T any](field string) *Value[T]

Header defines value resolver from request header

func NewValue

func NewValue[T any](field string, src ValueSource) *Value[T]

NewValue define new value resolver instance

func Path

func Path[T any](field string) *Value[T]

Path defines value resolver from url path params

func Reader

func Reader[T any](cb func(http.ResponseWriter, *http.Request) (T, error)) *Value[T]

Reader defines value resolver using custom reader

func Struct

func Struct[T any]() *Value[T]

Struct defines struct resolver for default source

func StructFrom

func StructFrom[T any](src ValueSource) *Value[T]

StructFrom defines new struct resolver instance

func (*Value[T]) Alias

func (v *Value[T]) Alias() string

func (*Value[T]) As

func (v *Value[T]) As(n string) *Value[T]

As sets alias name for field

func (*Value[T]) Default

func (v *Value[T]) Default() any

func (*Value[T]) Field

func (v *Value[T]) Field() string

func (*Value[T]) Optional

func (v *Value[T]) Optional() *Value[T]

Optional marks value resolver as optional value

func (*Value[T]) Reader

func (v *Value[T]) Reader(cb func(http.ResponseWriter, *http.Request) (T, error)) *Value[T]

Reader stores custom value reader for value resolver

func (*Value[T]) Required

func (v *Value[T]) Required() bool

func (Value[T]) Resolve

func (v Value[T]) Resolve(w http.ResponseWriter, r *http.Request) (any, error)

func (*Value[T]) Type

func (v *Value[T]) Type() reflect.Type

func (*Value[T]) Validate

func (v *Value[T]) Validate(cb func(*http.Request, T) error) *Value[T]

Validate adds value validator to resolved value

type ValueFailHandler

type ValueFailHandler func(ValueResolver, error) error

type ValueResolver

type ValueResolver interface {

	// Field returns the name of the field
	Field() string

	// Alias returns the alias of the field
	Alias() string

	// Type returns [reflect.Type] of value
	Type() reflect.Type

	// Default returns default value of type
	Default() any

	// Checks if value is required
	Required() bool

	// Resolve evaluates and return the resulting value else error
	Resolve(http.ResponseWriter, *http.Request) (any, error)
}

ValueResolver defines an interface to be used by handler

type ValueSource

type ValueSource int

ValueSource defines the source of value

const (
	ValueSourcePath    ValueSource = iota // reads from path params
	ValueSourceGet                        // reads from url query
	ValueSourceBody                       // reads from request body
	ValueSourceHeader                     // reads from request header
	ValueSourceContext                    // reads from request context default data
	ValueSourceDefault                    // reads from source based on request method
)

type WalkCallback

type WalkCallback func(RouteStat)

WalkCallback defines function signature for walk callback

type WalkConfig

type WalkConfig struct {
	Prefix   string
	Mappings map[string]*http.ServeMux
}

WalkConfig defines configurations for walk

func NewWalkConfig

func NewWalkConfig() *WalkConfig

NewWalkConfig creates walk config instance

func (*WalkConfig) Clone

func (c *WalkConfig) Clone(prefix string) *WalkConfig

Clone creates new config with updated prefix

func (*WalkConfig) Get

func (c *WalkConfig) Get(path string) *http.ServeMux

Get retrieves mapping item

func (*WalkConfig) Has

func (c *WalkConfig) Has(path string) bool

Has checks for mapping item

func (*WalkConfig) Set

func (c *WalkConfig) Set(path string, mux *http.ServeMux) *WalkConfig

Set registers mapping item

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL