compose is the centerpiece primitive of go-composites — the operation that names the org. It provides railway-oriented pipelines: small steps composed left-to-right that thread a Result along a success track and short-circuit onto an error track the instant something fails.
Picture two parallel rails: a success track carrying a payload, and an error track carrying a failure. Every step is a switch:
- given a payload, a step either stays on the success track (produces a payload-bearing
Result), or - diverts onto the error track (produces a
ResultwhoseHasError()istrue).
Pipe wires steps together. As long as steps stay on the success rail, each one's payload feeds the next. The moment a step diverts, the whole pipeline short-circuits: the remaining steps are skipped and the error Result is returned unchanged. This replaces nested if err != nil ladders with a single linear flow, and — true to the go-composites null-object discipline — a step or pipe never returns a bare nil; it always returns a real Result.
go get github.com/go-composites/compose@maintype Step func(input interface{}) Result.Interface
func Pipe(steps ...Step) Step // compose left-to-right; short-circuit on error
func Run(input interface{}, steps ...Step) Result.Interface // Pipe(steps...)(input)
func Then(transform func(interface{}) interface{}) Step // lift an infallible transform onto the success track
func Map(transform func(interface{}) interface{}) Step // alias of Then
func Recover(step Step, handler func(Result.Interface) Result.Interface) Step // handle the error track
func Fail(message string) Step // a step that always diverts to the error trackSemantics:
Pipe(steps...)returns a reusableStep. Running it wraps the input in a successfulResult, then runs each step on the previous step'sPayload(). If any step'sHasError()is true, that errorResultis returned immediately and later steps do not run. With no steps it is the identity track (wraps the input).Run(input, steps...)isPipe(steps...)(input).Then/Maplift a plain transformfunc(interface{}) interface{}into aStepthat cannot fail.Recover(step, handler)runshandleronly whenstepdiverts to the error track, giving you a chance to return a fallbackResultand get back on the success track. On success theResultpasses through untouched.Fail(message)is aStepthat always diverts with the given message.
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 & double skipped)See main.go for a runnable demo of both the success path and the short-circuit-on-error path.
BSD-3-Clause. See LICENSE.
