calloptions

package module
v0.0.0-...-4d61e3e Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 28, 2022 License: MIT Imports: 0 Imported by: 3

README

CallOptions - One Option to Rule them All

Introduction

Most of the time, optional arguments to methods are done with the help of option methods. You see functions like WithAccount() that you pass in as a variadic argument.

But what happens when you end up needing to share an option? I've seen a few methods to achieve this functionality, but they generally are lacking in some feature.

You can use a method like WithSharable(WithAccount()), but its ugly as your wrapping a With() inside a WithSharable().

We want the same semantics for the user as when they aren't shared.

Other methods have a generic call option and give runtime errors when they are the wrong type of option passed. But we want it to be a compile error.

Some people fall back to the passing structs like AMethodOptions{}. But that has other problems with setting defaults correctly and having "optional" values that aren't actually optional. We can do better.

This package and its methodology provide the same user semantics as non-shareable call options, but with support to have some call options work with multiple methods and compiler type safety.

The godoc has everything you need to understand how ou can provide a user experience like this (with compiler checks):

	client.UseToken(WithName("jdoak"), WithToken(token))
	client.UsePass(WithName("jdoak"), WithPass(pass))
	// Uncommenting this is a compile error, as WithPass isn't valid with UseToken()
	// client.UseToken(WithName("jdoak"), WithPass(pass))

Documentation

Overview

Package calloptions provides a way to construct method call options that can be used between multiple methods

while providing type safety guarantees. This is a little verbose, but the godoc is short and the client is clean.

Example:

type Client struct{}

// aCallOptions holds the options used in the A() method call.
type aCallOptions struct {
	accountName string
	id int
}

// methodAOption represents an options argument to A().
type methodAOption interface{
	a() // This ensures only this package can implement this.
}

// WithAccount sets the AccountName to name. It can be used on A() or B().
func WithAccount(name string) interface{ // The returned interface should implement CallOption and any method option that can use it.
	calloptions.CallOption
	methodAOption
	methodBOption
}{
	return struct{ // This implements the returned interface.
		methodAOption
		methodBOption
		calloptions.CallOption
	}{
		CallOption: calloptions.New(
			func(a any) error{ // This applies the option
				switch t := a.(type) {
				case *aCallOptions:
					t.accountName = name
				case *bCallOptions:
					t.accountName = name
				default:
					panic("bug")
				}
				return nil
			},
		),
	}
}

// WithID sets the ID. It can be used on A().
func WithID(id int) interface{
	methodAOption
	calloptions.CallOption
}{
	return struct{
		methodAOption
		calloptions.CallOption
	}{
		CallOption: calloptions.New(
			func(a any) error{
				a.(*aCallOptions).id = id
				return nil
			},
		),
	}
}

func (c Client) A(options ...methodAOption) error {
	opts := aCallOptions{}
	if err := calloptions.ApplyOptions(&opts, options); err != nil {
		return err
	}
	log.Printf("method A received: %#+v", opts)
	return nil
}

type bCallOptions struct {
	accountName string
	turnOn bool
}

type methodBOption interface{
	b()
}

// WithTurnOn turns on something. It only works with B().
func WithTurnOn() interface{
	methodBOption
	calloptions.CallOption
}{
	return struct{
		methodBOption
		calloptions.CallOption
	}{
		CallOption: calloptions.New(
			func(a any) error{
				a.(*bCallOptions).turnOn = true
				return nil
			},
		),
	}
}

func (c Client) B(options ...methodBOption) error {
	opts := bCallOptions{}
	if err := calloptions.ApplyOptions(&opts, options); err != nil {
		return err
	}
	log.Printf("method B received: %#+v", opts)
	return nil
}

Now above we have an option, WithAccount() that can be used with .A() or .B() . We also have WithID() that can only be used with .A() and WithTurnOn() that can only be used with .B(). These are all compile time checked. This example is runable in the example directory.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ApplyOptions

func ApplyOptions[O any, C any](options O, callOptions []C) error

ApplyOptions applies all the callOptions to options. options must be a pointer to a struct and callOptions must be a list of objects that implement CallOption or it will panic.

Types

type CallOption

type CallOption interface {
	Do(a any) error
	// contains filtered or unexported methods
}

CallOption is a type that is implementing an optional argument to a method call.

func New

func New(f func(a any) error) CallOption

New returns a new CallOption where a call to the Do() method calls function "f".

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL