lib/borsh_ex.ex

defmodule BorshEx do
  @moduledoc """
  Elixir implementation of Binary Object Representation Serializer for Hashing ([borsh](borsh.io))

  ## Usage

  Define schema using `BorshEx.Schema`
      defmodule SubData do
        use BorshEx.Schema
        defstruct address: nil

        borsh_schema do
          field :address, "string"
        end
      end

      defmodule Data do
        use BorshEx.Schema
        defstruct name: nil

        borsh_schema do
          field :id, "u16"
          field :sub_data, SubData
        end
      end

  Once schema is define, we are able to serialize a struct into a binary or deserialize to a struct.

      iex> data = %Data{id: 45, sub_data: %SubData{address: "Hello world!"}}
      %Data{id: 45, sub_data: %SubData{address: "Hello world!"}}

      iex> bistring = Data.serialize(data)
      <<45, 0, 12, 0, 0, 0, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33>>

      iex> data = Data.deserialize(bitstring)
      {:ok, %Data{id: 45, sub_data: %SubData{address: "Hello world!"}}}

  In some case the bitstring is longer than necessary, some bytes are not used

      iex> bitstring = <<45, 0, 12, 0, 0, 0, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33, 5, 0>>
      <<45, 0, 12, 0, 0, 0, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33, 10, 5, 0>>

      iex> data = Data.deserialize(bitstring)
      {:error, %Data{id: 45, sub_data: %SubData{address: "Hello world!"}}, <<5, 0>>}

  `<<5, 0>>` returned are unused bytes.

  ## Basic types

  | type                      | example               |
  | ------------------------- | --------------------- |
  | `u8`, `u16`, `u32`, `u64` | `field :id, "u8"`     |
  | `i8`, `i16`, `i32`, `i64` | `field :id, "i8"`     |
  | `boolean`                 | `field :id, "bool"`   |
  | `string`                  | `field :id, "string"` |
  | `struct`                  | `field :id, MyStruct` |

  ## Complex types

  All types can be used in a more complex one

  ### Array

  An array can have a fixed size

      # ids is a list of 32 `u16` values
      # sub_data is a list of 32 Subdata strut
      borsh_schema do
        field :ids, {"array", {"u16", 32}}
        field :sub_data, {"array", {Subdata, 32}}
      end

  Or variable length

      # ids is a list `u16` value
      # sub_data is a list of Subdata strut
      borsh_schema do
        field :ids, {"array", "u16"}
        field :sub_data, {"array", {Subdata, 32}}
      end

  ### Option

  a field can be optional

      # if name can be nil or a string
      borsh_schema do
        field :name, {"option", "string"}
      end

      iex> data = %Data{name: nil}
      %Data{name: nil}

      iex> Data.serialize(data)
      <<0>>

      iex> data = %Data{name: "Hello"}
      %Data{name: "Hello"}

      iex> Data.serialize(data)
      <<1, 5, 0, 0, 0, 72, 101, 108, 108, 111>>
  """
end