Current section

Files

Jump to
exproto lib protobuf validator.ex
Raw

lib/protobuf/validator.ex

defmodule Protobuf.Validator do
alias Protobuf.MessageProps
def validate!(struct) do
case valid?(struct) do
true -> true
{:invalid, msg} -> raise(Protobuf.InvalidError, message: msg)
end
end
def valid?(%{__struct__: mod} = struct) do
msg_props = mod.__message_props__
valid = if MessageProps.has_oneof?(msg_props), do: oneof_valid?(struct, msg_props), else: true
ctx = %{syntax: msg_props.syntax}
if valid == true do
Enum.reduce_while(msg_props.field_props, true, fn {_, props}, _ ->
if field_valid?(ctx, props, Map.get(struct, props.name_atom)) do
{:cont, true}
else
{:halt,
{:invalid,
"""
#{inspect(mod)}##{props.name_atom} is invalid!
struct: #{inspect(struct)}
"""}}
end
end)
else
valid
end
end
def field_valid?(_, %{required?: true}, nil), do: false
def field_valid?(_, %{repeated?: true, embedded?: true, type: type}, list) when is_list(list) do
Enum.all?(list, fn val -> match_and_valid?(type, val) end)
end
def field_valid?(_, %{repeated?: true, embedded?: false, type: type}, list)
when is_list(list) do
Enum.all?(list, fn val -> type_valid?(type, val) end)
end
def field_valid?(_, %{map?: true, type: type}, map) when is_map(map) do
key_type = type.__message_props__.field_props[1].type
val_type = type.__message_props__.field_props[2].type
Enum.all?(map, fn {k, v} ->
type_valid?(key_type, k) && match_and_valid?(val_type, v)
end)
end
# oneof fields are not present in struct
def field_valid?(_, %{oneof: oneof}, nil) when is_integer(oneof), do: true
# nil is allowed for singular embedded message
def field_valid?(_, %{embedded?: true}, nil), do: true
def field_valid?(_, %{embedded?: true, type: type}, val) do
match_and_valid?(type, val)
end
# nil is allowed for proto2
def field_valid?(%{syntax: :proto2}, _, nil), do: true
def field_valid?(_, %{type: type}, val), do: type_valid?(type, val)
defp match_and_valid?(mod, %{__struct__: mod} = val) do
valid?(val)
end
defp match_and_valid?(type, val), do: type_valid?(type, val)
defp type_valid?(:int32, val) when is_integer(val), do: true
defp type_valid?(:int64, val) when is_integer(val), do: true
defp type_valid?(:uint32, val) when is_integer(val) and val >= 0, do: true
defp type_valid?(:uint64, val) when is_integer(val) and val >= 0, do: true
defp type_valid?(:sint32, val) when is_integer(val), do: true
defp type_valid?(:sint64, val) when is_integer(val), do: true
defp type_valid?(:bool, val) when is_boolean(val), do: true
defp type_valid?(:enum, val) when is_atom(val), do: true
defp type_valid?(:enum, val) when is_integer(val), do: true
defp type_valid?(:fixed32, val) when is_integer(val) and val >= 0, do: true
defp type_valid?(:sfixed32, val) when is_integer(val), do: true
defp type_valid?(:fixed64, val) when is_integer(val) and val >= 0, do: true
defp type_valid?(:sfixed64, val) when is_integer(val), do: true
defp type_valid?(:float, val) when is_number(val), do: true
defp type_valid?(:double, val) when is_number(val), do: true
defp type_valid?(:bytes, val) when is_binary(val), do: true
defp type_valid?(:string, val) when is_binary(val), do: true
# TODO
defp type_valid?(:group, _), do: true
defp type_valid?(_, _), do: false
defp oneof_valid?(struct, msg_props) do
field_tags = msg_props.field_tags
field_props = msg_props.field_props
ctx = %{syntax: msg_props.syntax}
Enum.reduce_while(msg_props.oneof, true, fn {oneof_name, index}, _ ->
case Map.get(struct, oneof_name) do
{field, val} ->
field_props = field_props[field_tags[field]]
check_oneof_field(field, oneof_name, field_props, index, ctx, val)
nil ->
{:cont, true}
_ ->
{:halt, {:invalid, ":#{oneof_name} should be a tuple {:field, val}"}}
end
end)
end
defp check_oneof_field(field, oneof_name, field_props, index, ctx, val) do
cond do
field_props.oneof != index ->
{:halt, {:invalid, ":#{field} doesn't match oneof field :#{oneof_name}"}}
!field_valid?(ctx, field_props, val) ->
{:halt, {:invalid, ":#{field} of :#{oneof_name} is invalid"}}
true ->
{:cont, true}
end
end
end