DEV Community

Cover image for To the point: options as function for your APIs
HM
HM

Posted on

To the point: options as function for your APIs

Problem

You have created a super performant csv reader that look like:

package awesomecsvreader

type csvReader struct{
    filename string
    delim string
    escape string
}

func (*csvReader) Read() {
    //...your awesome secret sauce
}
Enter fullscreen mode Exit fullscreen mode

But whats missing it a simple API for users to use your module, that doesn;t look like this:

func NewCsvReader(filename string, delim string, escape string) {
    ....
} 
// ? what happens when new options are added like "separator". 
// red alert: api could break!
Enter fullscreen mode Exit fullscreen mode

Solution

Thoughts:

One could use structs for options but that has the following demerits:

  • there is no good way of setting up default values for the options, unless the defaults are equal to zero values
  • an empty options struct needs to be passed by the user of the api

So, instead of using struct, use the power of functions with closures

Let's code

Commented inline for brevity.

// your module
package awesomecsvreader

import (
    "fmt"
)

// this is what my csvReader struct looks like
type csvReader struct{
    filename string
    delim string
    escape string
}

// I define a type that is a func that takes csvReader and sets the option
type option func(*csvReader)

// Take the delim string and return the option
func SetDelim(delim string) option{
    return func(cr *csvReader) {
        (*cr).delim = delim
    }
}

// Take the escape string and return the option
func SetEscape(escape string) option{
    return func(cr *csvReader) {
        cr.escape = escape
    }
}

// Constructor that takes required values like filename
//  and setters that are of type option
func NewCsvReader(filename string, setters ...option) *csvReader{

    // construct a csvReader with default values
    cr := csvReader{
        filename: filename,
        delim: "CRLB",
        escape: "ESC",
    }

    // change the default values as per those supplied by the user
    // by calling the option
    for _, setter := range setters {
        setter(&cr)
    }

    return &cr
}
Enter fullscreen mode Exit fullscreen mode
// I am a user of your API
package main

func main() {

    //
    // create a csvReader with default options
    fmt.Println(NewCsvReader("file123"))

    //
    // create a csvReader with mix of default and custom options
    r1 := NewCsvReader("file123", SetDelim("XXX"))
    fmt.Println(r1)

    //
    // create a csvReader with all custom options
    r2 := NewCsvReader("file123", SetDelim("XXX"), SetEscape("YYY"))
    fmt.Println(r2)
}
Enter fullscreen mode Exit fullscreen mode
// output
&{file123 CRLB ESC}
&{file123 XXX ESC}
&{file123 XXX YYY}
Enter fullscreen mode Exit fullscreen mode

Good ref: https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis

Top comments (0)