Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the way to set default values on keys in lists when unmarshalling Yaml in Go?

I have a YAML like one below.

connections:
  - name: demo
    hosts:
      - demo.example.com:9200
    username: admin
    password: password
    ssl: true
    ssl_verify: true
    version: 7
  - name: test
    hosts:
      - "test.example.com:9200"
    username: admin
    password: password

As you can see ssl and ssl_verify is not set in the second item of the list. I want them by default to be true, however, it is not happening. I tried different solutions.

  1. Viper defaults - does not work.
viper.SetDefault("connections[].ssl", "true")
  1. https://github.com/creasty/defaults - does not work.
type Config struct {
    Connections []struct {
        Name      string
        Hosts     []string
        Username  string
        Password  string
        Ssl       bool `default:"true"`
        SslVerify bool `default:"true"`
        Version   int
    }
}

...

err := defaults.Set(config)

  1. Manually looping through the list of structures. While these method work with strings, it does not work with boolean values because they are already initialized with false after unmarshalling and we don't know for sure whether false is entered by the user or not.

  2. Using pointers with boolean values. This works as uninitialized values are equal to nil and they are easy to catch. However, it will require to dereference pointers when using config struct, which is not very convenient. Alternatively, a new struct can be generated based on the one that comes from unmarshalling.

type Config struct {
    Connections []struct {
        Name      string
        Hosts     []string
        Username  string
        Password  string
        Ssl       *bool
        SslVerify *bool
        Version   int
    }
}
  1. Using hashmap instead of struct. This works because empty values are not initialized, however, it will require to run checks on the map elements before accessing them or to convert a map to the struct.

Solutions 4 and 5 will likely work, but I am wondering if there anything better than that.

Any ideas?

like image 733
Vadym Myrgorod Avatar asked May 08 '19 21:05

Vadym Myrgorod


2 Answers

You can use a function to do the job:

type Connection struct {
    Name      string
    Hosts     []string
    Username  string
    Password  string
    Ssl       *bool
    SslVerify bool
    Version   int
}

// If Ssl is nil, returns true
// otherwise the value
func (c Connection) IsSSL() bool {
    return c.Ssl == nil || *c.Ssl
}

type Config struct {
    Connections []Connection
}

EDIT

Or, even better, simply invert the logic of your booleans:

type Connection struct {
    Name          string
    Hosts         []string
    Username      string
    Password      string
    SkipSsl       bool
    SkipSslVerify bool
    Version       int
}

This way, SSL would be used unless explicitly told different in the config - which would stand out when somebody reads the config.

like image 22
Markus W Mahlberg Avatar answered Sep 28 '22 07:09

Markus W Mahlberg


I figured it out. I am using github.com/creasty/defaults in the UnmarshalYAML callback.


type Config struct {
    Connections []Connection
}

type Connection struct {
    Name      string
    Hosts     []string
    Username  string
    Password  string
    Ssl       bool `default:"true"`
    SslVerify bool `default:"true" yaml:"ssl_verify"`
    Version   int  `version:"7"`
}

func (s *Connection) UnmarshalYAML(unmarshal func(interface{}) error) error {
    defaults.Set(s)

    type plain Connection
    if err := unmarshal((*plain)(s)); err != nil {
        return err
    }

    return nil
}

Also this solution pairs well with https://github.com/dealancer/validate for validation purpose.

like image 129
Vadym Myrgorod Avatar answered Sep 28 '22 07:09

Vadym Myrgorod