Elixir XDR v0.1.1 XDR View Source
Basic XDR usage
Link to this section Summary
Types
Because the protocol function signatures must match for all types,
arguments are sometimes unused. The ignored()
type is used to annotate them.
Functions
Build a concrete XDR type by providing the type module and any configuration options for that type.
To build a concrete value, supply the fully-built type and a value appropriate to that type's definition. For simple types, just supply the raw value
Just like XDR.build_value/2
, but returns raw values on success instead of tuples,
and raises on failure.
Decode a binary representation into an XDR type with value. Since the binary representation does not contain type info itself, it must be supplied as the first parameter.
Just like XDR.decode/2
, but returns raw values on success instead of tuples,
and raises on failure.
Encode an XDR value (created with e.g. XDR.build_value/2
) into its binary representation.
Just like XDR.encode/1
, but returns raw binaries on success instead of tuples,
and raises on failure.
XDR data structures created from XDR.build_value/2
and XDR.decode/2
include
lots of type metadata, and the different types don't always store their inner
state in the same way. XDR.extract_value/1
acts as a uniform way to pull
out the underlying values as native elixir types.
Just like XDR.extract_value/1
, but returns raw values on success instead of tuples,
and raises on failure.
Link to this section Types
Because the protocol function signatures must match for all types,
arguments are sometimes unused. The ignored()
type is used to annotate them.
Link to this section Functions
build_type(type, options \\ [])
View Sourcebuild_type(XDR.Type.Array, XDR.Type.Array.options()) :: XDR.Type.Array.t()
build_type(XDR.Type.Bool, ignored()) :: XDR.Type.Bool.t()
build_type(XDR.Type.Const, any()) :: XDR.Type.Const.t()
build_type(XDR.Type.Double, ignored()) :: XDR.Type.Double.t()
build_type(XDR.Type.Enum, XDR.Type.Enum.options()) :: XDR.Type.Enum.t()
build_type(XDR.Type.Float, ignored()) :: XDR.Type.Float.t()
build_type(XDR.Type.HyperInt, ignored()) :: XDR.Type.HyperInt.t()
build_type(XDR.Type.Int, ignored()) :: XDR.Type.Int.t()
build_type(XDR.Type.Opaque, XDR.Size.t()) :: XDR.Type.Opaque.t()
build_type(XDR.Type.Optional, XDR.Type.t()) :: XDR.Type.Optional.t()
build_type(XDR.Type.String, XDR.Size.t() | []) :: XDR.Type.String.t()
build_type(XDR.Type.Struct, XDR.Type.Struct.fields()) :: XDR.Type.Struct.t()
build_type(XDR.Type.Union, XDR.Type.Union.options()) :: XDR.Type.Union.t()
build_type(XDR.Type.UnsignedHyperInt, ignored()) :: XDR.Type.UnsignedHyperInt.t()
build_type(XDR.Type.UnsignedInt, ignored()) :: XDR.Type.UnsignedInt.t()
build_type(XDR.Type.VariableArray, XDR.Type.VariableArray.options()) :: XDR.Type.VariableArray.t()
build_type(XDR.Type.VariableOpaque, XDR.Size.t() | []) :: XDR.Type.VariableOpaque.t()
build_type(XDR.Type.Void, ignored()) :: XDR.Type.Void.t()
Build a concrete XDR type by providing the type module and any configuration options for that type.
Examples
Some types don't take any configuration. These include
XDR.Type.Bool
XDR.Type.Double
XDR.Type.Float
XDR.Type.HyperInt
XDR.Type.Int
XDR.Type.UnsignedHyperInt
XDR.Type.UnsignedInt
XDR.Type.Void
Examples:
iex> XDR.build_type(XDR.Type.Int)
%XDR.Type.Int{type_name: "Int", value: nil}
iex> XDR.build_type(XDR.Type.Float)
%XDR.Type.Float{type_name: "Float", value: nil}
XDR.Type.VariableOpaque
and XDR.Type.String
have an optional max_length
option,
with a default max defined in XDR.Size
.
iex> XDR.build_type(XDR.Type.VariableOpaque)
%XDR.Type.VariableOpaque{type_name: "VariableOpaque", max_length: XDR.Size.max(), value: nil}
iex> XDR.build_type(XDR.Type.VariableOpaque, 100)
%XDR.Type.VariableOpaque{type_name: "VariableOpaque", max_length: 100, value: nil}
XDR.Type.VariableArray
has a required type
and an optional max_length
.
iex> XDR.build_type(XDR.Type.VariableArray, type: XDR.build_type(XDR.Type.Int))
%XDR.Type.VariableArray{type_name: "VariableArray", data_type: %XDR.Type.Int{}, max_length: XDR.Size.max(), values: []}
XDR.Type.Array
and XDR.Type.Opaque
are fixed-length, so the length is required when building the type:
iex> XDR.build_type(XDR.Type.Array, type: XDR.build_type(XDR.Type.Bool), length: 4)
%XDR.Type.Array{type_name: "Array", data_type: %XDR.Type.Bool{}, length: 4, values: []}
iex> XDR.build_type(XDR.Type.Opaque, 4)
%XDR.Type.Opaque{type_name: "Opaque", length: 4, value: nil}
iex> XDR.build_type(XDR.Type.Opaque)
** (XDR.Error) A valid size must be provided
XDR.Type.Enum
is an enumeration with atom keys and signed int values, provided as a keyword list
iex> _enum_type = XDR.build_type(XDR.Type.Enum, metric: 0, imperial: 1, other: 2)
%XDR.Type.Enum{type_name: "Enum", options: [metric: 0, imperial: 1, other: 2], value: nil}
Creating a concrete XDR.Type.Optional
type requires that a fully built base type be provided:
iex> XDR.build_type(XDR.Type.Optional, XDR.build_type(XDR.Type.Float))
%XDR.Type.Optional{type_name: "Optional", data_type: %XDR.Type.Float{}, value: nil}
XDR.Type.Struct
allows the nesting of data into trees, by associating a key with an XDR
data type to be stored under that key.
iex> _user_type = XDR.build_type(
...> XDR.Type.Struct,
...> first_name: XDR.build_type(XDR.Type.VariableOpaque),
...> last_name: XDR.build_type(XDR.Type.VariableOpaque),
...> email: XDR.build_type(XDR.Type.VariableOpaque)
...> )
%XDR.Type.Struct{type_name: "Struct", fields: [
first_name: %XDR.Type.VariableOpaque{},
last_name: %XDR.Type.VariableOpaque{},
email: %XDR.Type.VariableOpaque{}
]}
XDR.Type.Union
is a discriminated union, with its main data type being determined
the the value of its switch. The switch can optionally be given a name, and must be
of type XDR.Type.Bool
, XDR.Type.Enum
, XDR.Type.Int
, or XDR.Type.UnsignedInt
.
Each entry in the switches
list maps the switch values to one of the arms
,
or in some cases directly to XDR.Type.Void
if no value is needed. The arms
themselves store the XDR type the union will take in that case.
iex> XDR.build_type(
...> XDR.Type.Union,
...> switch_name: "UserType",
...> switch_type: XDR.build_type(XDR.Type.Enum, business: 0, consumer: 1, none: 2),
...> switches: [
...> business: :business_account,
...> consumer: :consumer_account,
...> none: XDR.Type.Void
...> ],
...> arms: [
...> business_account: XDR.build_type(XDR.Type.Opaque, 16),
...> consumer_account: XDR.build_type(XDR.Type.Int)
...> ]
...> )
%XDR.Type.Union{
switch_name: "UserType",
switch: %XDR.Type.Enum{options: [business: 0, consumer: 1, none: 2]},
switches: [
business: :business_account,
consumer: :consumer_account,
none: XDR.Type.Void
],
arms: [
business_account: %XDR.Type.Opaque{length: 16},
consumer_account: %XDR.Type.Int{}
],
type_name: "Union"
}
Building data types on the fly isn't suitable for a complex problem domain, so
XDR.Base
is provided to allow an application to pre-define named XDR types for
use throughout the application. See the readme for more info.
build_value(type, value)
View Sourcebuild_value(XDR.Type.Array.t(), list()) :: {:ok, XDR.Type.Array.t()} | {:error, any()}
build_value(XDR.Type.Bool.t(), XDR.Type.Bool.value()) :: {:ok, XDR.Type.Bool.t()} | {:error, any()}
build_value(XDR.Type.Double.t(), XDR.Type.Double.value()) :: {:ok, XDR.Type.Double.t()} | {:error, any()}
build_value(XDR.Type.Enum.t(), atom()) :: {:ok, XDR.Type.Enum.t()} | {:error, any()}
build_value(XDR.Type.Float.t(), XDR.Type.Float.value()) :: {:ok, XDR.Type.Float.t()} | {:error, any()}
build_value(XDR.Type.HyperInt.t(), XDR.Type.HyperInt.value()) :: {:ok, XDR.Type.HyperInt.t()} | {:error, any()}
build_value(XDR.Type.Int.t(), XDR.Type.Int.value()) :: {:ok, XDR.Type.Int.t()} | {:error, any()}
build_value(XDR.Type.Opaque.t(), binary()) :: {:ok, XDR.Type.Opaque.t()} | {:error, any()}
build_value(XDR.Type.Optional.t(), XDR.Type.Optional.value()) :: {:ok, XDR.Type.Optional.t()} | {:error, any()}
build_value(XDR.Type.String.t(), binary()) :: {:ok, XDR.Type.String.t()} | {:error, any()}
build_value(XDR.Type.Struct.t(), keyword()) :: {:ok, XDR.Type.Struct.t()} | {:error, any()}
build_value(XDR.Type.Union.t(), XDR.Type.Union.value()) :: {:ok, XDR.Type.Union.t()} | {:error, any()}
build_value(XDR.Type.UnsignedHyperInt.t(), XDR.Type.UnsignedHyperInt.value()) :: {:ok, XDR.Type.UnsignedHyperInt.t()} | {:error, any()}
build_value(XDR.Type.UnsignedInt.t(), XDR.Type.UnsignedInt.value()) :: {:ok, XDR.Type.UnsignedInt.t()} | {:error, any()}
build_value(XDR.Type.VariableArray.t(), list()) :: {:ok, XDR.Type.VariableArray.t()} | {:error, any()}
build_value(XDR.Type.VariableOpaque.t(), binary()) :: {:ok, XDR.Type.VariableOpaque.t()} | {:error, any()}
To build a concrete value, supply the fully-built type and a value appropriate to that type's definition. For simple types, just supply the raw value:
iex> int_type = XDR.build_type(XDR.Type.Int)
...> {:ok, int_val} = XDR.build_value(int_type, 123)
...> int_val.value
123
iex> us_zip_type = XDR.build_type(XDR.Type.Opaque, 5)
...> {:ok, zip_val} = XDR.build_value(us_zip_type, "70119")
...> zip_val.value
"70119"
iex> enum_type = XDR.build_type(XDR.Type.Enum, metric: 0, imperial: 1, other: 2)
...> {:ok, enum_val} = XDR.build_value(enum_type, :metric)
...> enum_val.value
:metric
iex> bool_type = XDR.build_type(XDR.Type.Bool)
...> {:ok, bool_value} = XDR.build_value(bool_type, true)
...> bool_value.value
true
Arrays work similarly. Just supply a list of appropriate values:
iex> scores_type = XDR.build_type(XDR.Type.VariableArray, type: XDR.build_type(XDR.Type.Int))
...> {:ok, scores} = XDR.build_value(scores_type, [1, 2, 3, 4, 5, 6])
...> Enum.map(scores.values, & &1.value)
[1, 2, 3, 4, 5, 6]
When building a struct's value, we supply the raw values of the inner types:
iex> user_type = XDR.build_type(XDR.Type.Struct,
...> name: XDR.build_type(XDR.Type.VariableOpaque),
...> email: XDR.build_type(XDR.Type.VariableOpaque)
...> )
...> {:ok, value} = XDR.build_value(user_type, name: "Marvin", email: "marvin@megadodo.co")
...> value.fields[:name].value
"Marvin"
An optional type can be specified in a few different ways for convenience:
iex> int_type = XDR.build_type(XDR.Type.Int)
...> optional_int = XDR.build_type(XDR.Type.Optional, int_type)
...> {:ok, no_val_1} = XDR.build_value(optional_int, nil)
...> {:ok, no_val_2} = XDR.build_value(optional_int, false)
...> {:ok, no_val_3} = XDR.build_value(optional_int, {false, "ignored"})
...> {:ok, with_val_1} = XDR.build_value(optional_int, {true, 123})
...> {:ok, with_val_2} = XDR.build_value(optional_int, 123)
...> [no_val_1.value, no_val_2.value, no_val_3.value, with_val_1.value.value, with_val_2.value.value]
[%XDR.Type.Void{}, %XDR.Type.Void{}, %XDR.Type.Void{}, 123, 123]
To build a value for XDR.Type.Union
, supply a tuple including the switch value (an int or atom),
followed by the value of the corresponding inner type. If the inner type is XDR.Type.Void
, then
the switch value alone is enough.
iex> account_id_type = XDR.build_type(
...> XDR.Type.Union,
...> switch_name: "UserType",
...> switch_type: XDR.build_type(XDR.Type.Enum, business: 0, consumer: 1, none: 2),
...> switches: [
...> business: :business_account,
...> consumer: :consumer_account,
...> none: XDR.Type.Void
...> ],
...> arms: [
...> business_account: XDR.build_type(XDR.Type.Opaque, 16),
...> consumer_account: XDR.build_type(XDR.Type.Int)
...> ]
...> )
...> {:ok, business_id} = XDR.build_value(account_id_type, {:business, "0123456789abcdef"})
...> {:ok, consumer_id} = XDR.build_value(account_id_type, {:consumer, 23456})
...> {:ok, no_id} = XDR.build_value(account_id_type, {:none, nil})
...> {:ok, no_id_2} = XDR.build_value(account_id_type, :none)
...> [business_id.value.value, consumer_id.value.value, no_id.value, no_id_2.value]
["0123456789abcdef", 23456, %XDR.Type.Void{}, %XDR.Type.Void{}]
NOTE: in all of these examples, the underlying values are accessed directly, which
requires some knowledge of the underlying XDR.Type
structs. In practice,
it's better to use XDR.extract_value/1
rather than reaching into these structs.
build_value!(type, value)
View Sourcebuild_value!(XDR.Type.Array.t(), list()) :: XDR.Type.Array.t()
build_value!(XDR.Type.Bool.t(), XDR.Type.Bool.value()) :: XDR.Type.Bool.t()
build_value!(XDR.Type.Double.t(), XDR.Type.Double.value()) :: XDR.Type.Double.t()
build_value!(XDR.Type.Enum.t(), atom()) :: XDR.Type.Enum.t()
build_value!(XDR.Type.Float.t(), XDR.Type.Float.value()) :: XDR.Type.Float.t()
build_value!(XDR.Type.HyperInt.t(), XDR.Type.HyperInt.value()) :: XDR.Type.HyperInt.t()
build_value!(XDR.Type.Int.t(), XDR.Type.Int.value()) :: XDR.Type.Int.t()
build_value!(XDR.Type.Opaque.t(), binary()) :: XDR.Type.Opaque.t()
build_value!(XDR.Type.Optional.t(), XDR.Type.Optional.value()) :: XDR.Type.Optional.t()
build_value!(XDR.Type.String.t(), binary()) :: XDR.Type.String.t()
build_value!(XDR.Type.Struct.t(), keyword()) :: XDR.Type.Struct.t()
build_value!(XDR.Type.Union.t(), XDR.Type.Union.value()) :: XDR.Type.Union.t()
build_value!(XDR.Type.UnsignedHyperInt.t(), XDR.Type.UnsignedHyperInt.value()) :: XDR.Type.UnsignedHyperInt.t()
build_value!(XDR.Type.UnsignedInt.t(), XDR.Type.UnsignedInt.value()) :: XDR.Type.UnsignedInt.t()
build_value!(XDR.Type.VariableArray.t(), list()) :: XDR.Type.VariableArray.t()
build_value!(XDR.Type.VariableOpaque.t(), binary()) :: XDR.Type.VariableOpaque.t()
Just like XDR.build_value/2
, but returns raw values on success instead of tuples,
and raises on failure.
decode(type, encoding)
View Sourcedecode(XDR.Type.t(), binary()) :: {:ok, XDR.Type.t()} | {:error, any()}
Decode a binary representation into an XDR type with value. Since the binary representation does not contain type info itself, it must be supplied as the first parameter.
iex> encoding = <<0, 0, 0, 6>> <> "abcdef" <> <<0, 0>>
...> {:ok, type_with_value} = XDR.decode(XDR.build_type(XDR.Type.VariableOpaque), encoding)
...> {type_with_value.length, type_with_value.value}
{6, "abcdef"}
iex> encoding = "abcdef" <> <<0, 0>>
...> {:ok, type_with_value} = XDR.decode(XDR.build_type(XDR.Type.Opaque, 6), encoding)
...> {type_with_value.length, type_with_value.value}
{6, "abcdef"}
As with XDR.build_value/2
above, we're accessing the values directly inside
the type structs. A more practical way to access inner values is to use XDR.extract_value/1
.
decode!(type, encoding)
View Sourcedecode!(XDR.Type.t(), binary()) :: XDR.Type.t()
Just like XDR.decode/2
, but returns raw values on success instead of tuples,
and raises on failure.
encode(type_with_value)
View Sourceencode(XDR.Type.t()) :: {:ok, binary()} | {:error, any()}
Encode an XDR value (created with e.g. XDR.build_value/2
) into its binary representation.
iex> {:ok, value} = XDR.build_value(XDR.build_type(XDR.Type.Opaque, 6), "abcdef")
...> XDR.encode(value)
{:ok, "abcdef" <> <<0, 0>>}
Each type's binary representation is determined by its own rules, as defined in the XDR spec. In the case of Opaque, the binary contents are passed through, with padding added to achieve an even multiple of 4 bytes. Variable-length types will be preceded by a four-byte integer describing the length of the contained value.
iex> {:ok, value} = XDR.build_value(XDR.build_type(XDR.Type.VariableOpaque), "abcdef")
...> XDR.encode(value)
{:ok, <<0, 0, 0, 6>> <> "abcdef" <> <<0, 0>>}
Note that type info is not contained in the binary representation, and is therefore required to decode the binary.
Just like XDR.encode/1
, but returns raw binaries on success instead of tuples,
and raises on failure.
extract_value(type_with_value)
View Sourceextract_value(XDR.Type.t()) :: {:ok | :error, any()}
XDR data structures created from XDR.build_value/2
and XDR.decode/2
include
lots of type metadata, and the different types don't always store their inner
state in the same way. XDR.extract_value/1
acts as a uniform way to pull
out the underlying values as native elixir types.
iex> us_address = XDR.build_type(XDR.Type.Struct,
...> street: XDR.build_type(XDR.Type.VariableOpaque),
...> city: XDR.build_type(XDR.Type.VariableOpaque),
...> state: XDR.build_type(XDR.Type.Opaque, 2),
...> zip: XDR.build_type(XDR.Type.Opaque, 5)
...> )
...> user_type = XDR.build_type(XDR.Type.Struct,
...> name: XDR.build_type(XDR.Type.VariableOpaque),
...> email: XDR.build_type(XDR.Type.VariableOpaque),
...> address: us_address
...> )
...> {:ok, user} = XDR.build_value(user_type,
...> name: "Marvin",
...> email: "marvin@megadodo.co",
...> address: [
...> street: "123 Shakedown St",
...> city: "New Orleans",
...> state: "LA",
...> zip: "70119",
...> ]
...> )
...> {:ok, user_info} = XDR.extract_value(user)
...> user_info
[
name: "Marvin",
email: "marvin@megadodo.co",
address: [
street: "123 Shakedown St",
city: "New Orleans",
state: "LA",
zip: "70119",
]
]
extract_value!(type_with_value)
View Sourceextract_value!(XDR.Type.t()) :: any()
Just like XDR.extract_value/1
, but returns raw values on success instead of tuples,
and raises on failure.