options.go raw

   1  // Copyright (c) Microsoft Corporation.
   2  // Licensed under the MIT license.
   3  
   4  package options
   5  
   6  import (
   7  	"errors"
   8  	"fmt"
   9  )
  10  
  11  // CallOption implements an optional argument to a method call. See
  12  // https://blog.devgenius.io/go-call-option-that-can-be-used-with-multiple-methods-6c81734f3dbe
  13  // for an explanation of the usage pattern.
  14  type CallOption interface {
  15  	Do(any) error
  16  	callOption()
  17  }
  18  
  19  // ApplyOptions applies all the callOptions to options. options must be a pointer to a struct and
  20  // callOptions must be a list of objects that implement CallOption.
  21  func ApplyOptions[O, C any](options O, callOptions []C) error {
  22  	for _, o := range callOptions {
  23  		if t, ok := any(o).(CallOption); !ok {
  24  			return fmt.Errorf("unexpected option type %T", o)
  25  		} else if err := t.Do(options); err != nil {
  26  			return err
  27  		}
  28  	}
  29  	return nil
  30  }
  31  
  32  // NewCallOption returns a new CallOption whose Do() method calls function "f".
  33  func NewCallOption(f func(any) error) CallOption {
  34  	if f == nil {
  35  		// This isn't a practical concern because only an MSAL maintainer can get
  36  		// us here, by implementing a do-nothing option. But if someone does that,
  37  		// the below ensures the method invoked with the option returns an error.
  38  		return callOption(func(any) error {
  39  			return errors.New("invalid option: missing implementation")
  40  		})
  41  	}
  42  	return callOption(f)
  43  }
  44  
  45  // callOption is an adapter for a function to a CallOption
  46  type callOption func(any) error
  47  
  48  func (c callOption) Do(a any) error {
  49  	return c(a)
  50  }
  51  
  52  func (callOption) callOption() {}
  53