Skip to main content

lib/kiwi_codec.ex

defmodule KiwiCodec do
  @moduledoc """
  Pure Elixir codec for Kiwi schema binary messages.

  Kiwi is a compact schema-driven format with message, struct, enum, and primitive
  encodings that differ from Protocol Buffers. This package is intentionally
  generic; product-specific schemas can live in companion packages.
  """

  defmacro __using__(opts) do
    unless Keyword.has_key?(opts, :kind) do
      raise ArgumentError, "expected :kind option"
    end

    quote location: :keep do
      import KiwiCodec.DSL, only: [field: 3, enum_value: 2]
      Module.register_attribute(__MODULE__, :kiwi_fields, accumulate: true)
      Module.register_attribute(__MODULE__, :kiwi_enum_values, accumulate: true)
      @kiwi_options unquote(opts)
      @before_compile KiwiCodec.DSL

      def transform_module, do: nil
      defoverridable transform_module: 0
    end
  end

  @doc """
  Parses `.kiwi` schema text into a schema AST.
  """
  @spec parse_schema!(String.t()) :: KiwiCodec.Schema.t()
  defdelegate parse_schema!(text), to: KiwiCodec.Schema.Parser, as: :parse!

  @doc """
  Compiles `.kiwi` schema text into generated Elixir modules in memory.

  This is a convenience for tests and tooling. For application code, prefer the
  `mix kiwi.gen` task so generated modules are written to source files.
  """
  @spec compile_schema!(String.t(), keyword()) :: [module()]
  defdelegate compile_schema!(text, opts), to: KiwiCodec.ModuleCompiler, as: :compile_string!

  @doc """
  Encodes a Kiwi struct.
  """
  @spec encode(struct()) :: binary()
  def encode(struct) do
    struct
    |> encode_to_iodata()
    |> IO.iodata_to_binary()
  end

  @doc """
  Encodes a Kiwi struct to iodata.
  """
  @spec encode_to_iodata(struct()) :: iodata()
  def encode_to_iodata(%module{} = struct) do
    KiwiCodec.Encoder.encode_to_iodata(struct, module)
  end

  @doc """
  Decodes a binary as the given Kiwi module.
  """
  @spec decode(binary(), module()) :: struct()
  def decode(binary, module) when is_binary(binary) and is_atom(module) do
    KiwiCodec.Decoder.decode(binary, module)
  end
end