ExZarr.Codecs.Codec behaviour (ExZarr v1.1.0)

View Source

Behavior for implementing custom compression/checksum codecs.

Custom codecs can be registered and used alongside built-in codecs, allowing extensibility without modifying ExZarr's core.

Overview

A codec is responsible for encoding (compressing/transforming) and decoding (decompressing/transforming) binary data. Codecs implement this behavior to provide a consistent interface for data transformation.

Codec Types

  • :compression - Reduces data size (zlib, zstd, lz4, etc.)
  • :checksum - Adds integrity verification (crc32c)
  • :filter - Transforms data without size reduction (shuffle, delta)
  • :transformation - General data transformation

Implementing a Custom Codec

To create a custom codec:

  1. Define a module implementing this behavior
  2. Implement all required callbacks
  3. Register the codec with ExZarr.Codecs.register_codec/1
  4. Use it like any built-in codec

Example

defmodule MyApp.UppercaseCodec do
  @moduledoc "Transforms text to uppercase (example codec)"
  @behaviour ExZarr.Codecs.Codec

  @impl true
  def codec_id, do: :uppercase

  @impl true
  def codec_info do
    %{
      name: "Uppercase Transform",
      version: "1.0.0",
      type: :transformation,
      description: "Converts text to uppercase"
    }
  end

  @impl true
  def available?, do: true

  @impl true
  def encode(data, _opts) when is_binary(data) do
    {:ok, String.upcase(data)}
  rescue
    e -> {:error, {:encode_failed, e}}
  end

  @impl true
  def decode(data, _opts) when is_binary(data) do
    {:ok, String.downcase(data)}
  rescue
    e -> {:error, {:decode_failed, e}}
  end

  @impl true
  def validate_config(_opts), do: :ok
end

# Register and use
ExZarr.Codecs.register_codec(MyApp.UppercaseCodec)
{:ok, encoded} = ExZarr.Codecs.compress("hello", :uppercase)
# encoded = "HELLO"

Codec Configuration

Codecs can accept configuration options through the opts parameter:

{:ok, compressed} = ExZarr.Codecs.compress(data, :zstd, level: 9)
{:ok, encoded} = ExZarr.Codecs.compress(data, :my_codec, threshold: 100)

Error Handling

Encode and decode functions should return:

  • {:ok, binary()} on success
  • {:error, term()} on failure

It's recommended to rescue exceptions and convert them to error tuples.

Availability Checking

The available?/0 callback allows codecs to check runtime dependencies:

def available? do
  case Application.ensure_loaded(:some_dependency) do
    :ok -> true
    _ -> false
  end
end

Thread Safety

Codec implementations should be thread-safe as they may be called concurrently from multiple processes.

Summary

Callbacks

Checks if the codec is available at runtime.

Returns the unique identifier for this codec.

Returns metadata about the codec.

Decodes (decompresses/transforms) the input data.

Encodes (compresses/transforms) the input data.

Validates codec configuration.

Functions

Helper function to check if a module implements the Codec behavior.

Types

codec_config()

@type codec_config() :: keyword()

codec_info()

@type codec_info() :: %{
  name: String.t(),
  version: String.t(),
  type: codec_type(),
  description: String.t()
}

codec_type()

@type codec_type() :: :compression | :checksum | :filter | :transformation

decode_result()

@type decode_result() :: {:ok, binary()} | {:error, term()}

encode_result()

@type encode_result() :: {:ok, binary()} | {:error, term()}

Callbacks

available?()

@callback available?() :: boolean()

Checks if the codec is available at runtime.

Should return true if the codec can be used, false otherwise. This allows codecs to check for required dependencies or system capabilities.

Examples

# Always available
def available?, do: true

# Check for dependency
def available? do
  case Application.ensure_loaded(:my_dependency) do
    :ok -> true
    _ -> false
  end
end

# Check for system library
def available? do
  System.find_executable("some_tool") != nil
end

codec_id()

@callback codec_id() :: atom()

Returns the unique identifier for this codec.

The codec ID is an atom that users will use to reference this codec. It must be unique across all registered codecs.

Examples

iex> MyCustomCodec.codec_id()
:my_custom_codec

codec_info()

@callback codec_info() :: codec_info()

Returns metadata about the codec.

Should return a map with:

  • :name - Human-readable name
  • :version - Semantic version string
  • :type - One of :compression, :checksum, :filter, :transformation
  • :description - Brief description of what the codec does

Examples

iex> MyCustomCodec.codec_info()
%{
  name: "My Custom Codec",
  version: "1.0.0",
  type: :compression,
  description: "Custom compression algorithm"
}

decode(data, config)

@callback decode(data :: binary(), config :: codec_config()) :: decode_result()

Decodes (decompresses/transforms) the input data.

Takes encoded binary data and optional configuration, returns the decoded binary or an error tuple.

Parameters

  • data - Binary data to decode
  • config - Keyword list of codec-specific options

Returns

  • {:ok, binary()} - Successfully decoded data
  • {:error, term()} - Decoding failed

Examples

def decode(data, _opts) when is_binary(data) do
  case my_decompress(data) do
    {:ok, decompressed} -> {:ok, decompressed}
    error -> {:error, {:decode_failed, error}}
  end
rescue
  e -> {:error, {:decode_failed, e}}
end

encode(data, config)

@callback encode(data :: binary(), config :: codec_config()) :: encode_result()

Encodes (compresses/transforms) the input data.

Takes binary data and optional configuration, returns the encoded binary or an error tuple.

Parameters

  • data - Binary data to encode
  • config - Keyword list of codec-specific options

Returns

  • {:ok, binary()} - Successfully encoded data
  • {:error, term()} - Encoding failed

Examples

def encode(data, opts) when is_binary(data) do
  level = Keyword.get(opts, :level, 5)
  case my_compress(data, level) do
    {:ok, compressed} -> {:ok, compressed}
    error -> {:error, {:encode_failed, error}}
  end
rescue
  e -> {:error, {:encode_failed, e}}
end

validate_config(config)

(optional)
@callback validate_config(config :: codec_config()) :: :ok | {:error, term()}

Validates codec configuration.

Optional callback that validates the configuration before encoding/decoding. If not implemented, all configurations are considered valid.

Parameters

  • config - Keyword list of codec-specific options

Returns

  • :ok - Configuration is valid
  • {:error, term()} - Configuration is invalid

Examples

def validate_config(opts) do
  case Keyword.get(opts, :level) do
    nil -> :ok
    n when is_integer(n) and n >= 1 and n <= 9 -> :ok
    _ -> {:error, :invalid_level}
  end
end

Functions

implements?(module)

@spec implements?(module()) :: boolean()

Helper function to check if a module implements the Codec behavior.

Examples

iex> ExZarr.Codecs.Codec.implements?(MyCodec)
true