import "github.com/go-coldbrew/data-builder"
- Variables
- func AddResultToCtx(ctx context.Context, r Result) context.Context
- func BuildGraph(executionPlan Plan, format, file string) error
- func GetFromResult(ctx context.Context, obj interface{}) interface{}
- func IsValidBuilder(builder interface{}) error
- func MaxPlanParallelism(pl Plan) (uint, error)
- type DataBuilder
- type Plan
- type Result
var (
// ErrInvalidBuilder is returned when the builder is not valid
ErrInvalidBuilder = errors.New("The provided builder is invalid")
// ErrInvalidBuilderKind is returned when the builder is not a function
ErrInvalidBuilderKind = errors.New("invalid builder, should only be a function")
// ErrInvalidBuilderNumInput is returned when the builder does not have 1 input
ErrInvalidBuilderNumOutput = errors.New("invalid builder, should always return two values")
// ErrInvalidBuilderFirstOutput is returned when the builder does not return a struct as first output
ErrInvalidBuilderFirstOutput = errors.New("invalid builder, first return type should be a struct")
// ErrInvalidBuilderSecondOutput is returned when the builder does not return an error as second output
ErrInvalidBuilderSecondOutput = errors.New("invalid builder, second return type should be error")
// ErrInvalidBuilderMissingContext is returned when the builder does not have a context as first input
ErrInvalidBuilderMissingContext = errors.New("invalid builder, missing context")
// ErrInvalidBuilderInput is returned when the builder does not have a struct as input
ErrInvalidBuilderInput = errors.New("invalid builder, input should be a struct")
// ErrInvalidBuilderOutput is returned when the builder does not have a struct as output
ErrMultipleBuilderSameOutput = errors.New("invalid, multiple builders CAN NOT produce the same output")
// ErrSameInputAsOutput is returned when the builder has the same input and output
ErrSameInputAsOutput = errors.New("invalid builder, input and output should NOT be same")
// ErrCouldNotResolveDependency is returned when the builder can not be resolved
ErrCouldNotResolveDependency = errors.New("dependency can not be resolved")
// ErrMultipleInitialData is returned when the initial data is provided twice
ErrMultipleInitialData = errors.New("initial data provided twice")
// ErrInitialDataMissing is returned when the initial data is not provided
ErrInitialDataMissing = errors.New("need complile time defined initial data to run")
)
ErrWTF is the error returned in case we find dependency resolution related errors, please report these
var ErrWTF = errors.New("What a Terrible Failure!, This is likely a bug in dependency resolution, please report this :|")
func AddResultToCtx(ctx context.Context, r Result) context.Context
this function should ideally only be used in your tests and/or for debugging modification made to Result obj will NOT persist
func BuildGraph(executionPlan Plan, format, file string) error
BuildGraph helps understand the execution plan, it renders the plan in the given format please note we depend on graphviz, please ensure you have graphviz installed
func GetFromResult(ctx context.Context, obj interface{}) interface{}
this function enables optional access to data, your code should not rely on values being present, if you have explicit dependency please add them to your function parameters
func IsValidBuilder(builder interface{}) error
IsValidBuilder checks if the given function is valid or not
func MaxPlanParallelism(pl Plan) (uint, error)
for a given plan
this number does not take into account if the builder are cpu intensive or netwrok intensive it may not be benificial to run builders at max parallelism if they are cpu intensive
DataBuilder is the interface for DataBuilder
type DataBuilder interface {
// AddBuilders adds the builders to the DataBuilder. The builders are added to the DataBuilder
AddBuilders(fn ...interface{}) error
// Compile compiles the builders and returns a plan that can be used to run the builders
// The initial data is used to resolve the dependencies of the builders. The initial data should be a struct that contains the fields that are used as input for the builders when this Plan is executed.
Compile(initialData ...interface{}) (Plan, error)
}
Example
package main
import (
"context"
"fmt"
"strings"
)
// lets say we have some data being produced by a set of functions
// but we need to define how their interaction should be and how their dependency
// should be resolved
type AppRequest struct {
FirstName string
CityName string
UpperCase bool
LowerCase bool
}
type AppResponse struct {
Msg string
}
type NameMsg struct {
Msg string
}
type CityMsg struct {
Msg string
}
type CaseMsg struct {
Msg string
}
// Lets try to build a sample builder with some dependency
// Assuming we have an App that acts on the request
// processes it in multiple steps and returns a Response
// we can think of this process as a series of functions
// NameMsgBuilder builds name salutation from our AppRequest
func NameMsgBuilder(_ context.Context, req AppRequest) (NameMsg, error) {
return NameMsg{
Msg: fmt.Sprintf("Hello %s!", req.FirstName),
}, nil
}
// CityMsgBuilder builds city welcome msg from our AppRequest
func CityMsgBuilder(_ context.Context, req AppRequest) (CityMsg, error) {
return CityMsg{
Msg: fmt.Sprintf("Welcome to %s", req.CityName),
}, nil
}
// CaseMsgBuilder handles the case transformation of the message
func CaseMsgBuilder(_ context.Context, name NameMsg, city CityMsg, req AppRequest) (CaseMsg, error) {
msg := fmt.Sprintf("%s\n%s", name.Msg, city.Msg)
if req.UpperCase {
msg = strings.ToUpper(msg)
} else if req.LowerCase {
msg = strings.ToLower(msg)
}
return CaseMsg{
Msg: msg,
}, nil
}
// ResponseBuilder builds Application response from CaseMsg
func ResponseBuilder(_ context.Context, m CaseMsg) (AppResponse, error) {
return AppResponse{
Msg: m.Msg,
}, nil
}
func main() {
// First we build an object of the builder interface
b := New()
// Then we add all the builders
// its okay to call `AddBuilders` multiple times
err := b.AddBuilders(
NameMsgBuilder,
CityMsgBuilder,
CaseMsgBuilder,
)
fmt.Println(err == nil)
// lets ass all builders
err = b.AddBuilders(ResponseBuilder)
fmt.Println(err == nil)
// next we we compile this into a plan
// the compilation ensures we have a resolved dependency graph
_, err = b.Compile()
fmt.Println(err != nil)
// Why did we get the error ?
// if we look at our dependency graph, there is no builder that produces AppRequest
// in order of dependency resolution to work we need to tell
// the Compile method that we will provide it some initial Data
// we can do that by passing empty structs
// compiler just needs the type, values will come in later
ep, err := b.Compile(AppRequest{})
fmt.Println(err == nil)
// once the Compilation has finished, we get an execution plan
// the execution plan once created can be cached and is side effect free
// It can be executed across multiple go routines
// lets run the Plan, remember to pass in the initial value
result, err := ep.Run(
context.Background(), // context is passed on the builders
AppRequest{
FirstName: "Ankur",
CityName: "Singapore",
LowerCase: true,
},
)
fmt.Println(err == nil)
// once the execution is done, we can read all the values from the result
resp := AppResponse{}
resp = result.Get(resp).(AppResponse)
fmt.Println(resp.Msg)
}
true
true
true
true
true
hello ankur!
welcome to singapore
func New() DataBuilder
New Creates a new DataBuilder
Plan is the interface that wraps execution of Plans created by DataBuilder.Compile method.
type Plan interface {
// Replace replaces the builder function used in compile with a different function. The builder function should be the same as the one used in AddBuilders
Replace(ctx context.Context, from, to interface{}) error
// Run runs the builders in the plan. The initial data is used to resolve the dependencies of the builders. The initial data should be a struct that contains the fields that are used as input for the builders when this Plan is executed.
Run(ctx context.Context, initValues ...interface{}) (Result, error)
// RunParallel runs the builders in the plan in parallel. The initial data is used to resolve the dependencies of the builders. The initial data should be a struct that contains the fields that are used as input for the builders when this Plan is executed.
RunParallel(ctx context.Context, count uint, initValues ...interface{}) (Result, error)
}
Example
{
b := New()
err := b.AddBuilders(DBTestFunc, DBTestFunc4)
fmt.Println(err == nil)
ep, err := b.Compile(TestStruct1{})
fmt.Println(err == nil)
_, err = ep.Run(context.Background(), TestStruct1{})
fmt.Println(err == nil)
err = ep.Replace(context.Background(), DBTestFunc, DBTestFunc5)
fmt.Println(err == nil)
_, err = ep.Run(context.Background(), TestStruct1{})
fmt.Println(err == nil)
}
true
true
CALLED DBTestFunc
CALLED DBTestFunc4
true
true
CALLED DBTestFunc5
CALLED DBTestFunc4
true
Result is the result of the Plan.Run method
type Result map[string]interface{}
func GetResultFromCtx(ctx context.Context) Result
this function should ideally only be used in your tests and/or for debugging modification made to Result obj may or may not persist
func (r Result) Get(obj interface{}) interface{}
Result.Get returns the value of the struct from the result if the struct is not found in the result, nil is returned
Generated by gomarkdoc