Packages
grizzly
0.6.1
9.1.4
9.1.2
9.1.1
9.1.0
9.0.0
8.15.3
8.15.2
8.15.1
8.15.0
8.14.0
8.13.0
8.12.0
8.11.3
8.11.2
8.11.1
8.11.0
8.10.0
8.9.0
8.8.1
8.8.0
8.7.1
8.7.0
8.6.12
8.6.11
8.6.10
8.6.9
8.6.8
8.6.7
retired
8.6.6
8.6.5
8.6.4
8.6.3
8.6.2
8.6.1
8.6.0
8.5.3
8.5.2
8.5.1
8.5.0
8.4.0
8.3.0
8.2.3
8.2.2
8.2.1
8.2.0
8.1.0
8.0.1
8.0.0
7.4.3
7.4.2
7.4.1
7.4.0
7.3.0
7.2.0
7.1.4
7.1.3
7.1.2
7.1.1
7.1.0
7.0.4
7.0.3
7.0.2
7.0.1
7.0.0
6.8.8
6.8.7
6.8.6
6.8.5
6.8.4
6.8.3
6.8.2
6.8.1
6.8.0
6.7.1
6.7.0
6.6.1
6.6.0
6.5.1
6.5.0
6.4.0
6.3.0
6.2.0
6.1.1
6.1.0
6.0.1
6.0.0
5.4.1
5.4.0
5.3.0
5.2.8
5.2.7
5.2.6
5.2.5
5.2.4
5.2.3
5.2.2
5.2.1
5.2.0
5.1.2
5.1.1
5.1.0
5.0.2
5.0.1
5.0.0
4.0.1
4.0.0
3.0.0
2.1.0
2.0.0
1.0.1
1.0.0
0.22.7
0.22.6
0.22.5
0.22.4
0.22.3
0.22.2
0.22.1
0.22.0
0.21.1
0.21.0
0.20.2
0.20.1
0.20.0
0.19.1
0.19.0
0.18.3
0.18.2
0.18.1
0.18.0
0.17.7
0.17.6
0.17.5
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.2
0.16.1
0.16.0
0.15.11
0.15.10
0.15.9
0.15.8
0.15.7
0.15.6
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.8
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.13.0
0.12.3
0.12.2
0.12.1
0.12.0
0.11.0
0.10.3
0.10.2
0.10.1
0.10.0
0.9.0
0.9.0-rc.4
0.9.0-rc.3
0.9.0-rc.2
0.9.0-rc.1
0.9.0-rc.0
0.8.8
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.0
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.0
0.4.3
0.4.2
Elixir Z-Wave library
Current section
Files
Jump to
Current section
Files
lib/grizzly/command/encoding.ex
defmodule Grizzly.Command.Encoding do
@moduledoc "Utility module to validate command arguments prior to command encoding"
alias Grizzly.Command.EncodeError
require Logger
@type size :: non_neg_integer | atom
@type sizable :: :bits | :bytes
@type specs ::
spec
| [specs]
| {:encode_with, atom}
| {:encode_with, atom, atom}
| {:range, integer, integer}
| %{required(atom) => specs}
@type spec ::
:byte
| :byte
| :integer
| :binary
| :bit
| {sizable, size}
| {[specs], size}
@spec encode_and_validate_args(struct(), %{required(atom()) => specs}, [atom()]) ::
{:ok, struct()} | {:error, EncodeError.t()}
@doc "Verifies that the (sub)arguments of a command, possibly after encoding, meet the given type specs"
def encode_and_validate_args(command, type_specs, sub_arguments_path \\ []) do
command_module = command.__struct__
Enum.reduce_while(
type_specs,
{:ok, command},
fn {arg_name, specs}, {:ok, acc} ->
case arg_value(command, sub_arguments_path, arg_name) do
nil ->
_ =
Logger.warn(
"Command arg #{inspect(arg_name)} not found for specs #{inspect(specs)}"
)
error = EncodeError.new({:invalid_argument_value, arg_name, nil, command_module})
{:halt, {:error, error}}
value ->
case validate_arg(specs, value, command) do
{:ok, maybe_encoded_value} ->
{:cont, {:ok, Map.put(acc, arg_name, maybe_encoded_value)}}
_ ->
error =
EncodeError.new({:invalid_argument_value, arg_name, value, command_module})
{:halt, {:error, error}}
end
end
end
)
end
defp arg_value(args, [], arg_name) do
Map.get(args, arg_name)
end
defp arg_value(args, [sub | rest], arg_name) do
Map.get(args, sub) |> arg_value(rest, arg_name)
end
defp validate_arg(:bit, value, _command) when value in 0..1 do
{:ok, value}
end
defp validate_arg(:byte, value, _command) when value in 0..255 do
{:ok, value}
end
defp validate_arg(:integer, value, _command) when is_integer(value) do
{:ok, value}
end
defp validate_arg(:binary, value, _command) when is_binary(value) do
{:ok, value}
end
defp validate_arg({:bits, n}, value, _command)
when is_integer(n) and is_integer(value) do
max = :math.pow(2, n) |> round()
if value in 0..max do
{:ok, value}
else
{:error, :invalid_arg, value}
end
end
defp validate_arg({:bytes, n}, value, _command)
when is_integer(n) and is_integer(value) do
max = round(:math.pow(2, n * 8)) - 1
if value in 0..max do
{:ok, value}
else
{:error, :invalid_arg, value}
end
end
defp validate_arg({[spec], n}, value, command) when is_integer(n) and is_list(value) do
if Enum.count(value) == n do
validate_arg([spec], value, command)
else
{:error, :invalid_arg, value}
end
end
defp validate_arg({sizable, field}, value, command)
when sizable in [:bits, :bytes] and is_atom(field) and is_integer(value) do
case Map.get(command, field) do
nil ->
{:error, :invalid_arg, value}
n when is_integer(n) ->
validate_arg({sizable, n}, value, command)
_ ->
{:error, :invalid_arg, value}
end
end
defp validate_arg([spec], value_list, command) when is_list(value_list) do
results =
Enum.reduce_while(
value_list,
{:ok, []},
fn value, {:ok, acc} ->
case validate_arg(spec, value, command) do
{:ok, maybe_encoded_value} ->
{:cont, {:ok, [maybe_encoded_value | acc]}}
error ->
{:halt, error}
end
end
)
case results do
{:ok, list} ->
{:ok, Enum.reverse(list)}
error ->
error
end
end
defp validate_arg(specs_map, value_map, command) when is_map(specs_map) and is_map(value_map) do
Enum.reduce_while(
value_map,
{:ok, %{}},
fn {key, val}, {:ok, acc} ->
case Map.get(specs_map, key) do
nil ->
{:halt, {:error, :invalid_arg, value_map}}
specs ->
case validate_arg(specs, val, command) do
{:ok, maybe_encoded_val} ->
{:cont, {:ok, Map.put(acc, key, maybe_encoded_val)}}
{:error, :invalid_arg, _} ->
{:halt, {:error, :invalid_arg, value_map}}
end
end
end
)
end
defp validate_arg({:range, low, high}, value, _command) when is_number(value) do
if value >= low and value <= high, do: {:ok, value}, else: {:error, :invalid_arg, value}
end
defp validate_arg({:range, _low, _high}, value, _command) do
{:error, :invalid_arg, value}
end
defp validate_arg({:encode_with, encode_method}, value, command) do
command_module = command.__struct__
apply(command_module, encode_method, [value])
end
defp validate_arg({:encode_with, module, encode_method}, value, _command) do
apply(module, encode_method, [value])
end
defp validate_arg(_spec, value, _command) do
{:error, :invalid_arg, value}
end
end