defmodule ExCodecs do
@moduledoc """
An extensible BEAM-native codec framework for Elixir.
ExCodecs provides a unified API for compression, decompression, hashing,
checksums, binary encodings, and future content-addressing codecs.
## Quick Start
# Compression
{:ok, compressed} = ExCodecs.encode(:zstd, my_binary)
{:ok, original} = ExCodecs.decode(:zstd, compressed)
# With options
{:ok, compressed} = ExCodecs.encode(:zstd, my_binary, level: 3)
{:ok, compressed} = ExCodecs.encode(:blosc2, my_binary, cname: :zstd, clevel: 5, shuffle: :byte)
# Discovery
ExCodecs.available_codecs() #=> [:blosc2, :bzip2, :lz4, :snappy, :zstd]
ExCodecs.supports?(:zstd) #=> true
ExCodecs.codec_info(:zstd) #=> {:ok, %ExCodecs.Codec{...}}
## Supported Codecs
| Codec | Category | Description |
|----------|-------------|--------------------------------------|
| `:zstd` | compression | Zstandard - high ratio, good speed |
| `:lz4` | compression | LZ4 - extremely fast |
| `:snappy`| compression | Snappy - fast, low overhead |
| `:bzip2` | compression | Bzip2 - high ratio, slower |
| `:blosc2`| compression | Blosc2 - meta-compressor for arrays |
## Design Philosophy
ExCodecs is not a compression library. It is a codec framework.
Compression is merely the first codec category. The architecture
supports future expansion into hashing, checksums, binary encodings,
content addressing, and streaming — without changing the public API.
"""
alias ExCodecs.{Codec, CodecRegistry, Error}
@doc """
Encodes data using the specified codec.
For compression codecs, this compresses the data. For other codec
categories, the semantics depend on the codec type.
## Arguments
* `codec` - The codec atom (e.g., `:zstd`, `:lz4`)
* `data` - The binary data to encode
* `opts` - Codec-specific options (default: `[]`)
## Returns
* `{:ok, encoded_binary}` - Successfully encoded data
* `{:error, %ExCodecs.Error{}}` - Encoding failed
## Examples
iex> {:ok, compressed} = ExCodecs.encode(:zstd, "hello world")
iex> is_binary(compressed)
true
iex> {:ok, compressed} = ExCodecs.encode(:zstd, "hello world", level: 3)
iex> is_binary(compressed)
true
"""
@spec encode(atom(), binary(), keyword()) :: {:ok, binary()} | {:error, Error.t()}
def encode(codec, data, opts \\ [])
def encode(codec, data, opts) when is_atom(codec) and is_binary(data) and is_list(opts) do
case CodecRegistry.lookup(codec) do
{:ok, {module, _category, info}} ->
case ensure_available(info, codec) do
:ok -> module.encode(data, opts)
{:error, %Error{} = error} -> {:error, error}
end
{:error, :unsupported_codec} ->
{:error, Error.new(:unsupported_codec, codec: codec)}
end
end
def encode(_codec, _data, _opts) do
{:error, Error.new(:invalid_data, message: "Data must be a binary")}
end
@doc """
Decodes data using the specified codec.
For compression codecs, this decompresses the data.
## Arguments
* `codec` - The codec atom (e.g., `:zstd`, `:lz4`)
* `data` - The binary data to decode
* `opts` - Codec-specific options (default: `[]`)
## Returns
* `{:ok, decoded_binary}` - Successfully decoded data
* `{:error, %ExCodecs.Error{}}` - Decoding failed
## Examples
iex> {:ok, compressed} = ExCodecs.encode(:zstd, "hello world")
iex> {:ok, original} = ExCodecs.decode(:zstd, compressed)
iex> original
"hello world"
"""
@spec decode(atom(), binary(), keyword()) :: {:ok, binary()} | {:error, Error.t()}
def decode(codec, data, opts \\ [])
def decode(codec, data, opts) when is_atom(codec) and is_binary(data) and is_list(opts) do
case CodecRegistry.lookup(codec) do
{:ok, {module, _category, info}} ->
case ensure_available(info, codec) do
:ok -> module.decode(data, opts)
{:error, %Error{} = error} -> {:error, error}
end
{:error, :unsupported_codec} ->
{:error, Error.new(:unsupported_codec, codec: codec)}
end
end
def decode(_codec, _data, _opts) do
{:error, Error.new(:invalid_data, message: "Data must be a binary")}
end
@doc """
Returns a list of all available codec names.
Only codecs that are loadable and functional are included.
## Examples
iex> :blosc2 in ExCodecs.available_codecs()
true
"""
@spec available_codecs() :: [atom()]
def available_codecs do
CodecRegistry.available_codecs()
end
@doc """
Checks if a codec is supported and available at runtime.
Returns `true` only if the codec is both registered and its
native implementation is loaded.
## Examples
iex> ExCodecs.supports?(:zstd)
true
iex> ExCodecs.supports?(:nonexistent)
false
"""
@spec supports?(atom()) :: boolean()
def supports?(codec) when is_atom(codec) do
CodecRegistry.supports?(codec)
end
@doc """
Returns detailed information about a codec.
## Returns
* `{:ok, %ExCodecs.Codec{}}` - Codec information
* `{:error, :unsupported_codec}` - Codec not found
## Examples
iex> {:ok, info} = ExCodecs.codec_info(:zstd)
iex> info.name
:zstd
iex> {:ok, info} = ExCodecs.codec_info(:zstd)
iex> info.category
:compression
"""
@spec codec_info(atom()) :: {:ok, Codec.t()} | {:error, :unsupported_codec}
def codec_info(codec) when is_atom(codec) do
CodecRegistry.codec_info(codec)
end
defp ensure_available(info, codec) do
if info.module != nil do
:ok
else
{:error, Error.new(:codec_unavailable, codec: codec)}
end
end
end