defmodule Errata.Env do @moduledoc """ A struct that holds compile time environment information that is used for the `:env` field of Errata error types. This struct is a subset of `Macro.Env` and includes the following fields: * `context` - the context of the environment; it can be nil (default context), :guard (inside a guard) or :match (inside a match) * `context_modules` - a list of modules defined in the current context * `file` - the current absolute file name as a binary * `function` - a tuple as {atom, integer}, where the first element is the function name and the second its arity; returns nil if not inside a function * `line` - the current line as an integer * `module` - the current module name * `stacktrace` - the stacktrace for the current process at the time of creation """ @typedoc """ Type to represent the `:env` field of error structs. This struct is a subset of `Macro.Env`. """ @type t :: %Errata.Env{ context: Macro.Env.context(), context_modules: Macro.Env.context_modules(), file: Macro.Env.file(), function: Macro.Env.name_arity() | nil, line: Macro.Env.line(), module: module(), stacktrace: Exception.stacktrace() | nil } @typedoc """ Type to represent an `Errata.Env` struct as a plain, JSON-encodable map. """ @type env_map :: %{ module: String.t(), function: String.t(), file: Macro.Env.file(), line: Macro.Env.line(), file_line: String.t() } defstruct [:context, :context_modules, :file, :function, :line, :module, :stacktrace] @doc """ Creates a new `Errata.Env` struct from the given `Macro.Env` struct. """ @spec new(Macro.Env.t()) :: Errata.Env.t() def new(%Macro.Env{} = env, stacktrace \\ nil) do stacktrace = if is_list(stacktrace) do stacktrace else {:current_stacktrace, stacktrace} = Process.info(self(), :current_stacktrace) stacktrace end params = env |> Map.from_struct() |> Map.put(:stacktrace, stacktrace) struct(__MODULE__, params) end @doc """ Converts the given `Errata.Env` struct to a plain, JSON-encodable map. """ @spec to_map(Errata.Env.t()) :: Errata.Env.env_map() def to_map(%__MODULE__{module: module, file: file, line: line} = env) do %{ # Use `inspect/1` so module names serialize as "MyApp.Foo" rather than # the raw atom form "Elixir.MyApp.Foo". module: inspect(module), function: format_mfa(env), file: file, line: line, # `Exception.format_file_line/2` appends a trailing ":"; drop it. file_line: String.trim_trailing(Exception.format_file_line(file, line), ":") } end def to_map(_), do: %{} defp format_mfa(%{module: module, function: {function, arity}}), do: Exception.format_mfa(module, function, arity) defp format_mfa(_), do: nil end