Packages

CrucibleFramework: A thin orchestration layer for experiment pipelines. Provides pipeline execution, stage behaviour, and optional persistence.

Current section

Files

Jump to
crucible_framework lib crucible registry.ex
Raw

lib/crucible/registry.ex

defmodule Crucible.Registry do
@moduledoc """
Resolves stage modules from application configuration.
Stages can be resolved either by:
1. Explicit module specification in the `StageDef` struct
2. Name-based lookup from the configured stage registry
## Configuration
Configure the stage registry in your application config:
config :crucible_framework,
stage_registry: %{
validate: Crucible.Stage.Validate,
bench: Crucible.Stage.Bench,
report: Crucible.Stage.Report,
# Add custom stages here
my_stage: MyApp.Stage.Custom
}
## Schema Access
The registry provides functions to access stage schemas:
Crucible.Registry.list_stages_with_schemas()
# => [{:bench, Crucible.Stage.Bench, %{name: :bench, ...}}, ...]
Crucible.Registry.stage_schema(:bench)
# => {:ok, %{name: :bench, description: "...", ...}}
"""
alias Crucible.Stage.Schema.Normalizer
@doc """
Resolves a stage module by name from the configured registry.
## Examples
{:ok, Crucible.Stage.Bench} = Crucible.Registry.stage_module(:bench)
{:error, {:unknown_stage, :missing}} = Crucible.Registry.stage_module(:missing)
"""
@spec stage_module(atom()) :: {:ok, module()} | {:error, term()}
def stage_module(name) when is_atom(name) do
case Application.fetch_env(:crucible_framework, :stage_registry) do
{:ok, map} ->
case Map.fetch(map, name) do
{:ok, mod} -> {:ok, mod}
:error -> {:error, {:unknown_stage, name}}
end
:error ->
{:error, :no_stage_registry}
end
end
@doc """
Lists all registered stages with their modules and schemas.
Returns a list of tuples `{name, module, schema}` for each registered stage.
Schemas are normalized to canonical format.
## Examples
Crucible.Registry.list_stages_with_schemas()
# => [
# {:bench, Crucible.Stage.Bench, %{name: :bench, ...}},
# {:validate, Crucible.Stage.Validate, %{name: :validate, ...}}
# ]
"""
@spec list_stages_with_schemas() :: [{atom(), module(), map()}]
def list_stages_with_schemas do
case Application.fetch_env(:crucible_framework, :stage_registry) do
{:ok, registry} ->
registry
|> Enum.map(fn {name, mod} ->
schema = get_stage_schema(mod)
{name, mod, schema}
end)
|> Enum.sort_by(&elem(&1, 0))
:error ->
[]
end
end
@doc """
Gets the schema for a specific registered stage by name.
Returns `{:ok, schema}` with the normalized schema, or
`{:error, reason}` if the stage is not found or has no schema.
## Examples
{:ok, schema} = Crucible.Registry.stage_schema(:bench)
schema.name
# => :bench
{:error, {:unknown_stage, :missing}} = Crucible.Registry.stage_schema(:missing)
"""
@spec stage_schema(atom()) ::
{:ok, Crucible.Stage.Schema.t()}
| {:error,
:no_stage_registry
| {:no_describe_callback, atom()}
| {:unknown_stage, atom()}}
def stage_schema(name) when is_atom(name) do
case stage_module(name) do
{:ok, mod} ->
Code.ensure_loaded!(mod)
if function_exported?(mod, :describe, 1) do
schema = mod.describe(%{}) |> Normalizer.normalize()
{:ok, schema}
else
{:error, {:no_describe_callback, mod}}
end
{:error, _} = error ->
error
end
end
@doc """
Lists all registered stage names.
## Examples
Crucible.Registry.list_stages()
# => [:bench, :data_checks, :guardrails, :report, :validate]
"""
@spec list_stages() :: [atom()]
def list_stages do
case Application.fetch_env(:crucible_framework, :stage_registry) do
{:ok, registry} ->
registry |> Map.keys() |> Enum.sort()
:error ->
[]
end
end
# Private helpers
defp get_stage_schema(mod) do
Code.ensure_loaded!(mod)
if function_exported?(mod, :describe, 1) do
mod.describe(%{}) |> Normalizer.normalize()
else
%{name: nil, description: "No describe/1 callback", required: [], optional: [], types: %{}}
end
end
end