README.md

![](https://raw.githubusercontent.com/Qqwy/elixir_specify/master/brand/logo-text.png)

`Specify` is a library to create Comfortable, Explicit, Multi-Layered and Well-Documented Specifications for all your configurations, settings and options in Elixir.

[![hex.pm version](https://img.shields.io/hexpm/v/specify.svg)](https://hex.pm/packages/specify)
[![Build Status](https://travis-ci.org/Qqwy/elixir_confy.svg?branch=master)](https://travis-ci.org/Qqwy/elixir_confy)
[![Documentation](https://img.shields.io/badge/hexdocs-latest-blue.svg)](https://hexdocs.pm/specify/index.html)
[![Inline docs](http://inch-ci.org/github/qqwy/elixir_specify.svg)](http://inch-ci.org/github/qqwy/elixir_specify)

---

Basic features:

- Configuration is converted to a struct, with fields being parsed to their appropriate types.
- Specify a stack of sources to fetch the configuration from.
- Always possible to override local configuration using plain arguments to a function call.
- Fail-fast on missing or malformed values.
- Auto-generated documentation based on your config specification.

Specify can be used both to create normalized configuration structs during runtime and compile-time using both implicit external configuration sources and explicit arguments to a function call.

## Installation

You can install Specify by adding `specify` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:specify, "~> 0.7.0"}
  ]
end
```

Documentation can be found at [https://hexdocs.pm/specify](https://hexdocs.pm/specify).

## Examples


Basic usage is as follows, using `Specify.defconfig/1`:


```elixir

defmodule Cosette.CastleOnACloud do
  require Specify
  Specify.defconfig do
    @doc "there are no floors for me to sweep"
    field :floors_to_sweep, :integer, default: 0

    @doc "there are a hundred boys and girls"
    field :amount_boys_and_girls, :integer, default: 100

    @doc "The lady all in white holds me and sings a lullaby"
    field :lullaby, :string

    @doc "Crying is usually not allowed"
    field :crying_allowed, :boolean, default: false
  end
end
```

and later `Specify.load/2`, `Specify.load_explicit/3` (or `YourModule.load/1`, `YourModule.load_explicit/2` which are automatically defined).
```
iex> Cosette.CastleOnACloud.load(explicit_values: [lullaby: "I love you very much", crying_allowed: true])
%Cosette.CastleOnACloud{
  crying_allowed: true,
  floors_to_sweep: 0,
  lullaby: "I love you very much",
  amount_boys_and_girls: 100
}

```

### Mandatory Fields 

Notice that since the `:lullaby`-field is mandatory, if it is not defined in any of the configuration sources, an error will be thrown:

```elixir
Cosette.CastleOnACloud.load
** (Specify.MissingRequiredFieldsError) Missing required fields for `Elixir.Cosette.CastleOnACloud`: `:lullaby`.
    (specify) lib/specify.ex:179: Specify.prevent_missing_required_fields!/3
    (specify) lib/specify.ex:147: Specify.load/2
```

### Multiple parsers

It is possible to specify several parsers for a unique field, using a list of parsers. They
will be tried consecutively in list order. For instance:

```elixir
field :some_field, [:string, :boolean], default: true
```

### Loading from Sources

Loading from another source is easy:

```elixir
iex> Application.put_env(Cosette.CastleOnACloud, :lullaby, "sleep little darling")
# or: in a Mix config.ex file
config Cosette.CastleOnACloud, lullaby: "sleep little darling"
```
```elixir
iex> Cosette.CastleOnACloud.load(sources: [Specify.Provider.MixEnv])
%Cosette.CastleOnACloud{
  crying_allowed: false,
  floors_to_sweep: 0,
  lullaby: "sleep little darling",
  no_boys_and_girls: 100
}
```

Rather than passing in the sources when loading the configuration, it often makes more sense to specify them when defining the configuration:

```elixir
defmodule Cosette.CastleOnACloud do
  require Specify
  Specify.defconfig sources: [Specify.Provider.MixEnv] do
    # ...
  end
end
```

## Providers

Providers can be specified by passing them to the `sources:` option (while loading the configuration structure or while defining it).
They can also be set globally by altering the `sources:` key of the `Specify` application environment, or per-process using the `:sources` subkey of the `Specify` key in the current process' dictionary (`Process.put_env`).

Be aware that for bootstrapping reasons, it is impossible to override the `:sources` field globally in an external source (because Specify would not know where to find it).

`Specify` comes with the following built-in providers:

- `Specify.Provider.MixEnv`, which uses `Mix.env` / `Application.get_env` to read from the application environment.
- `Specify.Provider.SystemEnv`, which uses `System.get_env` to read from system environment variables.
- `Specify.Provider.Process`, which uses `Process.get` to read from the current process' dictionary.

Often, Providers have sensible default values on how they work, making their usage simpler:
- `Specify.Provider.Process` will look at the configured `key`, but will default to the configuration specification module name.
- `Specify.Provider.MixEnv` will look at the configured `application_name` and `key`, but will default to the whole environment of an application (`Application.get_all_env`) if no key was set, with `application_name` defaulting to the configuration specification module name.
- `Specify.Provider.SystemEnv` will look at the configured `prefix` but will default to the module name (in all caps), followed by the field name (in all caps, separated by underscores). What names should be used for a field is also configurable.

## Writing Providers

Providers implement the `Specify.Provider` protocol, which consists of only one function: `load/2`.
Its first argument is the implementation's own struct, the second argument being the configuration specification's module name.
If extra information is required about the configuration specification to write a good implementation, the Reflection function `module_name.__specify__`  can be used to look these up.


## Roadmap

- [x] Compound parsers for collections using `{collection_parser, element_parser}`-syntax, with provided `:list` parser.
- [x] Main functionality documentation.
- [x] Parsers documentation.
- [x] Writing basic Tests
  - [x] Specify.Parsers
  - [x] Main Specify module and functionality.
- [x] Thinking on how to handle environment variable names (capitalization, prefixes).
- [x] Environment Variables (System.get_env) provider
- [x] Specify Provider Tests.
- [ ] Better/more examples
- [ ] Stable release

## Possibilities for the future

- (D)ETS provider
- CLI arguments provider, which could be helpful for defining e.g. Mix tasks.
- .env files provider.
- JSON and YML files provider.
- Nested configs?
- Possibility to load without raising on parsing falure (instead returning a success/failure tuple?)
- Watching for updates and call a configurable handler function when configuration has changed.

## Changelog

- 0.9.0 - Allows multi-value parsers by specifying a list of parsers. Thank you, @tanguilp!
- 0.8.0 - Makes string-parsers work on more of Elixir's builtin terms including lists and maps of other types (including lists and maps themselves). Thank you, @tanguilp!
- 0.7.2 - Makes functions clickable in the generated documentation. Thank you, @tanguilp!
- 0.7.1 - Pretty-prints long default values in a multi-line code block in the documentation (#2).
- 0.7 - Adds an `optional` key to the built-in providers. They will only return `{error, :not_found}` if they are not set to optional. Also adds two new ways to indicate sources, which are helpful in environments where you do not have access to the structs directly (such as `Mix.Config` or the newer `Elixir.Config` files.)
- 0.6 - Adds the `mfa` and `function` builtin parsers.
- 0.5 - Adds the `nonnegative_integer`, `positive_integer`, `nonnegative_float`, `positive_float` and `timeout` builtin parsers.
- 0.4.5 - Fixes built-in `integer` and `float` parsers to not crash on input like `"10a"` (but instead return `{:error, _}`).
- 0.4.4 - Fixes references to validation/parsing functions in documentation.
- 0.4.2 - Finishes provider tests; bugfix for the MixEnv provider.
- 0.4.1 - Improves documentation.
- 0.4.0 - Name change: from 'Confy' to 'Specify'. This name has been chosen to be more clear about the intent of the library.
- 0.3.0 - Changed `overrides:` to `explicit_values:` and added `Specify.load_explicit/3` function. (Also added tests and fixed parser bugs).
- 0.2.0 - Initially released version


## Attribution

I want to thank Chris Keathley for his interesting library [Vapor](https://github.com/keathley/vapor) which helped inspire Specify.

I also want to thank José Valim for the great conversations we've had about the advantages and disadvantages of various approaches to configuring Elixir applications.