tranquility

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Feb 6, 2025 License: MIT Imports: 4 Imported by: 0

README

tranquility

Go Reference Go Reportcard

LICENSE

easily write your HTTP handlers in Go with Generics

Why tranquility?

tranquility allows you to write HTTP handlers where you can easily visualize the structure of the incoming request, as well as the outgoing response, by leveraging Generics and still being net/http compliant. This is inspired by Go's design philosophy of focusing on being readable. On top of that, if you so choose, you can structure your code to keep a handler and its models together. Eg:

-- handlers
 |
 |__ fizz
 | |__ buzz.go // model for incoming request
 | |__ bazz.go // model for outgoing response
 | |__ handler.go // fizz handler (contains business logic)
 |
 |__ hello
 | |__ language.go // model for incoming request
 | |__ greeting.go // model for outgoing response
 | |__ handler.go // hello handler (contains business logic)
 |
 |...

How do I use tranquility?

Installing

To install tranquility in a repo, simply run

go get github.com/syke99/tranquility

Then you can import the package in any go file you'd like

import "github.com/syke99/tranquility"
Basic Usage

First, define your models and your handler func:

type Fizz struct {
	Language string `json:"language"`
}


type Buzz struct {
    Greeting string `json:"greeting"`
}

func MyHandler(ctx context.Context, in *Fizz) (*Buzz, error) {
    if in.Language != "english" {
        return nil, BadLanguage
    }
    return &Buzz{
        Greeting: "hello world!",
    }, nil
}

After that, simply create it as a tranquility handler:

myCoolNewHandler := tranquility.NewHandler(MyHandler)

Then you can register this handler just like you would any other!

mux := http.NewServeMux()

mux.Handle("GET /hello", myCoolNewHandler)
Advanced Usage
Custom Serialization

tranquility defaults to using JSON for serializing the request and response bodies. Eg:

To use a different form of serialization, you can define a struct that satisfies the tranquility.Codec interface

// in this example, we'll instead use github.com/golang/protobuf/proto (un)marshaling
type MyCodec[In any, Out any] struct {}

func (c *MyCodec[In, Out]) Marshal(out *Out) ([]byte, error) {
    return proto.Marshal(out)
}

func (c *MyCodec[In, Out]) Unmarshal(data []byte, in *In) error {
    return proto.Unmarshal(data, in)
}

Then simply use the tranquility.WithCodec option whenever creating your new Handler, passing in the Codec you just created.

myCoolNewHandler := tranquility.NewHandler(
	MyHandler
	tranquility.WithCodec[Fizz, Buzz](&MyCodec{})
)
Custom Headers

You can also provide a function for adding custom headers to your response by using tranquility.WithHeaderFunc whenever creating your tranquility handler and passing in a function that will return a map off key-value strings to be added to the response headers. Eg:

First, define your function for adding headers to a response

func MyHeaderFunc(ctx context.Context, in *Fizz, out *Buzz) map[string]string {
    return map[string]string{
        "x-language":   in.Language,
        "Content-Type": "application/json",
    }
}

Then, just like custom serialization, simply use the tranquility.WithHederFunc option whenever creating your tranquility handler

myCoolNewHandler := tranquility.NewHandler(
	MyHandler,
	tranquility.WithHederFunc(MyHeaderFunc)
)
Custom Headers

While tranquility defaults to a status code of 500 and just simply passing the error back to the caller, you can implement your own custom error handling by using the tranquility.WithErrorHandler option whenever creating your tranquility handler. Eg.

First, define your error handler func

var BadLanguage = errors.New("language not supported")

func MyErrorHandler(ctx context.Context, err error) (int, error) {
    if errors.Is(BadLanguage, err) {
        // do any custom error handling based on the specific types of errors and
        // return the appropriate status code, and the newly handled error
        return http.StatusBadRequest, err
    }
    return http.StatusInternalServerError, err
}

And again, it's as easy as including a call to tranquility.WithErrorHandler whenever creating your tranquility handler

myCoolNewHandler := tranquility.NewHandler(
	MyHandler,
	tranquility.WithErrorHandler[Fizz, Buzz](MyErrorHandler)
)
Accessing entire incoming Request

You can also access the entire incoming *http.Request via the injected context in the handler and any registered options. It can be found via the, you guessed it, "request" key

func MyHandler(ctx context.Context, in *Fizz) (*Buzz, error) {
    req := ctx.Value("request") // as with all context values, they must be coerced to their correct type as they're stored as an `any` type
	...
}

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewHandler

func NewHandler[In any, Out any](handler func(ctx context.Context, in *In) (*Out, error), opts ...func(Handler *Handler[In, Out])) http.Handler

func WithCodec

func WithCodec[In any, Out any](codec Codec[In, Out]) func(*Handler[In, Out])

WithCodec allows you to provide a codec for your tranquility handler to be able to inject custom serialization of your incoming request body and outgoing response body

func WithErrorHandler

func WithErrorHandler[In any, Out any](errorHandler func(ctx context.Context, err error) (int, error)) func(*Handler[In, Out])

WithErrorHandler allows you to inject custom error handling into your tranquility Handler. This is where you define the specific status code to be returned with an error, along with any error manipulation you may want to perform

func WithHeaderFunc

func WithHeaderFunc[In any, Out any](headerFunc func(ctx context.Context, in *In, out *Out) map[string]string) func(*Handler[In, Out])

WithHeaderFunc allows you to define any custom headers to be added to a successful request before the response is written back

Types

type Codec

type Codec[In any, Out any] interface {
	Marshal(out *Out) ([]byte, error)
	Unmarshal(data []byte, in *In) error
}

Codec interface can be used to provide custom serialization of an In and an Out so that you don't have to rely on tranquility's default json serialization format

type Handler

type Handler[In any, Out any] struct {
	// contains filtered or unexported fields
}

Handler groups a generic handler func with any func for custom headers, serialization(both marshalling and unmarshalling), and custom error handling added. The structure of the incoming request body gets unmarshalled to In, and Out will get marshalled to the response body. Because of this, the default method for marshalling and unmarshalling using tranquility is via json. However, a Codec may be provided to implement custom serialization. If you need access to the entire incoming request, you can find it in the injected context using the "request" key

func (*Handler[In, Out]) ServeHTTP

func (h *Handler[In, Out]) ServeHTTP(w http.ResponseWriter, r *http.Request)

Jump to

Keyboard shortcuts

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