weavejester/environ

How should I handle maps/vectors?

charlie-harvey opened this issue · 7 comments

Greetings.
The docs for this project are pretty slim because the principle is quite simple, so no big deal. But what happens if I want to pass a map or a vector? This is REAL easy in dev/test because the profiles.clj file is edn. But what happens when I build on my CI server? What happens when I go to production?

environ is expecting a bunch of strings either setup as bash variables or java variables. What happens to my maps? I'll tell you, they get destroyed.

Has anyone else tried to pass anything other than strings/numbers? What do you do for a collection of things?

{:env {:connections [{:uri "amqp://guest:guest@localhost:5672/abc"
                      :queue-names [ "foo" "bar" "baz" ]}
                     {:uri "amqp://guest:guest@localhost:5672/xyz"
                      :queue-names [ "foo" "bar" "baz" ]}]}}

Thanks.

Environ expects the values in the env map to always be strings. In future this will be policed this more vigorously (see #36), so using collections in your :env key is not recommended, and won't work in later versions.

If you want non-string types, you need to parse them in some way, or use a configuration file.

So then all I am putting into :env is the path to the configuration file. Not as fun or convenient. I was secretly hoping there would be some way to run my application and pass it a param which points to a configuration file, but that said configuration file would be read into :env. It reads from .lein-env. Could it read from any file that I pass in?

Thanks.

The .lein-env file is an implementation detail. It's just a way of passing data from the project file to the application, and may be replaced with a different mechanism in future.

Environ sources environment variables and system properties, neither of which have any native support for complex data types. Environ therefore only supports strings for values, as this is the least common denominator.

So this is my best shot so far at grouping things together:

{:env {:connection-1-uri "amqp://guest:guest@localhost:5672/abc"
       :connection-1-queue-names "foo, bar, baz"
       :connection-2-uri "amqp://guest:guest@localhost:5672/xyz"
       :connection-2-queue-names "foo,bar,baz"}}

I will have to split the comma-separated values myself though, which is a bummer.

But properties files do support lists as values. And bash supports arrays as values (separated by :). And System.getProperties() can get you complex types as well (Lists). So not everything has to be a string. The can be a list of strings.

Any advice on this one? Just split the list myself?

Thanks.

Neither System/getenv nor System/getProperties appear to support anything beyond string to string maps. Even if Bash supports arrays, and those arrays can be parsed in Java, what about other shells, particularly on non-unix platforms? Strings are the only universal way of representing a configuration value.

Regarding your configuration example, I'd personally do something like:

{:abc-url    "amqp://guest:guest@localhost:5672/abc"
 :abc-queues "foo,bar,baz"
 :xyz-url    "amqp://guest:guest@localhost:5672/xyz"
 :xyz-queues "foo,bar,baz"}

Environ also isn't the only option for configuration. There are libraries like Nomad as well that use configuration files that support more complex syntax.

Makes sense. I will have a look at Nomad. Although it doesn't seem all that different than doing:

(def props
  (edn/read-string (slurp (io/resource "application.edn")))

I just have to put in some code that passes in a path, probably from environ.

Thanks for all the help.

devth commented

@charlie-harvey I released a library called dec that addresses this exact issue. It lets you encode maps and lists as flat key/val pairs and explode them when you need to. Example from the README:

(explode
  {:dec-level "debug"
   :dec-hosts-0 "a.host.com"
   :dec-hosts-1 "b.host.com"})

{:dec {:hosts ["a.host.com" "b.host.com"], :level "debug"}}