defmodule XDR.Optional do @moduledoc """ This module manages the `Optional-Data` type based on the RFC4506 XDR Standard. """ @behaviour XDR.Declaration alias XDR.Bool alias XDR.Error.Optional, as: OptionalError defstruct [:type] @typedoc """ `XDR.Optional` structure type specification. """ @type t :: %XDR.Optional{type: nil | any()} @doc """ Create a new `XDR.Optional` structure with the `type` passed. """ @spec new(type :: any()) :: t def new(type), do: %XDR.Optional{type: type} @impl XDR.Declaration @doc """ Encode a `XDR.Optional` structure into a XDR format. """ @spec encode_xdr(optional :: t) :: {:ok, binary} | {:error, :not_valid} def encode_xdr(%{type: type}) when is_bitstring(type), do: {:error, :not_valid} def encode_xdr(%{type: type}) when is_list(type), do: {:error, :not_valid} def encode_xdr(%{type: type}) when is_tuple(type), do: {:error, :not_valid} def encode_xdr(%{type: type}) when is_boolean(type), do: {:error, :not_valid} def encode_xdr(%{type: nil}), do: false |> Bool.new() |> Bool.encode_xdr() def encode_xdr(%{type: type}) do module = type.__struct__ encoded_value = module.encode_xdr!(type) bool = true |> Bool.new() |> Bool.encode_xdr!() {:ok, bool <> encoded_value} end @impl XDR.Declaration @doc """ Encode a `XDR.Optional` structure into a XDR format. If the `optional` is not valid, an exception is raised. """ @spec encode_xdr!(optional :: t) :: binary def encode_xdr!(optional) do case encode_xdr(optional) do {:ok, binary} -> binary {:error, reason} -> raise(OptionalError, reason) end end @impl XDR.Declaration @doc """ Decode the Optional-Data in XDR format to a `XDR.Optional` structure. """ @spec decode_xdr(bytes :: binary(), optional :: t | map()) :: {:ok, {t, binary()}} | {:error, :not_binary | :not_module} def decode_xdr(bytes, _optional) when not is_binary(bytes), do: {:error, :not_binary} def decode_xdr(_bytes, %{type: type}) when not is_atom(type), do: {:error, :not_module} def decode_xdr(bytes, %{type: type}) do {bool, rest} = Bool.decode_xdr!(bytes) get_decoded_value(bool.identifier, rest, type) end @impl XDR.Declaration @doc """ Decode the Optional-Data in XDR format to a `XDR.Optional` structure. If the binaries are not valid, an exception is raised. """ @spec decode_xdr!(bytes :: binary(), optional :: t | map()) :: {t, binary()} def decode_xdr!(bytes, optional) do case decode_xdr(bytes, optional) do {:ok, result} -> result {:error, reason} -> raise(OptionalError, reason) end end @spec get_decoded_value(has_optional_value :: boolean(), rest :: binary(), type :: atom()) :: {:ok, {t, binary()}} | {:ok, {nil, binary()}} defp get_decoded_value(true, rest, type) do {decoded_type, rest} = type.decode_xdr!(rest) optional = new(decoded_type) {:ok, {optional, rest}} end defp get_decoded_value(false, rest, _type), do: {:ok, {nil, rest}} end