README ¶
builder
Generate golang builders.
Similar project:
- mapper: convert struct to struct and more
- getter: generate getters for private struct fields, with inlining
Why not constructor?
- use constructor when you need to ensure all fields needs to be valid for the creation of your object
- use constructor to validate required fields at compile time
- you can still do the same for builder by building the object, and validating them after it is build
- useful when you have many fields
- chaining each method can be done on a new line, keeping it readable, sortable
- constructor values are mandatory, builders are not. But the generated code checks if you have invoked all the setters and will panic if one of them is not called
- you can also call builder with build partial, useful in tests when you need to test only certain fields
- builder method are dumb setters, it may sound like a disadvantage, but actually it is an advantage. You dont always want to build your entity as a complete object, sometimes you just need to load all entity fields from the database. Allowing such flexibility on your domain entity by allowing all setters is not a good pattern. Domain entity should have private fields with public getters, but setters only that guards against invariants.
- builder separate setters from your entity
- dont mix constructor vs builder, constructor is useful when you need to create valid entity, builder allows you to set reconstruct the enity from existing data. Constructor might evolve over time to handle new or old fields vs business logic, which could break old data im db (which could be created externally outside the application)
- builder is also useful when you need to mass set private fields, which are not exported. Otherwise, you could just build the structs
- using constructor guarantees compile time checking for missing fields. Using builder does not, and it can only be checked during runtime. One way to make runtime check more guaranteed to capture errors from missing field is to run the code in the init function.
Installation
$ go install github.com/alextanhongpin/builder
Usage
//go:generate go run ../main.go -type Simple
type Simple struct {
name string
age int `build:"-"`
}
Output:
// Code generated by builder, DO NOT EDIT.
package main
import "fmt"
type SimpleBuilder struct {
simple Simple
fields []string
fieldsSet uint64
}
func NewSimpleBuilder(additionalFields ...string) *SimpleBuilder {
for _, field := range additionalFields {
if field == "" {
panic("builder: empty string in constructor")
}
}
exists := make(map[string]bool)
fields := append([]string{"name"}, additionalFields...)
for _, field := range fields {
if exists[field] {
panic(fmt.Sprintf("builder: duplicate field %q", field))
}
exists[field] = true
}
return &SimpleBuilder{fields: fields}
}
// WithName sets name.
func (b SimpleBuilder) WithName(name string) SimpleBuilder {
b.mustSet("name")
b.simple.name = name
return b
}
// Build returns Simple.
func (b SimpleBuilder) Build() Simple {
for i, field := range b.fields {
if !b.isSet(i) {
panic(fmt.Sprintf("builder: %q not set", field))
}
}
return b.simple
}
// Build returns Simple.
func (b SimpleBuilder) BuildPartial() Simple {
return b.simple
}
func (b *SimpleBuilder) mustSet(field string) {
i := b.indexOf(field)
if b.isSet(i) {
panic(fmt.Sprintf("builder: set %q twice", field))
}
b.fieldsSet |= 1 << i
}
func (b SimpleBuilder) isSet(pos int) bool {
return (b.fieldsSet & (1 << pos)) == (1 << pos)
}
func (b SimpleBuilder) indexOf(field string) int {
for i, f := range b.fields {
if f == field {
return i
}
}
panic(fmt.Sprintf("builder: field: %q not found", field))
}
Ignoring and setting custom setter.
Sometimes you want a custom type, but also need to take advantage of the Build()
which panics if not all the fields are set
// Extend simple builder and check if the field is set.
func (s SimpleBuilder) WithCustomAge(age int) SimpleBuilder {
s.mustSet("age")
s.simple.age = age
return s
}
Build
func main() {
builder := NewSimpleBuilder("age") // Pass custom fields that needs to be set.
log.Println(builder.BuildPartial()) // Allows the entity to be build partially.
log.Println(builder) // None of the values are set yet.
log.Println(builder.WithName("john")) // name is set to true
log.Println(builder.WithName("john").WithCustomAge(10).Build()) // name and age set and build success
log.Println(builder) // Every instance is immutable and they don't share state.
log.Println(builder.Build()) // This will panic, since "name" and "age" is not set yet.
}
V2
Better way for setting fields. Also, the complete builder with all fields should also be generated as comments so that users can copy and paste the implementation.
// You can edit this code!
// Click here and start typing.
package main
import (
"fmt"
"sort"
"strings"
)
func main() {
b := &Builder{
fields: make(map[string]int),
}
b.Register("Name")
b.Register("Age")
b.Register("MaritalStatus")
b.Set("MaritalStatus")
b.
WithName("john").
WithAge(10).
Build()
}
type User struct {
Name string
Age int
}
type Builder struct {
fields map[string]int
user User
set int
}
func (b *Builder) WithName(name string) *Builder {
b.user.Name = name
b.Set("Name")
return b
}
func (b *Builder) WithAge(age int) *Builder {
b.user.Age = age
b.Set("Age")
return b
}
func (b *Builder) Set(name string) bool {
n, ok := b.fields[name]
if !ok {
return false
}
b.set |= 1 << n
return true
}
func (b *Builder) Register(field string) error {
if _, ok := b.fields[field]; ok {
return fmt.Errorf("field %q exists", field)
}
b.fields[field] = len(b.fields)
return nil
}
func (b *Builder) Build() User {
if b.IsPartial() {
var fields []string
for field, n := range b.fields {
v := 1 << n
if b.set&v == v {
continue
}
fields = append(fields, field)
}
sort.Strings(fields)
panic("field " + strings.Join(fields, ", ") + " is not set")
}
return b.user
}
func (b *Builder) BuildPartial() User {
return b.user
}
func (b *Builder) IsPartial() bool {
all := 1<<len(b.fields) - 1
return all != b.set
}
Documentation ¶
There is no documentation for this package.
Click to show internal directories.
Click to hide internal directories.