kelseyhightower / envconfig

Golang library for managing configuration data from environment variables
MIT License
5.02k stars 377 forks source link

Allow map[string]string to support ':' in its keys and values #84

Closed sethcleveland closed 7 years ago

sethcleveland commented 7 years ago

This is useful for urls for either keys or values. As of now, envconfig fails with -- invalid map item at https://github.com/kelseyhightower/envconfig/blob/master/envconfig.go#L274.

sethcleveland commented 7 years ago

In my experience, I've seen this handled with doubling the character or escaping it.

teepark commented 7 years ago

Yeah I'm on the fence about this.

On the one hand there's nowhere else that we support escaping of any kind (so you also can't put commas into maps or slices for instance) and that's by design: it's more simple to implement and less to explain. Also there is a big workaround in the custom decoder support -- you can type alias map[string]string and implement your own escape-aware deserialization.

On the other hand the limited character sets are a pretty significant caveat.

sethcleveland commented 7 years ago

Agreed. From a practical standpoint the env value gets hard to understand with the colons etc... I find using the env as the map gets more understandable.

ex.

MAP_FIELD_K1=V1
MAP_FIELD_K2=V2
MAP_FIELD_k3=V3

type Config struct {
 Map map[string]string `envconfig:"MAP_FIELD"`
}

Then envconfig could build this map -- 

map[string]string = {
  "K1": V1,
  "K2": V2,
  "k3": V3,
}
teepark commented 7 years ago

I agree that's a lot clearer, but I'd suggest going one small step further and nail down precisely the map keys you're expecting with a struct type. Then it's covered by the nested struct support we already have.

The implementation change to support arbitrary key/vals under a common prefix like this is a bigger deal than it might seem. Today envconfig never iterates over the whole environment, it just identifies the specific keys it's looking for and asks for them by name.

sethcleveland commented 7 years ago

I agree with calling for values by name. From a programmatic perspective, iterating through the environment wouldn't be ideal. For now, we've implemented the map configuration lookup using a keys env variable with a subsequent call to load each map value from the env.

My concerns more along the operational side of things -- adding/removing entries from the map. This now requires two spots to assert -- keys and value. In my opinion, this works fine for small maps. However, problematic for larger maps. And easy to miss when new engineers come on board.

From a pragmatic perspective, I doubt the environment's gonna be large. Probably only a few hundred entries, if that for a 12-factor app. As a user of this envconfig, I'd like to see more flexibility with loading maps. Such that, when defining the map config variable, the engineer can decide to use the current behavior or the possibly slower one that iterates through the environment for values.

teepark commented 7 years ago

Hrmm. Maybe an alternative approach would just be to serialize your map to JSON in a single env var, and then load it with a custom decoder. That solves your "two places to update" problem at least, while not really being any worse of an environment variable abuse.

But really if your app configuration calls for a key/value store with a dynamic set of keys (especially a large set of keys), you've probably long since crossed the complexity threshold of a config file being more appropriate.

I worry that providing convenient support for complex cases like dynamic maps will encourage use of the environment in places for which it's really poorly suited. If you ask me, envconfig is already pretty far down that road today.

teepark commented 7 years ago

Thanks @sethcleveland for the thoughts. In the interest of clarity and simplicity and because this paradigm can be achieved with a custom decoder implementation, I'm going to close this here.

If you publish a package with a map[string]string alias that implements Decode in this fashion, I'll gladly drop a link in envconfig's README.