compose — railway-oriented pipelines
github.com/go-composites/compose (Go package Compose, imported from the
src/ sub-directory) is the org's composition primitive — the operation
that names go-composites. It wires small Result-returning steps into a
single pipeline that threads a payload forward along a success track and
short-circuits onto an error track the instant a step fails.
API
import Compose "github.com/go-composites/compose/src"
type Step func(input interface{}) Result.Interface
func Pipe(steps ...Step) Step
func Run(input interface{}, steps ...Step) Result.Interface
func Then(transform func(input interface{}) interface{}) Step
func Map(transform func(input interface{}) interface{}) Step
func Recover(step Step, handler func(failed Result.Interface) Result.Interface) Step
func Fail(message string) Step
A Step receives the payload produced by the previous stage and returns a
Result. Per the org's null-object discipline, a step never
returns a bare nil: it returns either a payload-bearing Result (success) or
a Result whose HasError() is true (failure).
Behaviour
| Function | Behaviour |
|---|---|
Pipe(steps...) |
Composes the steps left-to-right into one reusable Step. |
Run(input, steps...) |
Convenience for Pipe(steps...)(input) — compose and run in one call. |
Then(transform) |
Lifts an infallible func(interface{}) interface{} onto the success track; its return value becomes a successful Result's payload. |
Map(transform) |
Alias of Then. |
Recover(step, handler) |
Runs handler only when step short-circuits, giving it a chance to return a fallback Result and rejoin the success track. On success the Result passes through untouched. |
Fail(message) |
A Step that always short-circuits with an error carrying message. |
Data flow and short-circuiting
Pipe wraps its input in a successful Result, then runs each step in order
on the previous step's Payload() — data flows forward, left to right. As
soon as a step returns a Result with HasError() == true, the pipeline
returns that error Result unchanged and no later step runs. Otherwise the
final step's Result is returned.
Pipe() with no steps is the identity track: it simply wraps its input in a
successful Result. The returned Step is reusable and safe to run on many
inputs.
Usage
import (
Compose "github.com/go-composites/compose/src"
Error "github.com/go-composites/error/src"
Result "github.com/go-composites/result/src"
)
func parse(input interface{}) Result.Interface {
n, err := strconv.Atoi(input.(string))
if err != nil {
return Result.New(Result.WithError(Error.New("not an integer")))
}
return Result.New(Result.WithPayload(n))
}
func validate(input interface{}) Result.Interface {
if input.(int) <= 0 {
return Result.New(Result.WithError(Error.New("must be positive")))
}
return Result.New(Result.WithPayload(input))
}
// A reusable pipeline: parse -> validate -> double.
pipeline := Compose.Pipe(
parse,
validate,
Compose.Then(func(input interface{}) interface{} { return input.(int) * 2 }),
)
ok := pipeline("21") // HasError() == false, Payload() == 42
bad := pipeline("not-a-number") // HasError() == true, Error().Message() == "not an integer"
// (validate and the doubling step never run)
Run("21", parse, validate, ...) is the same as pipeline("21") without first
binding the pipeline to a variable.
Dependencies
compose depends on result and error (and
transitively on null).