Packages
protox
1.7.1
2.0.9
2.0.8
2.0.7
2.0.6
2.0.5
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
2.0.0-dev
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
retired
1.7.3
1.7.2
1.7.1
1.7.0
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.1
1.5.0
1.4.0
1.3.2
1.3.1
1.3.0
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.1.1
1.1.0
1.0.0
0.25.0
0.24.0
0.23.1
0.23.0
0.22.0
0.21.0
0.20.0
0.19.1
0.19.0
0.18.0
0.17.0
0.16.2
0.16.1
0.16.0
0.15.2
0.15.1
0.15.0
0.14.0
0.13.0
0.12.1
0.12.0
0.11.1
0.11.0
0.10.0
0.9.1
0.9.0
0.8.0
0.7.1
0.7.0
A fast, easy to use and 100% conformant Elixir library for Google Protocol Buffers (aka protobuf)
Current section
Files
Jump to
Current section
Files
lib/protox/json_decode.ex
defmodule Protox.JsonDecode do
@moduledoc false
alias Protox.{Field, JsonDecodingError, JsonEnumDecoder, JsonMessageDecoder}
import Protox.Guards
use Protox.{Float, Integer}
@spec decode!(iodata(), atom(), fun()) :: any() | no_return()
def decode!(input, mod, json_decode) do
case json_decode.(input) do
nil -> raise JsonDecodingError.new("invalid top-level null")
json -> JsonMessageDecoder.decode_message(struct(mod), json)
end
end
def decode_message(_initial_msg, nil), do: nil
def decode_message(initial_msg, json) when is_map(json) do
Enum.reduce(json, initial_msg, fn {field_json_name, field_value}, msg_acc ->
case get_field(initial_msg, field_json_name) do
{:ok, field} ->
case decode_msg_field(field, field_value, msg_acc) do
nil ->
msg_acc
{_field_name, nil} ->
msg_acc
{field_name, field_value} ->
Map.put(msg_acc, field_name, field_value)
end
{:error, :no_such_field} ->
msg_acc
end
end)
end
def decode_message(_initial_msg, _json) do
raise JsonDecodingError.new("Expected an object for message field")
end
def decode_enum(_enum, nil), do: nil
def decode_enum(enum, json) when is_integer(json) do
enum.__struct__.decode(json)
end
def decode_enum(enum, json) when is_binary(json) do
if enum.__struct__.encode(json) == json do
# It's quite a hack: generated encode/1 returns its argument unmodified if it's not
# part of the enum.
raise JsonDecodingError.new("#{json} is not value of #{enum.__struct__}")
else
String.to_atom(json)
end
end
def decode_value("Infinity", type) when is_protobuf_float(type), do: :infinity
def decode_value("-Infinity", type) when is_protobuf_float(type), do: :"-infinity"
def decode_value("NaN", type) when is_protobuf_float(type), do: :nan
def decode_value(true, :bool), do: true
def decode_value(false, :bool), do: false
def decode_value(json_value, type) when is_binary(json_value) and is_protobuf_float(type) do
case Float.parse(json_value) do
{value, ""} -> decode_value(value, type)
_ -> raise JsonDecodingError.new("#{json_value} is not a valid float")
end
end
def decode_value(json_value, type) when is_binary(json_value) and is_primitive(type) do
case Integer.parse(json_value) do
{value, ""} -> decode_value(value, type)
_ -> raise JsonDecodingError.new("#{json_value} is not a valid integer")
end
end
def decode_value(json_value, :bytes) when is_binary(json_value) do
try do
Base.url_decode64!(json_value, padding: false)
rescue
e in ArgumentError ->
reraise Protox.JsonDecodingError.new(Exception.message(e)), __STACKTRACE__
end
end
def decode_value(json_value, :string) when is_binary(json_value), do: json_value
def decode_value(json_value, {:enum, enum_mod}) do
JsonEnumDecoder.decode_enum(struct(enum_mod), json_value)
end
def decode_value(json_value, {:message, msg_mod}) do
JsonMessageDecoder.decode_message(struct(msg_mod), json_value)
end
def decode_value(json_value, type)
when type in [:uint32, :fixed32] and is_integer(json_value) and
json_value > @max_unsigned_32 do
raise JsonDecodingError.new("#{json_value} is too large for a #{type}")
end
def decode_value(json_value, type)
when type in [:int32, :sint32, :sfixed32] and is_integer(json_value) and
json_value > @max_signed_32 do
raise JsonDecodingError.new("#{json_value} is too large for a #{type}")
end
def decode_value(json_value, type)
when type in [:int32, :sint32, :sfixed32] and is_integer(json_value) and
json_value < @min_signed_32 do
raise JsonDecodingError.new("#{json_value} is too small for a #{type}")
end
def decode_value(json_value, type)
when type in [:uint64, :fixed64] and is_integer(json_value) and
json_value > @max_unsigned_64 do
raise JsonDecodingError.new("#{json_value} is too large for a #{type}")
end
def decode_value(json_value, type)
when type in [:int64, :sint64, :sfixed64] and is_integer(json_value) and
json_value > @max_signed_64 do
raise JsonDecodingError.new("#{json_value} is too large for a #{type}")
end
def decode_value(json_value, type)
when type in [:int64, :sint64, :sfixed64] and is_integer(json_value) and
json_value < @min_signed_64 do
raise JsonDecodingError.new("#{json_value} is too small for a #{type}")
end
def decode_value(json_value, type)
when type in [:uint32, :fixed32, :uint64, :fixed64] and is_integer(json_value) and
json_value < 0 do
raise JsonDecodingError.new("#{json_value} is too small for a #{type}")
end
def decode_value(json_value, type)
when type == :float and is_number(json_value) and json_value > @max_float do
raise JsonDecodingError.new("#{json_value} is too large for a #{type}")
end
def decode_value(json_value, type)
when type == :float and is_number(json_value) and json_value < @min_float do
raise JsonDecodingError.new("#{json_value} is too small for a #{type}")
end
def decode_value(json_value, type)
when type == :double and is_number(json_value) and json_value > @max_double do
raise JsonDecodingError.new("#{json_value} is too large for a #{type}")
end
def decode_value(json_value, type)
when type == :double and is_number(json_value) and json_value < @min_double do
raise JsonDecodingError.new("#{json_value} is too small for a #{type}")
end
def decode_value(json_value, type) when is_protobuf_integer(type) and is_float(json_value) do
decimal_float = Decimal.from_float(json_value)
if Decimal.equal?(Decimal.round(decimal_float), decimal_float) do
decode_value(Decimal.to_integer(decimal_float), type)
else
raise JsonDecodingError.new(
"cannot decode #{inspect(json_value)} for type #{inspect(type)}"
)
end
end
def decode_value(json_value, type) when is_protobuf_integer(type) and is_integer(json_value) do
json_value
end
def decode_value(json_value, type) when is_protobuf_float(type) and is_number(json_value) do
json_value
end
def decode_value(nil = _json_value, type)
when is_primitive(type) or type == :bytes or type == :string do
nil
end
def decode_value(json_value, type) do
raise JsonDecodingError.new("cannot decode #{inspect(json_value)} for type #{inspect(type)}")
end
# -- Private
defp decode_msg_field(%Field{kind: {:scalar, _default_value}} = field, json_value, _msg) do
{field.name, decode_value(json_value, field.type)}
end
defp decode_msg_field(%Field{label: :proto3_optional} = field, json_value, _msg) do
field_value = decode_value(json_value, field.type)
case field_value do
nil -> nil
field_value -> {field.name, field_value}
end
end
defp decode_msg_field(%Field{kind: {:oneof, parent_name}} = field, json_value, msg) do
field_value = decode_value(json_value, field.type)
if Map.fetch!(msg, parent_name) != nil and field_value != nil do
raise JsonDecodingError.new("oneof #{parent_name} already set")
else
case field_value do
nil -> nil
field_value -> {parent_name, {field.name, field_value}}
end
end
end
defp decode_msg_field(%Field{kind: :map} = _field, nil = _json_value, _msg), do: nil
defp decode_msg_field(
%Field{kind: :map, type: {key_type, value_type}} = field,
json_value,
_msg
)
when is_map(json_value) do
map =
json_value
|> Stream.map(fn
{_key, nil} ->
raise JsonDecodingError.new("found null value in map")
# A special case for booleans is required as they are accepted as strings
# when keys of a map, but not as values (which means we can't have a
# decode_value() function for :bool
{"true", value} when key_type == :bool ->
{true, decode_value(value, value_type)}
{"false", value} when key_type == :bool ->
{false, decode_value(value, value_type)}
{key, value} ->
{decode_value(key, key_type), decode_value(value, value_type)}
end)
|> Enum.into(%{})
{field.name, map}
end
defp decode_msg_field(%Field{kind: :map} = field, _json_value, _msg) do
raise JsonDecodingError.new("Expected a map for field #{field.name}")
end
defp decode_msg_field(%Field{label: :repeated} = _field, nil = _json_value, _msg), do: nil
defp decode_msg_field(%Field{label: :repeated} = field, json_value, _msg)
when is_list(json_value) do
list =
Enum.map(json_value, fn value ->
case decode_value(value, field.type) do
nil -> raise JsonDecodingError.new("found null value in array")
decoded_value -> decoded_value
end
end)
{field.name, list}
end
defp decode_msg_field(%Field{label: :repeated} = field, _json_value, _msg) do
raise JsonDecodingError.new("Expected a list for field #{field.name}")
end
defp get_field(msg, json_name) do
msg.__struct__.field_def(json_name)
end
end