auth/vendor/github.com/lestrrat-go/option
Maxim Lebedev dcf9e3c2ca
continuous-integration/drone/push Build is failing Details
📌 Vendored dependencies
2022-06-09 22:35:23 +05:00
..
.gitignore 📌 Vendored dependencies 2022-06-09 22:35:23 +05:00
LICENSE 📌 Vendored dependencies 2022-06-09 22:35:23 +05:00
README.md 📌 Vendored dependencies 2022-06-09 22:35:23 +05:00
option.go 📌 Vendored dependencies 2022-06-09 22:35:23 +05:00

README.md

option

Base object for what I call the "Optional Parameters Pattern".

The beauty of this pattern is that you can achieve a method that can
take the following simple calling style

obj.Method(mandatory1, mandatory2)

or the following, if you want to modify its behavior with optional parameters

obj.Method(mandatory1, mandatory2, optional1, optional2, optional3)

Intead of the more clunky zero value for optionals style

obj.Method(mandatory1, mandatory2, nil, "", 0)

or the equally cluncky config object style, which requires you to create a
struct with `NamesThatLookReallyLongBecauseItNeedsToIncludeMethodNamesConfig

cfg := &ConfigForMethod{
 Optional1: ...,
 Optional2: ...,
 Optional3: ...,
}
obj.Method(mandatory1, mandatory2, &cfg)

SYNOPSIS

This library is intended to be a reusable component to implement
a function with arguments that look like the following:

obj.Method(mandatory1, mandatory2, optional1, optional2, optional3, ...)

Internally, we just declare this method as follows:

func (obj *Object) Method(m1 Type1, m2 Type2, options ...Option) {
  ...
}

Option objects take two arguments, its identifier and the value it contains.
The identifier can be anything, but it's usually better to use a an unexported
empty struct so that only you have the ability to generate said option:

type identOptionalParamOne struct{}
type identOptionalParamTwo struct{}
type identOptionalParamThree struct{}

func WithOptionOne(v ...) Option {
	return option.New(identOptionalParamOne{}, v)
}

Then you can call the method we described above as

obj.Method(m1, m2, WithOptionOne(...), WithOptionTwo(...), WithOptionThree(...))

Options should be parsed in a code that looks somewhat like this

func (obj *Object) Method(m1 Type1, m2 Type2, options ...Option) {
  paramOne := defaultValueParamOne
  for _, option := range options {
    switch option.Ident() {
		case identOptionalParamOne{}:
      paramOne = option.Value().(...)
    }
  }
  ...
}

Simple usage

Most of the times all you need to do is to declare the Option type as an alias
in your code:

package myawesomepkg

import "github.com/lestrrat-go/option"

type Option = option.Interface

Then you can start definig options like they are described in the SYNOPSIS section.

Differentiating Options

When you have multiple methods and options, and those options can only be passed to
each one the methods, it's hard to see which options should be passed to which method.

func WithX() Option {}
func WithY() Option {}

// Now, which of WithX/WithY go to which method?
func (*Obj) Method1(options ...Option) {}
func (*Obj) Method2(options ...Option) {}

In this case the easiest way to make it obvious is to put an extra layer around
the options so that they have different types

type Method1Option interface {
  Option
  method1Option()
}

type method1Option struct { Option }
func (*method1Option) method1Option() {}

func WithX() Method1Option {
  return &methodOption{option.New(...)}
}

func (*Obj) Method1(options ...Method1Option) {}

This way the compiler knows if an option can be passed to a given method.