pipeline

package
v0.18.0 Latest Latest
Warning

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

Go to latest
Published: Aug 28, 2026 License: MIT Imports: 4 Imported by: 0

Documentation

Overview

Package pipeline sends a value through a list of stages, each free to inspect it, change it, hand it on or refuse it.

Pipeline is the fluent one: Send, Through, Pipe, Then, ThenReturn and Finally. Hub holds pipelines under names. Chain over Middleware is the same onion for a stack that wraps a handler instead of receiving the value.

Two shapes, one onion

  • Pipeline carries a value in and a value out. It is the shape the bus, the queue and every filter chain use, and every stage of one pipeline works on the same type.
  • Chain over Middleware is the shape where the value is curried into the handler. Middleware[http.Handler] is the standard func(http.Handler) http.Handler, so a stage wraps the handler rather than being handed a request, and the stack can answer with something that is not what it was given -- which no Pipeline[T] can say.

hesape/http names the second one -- hhttp.Middleware is an alias of pipeline.Middleware[http.Handler] -- and hesape/routing composes it with Chain. Neither of them has a composer of its own, and neither should get one: there is one Chain and one Pipeline, and the choice between them is the shape of the stage, not a preference.

A stage is a func, not a name

A Pipe is a func, so whatever it needs -- a logger, a repository, a clock -- is captured by the function that builds it:

func auditPipe(log *slog.Logger) pipeline.Pipe[Order]

Nothing here resolves a stage from a string, so a pipeline is read by following values, and a stage that was renamed fails the build rather than the request.

A stage that has to run before and after -- opening a transaction and committing it, timing the rest of the chain -- is written as one pipe that calls next between the two halves. That is what the onion is for, and it is the only form that says which resource it used.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Chain

func Chain[H any](h H, mws ...Middleware[H]) H

Chain composes middlewares around h. The first in the list is the outermost, so the order of the list is the order of execution. With no middleware it returns h.

Pipeline is the other shape, and the package comment says which is which.

func Identity

func Identity[H any](h H) H

Identity returns h unchanged.

It is the Middleware[H] to hand back when there is nothing to wrap. A feature that is switched off still has to produce a value where one is being collected into a slice, and the alternative -- a nil middleware that Chain would have to test for on every element -- moves the check to the wrong side.

Types

type Destination

type Destination[T any] func(passable T) (T, error)

Destination is the closure a pipeline ends with, and the closure every pipe is handed as the rest of the pipeline. Both take the passable, so both are this one type.

type Handler

type Handler[T any] func(ctx context.Context, v T) error

Handler ends a pipeline whose passable is a value rather than a request.

It is a type and not a method.

The context is first because it always is, and the error is returned rather than handled here: what a failure means belongs to the caller that built the pipeline, not to the composition.

type Hub

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

Hub is a set of pipelines under names, so a caller sends an object through one by name instead of holding the list of pipes it takes.

The callback is given a fresh Pipeline and the object, and it is the one that decides the pipes.

The zero value is an empty Hub and it is usable.

func NewHub

func NewHub[T any]() *Hub[T]

NewHub returns an empty hub, with no pipeline defined.

func (*Hub[T]) Defaults

func (h *Hub[T]) Defaults(callback func(p *Pipeline[T], passable T) (T, error))

Defaults defines the pipeline named "default", which is the one Hub.Pipe reaches for when it is given no name.

func (*Hub[T]) Pipe

func (h *Hub[T]) Pipe(object T, pipeline ...string) (T, error)

Pipe sends an object through one of the available pipelines, using the name given or "default" when there is none.

At most one name may be given. Each call builds a fresh Pipeline, so nothing a pipeline was sent leaks into the next one.

A name that was never defined is an error rather than a silent no-op, and a name defined with a nil callback is not defined.

func (*Hub[T]) Pipeline

func (h *Hub[T]) Pipeline(name string, callback func(p *Pipeline[T], passable T) (T, error))

Pipeline defines a named pipeline.

Defining a name twice keeps the second callback.

type Middleware

type Middleware[H any] func(H) H

Middleware wraps a handler and returns a handler of the same type.

It is a type and not a method.

H is whatever the caller already handles with: http.Handler for a request, a job handler for queued work. Nothing is invented for a particular H, which is what keeps middleware written elsewhere in the Go ecosystem compatible.

type Pipe

type Pipe[T any] func(passable T, next Destination[T]) (T, error)

Pipe is one stage of a Pipeline: it is given the passable and the rest of the pipeline, and answers with the result.

A pipe that returns without calling next stops the pipeline, which is what an authorization check or a rate limiter does.

A pipe that is nil fails the call when the pipeline reaches it.

type Pipeline

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

Pipeline sends an object through a list of pipes, each free to inspect it, change it, hand it on or refuse it, and a destination closes the onion.

The passable and the result are the same type parameter: a pipe returns what the next one takes, so the onion is closed over one type. A stack whose answer is not the value it was given is Chain over Middleware, which is the form the HTTP stack uses; the package comment says which shape belongs where.

The zero value is a pipeline with no pipes over the zero passable, and it is usable. A Pipeline is not safe for concurrent use: it is a builder, built and run in one place.

func New

func New[T any]() *Pipeline[T]

New returns a pipeline with no pipes over the zero passable, ready for Pipeline.Send and Pipeline.Through.

func (*Pipeline[T]) Finally

func (p *Pipeline[T]) Finally(callback func(passable T)) *Pipeline[T]

Finally sets a callback to run after the pipeline ends, whatever the outcome.

It is deferred, so it runs on the way out of a failure and of a panic as well. Calling Finally twice keeps the second callback.

func (*Pipeline[T]) Pipe

func (p *Pipeline[T]) Pipe(pipes ...Pipe[T]) *Pipeline[T]

Pipe pushes additional pipes onto the pipeline. It is the one that appends where Pipeline.Through replaces.

func (*Pipeline[T]) Send

func (p *Pipeline[T]) Send(passable T) *Pipeline[T]

Send sets the object being sent through the pipeline.

func (*Pipeline[T]) Then

func (p *Pipeline[T]) Then(destination Destination[T]) (T, error)

Then runs the pipeline with a final destination.

The first pipe given to Pipeline.Through is the outermost. The error returned is the first one a pipe or the destination reports.

A pipe that is nil fails the call, and only if the pipeline reaches it: a pipe that stops the pipeline before it shields it. A destination that is nil fails before anything runs, including the callback set with Pipeline.Finally.

func (*Pipeline[T]) ThenReturn

func (p *Pipeline[T]) ThenReturn() (T, error)

ThenReturn runs the pipeline with a destination that returns the passable unchanged, so the result is whatever the last pipe handed on.

func (*Pipeline[T]) Through

func (p *Pipeline[T]) Through(pipes ...Pipe[T]) *Pipeline[T]

Through sets the pipes, replacing whatever was there.

Calling Through twice keeps only the second list, and calling it with no pipes empties the list. Pipeline.Pipe is the one that appends. The list is copied, so a caller reusing its own slice cannot change a pipeline it already built.

func (*Pipeline[T]) Unless

func (p *Pipeline[T]) Unless(condition bool, callback, otherwise func(pipeline *Pipeline[T]) *Pipeline[T]) *Pipeline[T]

Unless is Pipeline.When with the condition negated.

func (*Pipeline[T]) When

func (p *Pipeline[T]) When(condition bool, callback, otherwise func(pipeline *Pipeline[T]) *Pipeline[T]) *Pipeline[T]

When applies callback to the pipeline when condition is true, and otherwise when it is false. A nil callback for the branch taken leaves the pipeline unchanged, so a chain never breaks on one.

pipeline.New[Order]().
	Send(order).
	Through(validate).
	When(cfg.Audit, func(p *pipeline.Pipeline[Order]) *pipeline.Pipeline[Order] {
		return p.Pipe(audit)
	}, nil).
	ThenReturn()

Jump to

Keyboard shortcuts

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