ExCodecs.CodecRegistry (ex_codecs v0.2.3)

Copy Markdown View Source

Shared runtime catalog of codec implementations (ETS-backed).

Populated at application start (and again if this process restarts). Entries carry a category and interface shape: binary codecs use the top-level registry API, while spatial entries are dispatched through ExCodecs.Spatial.

Crash behavior

The ETS table is owned by this Agent process. If the Agent crashes, the table is destroyed and only the :register callback (built-ins) is replayed on restart. Runtime third-party registrations made via register/3 or register/5 are lost and must be re-registered by the caller. If the Agent restarts while the table still exists (e.g. via a supervised :restart_for_type), existing entries are preserved and only overwritten by the callback.

Typical use

iex> ExCodecs.available_codecs()
[:blosc2, :bzip2, :gsplat, :lz4, :ply, :snappy, :spatial_binary, :zstd]

iex> ExCodecs.supports?(:zstd)
true

Summary

Functions

Sorted list of all registered names (including unavailable).

Sorted list of codec atoms with a non-nil module.

Sorted available catalog names in one category.

Returns %ExCodecs.Codec{} for name.

Lists %ExCodecs.Codec{} for a category atom (e.g. :compression).

Looks up {module, category, info} for name.

Registers a binary-interface module that exports encode/2 and decode/2.

Registers a module in the shared catalog with an explicit interface.

Registers a module with an explicit interface and optional metadata overrides.

Registers a known codec with module: nil (unavailable).

Registers a known codec as unavailable with an explicit interface.

Starts the registry Agent and optional re-registration callback.

Returns true if registered and module != nil.

Deletes a registry entry (tests / hot reload).

Functions

all_codecs()

@spec all_codecs() :: [atom()]

Sorted list of all registered names (including unavailable).

Arguments

None.

Returns

A sorted [atom()], including entries whose module is nil.

Raises

May raise ArgumentError if the registry ETS table does not exist.

Example

iex> :ok = ExCodecs.CodecRegistry.register_unavailable(:documented_optional, :compression)
iex> :documented_optional in ExCodecs.CodecRegistry.all_codecs()
true
iex> ExCodecs.CodecRegistry.unregister(:documented_optional)
:ok

available_codecs()

@spec available_codecs() :: [atom()]

Sorted list of codec atoms with a non-nil module.

Arguments

None.

Returns

A sorted [atom()]. Unavailable entries registered with register_unavailable/2 are omitted.

Raises

May raise ArgumentError if the registry ETS table does not exist.

Example

iex> codecs = ExCodecs.CodecRegistry.available_codecs()
iex> codecs == Enum.sort(codecs) and :zstd in codecs
true

available_codecs(category)

@spec available_codecs(atom()) :: [atom()]

Sorted available catalog names in one category.

Arguments

  • category — category atom such as :compression or :spatial

Returns

Available names with non-nil modules, sorted by name.

Raises

May raise ArgumentError if the registry table has not started.

Examples

iex> ExCodecs.CodecRegistry.available_codecs(:spatial)
[:gsplat, :ply, :spatial_binary]

codec_info(name)

@spec codec_info(atom()) :: {:ok, ExCodecs.Codec.t()} | {:error, :unsupported_codec}

Returns %ExCodecs.Codec{} for name.

Arguments

  • name (atom()) — codec registry key

Returns

  • {:ok, ExCodecs.Codec.t()} — metadata for a registered codec, including unavailable codecs
  • {:error, :unsupported_codec} — no registry entry exists

Raises

Raises FunctionClauseError when name is not an atom. May raise ArgumentError if the registry ETS table does not exist.

Example

iex> {:ok, %ExCodecs.Codec{name: :zstd, category: :compression}} =
...>   ExCodecs.CodecRegistry.codec_info(:zstd)
iex> true
true

codecs_by_category(category)

@spec codecs_by_category(atom()) :: [ExCodecs.Codec.t()]

Lists %ExCodecs.Codec{} for a category atom (e.g. :compression).

Arguments

  • category (atom()) — category to select, such as :compression

Returns

A [ExCodecs.Codec.t()] sorted by each struct's name. The list includes unavailable entries in that category and is empty when none match.

Raises

Raises FunctionClauseError when category is not an atom. May raise ArgumentError if the registry ETS table does not exist.

Example

iex> codecs = ExCodecs.CodecRegistry.codecs_by_category(:compression)
iex> Enum.all?(codecs, &match?(%ExCodecs.Codec{category: :compression}, &1))
true
iex> Enum.map(codecs, & &1.name) == Enum.sort(Enum.map(codecs, & &1.name))
true

lookup(name)

@spec lookup(atom()) ::
  {:ok, {module() | nil, atom(), ExCodecs.Codec.t()}}
  | {:error, :unsupported_codec}

Looks up {module, category, info} for name.

Arguments

  • name (atom()) — codec registry key

Returns

  • {:ok, {module() | nil, atom(), ExCodecs.Codec.t()}} — implementation, category, and metadata for a registered name

  • {:error, :unsupported_codec} — no entry exists for name

Raises

Raises FunctionClauseError when name is not an atom. May raise ArgumentError if the registry ETS table does not exist.

Example

iex> {:ok, {module, :compression, info}} = ExCodecs.CodecRegistry.lookup(:zstd)
iex> {module, info.name}
{ExCodecs.Compression.Zstd, :zstd}

register(name, module, category)

@spec register(atom(), module(), atom()) :: :ok | {:error, term()}

Registers a binary-interface module that exports encode/2 and decode/2.

Arguments

  • name (atom()) — registry key used for lookups
  • module (module()) — loaded module exporting encode/2 and decode/2
  • category (atom()) — grouping key, such as :compression

Returns

  • :ok — metadata was stored, replacing any entry with the same name
  • {:error, {:invalid_codec_module, module}} — the module cannot be loaded or does not export both codec callbacks

Raises

May raise ArgumentError if the registry ETS table does not exist. A codec's optional public __codec_info__/0 function is called during registration and any exception it raises propagates.

Examples

iex> :ok = ExCodecs.CodecRegistry.register(
...>   :zstd,
...>   ExCodecs.Compression.Zstd,
...>   :compression
...> )
iex> ExCodecs.CodecRegistry.supports?(:zstd)
true

register(name, module, category, interface)

@spec register(atom(), module(), atom(), :binary | :spatial) :: :ok | {:error, term()}

Registers a module in the shared catalog with an explicit interface.

:binary entries are dispatched by ExCodecs.encode/3 / decode/3. :spatial entries are discoverable from the same catalog but dispatched by ExCodecs.Spatial.

Arguments

  • name — catalog key
  • module — module exporting encode/2 and decode/2
  • category — grouping atom such as :compression or :spatial
  • interface:binary or :spatial

Returns

:ok on registration or {:error, {:invalid_codec_module, module}}.

Raises

May raise ArgumentError if the registry table has not started, or propagate an exception from a module's optional __codec_info__/0.

Examples

iex> :ok = ExCodecs.CodecRegistry.register(
...>   :documented_ply,
...>   ExCodecs.Spatial.Codec.PLY,
...>   :spatial,
...>   :spatial
...> )
iex> {:ok, info} = ExCodecs.CodecRegistry.codec_info(:documented_ply)
iex> {info.category, info.interface}
{:spatial, :spatial}
iex> ExCodecs.CodecRegistry.unregister(:documented_ply)
:ok

register(name, module, category, interface, metadata)

@spec register(atom(), module(), atom(), :binary | :spatial, keyword()) ::
  :ok | {:error, term()}

Registers a module with an explicit interface and optional metadata overrides.

Builds the stored %ExCodecs.Codec{} from name, module, category, and interface, then merges capability fields from the module's optional ExCodecs.Codec.__codec_info__/0 (when exported). Each key in metadata overrides the corresponding field from __codec_info__/0.

Arguments

  • name (atom()) — catalog key (always stored; not taken from metadata)

  • module (module()) — module exporting encode/2 and decode/2

  • category (atom()) — grouping atom such as :compression or :spatial

  • interface (:binary | :spatial) — public API shape for the entry

  • metadata (keyword()) — optional overrides for capability fields:

    • :native? (boolean()) — NIF-backed implementation
    • :streaming? (boolean()) — incremental API available
    • :configurable? (boolean()) — meaningful codec-specific options
    • :version (String.t() | nil) — backend version string

    Unknown keys are ignored. Omitted keys keep the value from __codec_info__/0, or nil when that function is not exported.

Returns

  • :ok — metadata was stored, replacing any entry with the same name
  • {:error, {:invalid_codec_module, module}} — the module cannot be loaded or does not export both codec callbacks

Raises

May raise ArgumentError if the registry ETS table does not exist. A codec's optional __codec_info__/0 is called during registration and any exception it raises propagates.

Persistence

This entry is stored in an ETS table owned by the registry Agent. If the Agent crashes, runtime registrations are not replayed — only the :register callback supplied at start_link/1 runs on restart. Re-register third-party codecs from your own supervision tree if you need crash survivability.

Examples

iex> :ok = ExCodecs.CodecRegistry.register(
...>   :documented_spatial_binary,
...>   ExCodecs.Spatial.Codec.Binary,
...>   :spatial,
...>   :spatial,
...>   native?: false,
...>   streaming?: false,
...>   configurable?: false,
...>   version: "EXCP 1"
...> )
iex> {:ok, info} = ExCodecs.CodecRegistry.codec_info(:documented_spatial_binary)
iex> {info.interface, info.version, info.native?}
{:spatial, "EXCP 1", false}
iex> ExCodecs.CodecRegistry.unregister(:documented_spatial_binary)
:ok

register_unavailable(name, category)

@spec register_unavailable(atom(), atom()) :: :ok

Registers a known codec with module: nil (unavailable).

Arguments

  • name (atom()) — known codec's registry key
  • category (atom()) — grouping key, such as :compression

Returns

Always :ok after storing a %ExCodecs.Codec{module: nil} entry.

Raises

May raise ArgumentError if the registry ETS table does not exist.

Example

iex> :ok = ExCodecs.CodecRegistry.register_unavailable(:example_optional, :compression)
iex> {:ok, info} = ExCodecs.CodecRegistry.codec_info(:example_optional)
iex> {info.module, ExCodecs.CodecRegistry.supports?(:example_optional)}
{nil, false}
iex> ExCodecs.CodecRegistry.unregister(:example_optional)
:ok

register_unavailable(name, category, interface)

@spec register_unavailable(atom(), atom(), :binary | :spatial) :: :ok

Registers a known codec as unavailable with an explicit interface.

Stores %ExCodecs.Codec{module: nil} with capability fields set to false and version: nil. Use when a codec name is part of the catalog but its implementation module cannot be loaded (for example a NIF-backed codec when the native library failed to load).

Arguments

  • name (atom()) — known codec's registry key
  • category (atom()) — grouping key, such as :compression or :spatial
  • interface (:binary | :spatial) — public API shape for the entry

Returns

Always :ok after storing the unavailable entry.

Raises

May raise ArgumentError if the registry ETS table does not exist.

Example

iex> :ok = ExCodecs.CodecRegistry.register_unavailable(
...>   :example_spatial_optional,
...>   :spatial,
...>   :spatial
...> )
iex> {:ok, info} = ExCodecs.CodecRegistry.codec_info(:example_spatial_optional)
iex> {info.module, info.interface, ExCodecs.CodecRegistry.supports?(:example_spatial_optional)}
{nil, :spatial, false}
iex> ExCodecs.CodecRegistry.unregister(:example_spatial_optional)
:ok

start_link(opts \\ [])

@spec start_link(keyword()) :: Agent.on_start()

Starts the registry Agent and optional re-registration callback.

Arguments

  • opts (keyword()) — accepts :register, a zero-arity function invoked after the named ETS table is created or cleared; it defaults to fn -> :ok end

Returns

  • {:ok, pid()} — the named registry Agent started and initialization ran
  • {:error, {:already_started, pid()}} — the registry is already running
  • {:error, reason} — Agent startup or the registration callback failed

Raises

With a valid keyword list, startup failures are returned by Agent. Passing a non-keyword value can raise FunctionClauseError while reading options.

Example

A supervision tree normally starts the registry and supplies the callback:

children = [
  {ExCodecs.CodecRegistry, register: &MyApp.Codecs.register_all/0}
]

Supervisor.start_link(children, strategy: :one_for_one)

supports?(name)

@spec supports?(atom()) :: boolean()

Returns true if registered and module != nil.

Arguments

  • name (atom()) — registry key to test

Returns

true only when name is registered with a non-nil module; otherwise false.

Raises

Raises FunctionClauseError when name is not an atom. May raise ArgumentError if the registry ETS table does not exist.

Example

iex> ExCodecs.CodecRegistry.supports?(:zstd)
true
iex> ExCodecs.CodecRegistry.supports?(:unknown_codec)
false

unregister(name)

@spec unregister(atom()) :: :ok

Deletes a registry entry (tests / hot reload).

Arguments

  • name (atom()) — registry key to remove

Returns

Always :ok, whether or not an entry existed.

Raises

Raises FunctionClauseError when name is not an atom. May raise ArgumentError if the registry ETS table does not exist.

Example

iex> :ok = ExCodecs.CodecRegistry.register_unavailable(:temporary_codec, :compression)
iex> :ok = ExCodecs.CodecRegistry.unregister(:temporary_codec)
iex> ExCodecs.CodecRegistry.lookup(:temporary_codec)
{:error, :unsupported_codec}