Specify is a library to create Comfortable, Explicit, Multi-Layered and Well-Documented Specifications for all your configurations, settings and options in Elixir.
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:
def deps do
[
{:specify, "~> 0.4.0"}
]
endDocumentation can be found at https://hexdocs.pm/specify.
Examples
Basic usage is as follows:
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
endiex> 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:
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/2Loading from Sources
Loading from another source is easy:
iex> Application.put_env(Cosette.CastleOnACloud, :lullaby, "sleep little darling")
# or: in a Mix config.ex file
config Cosette.CastleOnACloud, lullaby: "sleep little darling"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:
defmodule Cosette.CastleOnACloud do
require Specify
Specify.defconfig sources: [Specify.Provider.MixEnv] do
# ...
end
endProviders
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).
Most providers have sensible default values on how they work:
Specify.Provider.Processwill look at the configuredkey, but will default to the configuration specification module name.Specify.Providers.MixEnvwill look at the configuredapplication_nameandkey, but will default to the whole environment of an application (Application.get_all_env) if no key was set, withapplication_namedefaulting to the configuration specification module name.
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:listparser. - [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.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:toexplicit_values:and addedSpecify.load_explicit/3function. (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 which helped inspire Specify.