Skip to content

Commit

Permalink
Merge pull request #14 from sunny-g/v0.2.0
Browse files Browse the repository at this point in the history
update to elixir 1.6, replace macros with guards, mix format everything, unify `__using__` API
  • Loading branch information
sunny-g authored Aug 1, 2018
2 parents c3268e4 + 6e7ec3c commit 34c0268
Show file tree
Hide file tree
Showing 45 changed files with 592 additions and 591 deletions.
3 changes: 3 additions & 0 deletions .formatter.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[
inputs: ["{config,lib,test}/**/*.{ex,exs}"]
]
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Install from [Hex.pm](https://hex.pm/packages/xdr):

```elixir
def deps do
[{:xdr, "~> 0.1.2"}]
[{:xdr, "~> 0.2.0"}]
end
```

Expand Down Expand Up @@ -56,7 +56,8 @@ XDR.Type.Optional

| Version | Change Summary |
| ------- | -------------- |
| [v0.1.2](https://hex.pm/packages/xdr/0.1.2) | [negative integers in Enums](https://github.com/sunny-g/xdr/pull/11)
| [v0.2.0](https://hex.pm/packages/xdr/0.2.0) | [unify `__using__` API, upgrade to Elixir 1.6](https://github.com/sunny-g/xdr/pull/14) |
| [v0.1.2](https://hex.pm/packages/xdr/0.1.2) | [negative integers in Enums](https://github.com/sunny-g/xdr/pull/11) |
| [v0.1.1](https://hex.pm/packages/xdr/0.1.1) | [minor bugfix](https://github.com/sunny-g/xdr/pull/6) |
| [v0.1.0](https://hex.pm/packages/xdr/0.1.0) | initial release |

Expand Down
4 changes: 2 additions & 2 deletions lib/xdr/type/base.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ defmodule XDR.Type.Base do
"""

@type t :: any
@type xdr :: <<_ :: _ * 32>>
@type error :: atom | String.t
@type xdr :: <<_::_*32>>
@type error :: atom | String.t()

@doc """
Returns the expected length (in bytes) of an XDR-encoded binary of this type (sans padding)
Expand Down
4 changes: 2 additions & 2 deletions lib/xdr/type/bool.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule XDR.Type.Bool do
"""

@type t :: boolean
@type xdr :: Enum.xdr
@type xdr :: Enum.xdr()

use XDR.Type.Enum, spec: [false: 0, true: 1]
use XDR.Type.Enum, false: 0, true: 1
end
11 changes: 7 additions & 4 deletions lib/xdr/type/const.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ defmodule XDR.Type.Const do
RFC 4506, Section 4.17 - Const
"""

defmacro __using__(spec: spec) do
const = Keyword.get(spec, :value)
module = spec
defmacro __using__(opts) do
const = Keyword.get(opts, :value)

module =
opts
|> Keyword.get(:type)
|> Macro.expand(__ENV__)

module_name = Atom.to_string(module)

if not module.valid?(const) do
Expand All @@ -30,7 +33,7 @@ defmodule XDR.Type.Const do
def encode(unquote(const)), do: {:ok, unquote(const_xdr)}
def encode(_), do: {:error, :invalid_const}

def decode(<<unquote(const_xdr), rest :: binary>>), do: {:ok, {unquote(const), rest}}
def decode(<<unquote(const_xdr), rest::binary>>), do: {:ok, {unquote(const), rest}}
def decode(_), do: {:error, :invalid_const}
end
end
Expand Down
30 changes: 11 additions & 19 deletions lib/xdr/type/double_float.ex
Original file line number Diff line number Diff line change
@@ -1,58 +1,50 @@
defmodule XDR.Type.DoubleFloat.Validation do
@moduledoc false

defmacro is_valid_double_float?(float) do
quote do
is_float(unquote(float)) or is_integer(unquote(float))
end
end
end

defmodule XDR.Type.DoubleFloat do
@moduledoc """
RFC 4506, Section 4.7 - Double-precision Floating Point
"""

alias XDR.Type.Base
import XDR.Util.Macros
import XDR.Type.DoubleFloat.Validation

@behaviour XDR.Type.Base

@typedoc """
Double-precision float
"""
@type t :: number
@type xdr :: <<_ :: _ * 64>>
@type xdr :: <<_::_*64>>

@length 8

defguard is_xdr_double(double) when is_float(double) or is_integer(double)

@doc false
def length, do: @length

@doc false
def new(float \\ 0.0)
def new(float) when is_valid_double_float?(float), do: {:ok, float}
def new(float) when is_xdr_double(float), do: {:ok, float}
def new(_), do: {:error, :invalid}

@doc """
Determines if a value is a valid 8-byte float or integer
"""
@spec valid?(float :: t) :: boolean
def valid?(float), do: is_valid_double_float?(float)
def valid?(float), do: is_xdr_double(float)

@doc """
Encodes a double-precision float or integer into an 8-byte binary
"""
@spec encode(float :: t) :: {:ok, xdr :: xdr} | {:error, :invalid}
def encode(float) when not is_valid_double_float?(float), do: {:error, :invalid}
def encode(float), do: {:ok, <<float :: big-signed-float-size(64)>>}
def encode(float) when not is_xdr_double(float), do: {:error, :invalid}
def encode(float), do: {:ok, <<float::big-signed-float-size(64)>>}

@doc """
Decodes an 8-byte binary into an double-precision float
"""
@spec decode(xdr :: xdr) :: {:ok, {float :: t, rest :: Base.xdr}} | {:error, :invalid | :out_of_bounds}
def decode(xdr) when not is_valid_xdr?(xdr), do: {:error, :invalid}
def decode(<<float :: big-signed-float-size(64), rest :: binary>>), do: {:ok, {float, rest}}
@spec decode(xdr :: xdr) ::
{:ok, {float :: t, rest :: Base.xdr()}} | {:error, :invalid | :out_of_bounds}
def decode(xdr) when not is_valid_xdr(xdr), do: {:error, :invalid}
def decode(<<float::big-signed-float-size(64), rest::binary>>), do: {:ok, {float, rest}}
def decode(_), do: {:error, :invalid}
end
44 changes: 24 additions & 20 deletions lib/xdr/type/enum.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,37 +11,37 @@ defmodule XDR.Type.Enum do
A keyword list defining the spec for an Enum, where values are 4-byte integers
"""
@type t :: atom
@type spec :: [{t, Int.t}]
@type xdr :: Base.xdr
@type spec :: [{t, Int.t()}]
@type xdr :: Base.xdr()
@type decode_error :: {:error, :invalid_xdr | :invalid_enum}
@type encode_error :: {:error, :invalid | :invalid_name | :invalid_enum}

defmacro __using__(spec: spec) do
defmacro __using__(opts) do
# TODO: update this to statically compile spec into pattern-matched methods
if not Keyword.keyword?(spec) do
if not Keyword.keyword?(opts) do
raise "Enum spec must be a keyword list"
end

if Enum.any?(spec, fn
{_, {:-, _, [v]}} -> not is_number(v)
{_, v} -> not is_number(v)
end) do
if Enum.any?(opts, fn
{_, {:-, _, [v]}} -> not is_number(v)
{_, v} -> not is_number(v)
end) do
raise "all Enum values must be numbers"
end

quote do
@behaviour XDR.Type.Base

defdelegate length, to: unquote(__MODULE__)
def new(name), do: unquote(__MODULE__).new(name, unquote(spec))
def valid?(name), do: unquote(__MODULE__).valid?(name, unquote(spec))
def encode(name), do: unquote(__MODULE__).encode(name, unquote(spec))
def decode(name), do: unquote(__MODULE__).decode(name, unquote(spec))
def new(name), do: unquote(__MODULE__).new(name, unquote(opts))
def valid?(name), do: unquote(__MODULE__).valid?(name, unquote(opts))
def encode(name), do: unquote(__MODULE__).encode(name, unquote(opts))
def decode(name), do: unquote(__MODULE__).decode(name, unquote(opts))
end
end

@doc false
def length, do: Int.length
def length, do: Int.length()

@doc false
@spec new(name :: t, enum :: spec) :: {:ok, name :: t} | {:error, reason :: :invalid}
Expand All @@ -56,22 +56,25 @@ defmodule XDR.Type.Enum do
def valid?(name, _) when not is_atom(name), do: false
def valid?(_, enum) when not is_list(enum), do: false

def valid?(name, enum), do: Keyword.has_key?(enum, name) and
enum
|> Keyword.get(name)
|> Int.valid?
def valid?(name, enum),
do:
Keyword.has_key?(enum, name) and
enum
|> Keyword.get(name)
|> Int.valid?()

@doc """
Encodes an atom name and enum spec into the name's enum spec 4-byte binary
"""
@spec encode(name :: t, enum :: spec) :: {:ok, xdr :: xdr} | encode_error
def encode(name, _) when not is_atom(name), do: {:error, :invalid_name}
def encode(_, enum) when not is_list(enum), do: {:error, :invalid_enum}

def encode(name, enum) do
if valid?(name, enum) do
enum
|> Keyword.get(name)
|> Int.encode
|> Int.encode()
else
{:error, :invalid}
end
Expand All @@ -80,9 +83,10 @@ defmodule XDR.Type.Enum do
@doc """
Decodes a 4-byte binary and enum spec into the binary's enum spec name
"""
@spec decode(xdr :: xdr, enum :: spec) :: {:ok, {name :: t, rest :: Base.xdr}} | decode_error
def decode(xdr, _) when not is_valid_xdr?(xdr), do: {:error, :invalid_xdr}
@spec decode(xdr :: xdr, enum :: spec) :: {:ok, {name :: t, rest :: Base.xdr()}} | decode_error
def decode(xdr, _) when not is_valid_xdr(xdr), do: {:error, :invalid_xdr}
def decode(_, enum) when not is_list(enum), do: {:error, :invalid_enum}

def decode(xdr, enum) do
OK.with do
{val, rest} <- Int.decode(xdr)
Expand Down
56 changes: 31 additions & 25 deletions lib/xdr/type/fixed_array.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,21 @@ defmodule XDR.Type.FixedArray do
"""
@type t :: list
@type len :: non_neg_integer
@type xdr :: Base.xdr
@type xdr :: Base.xdr()
@type decode_error :: {:error, reason :: :invalid | :xdr_too_small}

defmacro __using__([len: len, type: type]) do
defmacro __using__(len: len, type: type) do
if not (is_integer(len) and len >= 0) do
raise "invalid length"
end

type_module = Macro.expand(type, __ENV__)
byte_length = case type_module.length do
type_len when is_integer(type_len) -> type_len * len
_ -> :variable
end

byte_length =
case type_module.length do
type_len when is_integer(type_len) -> type_len * len
_ -> :variable
end

quote do
@behaviour XDR.Type.Base
Expand All @@ -39,6 +41,7 @@ defmodule XDR.Type.FixedArray do
@doc false
@spec new(array :: t, type :: module, len :: len) :: {:ok, array :: t} | {:error, :invalid}
def new(array, type, len \\ 0)

def new(array, type, len) do
case valid?(array, type, len) do
true -> {:ok, array}
Expand All @@ -51,11 +54,8 @@ defmodule XDR.Type.FixedArray do
"""
@spec valid?(array :: t, type :: module, len :: len) :: boolean
def valid?(array, type, len) do
is_list(array)
and is_atom(type)
and is_integer(len)
and length(array) === len
and Enum.all?(array, &type.valid?/1)
is_list(array) and is_atom(type) and is_integer(len) and length(array) === len and
Enum.all?(array, &type.valid?/1)
end

@doc """
Expand All @@ -69,40 +69,45 @@ defmodule XDR.Type.FixedArray do
@doc """
Decodes an fixed array xdr binary by truncating it to the desired length
"""
@spec decode(xdr :: xdr, type :: module, len :: len) :: {:ok, {array :: t, rest :: Base.xdr}} | decode_error
def decode(xdr, _, _) when not is_valid_xdr?(xdr), do: {:error, :invalid}
def decode(xdr, _, len) when (len * 4) > byte_size(xdr), do: {:error, :xdr_too_small}
@spec decode(xdr :: xdr, type :: module, len :: len) ::
{:ok, {array :: t, rest :: Base.xdr()}} | decode_error
def decode(xdr, _, _) when not is_valid_xdr(xdr), do: {:error, :invalid}
def decode(xdr, _, len) when len * 4 > byte_size(xdr), do: {:error, :xdr_too_small}
def decode(xdr, type, len), do: xdr_to_array(xdr, type, len)

#-------------------------------------------------------------------------#
# -------------------------------------------------------------------------#
# HELPERS
#-------------------------------------------------------------------------#
# -------------------------------------------------------------------------#

# encodes each array element into a binary
defp array_to_xdr(array, type) do
Enum.reduce(array, <<>>, fn(elem, xdr) ->
encoded_elem = elem
Enum.reduce(array, <<>>, fn elem, xdr ->
encoded_elem =
elem
|> type.encode()
|> elem(1)

xdr <> encoded_elem
end)
end

# decodes each element of a binary into an array
defp xdr_to_array(xdr, type, array_length) do
{decoded, rest} = case function_exported?(type, :length, 0) do
true -> decode_fixed_type(xdr, type, array_length, [])
false -> decode_variable_type(xdr, type, array_length, [])
end
{decoded, rest} =
case function_exported?(type, :length, 0) do
true -> decode_fixed_type(xdr, type, array_length, [])
false -> decode_variable_type(xdr, type, array_length, [])
end

if is_list(decoded), do: {:ok, {Enum.reverse(decoded), rest}}, else: {:error, decoded}
end

# decodes an XDR of fixed type elements
defp decode_fixed_type(xdr, _, 0, array), do: {array, xdr}

defp decode_fixed_type(xdr, type, array_length, array) do
elem_length = type.length
<<elem :: bytes-size(elem_length), rest :: binary>> = xdr
<<elem::bytes-size(elem_length), rest::binary>> = xdr

case type.decode(elem) do
{:ok, {val, _}} -> decode_fixed_type(rest, type, array_length - 1, [val | array])
Expand All @@ -112,9 +117,10 @@ defmodule XDR.Type.FixedArray do

# decodes an XDR of variable type elements
defp decode_variable_type(xdr, _, 0, array), do: {array, xdr}

defp decode_variable_type(xdr, type, array_length, array) do
<<elem_length :: unsigned-integer, rest :: binary>> = xdr
<<elem :: bytes-size(elem_length), _padding :: binary>> = rest
<<elem_length::unsigned-integer, rest::binary>> = xdr
<<elem::bytes-size(elem_length), _padding::binary>> = rest

case type.decode(elem) do
{:ok, {val, _}} -> decode_variable_type(rest, type, array_length - 1, [val | array])
Expand Down
Loading

0 comments on commit 34c0268

Please sign in to comment.