zero

package module
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Jul 28, 2025 License: MIT Imports: 10 Imported by: 0

README

Zero

An opinionated tool for eliminating most of the boilerplate around constructing servers in Go.

Running zero on a codebase will generate a function that completely wires up a service from scratch, including request handlers, cron jobs, pubsub, etc.

A core tenet of Zero Services it that it will work with the normal Go development lifecycle, without any additional steps. Your code should build and be testable out of the box. Code generation is only required for full service construction, but even then it's possible to construct and test the service without code generation.

Dependency injection

Any function annotated with //zero:provider [weak] [multi] [require=<provider>,...] will be used to provide its return type during application construction.

eg. The following code will inject a *DAL type and provide a *Service type.

//zero:provider
func NewService(dal *DAL) (*Service, error) { ... }

This is somewhat similar to Google's Wire project.

Weak providers

Weak providers are marked with weak, and may be overridden implicitly by creating a non-weak provider, or explicitly by selecting the provider to use via --resolve.

Weak providers are selected if any of the following conditions are true:

  • They are the only provider of that type.
  • They been explicitly selected by the user.
  • They are injected by another provider.
Multi-providers

A multi-provider allows multiple providers to contribute to a single merged type value. The provided type must return a slice or a map. Note that slice order is not guaranteed.

eg. In the following example the slice []string{"hello", "world"} will be provided.

//zero:provider multi
func Hello() []string { return []string{"hello"} }

//zero:provider multi
func World() []string { return []string{"world"} }
Explicit dependencies

A weak provider may also explicitly request other weak dependencies be injected. This is useful when an injected parameter of the provider is itself reliant on an optional weak type.

eg. In this example the SQLCron() provider requires that the migrations provided by CronSQLMigrations() have already been applied to *sql.DB, which in turn requires []Migration. By explicitly specifiying require=CronSQLMigrations, the previously ignored weak provider will be added.

//zero:provider
func NewDB(config Config, migrations []Migration) *sql.DB { ... }

//zero:provider weak multi
func CronSQLMigrations() []Migration { ... }

//zero:provider weak require=CronSQLMigrations
func SQLCron(db *sql.DB) cron.Executor { ... }

Configuration

A struct annotated with //zero:config [prefix="<prefix>"] will be used as embedded Kong-annotated configuration, with corresponding config loading from JSON/YAML/HCL. These config structs can in turn be used during dependency injection.

Routes

Zero will automatically generate http.Handler implementations for any method annotated with //zero:api, providing request decoding, response encoding, path variable decoding, query parameter decoding, and error handling.

//zero:api [<method>] [<host>]/[<path>] [<label>[=<value>] ...]
func (s Struct) Method([pathVar0, pathVar1 string][, req Request]) ([<response>, ][error]) { ... }

http.ServeMux is used for routing and thus the pattern syntax is identical.

Response encoding

Depending on the type of the value, the response will be encoded in the following ways:

Type Encoding
nil/omitted 204 No Content
string text/html
[]byte application/octet-stream
io.Reader application/octet-stream
io.ReadCloser application/octet-stream
*http.Response Response structure is used as-is.
http.Handler The response type's ServeHTTP() method will be called.
* application/json

Responses may optionally implement the interface zero.StatusCode to control the returned HTTP status code.

Error responses

As with response bodies, if the returned error type implements http.Handler, its ServeHTTP() method will be called.

A default error handler may also be registered by creating a custom provider for zero.ErrorHandler.

Service Interfaces (NOT IMPLEMENTED)

Additionally, any user-defined interface matching a subset of API methods will have the service itself injected. That is, given the following service:

//zero:api GET /users
func (s *Service) ListUsers() ([]User, error) { ... }

//zero:api POST /users authenticated
func (s *Service) CreateUser(ctx context.Context, user User) error { ... }

Injecting any of the following interfaces will result in the service being injected to fulfil the interface:

interface {
  ListUsers() ([]User, error)
}

interface {
  CreateUser(ctx context.Context, user User) error
}

interface {
  ListUsers() ([]User, error)
  CreateUser(ctx context.Context, user User) error
}

This can be very useful for testing.

PubSub (NOT IMPLEMENTED)

A method annotated with //zero:subscribe will result in the method being called whenever the corresponding pubsub topic receives an event. The PubSub implementation itself is described by the zero.Topic[T] interface, which may be injected in order to publish to a topic. A topic's payload type is used to uniquely identify that topic.

To cater to arbitrarily typed PubSub topics, a generic provider function may be declared that returns a generic zero.Topic[T]. This will be called during injection with the event type of a subscriber or publisher.

eg.

//ftl:provider
func NewKafkaConnection(ctx context.Context, config KafkaConfig) (*kafka.Conn, error) {
  return kafka.DialContext(ctx, config.Network, config.Address)
}

//ftl:provider
func NewPubSubTopic[T any](ctx context.Context, conn *kafka.Conn) (zero.Topic[T], error) {
  // ...
}

Cron (NOT IMPLEMENTED)

A method annotated with //zero:cron <schedule> will be called on the given schedule.

Middleware

A function annotated with //zero:middleware [<label>] will be automatically used as HTTP middleware for any method matching the given <label> if provided, or applied globally if not. Option values can be retrieved from the request with zero.HandlerOptions(r).

eg.

//zero:middleware authenticated
func Auth(next http.Handler) http.Handler {
  return func(w http.ResponseWriter, r *http.Request) {
    auth := r.Header().Get("Authorization")
    // ...

}

Alternatively, for middleware that requires injection, the annotated middleware function can instead be one that returns a middleware function:

//zero:middleware authenticated role
func Auth(role string, dal *DAL) func(http.Handler) http.Handler {
  return func(next http.Handler) http.Handler {
    return func(w http.ResponseWriter, r *http.Request) {
      auth := r.Header().Get("Authorization")
      // ...
    }
  }
}

Infrastructure (NOT IMPLEMENTED)

While the base usage of Zero doesn't deal with infrastructure at all, it would be possible to automatically extract required infrastructure and inject provisioned implementations of those into the injection graph as it is being constructed.

For example, if a service consumes pubsub.Topic[T] and there is no provider, one could be provided by an external provisioning plugin. The plugin could get called with the missing type, and return code that provides that type, as well as eg. Terraform for provisioning the infrastructure.

This is not thought out in detail, but the basic approach should work.

Example

package app

type User struct {
}

type UserCreatedEvent User

//zero:config
type DatabaseConfig struct {
  DSN string `default:"postgres://localhost" help:"DSN for the service."`
}

//zero:provider
func NewDAL(config DatabaseConfig) (*DAL, error) {
  // ...
}

type Service struct {
  dal *DAL
}

//zero:provider
func NewService(dal *DAL) (*Service, error) {
  // Other initialisation
  return &Service{dal: dal}, nil
}

//zero:api GET /users
func (s *Service) ListUsers() ([]User, error) {
  // ...
}

//zero:api POST /users authenticated
func (s *Service) CreateUser(ctx context.Context, user User) error {
  // ...
}

//zero:subscribe
func (s *Service) OnUserCreated(user zero.Event[UserCreatedEvent]) error {
  // ...
}

//zero:cron 1h
func (s *Service) CheckUsers() error {
  // ...
}

Documentation

Overview

Package zero contains the runtime for Zero's.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DecodeRequest

func DecodeRequest[T any](method string, r *http.Request) (T, error)

DecodeRequest decodes the JSON request body into T for PATCH/POST/PUT methods, and query parameters for all other method types.

func EncodeResponse

func EncodeResponse[T any](r *http.Request, w http.ResponseWriter, errorHandler ErrorHandler, data T, outErr error)

EncodeResponse encodes the response body into JSON and writes it to the response writer.

Types

type APIError

type APIError interface {
	error
	http.Handler
}

An APIError is an error that is also a http.Handler used to encode the error.

Any request handler returning an error

func APIErrorf

func APIErrorf(code int, message string) APIError

APIErrorf can be used with HTTP handlers to return a JSON-encoded error body in the form {"error: <msg>", "code": <code>}

type EmptyResponse

type EmptyResponse []byte

EmptyResponse is used for handlers that don't return any content.

It will write an empty response with a status code based on the HTTP method used:

  • POST: StatusCreated
  • PUT: StatusAccepted
  • PATCH: StatusAccepted
  • Other: StatusOK

func (EmptyResponse) ServeHTTP

func (e EmptyResponse) ServeHTTP(w http.ResponseWriter, r *http.Request)

type ErrorHandler added in v0.6.0

type ErrorHandler func(w http.ResponseWriter, msg string, code int)

ErrorHandler represents a function for handling errors from Zero's generated code.

type Event

type Event[T EventPayload] struct {
	// contains filtered or unexported fields
}

Event represents a typed CloudEvent.

Marshals to/from a JSON CloudEvent (https://cloudevents.io/)

eg.

{
  "specversion": "1.0",
  "type": "github.com/alecthomas/zero.User",
  "source": "github.com/alecthomas/zero.PublishUserEvent",
  "id": "Bob",
  "data": {"name": "Bob", "age": 30}
}

func NewEvent

func NewEvent[T EventPayload](payload T) Event[T]

func (Event[T]) Created

func (e Event[T]) Created() time.Time

func (Event[T]) ID

func (e Event[T]) ID() string

ID returns the ID of the underlying payload.

func (Event[T]) MarshalJSON

func (e Event[T]) MarshalJSON() ([]byte, error)

func (Event[T]) Payload

func (e Event[T]) Payload() T

func (Event[T]) Source

func (e Event[T]) Source() string

func (*Event[T]) UnmarshalJSON

func (e *Event[T]) UnmarshalJSON(data []byte) error

type EventPayload

type EventPayload interface {
	// ID returns the unique identifier for the event.
	//
	// This is required for idempotence and deduplication in the face of multiple retries.
	ID() string
}

type Middleware added in v0.1.0

type Middleware func(http.Handler) http.Handler

Middleware is a convenience type for Zero middleware.

type StatusCode

type StatusCode interface {
	StatusCode() int
}

StatusCode is an interface that can be implemented by response types to provide a custom status code.

type Topic

type Topic[T EventPayload] interface {
	// Publish publishes a message to the topic.
	Publish(ctx context.Context, msg T) error
	// Subscribe subscribes to a topic.
	Subscribe(ctx context.Context, handler func(context.Context, T) error) error
}

Directories

Path Synopsis
cmd
zero command
internal
cloudevent
Package cloudevent models CloudEvents.
Package cloudevent models CloudEvents.
codewriter
Package codewriter is a simple helper for writing out source code.
Package codewriter is a simple helper for writing out source code.
depgraph
Package depgraph builds a Zero's dependeny injection type graph.
Package depgraph builds a Zero's dependeny injection type graph.
directiveparser
Package directiveparser implements a parser for the Zero's compiler directives.
Package directiveparser implements a parser for the Zero's compiler directives.
generator
Package generator generates the Zero's bootstrap code.
Package generator generates the Zero's bootstrap code.
Package providers contains a set of builtin providers for Zero.
Package providers contains a set of builtin providers for Zero.
logging
Package logging contains providers for common loggers.
Package logging contains providers for common loggers.
sql
Package sql contains types and providers for connecting to and migrating SQL databases.
Package sql contains types and providers for connecting to and migrating SQL databases.

Jump to

Keyboard shortcuts

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