This package aims to make it easy to use the options pattern in Go. It uses generics to ensure type safety, and has no dependencies outside the standard library.
go get github.com/broothie/option
import "github.com/broothie/option"
The API is small, so taking a look at the package index on pkg.go.dev should get you up to speed.
Let's say you have a Server
that you'd like to be configurable:
package server
import (
"database/sql"
"log/slog"
)
type Server struct {
logger *slog.Logger
db *sql.DB
}
First, provide callers with option builders:
func Logger(logger *slog.Logger) option.Func[*Server] {
return func(server *Server) (*Server, error) {
server.logger = logger
return server, nil
}
}
func DB(name string) option.Func[*Server] {
return func(server *Server) (*Server, error) {
db, err := sql.Open("pg", name)
if err != nil {
return nil, err
}
server.db = db
return server, nil
}
}
Then define a constructor that accepts a variadic argument of type option.Option[*Server]
.
In it, use option.Apply
to run the new server instance through the provided options.
func New(options ...option.Option[*Server]) (*Server, error) {
return option.Apply(new(Server), options...)
}
Now, callers can use the options pattern when instantiating your server:
srv, err := server.New(
server.Logger(slog.New(slog.NewTextHandler(os.Stdout, nil))),
server.DB("some-connection-string"),
)
Let's say you want your server to always respond with a set of HTTP headers:
type Server struct {
headers http.Header
}
You can create a custom Option
for configuring this headers like this:
type Headers http.Header
func (h Headers) Apply(server *Server) (*Server, error) {
server.headers = http.Header(h)
return server, nil
}
Now, a caller can configure their server's headers like this:
srv, err := server.New(server.Headers{
"Content-Type": {"application/json"},
"X-From": {"my-app"}
})