commit
b985c9ff0b
|
@ -0,0 +1 @@
|
|||
.idea/
|
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2022 John Olheiser
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,38 @@
|
|||
# opt
|
||||
|
||||
`opt` is a tiny package that reduces boilerplate for functional options.
|
||||
|
||||
Simply decide whether your options can return errors or not, then use the corresponding type.
|
||||
|
||||
Without errors:
|
||||
```go
|
||||
type Foo struct {
|
||||
Bar string
|
||||
}
|
||||
|
||||
func WithBar(b string) opt.Func[Foo] {
|
||||
return func(f *Foo) {
|
||||
f.Bar = b
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
With errors:
|
||||
```go
|
||||
type Foo struct {
|
||||
Bar string
|
||||
}
|
||||
|
||||
func WithBar(b string) opt.ErrorFunc[Foo] {
|
||||
return func(f *Foo) error {
|
||||
f.Bar = b
|
||||
return nil
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE)
|
||||
|
||||
Credit to `@segfaultax` in the [Discord Gophers](https://discord.gg/golang) server for the idea and starting implementation.
|
|
@ -0,0 +1,5 @@
|
|||
module go.jolheiser.com/opt
|
||||
|
||||
go 1.18
|
||||
|
||||
require github.com/matryer/is v1.4.0
|
|
@ -0,0 +1,2 @@
|
|||
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
||||
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
|
@ -0,0 +1,24 @@
|
|||
package opt
|
||||
|
||||
// Func is a functional option for A
|
||||
type Func[A any] func(*A)
|
||||
|
||||
// Apply applies all options to A
|
||||
func Apply[A any](a *A, opts ...Func[A]) {
|
||||
for _, opt := range opts {
|
||||
opt(a)
|
||||
}
|
||||
}
|
||||
|
||||
// ErrorFunc is a functional option for A that can return an error
|
||||
type ErrorFunc[A any] func(*A) error
|
||||
|
||||
// ApplyError applies all options to A, returning the first error
|
||||
func ApplyError[A any](a *A, opts ...ErrorFunc[A]) error {
|
||||
for _, opt := range opts {
|
||||
if err := opt(a); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package opt_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"go.jolheiser.com/opt"
|
||||
)
|
||||
|
||||
func ExampleApply() {
|
||||
f := &Foo{
|
||||
Bar: "default",
|
||||
}
|
||||
opt.Apply(f, WithBar("override"))
|
||||
|
||||
fmt.Println(f.Bar)
|
||||
// Output:
|
||||
// override
|
||||
}
|
||||
|
||||
func ExampleApplyError() {
|
||||
f := &Foo{
|
||||
Bar: "default",
|
||||
}
|
||||
err := opt.ApplyError(f, WithBarErr("uh oh!")) // WithBarErr always returns errors.New(b)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
// Output:
|
||||
// uh oh!
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package opt_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"go.jolheiser.com/opt"
|
||||
|
||||
"github.com/matryer/is"
|
||||
)
|
||||
|
||||
func TestOptions(t *testing.T) {
|
||||
assert := is.New(t)
|
||||
|
||||
f := NewFoo(WithBar("bar"))
|
||||
assert.Equal(f.Bar, "bar") // Option should set bar
|
||||
|
||||
opt.Apply(f, WithBaz(100))
|
||||
assert.Equal(f.Baz, 100) // Apply should set Baz
|
||||
|
||||
_, err := NewErrorFoo(WithBarErr("bar"))
|
||||
assert.True(err != nil) // ErrorOption should return error
|
||||
assert.Equal(err.Error(), "bar") // ErrorOption error should be bar
|
||||
|
||||
err = opt.ApplyError(f, WithBazErr(100))
|
||||
assert.True(err != nil) // ApplyError should return error
|
||||
assert.Equal(err.Error(), "100") // ApplyError error should be 100
|
||||
}
|
||||
|
||||
type Foo struct {
|
||||
Bar string
|
||||
Baz int
|
||||
}
|
||||
|
||||
func WithBar(b string) opt.Func[Foo] {
|
||||
return func(f *Foo) {
|
||||
f.Bar = b
|
||||
}
|
||||
}
|
||||
|
||||
func WithBaz(b int) opt.Func[Foo] {
|
||||
return func(f *Foo) {
|
||||
f.Baz = b
|
||||
}
|
||||
}
|
||||
|
||||
func NewFoo(opts ...opt.Func[Foo]) *Foo {
|
||||
f := &Foo{ /** some defaults **/ }
|
||||
opt.Apply(f, opts...)
|
||||
return f
|
||||
}
|
||||
|
||||
func NewErrorFoo(opts ...opt.ErrorFunc[Foo]) (*Foo, error) {
|
||||
f := &Foo{ /** some defaults **/ }
|
||||
return f, opt.ApplyError(f, opts...)
|
||||
}
|
||||
|
||||
func WithBarErr(b string) opt.ErrorFunc[Foo] {
|
||||
return func(f *Foo) error {
|
||||
return errors.New(b)
|
||||
}
|
||||
}
|
||||
|
||||
func WithBazErr(b int) opt.ErrorFunc[Foo] {
|
||||
return func(f *Foo) error {
|
||||
return errors.New(strconv.Itoa(b))
|
||||
}
|
||||
}
|
Reference in New Issue