A library to create Comfortable, Explicit, Multi-Layered and Well-Documented Specifications for all your configurations, settings and options in Elixir.
MIT License
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:
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"}
]
end
Documentation 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
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
}
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/2
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:
field :some_field, [:string, :boolean], default: true
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
end
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.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.
{collection_parser, element_parser}
-syntax, with provided :list
parser.option({atom, term})
parser that can be used to parse for instance keyword lists. Thank you, @tanguilp!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.)mfa
and function
builtin parsers.nonnegative_integer
, positive_integer
, nonnegative_float
, positive_float
and timeout
builtin parsers.integer
and float
parsers to not crash on input like "10a"
(but instead return {:error, _}
).overrides:
to explicit_values:
and added Specify.load_explicit/3
function. (Also added tests and fixed parser bugs).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.