defmodule Toml do
@external_resource "README.md"
@moduledoc File.read!(Path.join([__DIR__, "..", "README.md"]))
@type key :: binary | atom | term
@type opt ::
{:keys, :atoms | :atoms! | :string | (key -> term)}
| {:filename, String.t()}
| {:transforms, [Toml.Transform.t()]}
@type opts :: [opt]
@type reason :: {:invalid_toml, binary} | binary
@type error :: {:error, reason}
@doc """
Decode the given binary as TOML content
## Options
You can pass the following options to configure the decoder behavior:
* `:filename` - pass a filename to use in error messages
* `:keys` - controls how keys in the document are decoded. Possible values are:
* `:strings` (default) - decodes keys as strings
* `:atoms` - converts keys to atoms with `String.to_atom/1`
* `:atoms!` - converts keys to atoms with `String.to_existing_atom/1`
* `(key -> term)` - converts keys using the provided function
* `:transforms` - a list of custom transformations to apply to decoded TOML values,
see `c:Toml.Transform.transform/2` for details.
## Decoding keys to atoms
The `:atoms` option uses the `String.to_atom/1` call that can create atoms at runtime.
Since the atoms are not garbage collected, this can pose a DoS attack vector when used
on user-controlled data. It is recommended that if you either avoid converting to atoms,
by using `keys: :strings`, or require known keys, by using the `keys: :atoms!` option,
which will cause decoding to fail if the key is not an atom already in the atom table.
## Transformations
You should rarely need custom datatype transformations, but in some cases it can be quite
useful. In particular if you want to transform things like IP addresses from their string
form to the Erlang address tuples used in most `:inet` APIs, a custom transform can ensure
that all addresses are usable right away, and that validation of those addresses is done as
part of decoding the document.
Keep in mind that transforms add additional work to decoding, which may result in reduced
performance, if you don't need the convenience, or the validation, deferring such conversions
until the values are used may be a better approach, rather than incurring the overhead during decoding.
"""
@spec decode(binary) :: {:ok, map} | error
@spec decode(binary, opts) :: {:ok, map} | error
defdelegate decode(bin, opts \\ []), to: __MODULE__.Decoder
@doc """
Same as `decode/1`, but returns the document directly, or raises `Toml.Error` if it fails.
"""
@spec decode!(binary) :: map | no_return
@spec decode!(binary, opts) :: map | no_return
defdelegate decode!(bin, opts \\ []), to: __MODULE__.Decoder
@doc """
Decode the file at the given path as TOML
Takes same options as `decode/2`
"""
@spec decode_file(binary) :: {:ok, map} | error
@spec decode_file(binary, opts) :: {:ok, map} | error
defdelegate decode_file(path, opts \\ []), to: __MODULE__.Decoder
@doc """
Same as `decode_file/1`, but returns the document directly, or raises `Toml.Error` if it fails.
"""
@spec decode_file!(binary) :: map | no_return
@spec decode_file!(binary, opts) :: map | no_return
defdelegate decode_file!(path, opts \\ []), to: __MODULE__.Decoder
@doc """
Decode the given stream as TOML.
Takes same options as `decode/2`
"""
@spec decode_stream(Enumerable.t()) :: {:ok, map} | error
@spec decode_stream(Enumerable.t(), opts) :: {:ok, map} | error
defdelegate decode_stream(stream, opts \\ []), to: __MODULE__.Decoder
@doc """
Same as `decode_stream/1`, but returns the document directly, or raises `Toml.Error` if it fails.
"""
@spec decode_stream!(Enumerable.t()) :: map | no_return
@spec decode_stream!(Enumerable.t(), opts) :: map | no_return
defdelegate decode_stream!(stream, opts \\ []), to: __MODULE__.Decoder
end