XDR.Union - Discriminated Union

A discriminated union is a type composed of a discriminant followed by a type selected from a set of prearranged types according to the value of the discriminant. The component types are called arms of the union and are preceded by the value of the discriminant that implies their encoding or decoding.

Discriminated Union - RFC 4506

Implementation

The type of discriminant is either XDR.Int, XDR.UInt, or an XDR.Enum type.

The arms can be a keyword list or a map and the value of each arm can be either a struct or a module of any XDR type. You can define a default arm using :default as a key (The default arm is optional).

Usage

arms definition

For union with enumeration as discriminant:

iex(1)> arms = [case_1: %XDR.Int{datum: 1}, case_2: %XDR.Int{datum: 2}, case_3: XDR.Int, default: XDR.Float]

For union with integer as discriminant:

iex(1)> arms = %{1 => %XDR.Int{datum: 1}, 2 => %XDR.Int{datum: 2}, 3 => XDR.Int, default: XDR.Float}

Encoding

For union with enumeration as discriminant:

# encode_xdr

iex(1)> XDR.Enum.new([case_1: 1, case_2: 2], :case_1) |>
...(1)> XDR.Union.new(arms) |>
...(1)> XDR.Union.encode_xdr()
{:ok, <<0, 0, 0, 1, 0, 0, 0, 1>>}

# encode_xdr!

# Using an XDR module to encode.
# Note you must to pass the value to encode to the function `XDR.Union.new/3`.
iex(1)> XDR.Enum.new([case_1: 1, case_2: 2, case_3: 3], :case_3) |>
...(1)> XDR.Union.new(arms, 100) |>
...(1)> XDR.Union.encode_xdr!()
<<0, 0, 0, 3, 0, 0, 0, 100>>

# Encode a default arm.
iex(1)> XDR.Enum.new([case_1: 1, case_2: 2, case_3: 3, case_10: 10], :case_10) |>
...(1)> XDR.Union.new(arms, 12.3333) |>
...(1)> XDR.Union.encode_xdr()
{:ok, <<0, 0, 0, 10, 65, 69, 85, 50>>} 

For union with integer as discriminant:

iex(1)> XDR.Int.new(1) |> XDR.Union.new(arms) |> XDR.Union.encode_xdr()
{:ok, <<0, 0, 0, 1, 0, 0, 0, 1>>}

iex(1)> XDR.UInt.new(3) |> XDR.Union.new(arms, 100) |> XDR.Union.encode_xdr!()
<<0, 0, 0, 3, 0, 0, 0, 100>> 

Decoding

For union with enumeration as discriminant:

# decode_xdr

iex(1)> enum = XDR.Enum.new([case_1: 1, case_2: 2], nil)
iex(2)> union_spec = XDR.Union.new(enum, arms) # Define the union specification to decode.
iex(3)> XDR.Union.decode_xdr(<<0, 0, 0, 1, 0, 0, 0, 1>>, union_spec)
{:ok, {{:case_1, %XDR.Int{datum: 1}}, <<>>}}

# decode_xdr!

iex(1)> enum = XDR.Enum.new([case_1: 1, case_2: 2], nil)
iex(2)> union_spec = %{discriminant: enum, arms: arms} # Also you can pass a map as union specification.
iex(3)> XDR.Union.decode_xdr!(<<0, 0, 0, 2, 0, 0, 0, 20>>, union_spec)
{{:case_2, %XDR.Int{datum: 20}}, <<>>}

# Decode a default arm.
iex(1)> enum = XDR.Enum.new([case_1: 1, case_2: 2, case_3: 3, case_10: 10], :case_10)
...(1)> union_spec = XDR.Union.new(enum, arms)
...(1)> XDR.Union.decode_xdr(<<0, 0, 0, 10, 65, 69, 85, 50>>, union_spec)
{:ok, {{:case_10, %XDR.Float{float: 12.33329963684082}}, ""}}

For union with integer as discriminant:

# decode_xdr
iex(1)> integer = XDR.Int.new(nil)
iex(2)> union_spec = XDR.Union.new(integer, arms)
iex(3)> XDR.Union.decode_xdr(<<0, 0, 0, 1, 0, 0, 0, 1>>, union_spec)
{:ok, {{1, %XDR.Int{datum: 1}}, <<>>}}

#decode_xdr!
iex(1)> integer = XDR.UInt.new(nil)
iex(2)> union_spec = XDR.Union.new(integer, arms)
iex(3)> XDR.Union.decode_xdr!(<<0, 0, 0, 2, 0, 0, 0, 7>>, union_spec)
{{2, %XDR.Int{datum: 7}}, <<>>}

Custom Union example

defmodule CustomUnion do

  @arms [
    Type_1: Type1,
    Type_2: Type2,
    default: XDR.Void
  ]

  @type t :: XDR.Union.t()

  @spec new(value :: any(), union_type :: atom()) :: t
  def new(value, union_type) do
    union_type |> CustomEnum.new() |> XDR.Union.new(@arms, value)
  end

  @spec spec() :: t
  defp spec(), do: CustomEnum.new(nil) |> Union.new(@arms)

  defdelegate encode_xdr(union), to: XDR.Union
  defdelegate encode_xdr!(union), to: XDR.Union
  defdelegate decode_xdr(bytes, union \\ spec()), to: XDR.Union
  defdelegate decode_xdr!(bytes, union \\ spec()), to: XDR.Union
end
# Encode
value_depending_on_the_type |> CustomUnion.new(:type_1) |> CustomUnion.encode_xdr()

# Decode
CustomUnion.decode_xdr(<<1, 2, 3, 4, 5, 6, 7, 8>>)