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 versionBuild StatusInline docs


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.

Installation

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

def deps do
  [
    {:specify, "~> 0.4.0"}
  ]
end

Documentation 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
end
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:

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

Loading 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
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).

Most providers have sensible default values on how they work:

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

Possibilities for the future

Changelog

Attribution

I want to thank Chris Keathley for his interesting library Vapor which helped inspire Specify.