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.
You can install Specify by adding specify to your list of dependencies in mix.exs:
def deps do
[
{:specify, "~> 0.7.0"}
]
endDocumentation can be found at https://hexdocs.pm/specify.
Basic usage is as follows, using Specify.defconfig/1:
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
endand 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
}
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/2It 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:
field :some_field, [:string, :boolean], default: trueLoading 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 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 usesMix.env/Application.get_envto read from the application environment.Specify.Provider.SystemEnv, which usesSystem.get_envto read from system environment variables.Specify.Provider.Process, which usesProcess.getto read from the current process' dictionary.
Often, Providers have sensible default values on how they work, making their usage simpler:
Specify.Provider.Processwill look at the configuredkey, but will default to the configuration specification module name.Specify.Provider.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.Specify.Provider.SystemEnvwill look at the configuredprefixbut 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.
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.
- Compound parsers for collections using
{collection_parser, element_parser}-syntax, with provided:listparser. - Main functionality documentation.
- Parsers documentation.
- Writing basic Tests
- Specify.Parsers
- Main Specify module and functionality.
- Thinking on how to handle environment variable names (capitalization, prefixes).
- Environment Variables (System.get_env) provider
- Specify Provider Tests.
- Better/more examples
- Stable release
- (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.
- 0.10.0 - Adds an
option({atom, term})parser that can be used to parse for instance keyword lists. Thank you, @tanguilp! - 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
optionalkey 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 asMix.Configor the newerElixir.Configfiles.) - 0.6 - Adds the
mfaandfunctionbuiltin parsers. - 0.5 - Adds the
nonnegative_integer,positive_integer,nonnegative_float,positive_floatandtimeoutbuiltin parsers. - 0.4.5 - Fixes built-in
integerandfloatparsers 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:toexplicit_values:and addedSpecify.load_explicit/3function. (Also added tests and fixed parser bugs). - 0.2.0 - Initially released version
I want to thank Chris Keathley for his interesting library 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.
