Self-describing API declarations for Elixir. Define a function's documentation, machine-readable hints metadata, and runtime introspection with a single api() macro call — no separate @doc blocks needed.
Add descripex to your dependencies:
def deps do
[
{:descripex, "~> 0.3"}
]
enddefmodule MyLib.Funding do
use Descripex, namespace: "/funding"
api(:annualize, "Annualize a per-period funding rate to APR.",
params: [
rate: [kind: :value, description: "Per-period funding rate as decimal"],
period_hours: [kind: :value, default: 8, description: "Hours per period"]
],
returns: %{type: :float, description: "Annualized percentage rate"}
)
@spec annualize(number(), pos_integer()) :: float()
def annualize(rate, period_hours \\ 8) do
rate * (365 * 24 / period_hours) * 100
end
endThe api macro generates:
@doc— human-readable documentation from the description and params@doc hints:— machine-readable metadata for agent consumption__api__/0and__api__/1— runtime introspection functions
Descripex validates declarations at compile time:
- Every
api(:name, ...)must have a matchingdef name(...) - Declared param names must match actual function argument names by position
- Mismatches raise
CompileErrorbefore your code ever runs
Descripex automatically escapes { and } in description strings when generating @doc text. This prevents ExDoc's Earmark parser from misinterpreting Elixir-style return types (e.g., {:ok, %{current, history}}) as Inline Attribute Lists.
The raw (unescaped) descriptions are preserved in @doc hints: metadata — only the human-readable @doc text is escaped.
Discover a library's API incrementally — from overview to function detail:
# Make your library discoverable
defmodule MyLib do
use Descripex.Discoverable, modules: [MyLib.Funding, MyLib.Risk]
end
MyLib.describe() # Level 1: library overview
MyLib.describe(:funding) # Level 2: module functions
MyLib.describe(:funding, :annualize) # Level 3: function detailShort names are derived from the last module segment (e.g., MyLib.Funding → :funding). Full module atoms also work. Non-Descripex modules are included with basic function listings.
Or use the functional API directly:
modules = [MyLib.Funding, MyLib.Risk]
Descripex.Describe.describe(modules)
Descripex.Describe.describe(modules, :funding, :annualize)Build a JSON-serializable manifest from all declared modules:
Descripex.Manifest.build([MyLib.Funding, MyLib.Risk])Full documentation is available on HexDocs.
MIT