loading...
Cover image for Episode 3 - The configuration struct

Episode 3 - The configuration struct

victoravelar profile image Victor Hugo Avelar ・3 min read

Before you read

Thanks for spending some minutes reading this, I just want to let you know that from time to time, my ideas are like spaghetti, so if something is not 100% clear, please ask and I will be super happy to answer, probably we will both end with something new to spread around.
😄

Whenever you create a library in any programming language planning how your configuration objects is going to be implemented is super important, as in my opinion it heavily influences the way the code will look and behave.

So for our lovely API client, we are going to have a Config struct, in which we are going to pass the values that are:

  1. Filled from environment variables or secrets.
  2. Variables that affect overall the code behaviour

The first one is pretty much self-explanatory, you don't want to fetch your secrets from Vault (and not this is not sponsored, it just happens to be the one I am familiar with), or require the os package everywhere just to be able to call Getenv.

For our use case we have then the following configuration struct

type Config struct {
    APIKey       string
    InsecureOnly bool
}

As you can see it is convenient that we have one property for each of the scenarios described before, APIKey will be probably filled from a "secure" source that is not visible from your version control system for example, unless you want everyone being able to play around with your dev.to articles.

InsecureOnly is something that will affect how the API client will behave when performing requests to the API.

The constructor

Constructors in go as we are about to see, allows you to control how your client will be build, it is possible that the user will instantiate the struct directly, but as we want to be helpful as possible, we will also provide a friendly way to build a usable Config object.

func NewConfig(p bool, k string) (c *Config, err error) {
    if p == true && k == "" {
        return nil, ErrMissingRequiredParameter
    }

    return &Config{
        InsecureOnly: !p,
        APIKey:       k,
    }, nil
}

Our Config constructor takes a boolean (p) and a string (k), where p represents whether we will use protected endpoints or not, and k is our API key, now once we provide this values the constructor will check and react according to our input.

This check will save us some time, and also will save the dev.to API some unnecessary requests, if we say we want to access protected endpoints but we provide no key, then is obvious that when we perform the request the API is going to give us a lovely http.StatusUnauthorized.

Now if p is false we don't care about the key as for public endpoints a key is not required.

Now, be careful as p is something I like to call a user-friendly variable, this means you are asking the user for something that they can understand: Are you going to access protected endpoints?, easily than: Do you need the client to be insecure only?.

The second one may lead some people to crazy security assumptions, I will probably bet someone could even make a relation with HTTP/HTTPS, so in order to be as clear as possible, we will ask the user-friendly question and then take the opposite value that satisfies our configuration requirements.

And here is an example of how we will use it

package main

import (
    "fmt"
    "log"
    "os"

    "github.com/VictorAvelar/devto-api-go/devto"
)

var (
    k string
    p bool
)

func main() {
    k = os.Getenv("DEVTO_API_KEY") // if not set you will get an empty string
    p = true
    c, err := devto.NewConfig(p, k)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%+v", c)
}

See it in action at the Go playground

Posted on by:

victoravelar profile

Victor Hugo Avelar

@victoravelar

Tech Lead - Independent Hotels Domain / Rates 28, Mexican living in Germany.

Discussion

pic
Editor guide