Skip to content

Functional HTTP middleware with modular, composable handlers

License

Notifications You must be signed in to change notification settings

tetsuo/middleware

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

16 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

middleware

middleware provides monadic HTTP middleware, enabling seamless composition of HTTP handlers with a functional approach. Built on top of warp, it supports monadic chaining, error handling, and structured responses, making middleware logic modular and composable.

Features

  • Monadic Middleware Composition: Uses monadic patterns to compose complex middleware chains.
  • Seamless Error Handling: Defines error-handling middleware with custom status codes and messages.
  • Flexible Data Validation: Supports payload validation with ozzo-validation, integrating smoothly with Go structs.

Installation

go get github.com/tetsuo/middleware

Usage

The following example shows how to define and chain middleware actions, using monadic composition to handle requests and responses effectively.

package main

import (
    "fmt"
    "net/http"
    "github.com/tetsuo/warp/result"
    w "github.com/tetsuo/middleware"
    validation "github.com/go-ozzo/ozzo-validation"
)

// user represents the expected request body.
type user struct {
    Login string `json:"login"`
}

// Validate ensures Login meets length requirements.
func (d *user) Validate() error {
    return validation.ValidateStruct(d,
        validation.Field(&d.Login, validation.Required, validation.Length(2, 8)),
    )
}

// greeting is the response type.
type greeting struct {
    Message string `json:"message"`
}

var (
    decodeUserMiddleware   = w.DecodeBody[user]
    sendGreetingMiddleware = w.JSON[*greeting]
)

// greetingMiddlewareFromError sets a custom response on error.
func greetingMiddlewareFromError(err error) w.Middleware[*greeting] {
    return w.ApSecond(
        w.Status(202),
        w.FromResult(result.Ok(&greeting{Message: err.Error()})),
    )
}

// greetingFromUser creates a greeting message.
func greetingFromUser(u *user) *greeting {
    return &greeting{Message: fmt.Sprintf("Hello, %s!", u.Login)}
}

// app chains middleware to handle requests or respond with validation errors.
var app = w.Chain(
    w.OrElse(
        w.Map(decodeUserMiddleware, greetingFromUser),
        greetingMiddlewareFromError,
    ),
    sendGreetingMiddleware,
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", w.ToHandlerFunc(app, onError))

    server := &http.Server{Addr: "localhost:8080", Handler: mux}
    if err := server.ListenAndServe(); err != nil {
        fmt.Printf("error: %v\n", err)
    }
}

func onError(err error, c *w.Connection) {
    fmt.Printf("Uncaught error: %v\n", err)
    c.W.WriteHeader(500)
}

Example Requests

With the following request, the server validates user.Login, enforcing a length between 2 and 8:

curl -s -d "login=x" -X POST "http://localhost:8080"

Response (validation error, status 202):

{
  "message": "login: the length must be between 2 and 8."
}

If the input is valid:

curl -s -d "login=tetsuo" -X POST "http://localhost:8080"

Response (greeting, status 200):

{
  "message": "Hello, tetsuo!"
}

License

MIT License. See LICENSE for details.

About

Functional HTTP middleware with modular, composable handlers

Resources

License

Stars

Watchers

Forks

Languages