diff --git a/README.md b/README.md index 4847b981..ebc8f987 100644 --- a/README.md +++ b/README.md @@ -147,17 +147,15 @@ end ## Serialization -A `BinaryProtocol` module is generated for each Thrift struct, union, and -exception type. You can use this interface to easily serialize and deserialize -your own types. +Each thrift struct, union and exception also has a `SerDe` protocol generated for +it. This module lets you serialize and deserialize its own type easily via `Thrift.Serializable`. ```elixir iex> alias Calculator.Generated.Vector iex> data = %Vector{x: 1, y: 2, z: 3} -|> Vector.BinaryProtocol.serialize -|> IO.iodata_to_binary -iex> Vector.BinaryProtocol.deserialize(data) -{%Calculator.Generated.Vector{x: 1.0, y: 2.0, z: 3.0}, ""} +|> Thrift.Serializable.serialize(%Thrift.Binary{payload: ""}) +iex> Thrift.Serializable.deserialize(%Vector{}, data) +{%Calculator.Generated.Vector{x: 1.0, y: 2.0, z: 3.0}, %Thrift.Protocol.Binary{payload: ""}} ``` ## Thrift IDL Parsing diff --git a/lib/thrift.ex b/lib/thrift.ex index 0b6257b3..21804f13 100644 --- a/lib/thrift.ex +++ b/lib/thrift.ex @@ -78,15 +78,15 @@ defmodule Thrift do ... the generated code will be placed in the following modules under `lib/thrift/`: - Definition | Module - ------------------------- | ----------------------------------------------- - `User` struct | `Thrift.Test.User` - *└ binary protocol* | `Thrift.Test.User.BinaryProtocol` - `UserNotFound` exception | `Thrift.Test.UserNotFound` - *└ binary protocol* | `Thrift.Test.UserNotFound.BinaryProtocol` - `UserService` service | `Thrift.Test.UserService.Handler` - *└ binary framed client* | `Thrift.Test.UserService.Binary.Framed.Client` - *└ binary framed server* | `Thrift.Test.UserService.Binary.Framed.Server` + Definition | Module + -------------------------- | ----------------------------------------------- + `User` struct | `Thrift.Test.User` + *└ serialization protocol* | `Thrift.Test.User.SerDe` + `UserNotFound` exception | `Thrift.Test.UserNotFound` + *└ serialization protocol* | `Thrift.Test.UserNotFound.SerDe` + `UserService` service | `Thrift.Test.UserService.Handler` + *└ binary framed client* | `Thrift.Test.UserService.Binary.Framed.Client` + *└ binary framed server* | `Thrift.Test.UserService.Binary.Framed.Server` ### Namespaces diff --git a/lib/thrift/binary/framed/client.ex b/lib/thrift/binary/framed/client.ex index 18be648b..a89a9ff6 100644 --- a/lib/thrift/binary/framed/client.ex +++ b/lib/thrift/binary/framed/client.ex @@ -182,32 +182,34 @@ defmodule Thrift.Binary.Framed.Client do end end - @spec oneway(pid, String.t(), iodata, options) :: :ok + @spec oneway(pid, String.t(), struct, options) :: :ok @doc """ Execute a one way RPC. One way RPC calls do not generate a response, and as such, this implementation uses `GenServer.cast`. - The data argument must be a properly formatted Thrift message. + The argument must be a Thrift struct. """ - def oneway(conn, rpc_name, serialized_args, _opts) do + def oneway(conn, rpc_name, args, _opts) do + serialized_args = Thrift.Serializable.serialize(args, %Binary{payload: ""}) :ok = Connection.cast(conn, {:oneway, rpc_name, serialized_args}) end - @spec call(pid, String.t(), iodata, module, options) :: protocol_response + @spec call(pid, String.t(), struct, struct, options) :: protocol_response @doc """ - Executes a Thrift RPC. The data argument must be a correctly formatted - Thrift message. + Executes a Thrift RPC. The argument and response arguments must be Thrift structs. The `opts` argument takes the same type of keyword list that `start_link` takes. """ - def call(conn, rpc_name, serialized_args, deserialize_module, opts) do + def call(conn, rpc_name, args, resp, opts) do tcp_opts = Keyword.get(opts, :tcp_opts, []) gen_server_opts = Keyword.get(opts, :gen_server_opts, []) gen_server_timeout = Keyword.get(gen_server_opts, :timeout, 5000) + serialized_args = Thrift.Serializable.serialize(args, %Binary{payload: ""}) + case Connection.call(conn, {:call, rpc_name, serialized_args, tcp_opts}, gen_server_timeout) do {:ok, data} -> - data - |> deserialize_module.deserialize + resp + |> Thrift.Serializable.deserialize(%Binary{payload: data}) |> unpack_response {:error, _} = err -> @@ -222,7 +224,7 @@ defmodule Thrift.Binary.Framed.Client do # # As a small optimization, we only use this function if the response struct # has at least one exception field (hence the `map_size/1` guard check). - defp unpack_response({%{success: nil} = response, ""}) when map_size(response) > 2 do + defp unpack_response({%{success: nil} = response, %Binary{payload: ""}}) when map_size(response) > 2 do exception = response |> Map.from_struct() @@ -236,7 +238,7 @@ defmodule Thrift.Binary.Framed.Client do end end - defp unpack_response({%{success: result}, ""}), do: {:ok, result} + defp unpack_response({%{success: result}, %Binary{payload: ""}}), do: {:ok, result} defp unpack_response({:error, _} = error), do: error def handle_call(_, _, %{sock: nil} = s) do @@ -244,7 +246,7 @@ defmodule Thrift.Binary.Framed.Client do end def handle_call( - {:call, rpc_name, serialized_args, tcp_opts}, + {:call, rpc_name, %Binary{payload: serialized_args}, tcp_opts}, _, %{sock: {transport, sock}, seq_id: seq_id, timeout: default_timeout} = s ) do @@ -276,7 +278,7 @@ defmodule Thrift.Binary.Framed.Client do end def handle_cast( - {:oneway, rpc_name, serialized_args}, + {:oneway, rpc_name, %Binary{payload: serialized_args}}, %{sock: {transport, sock}, seq_id: seq_id} = s ) do s = %{s | seq_id: seq_id + 1} @@ -304,8 +306,7 @@ defmodule Thrift.Binary.Framed.Client do seq_id, rpc_name ) do - exception = Binary.deserialize(:application_exception, serialized_response) - {:error, {:exception, exception}} + {:error, {:exception, deserialize_exception(serialized_response)}} end defp handle_message({:ok, {message_type, seq_id, rpc_name, _}}, seq_id, rpc_name) do @@ -345,6 +346,18 @@ defmodule Thrift.Binary.Framed.Client do err end + defp deserialize_exception(payload) do + case TApplicationException.SerDe.deserialize(%Binary{payload: payload}) do + {exception, %Binary{payload: ""}} -> + exception + _ -> + TApplicationException.exception( + type: :protocol_error, + message: "Unable to decode exception (#{inspect payload})" + ) + end + end + defp to_host(host) when is_bitstring(host) do String.to_charlist(host) end diff --git a/lib/thrift/binary/framed/protocol_handler.ex b/lib/thrift/binary/framed/protocol_handler.ex index db721ce2..b1ad1d60 100644 --- a/lib/thrift/binary/framed/protocol_handler.ex +++ b/lib/thrift/binary/framed/protocol_handler.ex @@ -170,23 +170,35 @@ defmodule Thrift.Binary.Framed.ProtocolHandler do server_module, handler_module ) do - case server_module.handle_thrift(name, args_binary, handler_module) do - {:reply, serialized_reply} -> + {args, _} = server_module.deserialize(name, %Thrift.Protocol.Binary{payload: args_binary}) + case server_module.handle_thrift(args, handler_module) do + {:reply, reply} -> message = Protocol.Binary.serialize(:message_begin, {:reply, sequence_id, name}) + %Thrift.Protocol.Binary{payload: serialized_msg} = + Thrift.Serializable.serialize(reply, %Thrift.Protocol.Binary{payload: message}) - {:ok, :reply, [message | serialized_reply]} - - {:server_error, %TApplicationException{} = exc} -> - message = Protocol.Binary.serialize(:message_begin, {:exception, sequence_id, name}) - serialized_exception = Protocol.Binary.serialize(:application_exception, exc) - - {:error, {:server_error, [message | serialized_exception]}} - + {:ok, :reply, serialized_msg} :noreply -> message = Protocol.Binary.serialize(:message_begin, {:reply, sequence_id, name}) {:ok, :reply, [message | <<0>>]} end + catch + kind, reason -> + formatted_exception = Exception.format(kind, reason, System.stacktrace()) + Logger.error("Exception not defined in thrift spec was thrown: #{formatted_exception}") + + error = + Thrift.TApplicationException.exception( + type: :internal_error, + message: "Server error: #{formatted_exception}" + ) + + message = Protocol.Binary.serialize(:message_begin, {:exception, sequence_id, name}) + %Thrift.Protocol.Binary{payload: serialized_msg} = + TApplicationException.SerDe.Thrift.Protocol.Binary.serialize(%Thrift.Protocol.Binary{payload: message}, error) + + {:ok, :reply, serialized_msg} end defp handle_thrift_message( @@ -194,7 +206,8 @@ defmodule Thrift.Binary.Framed.ProtocolHandler do server_module, handler_module ) do - spawn(server_module, :handle_thrift, [name, args_binary, handler_module]) + {args, _} = server_module.deserialize(name, %Thrift.Protocol.Binary{payload: args_binary}) + spawn(server_module, :handle_thrift, [args, handler_module]) {:ok, :reply, <<0>>} end diff --git a/lib/thrift/exceptions.ex b/lib/thrift/exceptions.ex index 74f8a9b9..8b508308 100644 --- a/lib/thrift/exceptions.ex +++ b/lib/thrift/exceptions.ex @@ -4,6 +4,7 @@ defmodule Thrift.TApplicationException do Application-level exception """ + @type t() :: %__MODULE__{message: String.t, type: exception_type()} @enforce_keys [:message, :type] defexception message: "unknown", type: :unknown @@ -27,6 +28,11 @@ defmodule Thrift.TApplicationException do injected_failure: 13 ] + @typedoc """ + Exception types + """ + @type exception_type :: unquote(Enum.reduce(@exception_types, fn {type, _}, acc -> {:|, [] , [type, acc]} end)) + def exception(args) when is_list(args) do type = normalize_type(Keyword.fetch!(args, :type)) message = args[:message] || Atom.to_string(type) @@ -36,7 +42,7 @@ defmodule Thrift.TApplicationException do @doc """ Converts an exception type to its integer identifier. """ - @spec type_id(atom) :: non_neg_integer + @spec type_id(exception_type()) :: non_neg_integer() def type_id(type) for {type, id} <- @exception_types do @@ -46,6 +52,35 @@ defmodule Thrift.TApplicationException do end defp normalize_type(type) when is_integer(type), do: :unknown + + defprotocol SerDe do + @moduledoc """ + Serialize and deserialize protocol for `Thrift.TApplicationException.t` + """ + + @doc """ + Serialize `Thrift.TApplicationException.t` with a protocol payload. + """ + @spec serialize(payload, TApplicationException.t()) :: payload when payload: var + def serialize(payload, err) + + @doc """ + Deserialize `Thrift.TApplication.t` with a protocol payload. + """ + @spec deserialize(payload) :: {TApplicationException.t(), payload} | :error when payload: var + def deserialize(payload) + + @doc """ + Deserialize `Thrift.TApplication.t` with a protocol payload and default values. + """ + @spec deserialize(payload, TApplicationException.t()) :: {TApplicationException.t(), payload} | :error when payload: var + def deserialize(payload, err) + end + + defimpl Thrift.Serializable do + def serialize(err, payload), do: SerDe.serialize(payload, err) + def deserialize(err, payload), do: SerDe.deserialize(payload, err) + end end defmodule Thrift.ConnectionError do diff --git a/lib/thrift/generator/binary/framed/client.ex b/lib/thrift/generator/client.ex similarity index 75% rename from lib/thrift/generator/binary/framed/client.ex rename to lib/thrift/generator/client.ex index 32dcb6ed..e54cb9c3 100644 --- a/lib/thrift/generator/binary/framed/client.ex +++ b/lib/thrift/generator/client.ex @@ -1,4 +1,4 @@ -defmodule Thrift.Generator.Binary.Framed.Client do +defmodule Thrift.Generator.Client do @moduledoc false alias Thrift.AST.Function @@ -27,11 +27,6 @@ defmodule Thrift.Generator.Binary.Framed.Client do end defp generate_handler_function(function) do - args_module = Service.module_name(function, :args) - args_binary_module = Module.concat(args_module, :BinaryProtocol) - response_module = Service.module_name(function, :response) - rpc_name = Atom.to_string(function.name) - # Make two Elixir-friendly function names: an underscored version of the # Thrift function name and a "bang!" exception-raising variant. function_name = @@ -75,9 +70,7 @@ defmodule Thrift.Generator.Binary.Framed.Client do quote do def(unquote(function_name)(client, unquote_splicing(vars), rpc_opts \\ [])) do - args = %unquote(args_module){unquote_splicing(assignments)} - serialized_args = unquote(args_binary_module).serialize(args) - unquote(build_response_handler(function, rpc_name, response_module)) + unquote(build_call(function, assignments)) end def(unquote(bang_name)(client, unquote_splicing(vars), rpc_opts \\ [])) do @@ -95,18 +88,24 @@ defmodule Thrift.Generator.Binary.Framed.Client do end end - defp build_response_handler(%Function{oneway: true}, rpc_name, _response_module) do + defp build_call(%Function{oneway: true} = function, assignments) do + rpc_name = Atom.to_string(function.name) + args_module = Service.module_name(function, :args) quote do - :ok = ClientImpl.oneway(client, unquote(rpc_name), serialized_args, rpc_opts) + args = %unquote(args_module){unquote_splicing(assignments)} + :ok = ClientImpl.oneway(client, unquote(rpc_name), args, rpc_opts) {:ok, nil} end end - defp build_response_handler(%Function{oneway: false}, rpc_name, response_module) do - module = Module.concat(response_module, :BinaryProtocol) - + defp build_call(%Function{oneway: false} = function, assignments) do + rpc_name = Atom.to_string(function.name) + args_module = Service.module_name(function, :args) + response_module = Service.module_name(function, :response) quote do - ClientImpl.call(client, unquote(rpc_name), serialized_args, unquote(module), rpc_opts) + args = %unquote(args_module){unquote_splicing(assignments)} + resp = %unquote(response_module){} + ClientImpl.call(client, unquote(rpc_name), args, resp, rpc_opts) end end end diff --git a/lib/thrift/generator/binary/framed/server.ex b/lib/thrift/generator/server.ex similarity index 54% rename from lib/thrift/generator/binary/framed/server.ex rename to lib/thrift/generator/server.ex index aad79121..72e3087b 100644 --- a/lib/thrift/generator/binary/framed/server.ex +++ b/lib/thrift/generator/server.ex @@ -1,6 +1,5 @@ -defmodule Thrift.Generator.Binary.Framed.Server do +defmodule Thrift.Generator.Server do @moduledoc false - alias Thrift.AST.Function alias Thrift.Generator.{ Service, @@ -14,6 +13,8 @@ defmodule Thrift.Generator.Binary.Framed.Server do service.functions |> Map.values() |> Enum.map(&generate_handler_function(file_group, service_module, &1)) + |> Utils.merge_blocks() + |> Utils.sort_defs() quote do defmodule Binary.Framed.Server do @@ -32,28 +33,8 @@ defmodule Thrift.Generator.Binary.Framed.Server do end end - def generate_handler_function(file_group, service_module, %Function{params: []} = function) do - fn_name = Atom.to_string(function.name) - handler_fn_name = Utils.underscore(function.name) - response_module = Module.concat(service_module, Service.module_name(function, :response)) - - body = - quote do - rsp = handler_module.unquote(handler_fn_name)() - unquote(build_responder(function.return_type, response_module)) - end - - handler = wrap_with_try_catch(body, function, file_group, response_module) - - quote do - def handle_thrift(unquote(fn_name), _binary_data, handler_module) do - unquote(handler) - end - end - end - def generate_handler_function(file_group, service_module, function) do - fn_name = Atom.to_string(function.name) + rpc_name = Atom.to_string(function.name) args_module = Module.concat(service_module, Service.module_name(function, :args)) response_module = Module.concat(service_module, Service.module_name(function, :response)) @@ -63,16 +44,12 @@ defmodule Thrift.Generator.Binary.Framed.Server do end) quote do - def handle_thrift(unquote(fn_name), binary_data, handler_module) do - case unquote(args_module).BinaryProtocol.deserialize(binary_data) do - {%unquote(args_module){unquote_splicing(struct_matches)}, ""} -> - unquote(build_handler_call(file_group, function, response_module)) - - {_, extra} -> - raise Thrift.TApplicationException, - type: :protocol_error, - message: "Could not decode #{inspect(extra)}" - end + def deserialize(unquote(rpc_name), payload) do + unquote(args_module).SerDe.deserialize(payload) + end + + def handle_thrift(%unquote(args_module){unquote_splicing(struct_matches)}, handler_module) do + unquote(build_handler_call(file_group, function, response_module)) end end end @@ -101,26 +78,13 @@ defmodule Thrift.Generator.Binary.Framed.Server do quote do unquote(error_var) in unquote(dest_module) -> - response = %unquote(response_module){unquote(field_setter)} - {:reply, unquote(response_module).BinaryProtocol.serialize(response)} + {:reply, %unquote(response_module){unquote(field_setter)}} end end) quote do try do unquote(quoted_handler) - catch - kind, reason -> - formatted_exception = Exception.format(kind, reason, System.stacktrace()) - Logger.error("Exception not defined in thrift spec was thrown: #{formatted_exception}") - - error = - Thrift.TApplicationException.exception( - type: :internal_error, - message: "Server error: #{formatted_exception}" - ) - - {:server_error, error} rescue unquote(rescue_blocks) end @@ -136,8 +100,7 @@ defmodule Thrift.Generator.Binary.Framed.Server do defp build_responder(_, response_module) do quote do - response = %unquote(response_module){success: rsp} - {:reply, unquote(response_module).BinaryProtocol.serialize(response)} + {:reply, %unquote(response_module){success: rsp}} end end end diff --git a/lib/thrift/generator/service.ex b/lib/thrift/generator/service.ex index 3d5acbca..63e859c2 100644 --- a/lib/thrift/generator/service.ex +++ b/lib/thrift/generator/service.ex @@ -23,8 +23,8 @@ defmodule Thrift.Generator.Service do generate_response_struct(schema, function) end - framed_client = Generator.Binary.Framed.Client.generate(service) - framed_server = Generator.Binary.Framed.Server.generate(dest_module, service, file_group) + framed_client = Generator.Client.generate(service) + framed_server = Generator.Server.generate(dest_module, service, file_group) service_module = quote do diff --git a/lib/thrift/generator/struct_binary_protocol.ex b/lib/thrift/generator/struct_binary_protocol.ex index 5e415942..c31c1d68 100644 --- a/lib/thrift/generator/struct_binary_protocol.ex +++ b/lib/thrift/generator/struct_binary_protocol.ex @@ -59,15 +59,28 @@ defmodule Thrift.Generator.StructBinaryProtocol do field_deserializers = fields - |> Enum.map(&field_deserializer(&1.type, &1, :deserialize, file_group)) + |> Enum.map(&field_deserializer(&1.type, &1, :deserialize_struct, file_group)) |> Utils.merge_blocks() quote do - def deserialize(binary) do - deserialize(binary, %unquote(name){}) + def deserialize(binary) when is_binary(binary) do + deserialize_struct(binary, %unquote(name){}) end - defp deserialize(<<0, rest::binary>>, %unquote(name){} = acc) do + def deserialize(other) do + deserialize(other, %unquote(name){}) + end + + def deserialize(%Thrift.Protocol.Binary{payload: payload} = binary, acc) do + case payload |> IO.iodata_to_binary() |> deserialize_struct(acc) do + {result, rest} -> + {result, %Thrift.Protocol.Binary{binary | payload: rest}} + :error -> + :error + end + end + + defp deserialize_struct(<<0, rest::binary>>, %unquote(name){} = acc) do {acc, rest} end @@ -75,13 +88,13 @@ defmodule Thrift.Generator.StructBinaryProtocol do # Skip over unknown fields. This can happen when we encounter serialized # data from a newer schema than our own. - defp deserialize(<>, acc) do + defp deserialize_struct(<>, acc) do rest |> Thrift.Protocol.Binary.skip_field(field_type) - |> deserialize(acc) + |> deserialize_struct(acc) end - defp deserialize(_, _), do: :error + defp deserialize_struct(_, _), do: :error end end @@ -222,7 +235,7 @@ defmodule Thrift.Generator.StructBinaryProtocol do <>, acc ) do - case unquote(dest_module).BinaryProtocol.deserialize(rest) do + case unquote(dest_module).SerDe.Thrift.Protocol.Binary.deserialize(rest) do {value, rest} -> unquote(name)(rest, %{acc | unquote(field.name) => value}) @@ -241,7 +254,7 @@ defmodule Thrift.Generator.StructBinaryProtocol do <>, acc ) do - case unquote(dest_module).BinaryProtocol.deserialize(rest) do + case unquote(dest_module).SerDe.Thrift.Protocol.Binary.deserialize(rest) do {value, rest} -> unquote(name)(rest, %{acc | unquote(field.name) => value}) @@ -260,7 +273,7 @@ defmodule Thrift.Generator.StructBinaryProtocol do <>, acc ) do - case unquote(dest_module).BinaryProtocol.deserialize(rest) do + case unquote(dest_module).SerDe.Thrift.Protocol.Binary.deserialize(rest) do {value, rest} -> unquote(name)(rest, %{acc | unquote(field.name) => value}) @@ -435,7 +448,7 @@ defmodule Thrift.Generator.StructBinaryProtocol do quote do defp unquote(key_name)(<>, stack) do - case unquote(dest_module).BinaryProtocol.deserialize(rest) do + case unquote(dest_module).SerDe.Thrift.Protocol.Binary.deserialize(rest) do {key, rest} -> unquote(value_name)(rest, key, stack) @@ -451,7 +464,7 @@ defmodule Thrift.Generator.StructBinaryProtocol do quote do defp unquote(key_name)(<>, stack) do - case unquote(dest_module).BinaryProtocol.deserialize(rest) do + case unquote(dest_module).SerDe.Thrift.Protocol.Binary.deserialize(rest) do {key, rest} -> unquote(value_name)(rest, key, stack) @@ -467,7 +480,7 @@ defmodule Thrift.Generator.StructBinaryProtocol do quote do defp unquote(key_name)(<>, stack) do - case unquote(dest_module).BinaryProtocol.deserialize(rest) do + case unquote(dest_module).SerDe.Thrift.Protocol.Binary.deserialize(rest) do {key, rest} -> unquote(value_name)(rest, key, stack) @@ -655,7 +668,7 @@ defmodule Thrift.Generator.StructBinaryProtocol do quote do defp unquote(value_name)(<>, key, [map, remaining | stack]) do - case unquote(dest_module).BinaryProtocol.deserialize(rest) do + case unquote(dest_module).SerDe.Thrift.Protocol.Binary.deserialize(rest) do {value, rest} -> unquote(key_name)(rest, [Map.put(map, key, value), remaining - 1 | stack]) @@ -671,7 +684,7 @@ defmodule Thrift.Generator.StructBinaryProtocol do quote do defp unquote(value_name)(<>, key, [map, remaining | stack]) do - case unquote(dest_module).BinaryProtocol.deserialize(rest) do + case unquote(dest_module).SerDe.Thrift.Protocol.Binary.deserialize(rest) do {value, rest} -> unquote(key_name)(rest, [Map.put(map, key, value), remaining - 1 | stack]) @@ -687,7 +700,7 @@ defmodule Thrift.Generator.StructBinaryProtocol do quote do defp unquote(value_name)(<>, key, [map, remaining | stack]) do - case unquote(dest_module).BinaryProtocol.deserialize(rest) do + case unquote(dest_module).SerDe.Thrift.Protocol.Binary.deserialize(rest) do {value, rest} -> unquote(key_name)(rest, [Map.put(map, key, value), remaining - 1 | stack]) @@ -865,7 +878,7 @@ defmodule Thrift.Generator.StructBinaryProtocol do quote do defp unquote(name)(<>, [list, remaining | stack]) do - case unquote(dest_module).BinaryProtocol.deserialize(rest) do + case unquote(dest_module).SerDe.Thrift.Protocol.Binary.deserialize(rest) do {element, rest} -> unquote(name)(rest, [[element | list], remaining - 1 | stack]) @@ -881,7 +894,7 @@ defmodule Thrift.Generator.StructBinaryProtocol do quote do defp unquote(name)(<>, [list, remaining | stack]) do - case unquote(dest_module).BinaryProtocol.deserialize(rest) do + case unquote(dest_module).SerDe.Thrift.Protocol.Binary.deserialize(rest) do {element, rest} -> unquote(name)(rest, [[element | list], remaining - 1 | stack]) @@ -897,7 +910,7 @@ defmodule Thrift.Generator.StructBinaryProtocol do quote do defp unquote(name)(<>, [list, remaining | stack]) do - case unquote(dest_module).BinaryProtocol.deserialize(rest) do + case unquote(dest_module).SerDe.Thrift.Protocol.Binary.deserialize(rest) do {element, rest} -> unquote(name)(rest, [[element | list], remaining - 1 | stack]) @@ -1002,20 +1015,32 @@ defmodule Thrift.Generator.StructBinaryProtocol do field_serializers = [required_field_serializer(match_field, file_group)] quote do - def serialize(%unquote(name){unquote_splicing(field_matchers)}) do + defp serialize_struct(%unquote(name){unquote_splicing(field_matchers)}) do unquote(Utils.optimize_iolist([field_serializers, <<0>>])) end end end) quote do - def serialize(%unquote(name){unquote_splicing(all_fields_nil)}) do + def serialize("", union) do + serialize_struct(union) + end + + def serialize(iodata, union) when is_binary(iodata) or is_list(iodata) do + [iodata | serialize_struct(union)] + end + + def serialize(%Thrift.Protocol.Binary{payload: payload} = binary, union) do + %Thrift.Protocol.Binary{binary | payload: serialize(payload, union)} + end + + defp serialize_struct(%unquote(name){unquote_splicing(all_fields_nil)}) do <<0>> end unquote_splicing(single_field_serializers) - def serialize(%unquote(name){} = value) do + defp serialize_struct(%unquote(name){} = value) do set_fields = value |> Map.from_struct() @@ -1043,7 +1068,19 @@ defmodule Thrift.Generator.StructBinaryProtocol do field_serializers = Enum.map(fields, &field_serializer(&1, name, file_group)) quote do - def serialize(%unquote(name){unquote_splicing(field_matchers)}) do + def serialize("", struct) do + serialize_struct(struct) + end + + def serialize(iodata, struct) when is_binary(iodata) or is_list(iodata) do + [iodata | serialize_struct(struct)] + end + + def serialize(%Thrift.Protocol.Binary{payload: payload} = binary, struct) do + %Thrift.Protocol.Binary{binary | payload: serialize(payload, struct)} + end + + defp serialize_struct(%unquote(name){unquote_splicing(field_matchers)}) do unquote(Utils.optimize_iolist([field_serializers, <<0>>])) end end @@ -1208,7 +1245,7 @@ defmodule Thrift.Generator.StructBinaryProtocol do dest_module = FileGroup.dest_module(file_group, struct) quote do - unquote(dest_module).serialize(unquote(var)) + unquote(dest_module).SerDe.Thrift.Protocol.Binary.serialize("", unquote(var)) end end @@ -1216,7 +1253,7 @@ defmodule Thrift.Generator.StructBinaryProtocol do dest_module = FileGroup.dest_module(file_group, union) quote do - unquote(dest_module).serialize(unquote(var)) + unquote(dest_module).SerDe.Thrift.Protocol.Binary.serialize("", unquote(var)) end end @@ -1224,7 +1261,7 @@ defmodule Thrift.Generator.StructBinaryProtocol do dest_module = FileGroup.dest_module(file_group, struct) quote do - unquote(dest_module).serialize(unquote(var)) + unquote(dest_module).SerDe.Thrift.Protocol.Binary.serialize("", unquote(var)) end end diff --git a/lib/thrift/generator/struct_generator.ex b/lib/thrift/generator/struct_generator.ex index 8c95f0b3..f18e3809 100644 --- a/lib/thrift/generator/struct_generator.ex +++ b/lib/thrift/generator/struct_generator.ex @@ -10,7 +10,7 @@ defmodule Thrift.Generator.StructGenerator do Union } - alias Thrift.Generator.{StructBinaryProtocol, Utils} + alias Thrift.Generator.Utils alias Thrift.Parser.FileGroup def generate(label, schema, name, struct) when label in [:struct, :union, :exception] do @@ -19,13 +19,7 @@ defmodule Thrift.Generator.StructGenerator do {name, Utils.quote_value(default, type, schema)} end) - binary_protocol_defs = - [ - StructBinaryProtocol.struct_serializer(struct, name, schema.file_group), - StructBinaryProtocol.struct_deserializer(struct, name, schema.file_group) - ] - |> Utils.merge_blocks() - |> Utils.sort_defs() + binary_protocol_impl = Thrift.Protocol.Binary.serde_impl(name, struct, schema.file_group) define_block = case label do @@ -63,22 +57,37 @@ defmodule Thrift.Generator.StructGenerator do def new, do: %__MODULE__{} unquote_splicing(List.wrap(extra_defs)) - defmodule BinaryProtocol do - @moduledoc false - unquote_splicing(binary_protocol_defs) + defprotocol SerDe do + @moduledoc """ + Serialize and deserialize helpers for #{unquote(name)} + """ + + @doc """ + Serialize #{unquote(name)} to payload + """ + @spec serialize(payload, unquote(name).t) :: payload when payload: var + def serialize(payload, struct) + + @doc """ + Deserialize #{unquote(name)} from payload + """ + @spec deserialize(payload) :: {unquote(name).t, payload} | :error when payload: var + def deserialize(payload) + + @doc """ + Deserialize #{unquote(name)} from payload with default values + """ + @spec deserialize(payload, unquote(name).t) :: + {unquote(name).t, payload} | :error when payload: var + def deserialize(payload, struct) end - def serialize(struct) do - BinaryProtocol.serialize(struct) + defimpl Thrift.Serializable, for: unquote(name) do + def serialize(struct, payload), do: SerDe.serialize(payload, struct) + def deserialize(struct, payload), do: SerDe.deserialize(payload, struct) end - def serialize(struct, :binary) do - BinaryProtocol.serialize(struct) - end - - def deserialize(binary) do - BinaryProtocol.deserialize(binary) - end + unquote(binary_protocol_impl) end end end diff --git a/lib/thrift/protocol.ex b/lib/thrift/protocol.ex new file mode 100644 index 00000000..95cc8d37 --- /dev/null +++ b/lib/thrift/protocol.ex @@ -0,0 +1,8 @@ +defmodule Thrift.Protocol do + @moduledoc false + + @doc """ + Generate quoted expression for implementing SerDe protocol for a struct. + """ + @callback serde_impl(module, Thrift.AST.Struct.t, Thrift.FileGroup.t) :: Macro.expr +end diff --git a/lib/thrift/protocol/binary.ex b/lib/thrift/protocol/binary.ex index d2274eda..ba8b59e1 100644 --- a/lib/thrift/protocol/binary.ex +++ b/lib/thrift/protocol/binary.ex @@ -9,17 +9,40 @@ defmodule Thrift.Protocol.Binary do """ alias Thrift.{NaN, TApplicationException} + alias Thrift.Generator.{StructBinaryProtocol, Utils} require Thrift.Protocol.Binary.Type, as: Type - @type serializable :: Thrift.data_type() | :message_begin | :application_exception - @type deserializable :: :message_begin | :application_exception + @behaviour Thrift.Protocol + + @type serializable :: Thrift.data_type() | :message_begin + @type deserializable :: :message_begin @stop 0 @typedoc "Binary protocol message type identifier" @type message_type_id :: 1..4 + @type t() :: %__MODULE__{payload: iodata} + @enforce_keys [:payload] + defstruct [:payload] + + @impl Thrift.Protocol + def serde_impl(name, struct, file_group) do + protocol_defs = + [ + StructBinaryProtocol.struct_serializer(struct, name, file_group), + StructBinaryProtocol.struct_deserializer(struct, name, file_group) + ] + |> Utils.merge_blocks() + |> Utils.sort_defs() + quote do + defimpl SerDe, for: unquote(__MODULE__) do + unquote_splicing(protocol_defs) + end + end + end + @spec from_message_type(Thrift.message_type()) :: message_type_id defp from_message_type(:call), do: 1 defp from_message_type(:reply), do: 2 @@ -73,12 +96,9 @@ defmodule Thrift.Protocol.Binary do [<>, rest] end - def serialize(:struct, %{__struct__: mod} = struct) do - mod.serialize(struct, :binary) - end - - def serialize(:union, %{__struct__: mod} = struct) do - mod.serialize(struct, :binary) + def serialize(struct, term) when struct in [:struct, :union] do + %Thrift.Protocol.Binary{payload: payload} = Thrift.Serializable.serialize(term, %Thrift.Protocol.Binary{payload: ""}) + payload end def serialize(:message_begin, {message_type, sequence_id, name}) do @@ -133,36 +153,6 @@ defmodule Thrift.Protocol.Binary do {:error, {:cant_decode_message, rest}} end - def deserialize(:application_exception, binary) when is_binary(binary) do - do_read_application_exception(binary, Keyword.new()) - end - - defp do_read_application_exception( - <>, - opts - ) do - do_read_application_exception(rest, Keyword.put(opts, :message, message)) - end - - defp do_read_application_exception( - <>, - opts - ) do - do_read_application_exception(rest, Keyword.put(opts, :type, type)) - end - - defp do_read_application_exception(<<@stop>>, opts) do - TApplicationException.exception(opts) - end - - defp do_read_application_exception(error, _) do - TApplicationException.exception( - type: :protocol_error, - message: "Unable to decode exception (#{inspect(error)})" - ) - end - @doc """ Skips over the bytes representing a binary-encoded field. @@ -232,4 +222,65 @@ defmodule Thrift.Protocol.Binary do end defp skip_struct(_), do: :error + + defimpl TApplicationException.SerDe do + @stop 0 + + def serialize("", err) do + serialize_struct(err) + end + + def serialize(iodata, err) when is_binary(iodata) or is_list(iodata) do + [iodata | serialize_struct(err)] + end + + def serialize(%Thrift.Protocol.Binary{payload: payload}, err) do + %Thrift.Protocol.Binary{payload: serialize(payload, err)} + end + + defp serialize_struct(%TApplicationException{message: message, type: type}) do + type_id = TApplicationException.type_id(type) + + <> + end + + def deserialize(binary) when is_binary(binary) do + deserialize_struct(binary, []) + end + + def deserialize(%Thrift.Protocol.Binary{payload: iodata} = binary) do + case iodata |> IO.iodata_to_binary() |> deserialize_struct([]) do + {err, rest} -> + {err, %Thrift.Protocol.Binary{binary | payload: rest}} + :error -> + :error + end + end + + def deserialize(%Thrift.Protocol.Binary{payload: iodata} = binary, %Thrift.TApplicationException{message: msg, type: type}) do + case iodata |> IO.iodata_to_binary() |> deserialize_struct([message: msg, type: type]) do + {err, rest} -> + {err, %Thrift.Protocol.Binary{binary | payload: rest}} + :error -> + :error + end + end + + defp deserialize_struct(<>, opts) do + deserialize_struct(rest, [message: message] ++ opts) + end + + defp deserialize_struct(<>, opts) do + deserialize_struct(rest, [type: type] ++ opts) + end + + defp deserialize_struct(<<@stop, rest::binary>>, opts) do + {TApplicationException.exception(opts), rest} + end + + defp deserialize_struct(<<_::binary>>, _opts) do + :error + end + end end diff --git a/lib/thrift/serializable.ex b/lib/thrift/serializable.ex new file mode 100644 index 00000000..128f1ea1 --- /dev/null +++ b/lib/thrift/serializable.ex @@ -0,0 +1,18 @@ +defprotocol Thrift.Serializable do + @moduledoc """ + Protocol to serialize and deserialize thrift structs. + """ + + @doc """ + Serialize a struct to a payload + """ + @spec serialize(thrift_struct, payload) :: payload when thrift_struct: struct, payload: var + def serialize(thrift_struct, payload) + + @doc """ + Deserialize a struct from a payload. + """ + @spec deserialize(thrift_struct, payload) :: + {thrift_struct, payload} | :error when thrift_struct: struct, payload: var + def deserialize(thrift_struct, payload) +end diff --git a/mix.exs b/mix.exs index 6d28b517..a910876b 100644 --- a/mix.exs +++ b/mix.exs @@ -21,6 +21,7 @@ defmodule Thrift.Mixfile do # Build Environment elixirc_paths: elixirc_paths(Mix.env()), + consolidate_protocols: Mix.env() != :test, compilers: [:leex, :yecc, :erlang, :elixir, :app], # Testing diff --git a/test/support/lib/parser_utils.ex b/test/support/lib/parser_utils.ex index 549e20ec..d86d9495 100644 --- a/test/support/lib/parser_utils.ex +++ b/test/support/lib/parser_utils.ex @@ -26,6 +26,7 @@ end defmodule ParserUtils do @moduledoc false alias Thrift.Parser + alias Thrift.Protocol.Binary def parse_thrift(file_path) do Parser.parse_file(file_path) @@ -101,7 +102,7 @@ defmodule ParserUtils do end def serialize_user_elixir(user, opts \\ []) do - serialized = User.BinaryProtocol.serialize(user) + %Binary{payload: serialized} = Thrift.Serializable.serialize(user, %Binary{payload: ""}) if Keyword.get(opts, :convert_to_binary, true) do IO.iodata_to_binary(serialized) @@ -111,7 +112,7 @@ defmodule ParserUtils do end def deserialize_user_elixir(binary_data) do - {%User{}, ""} = User.BinaryProtocol.deserialize(binary_data) + {%User{}, %Binary{payload: ""}} = Thrift.Serializable.deserialize(%Binary{payload: binary_data}, %User{}) end def deserialize_user_erlang(binary_data) do @@ -142,8 +143,7 @@ defmodule ParserUtils do def serialize_nesting(nesting, opts \\ []) def serialize_nesting(nesting, opts) when is_map(nesting) do - alias Nesting.BinaryProtocol - serialized = BinaryProtocol.serialize(:struct, nesting) + %Binary{payload: serialized} = Thrift.Serializable.serialize(nesting, %Binary{payload: %{}}) if Keyword.get(opts, :convert_to_binary, true) do IO.iodata_to_binary(serialized) diff --git a/test/support/lib/thrift_test_case.ex b/test/support/lib/thrift_test_case.ex index 82173236..d68b232e 100644 --- a/test/support/lib/thrift_test_case.ex +++ b/test/support/lib/thrift_test_case.ex @@ -77,11 +77,18 @@ defmodule ThriftTestCase do [] modules -> - parts = Enum.map(modules, fn {module, _} -> Module.split(module) end) + parts = modules + |> Enum.reject(fn {module, _} -> protocol?(module) end) + |> Enum.map(fn {module, _} -> Module.split(module) end) for part <- parts, alias?(part, parts), do: Module.concat(part) end end + defp protocol?(module) do + attrs = module.__info__(:attributes) + Keyword.has_key?(attrs, :protocol) or Keyword.has_key?(attrs, :protocol_impl) + end + defp alias?(module, modules) do # alias when parent module in namespace does not exist not Enum.any?(modules, &(:lists.prefix(&1, module) and &1 != module)) diff --git a/test/thrift/generator/binary_protocol_test.exs b/test/thrift/generator/binary_protocol_test.exs index 4463766a..ab004310 100644 --- a/test/thrift/generator/binary_protocol_test.exs +++ b/test/thrift/generator/binary_protocol_test.exs @@ -5,8 +5,9 @@ defmodule Thrift.Generator.BinaryProtocolTest do alias Thrift.Union.TooManyFieldsSetError def assert_serializes(%{__struct__: mod} = struct, binary) do - assert binary == IO.iodata_to_binary(Binary.serialize(:struct, struct)) - assert {^struct, ""} = mod.deserialize(binary) + serde = Module.concat(mod, SerDe.Thrift.Protocol.Binary) + assert binary == IO.iodata_to_binary(serde.serialize("", struct)) + assert {^struct, ""} = serde.deserialize(binary) # If we randomly mutate any byte in the binary, it may deserialize to a # struct of the proper type, or it may return :error. But it should never @@ -18,7 +19,7 @@ defmodule Thrift.Generator.BinaryProtocolTest do |> List.replace_at(i - 1, :rand.uniform(256) - 1) |> :binary.list_to_bin() - case mod.deserialize(mutated_binary) do + case serde.deserialize(mutated_binary) do {%{__struct__: ^mod}, _} -> :ok :error -> :ok end @@ -30,8 +31,9 @@ defmodule Thrift.Generator.BinaryProtocolTest do binary, %{__struct__: mod} = deserialized_struct ) do - assert binary == IO.iodata_to_binary(Binary.serialize(:struct, struct)) - assert {^deserialized_struct, ""} = mod.deserialize(binary) + serde = Module.concat(mod, SerDe.Thrift.Protocol.Binary) + assert binary == IO.iodata_to_binary(serde.serialize("", struct)) + assert {^deserialized_struct, ""} = serde.deserialize(binary) end @thrift_file name: "bool.thrift", @@ -480,7 +482,7 @@ defmodule Thrift.Generator.BinaryProtocolTest do ) assert_raise TooManyFieldsSetError, fn -> - Binary.serialize(:struct, %UStruct{my_union: %Union{int_field: 123, string_field: "oops"}}) + UStruct.SerDe.Thrift.Protocol.Binary.serialize("", %UStruct{my_union: %Union{int_field: 123, string_field: "oops"}}) end end @@ -665,7 +667,7 @@ defmodule Thrift.Generator.BinaryProtocolTest do "Required boolean field :val on Thrift.Generator.BinaryProtocolTest.RequiredBool must be true or false" assert_raise Thrift.InvalidValueError, message, fn -> - RequiredBool.serialize(%RequiredBool{}) + RequiredBool.SerDe.Thrift.Protocol.Binary.serialize("", %RequiredBool{}) end end @@ -684,7 +686,7 @@ defmodule Thrift.Generator.BinaryProtocolTest do "Required field :val on Thrift.Generator.BinaryProtocolTest.RequiredField must not be nil" assert_raise Thrift.InvalidValueError, message, fn -> - RequiredField.serialize(%RequiredField{}) + RequiredField.SerDe.Thrift.Protocol.Binary.serialize("", %RequiredField{}) end end @@ -907,14 +909,14 @@ defmodule Thrift.Generator.BinaryProtocolTest do thrift_test "lists serialize into maps" do binary = <<13, 0, 2, 3, 3, 0, 0, 0, 1, 91, 92, 0>> - assert binary == %Byte{val_map: %{91 => 92}} |> Byte.serialize() |> IO.iodata_to_binary() - assert binary == %Byte{val_map: [{91, 92}]} |> Byte.serialize() |> IO.iodata_to_binary() + assert binary == Byte.SerDe.Thrift.Protocol.Binary.serialize("", %Byte{val_map: %{91 => 92}}) |> IO.iodata_to_binary() + assert binary == Byte.SerDe.Thrift.Protocol.Binary.serialize("", %Byte{val_map: [{91, 92}]}) |> IO.iodata_to_binary() end thrift_test "lists serialize into sets" do binary = <<14, 0, 3, 3, 0, 0, 0, 1, 91, 0>> - assert binary == %Byte{val_set: MapSet.new([91])} |> Byte.serialize() |> IO.iodata_to_binary() - assert binary == %Byte{val_set: [91]} |> Byte.serialize() |> IO.iodata_to_binary() + assert binary == Byte.SerDe.Thrift.Protocol.Binary.serialize("", %Byte{val_set: MapSet.new([91])}) |> IO.iodata_to_binary() + assert binary == Byte.SerDe.Thrift.Protocol.Binary.serialize("", %Byte{val_set: [91]}) |> IO.iodata_to_binary() end @thrift_file name: "additions.thrift", @@ -977,8 +979,7 @@ defmodule Thrift.Generator.BinaryProtocolTest do assert %AlreadyNamespaced{}.namespaced == ChocolateAdditionsType.almonds() actual = - choco - |> Chocolate.serialize() + Chocolate.SerDe.Thrift.Protocol.Binary.serialize("", choco) |> IO.iodata_to_binary() expected = <<14, 0, 1, 8, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 2, 8, 0, 2, 0, 0, 0, 3, 0>> diff --git a/test/thrift/generator/service_test.exs b/test/thrift/generator/service_test.exs index 1c0af60a..3544fd8e 100644 --- a/test/thrift/generator/service_test.exs +++ b/test/thrift/generator/service_test.exs @@ -1,6 +1,9 @@ defmodule Thrift.Generator.ServiceTest do use ThriftTestCase + alias Thrift.Serializable + alias Thrift.Protocol.Binary + @thrift_file name: "simple_service.thrift", contents: """ namespace elixir Services.Simple @@ -168,7 +171,8 @@ defmodule Thrift.Generator.ServiceTest do serialized = %UpdateUsernameArgs{id: 1234, new_username: "stinkypants"} - |> UpdateUsernameArgs.BinaryProtocol.serialize() + |> Serializable.serialize(%Binary{payload: ""}) + |> Map.fetch!(:payload) |> IO.iodata_to_binary() assert <<10, 0, 1, 0, 0, 0, 0, 0, 0, 4, 210, 11, 0, 2, 0, 0, 0, 11, "stinkypants", 0>> == @@ -180,7 +184,8 @@ defmodule Thrift.Generator.ServiceTest do serialized = %UpdateUsernameResponse{success: true} - |> UpdateUsernameResponse.BinaryProtocol.serialize() + |> Serializable.serialize(%Binary{payload: ""}) + |> Map.fetch!(:payload) |> IO.iodata_to_binary() assert <<2, 0, 0, 1, 0>> == serialized @@ -194,7 +199,8 @@ defmodule Thrift.Generator.ServiceTest do serialized = %UpdateUsernameResponse{taken: %UsernameTakenException{message: "That username is taken"}} - |> UpdateUsernameResponse.BinaryProtocol.serialize() + |> Serializable.serialize(%Binary{payload: ""}) + |> Map.fetch!(:payload) |> IO.iodata_to_binary() assert <<12, 0, 1, 11, 0, 1, 0, 0, 0, 22, rest::binary>> = serialized diff --git a/test/thrift/protocol/binary_test.exs b/test/thrift/protocol/binary_test.exs index 57a503eb..5cef4600 100644 --- a/test/thrift/protocol/binary_test.exs +++ b/test/thrift/protocol/binary_test.exs @@ -2,26 +2,15 @@ defmodule BinaryProtocolTest do use ThriftTestCase, async: true alias Thrift.Protocol.Binary - - defp serialize(module, struct) do - struct - |> module.serialize - |> IO.iodata_to_binary() - end - - defp deserialize(module, binary_data) do - {struct, ""} = module.deserialize(binary_data) - struct - end + alias Thrift.Serializable defp assert_serde(%module{} = struct, thrift_binary_file) do thrift_binary_file = Path.join("test/data/binary", thrift_binary_file) - binary_protocol_module = Module.safe_concat(module, BinaryProtocol) thrift_binary = File.read!(thrift_binary_file) - serialized = serialize(binary_protocol_module, struct) - deserialized_test_data = deserialize(binary_protocol_module, thrift_binary) - assert deserialize(binary_protocol_module, serialized) == deserialized_test_data + serialized = Serializable.serialize(struct, %Binary{payload: ""}) + deserialized_test_data = Serializable.deserialize(module.new(), %Binary{payload: thrift_binary}) + assert Serializable.deserialize(module.new(), serialized) == deserialized_test_data end @thrift_file name: "enums.thrift", @@ -75,7 +64,7 @@ defmodule BinaryProtocolTest do end thrift_test "it should not encode unset fields" do - assert <<0>> == IO.iodata_to_binary(Scalars.serialize(%Scalars{})) + assert <<0>> == IO.iodata_to_binary(Serializable.serialize(%Scalars{}, %Binary{payload: ""}).payload) end @thrift_file name: "containers.thrift", @@ -236,8 +225,8 @@ defmodule BinaryProtocolTest do new_enum: Grooviness.partially_good() } - serialized = serialize(ChangeyStruct.BinaryProtocol, changey) - {deserialized, ""} = OldChangeyStruct.BinaryProtocol.deserialize(serialized) + serialized = Serializable.serialize(changey, %Binary{payload: ""}) + {deserialized, %Binary{payload: ""}} = Serializable.deserialize(%OldChangeyStruct{}, serialized) assert %OldChangeyStruct{id: 12345, username: "stinkypants"} == deserialized end @@ -251,8 +240,8 @@ defmodule BinaryProtocolTest do new_substruct: sub_struct } - serialized = serialize(ChangeyStruct.BinaryProtocol, changey) - {deserialized, ""} = OldChangeyStruct.BinaryProtocol.deserialize(serialized) + serialized = Serializable.serialize(changey, %Binary{payload: ""}) + {deserialized, %Binary{payload: ""}} = Serializable.deserialize(%OldChangeyStruct{}, serialized) assert %OldChangeyStruct{id: 12345, username: "stinkypants"} == deserialized end @@ -262,8 +251,8 @@ defmodule BinaryProtocolTest do sub = %SubStruct{password: "1234", sub_sub: sub_sub} changey = %ChangeyStruct{id: 12345, username: "stinkypants", new_substruct: sub} - serialized = serialize(ChangeyStruct.BinaryProtocol, changey) - {deserialized, ""} = OldChangeyStruct.BinaryProtocol.deserialize(serialized) + serialized = Serializable.serialize(changey, %Binary{payload: ""}) + {deserialized, %Binary{payload: ""}} = Serializable.deserialize(%OldChangeyStruct{}, serialized) assert %OldChangeyStruct{id: 12345, username: "stinkypants"} == deserialized end @@ -272,8 +261,8 @@ defmodule BinaryProtocolTest do sub = %SubStruct{password: "1234"} changey = %ChangeyStruct{id: 12345, username: "stinkypants", new_list: [sub]} - serialized = serialize(ChangeyStruct.BinaryProtocol, changey) - {deserialized, ""} = OldChangeyStruct.BinaryProtocol.deserialize(serialized) + serialized = Serializable.serialize(changey, %Binary{payload: ""}) + {deserialized, %Binary{payload: ""}} = Serializable.deserialize(%OldChangeyStruct{}, serialized) assert %OldChangeyStruct{id: 12345, username: "stinkypants"} == deserialized end @@ -282,8 +271,8 @@ defmodule BinaryProtocolTest do sub = %SubStruct{password: "1234"} changey = %ChangeyStruct{id: 12345, username: "stinkypants", new_set: MapSet.new([sub, sub])} - serialized = serialize(ChangeyStruct.BinaryProtocol, changey) - {deserialized, ""} = OldChangeyStruct.BinaryProtocol.deserialize(serialized) + serialized = Serializable.serialize(changey, %Binary{payload: ""}) + {deserialized, %Binary{payload: ""}} = Serializable.deserialize(%OldChangeyStruct{}, serialized) assert %OldChangeyStruct{id: 12345, username: "stinkypants"} == deserialized end @@ -292,8 +281,8 @@ defmodule BinaryProtocolTest do sub = %SubStruct{password: "1234"} changey = %ChangeyStruct{id: 12345, username: "stinkypants", new_map: %{1 => sub, 2 => sub}} - serialized = serialize(ChangeyStruct.BinaryProtocol, changey) - {deserialized, ""} = OldChangeyStruct.BinaryProtocol.deserialize(serialized) + serialized = Serializable.serialize(changey, %Binary{payload: ""}) + {deserialized, %Binary{payload: ""}} = Serializable.deserialize(%OldChangeyStruct{}, serialized) assert %OldChangeyStruct{id: 12345, username: "stinkypants"} == deserialized end @@ -318,8 +307,8 @@ defmodule BinaryProtocolTest do changey = %ChangeyStruct{id: 12345, username: "stinkypants", my_twin: twin} - serialized = serialize(ChangeyStruct.BinaryProtocol, changey) - {deserialized, ""} = OldChangeyStruct.BinaryProtocol.deserialize(serialized) + serialized = Serializable.serialize(changey, %Binary{payload: ""}) + {deserialized, %Binary{payload: ""}} = Serializable.deserialize(%OldChangeyStruct{}, serialized) assert %OldChangeyStruct{id: 12345, username: "stinkypants"} == deserialized end