Repository: elixir-grpc/grpc
Pull request #195 (2021-05-03 13:34:08)

Allow to use iolists in GRPC.Message.to_data/2
This is small change that allows to use iolists returned from `codec` modules with `GRPC.Message.to_data/2` function.


Patch:
diff --git a/lib/grpc/message.ex b/lib/grpc/message.ex
index ba908c7..a4e372d 100644
--- a/lib/grpc/message.ex
+++ b/lib/grpc/message.ex
@@ -19,19 +19,33 @@ defmodule GRPC.Message do
   @doc """
   Transform protobuf data to gRPC body
 
+  ## Options
+
+    * `:compressor` - the optional `GRPC.Compressor` to be used.
+    * `:iolist` - if `true`, encodes the data as an `t:iolist()` instead of a `t:binary()`
+    * `:max_message_length` - the maximum number of bytes for the encoded message.
+
   ## Examples
 
-      iex> message = <<1, 2, 3, 4, 5, 6, 7, 8>>
+      iex> message = ["m", [["es", "sa"], "ge"]]
       iex> GRPC.Message.to_data(message)
-      {:ok, <<0, 0, 0, 0, 8, 1, 2, 3, 4, 5, 6, 7, 8>>, 13}
+      {:ok, <<0, 0, 0, 0, 7, "message">>, 12}
+      iex> GRPC.Message.to_data(message, iolist: true)
+      {:ok, [0, <<0, 0, 0, 7>>, ["m", [["es", "sa"], "ge"]]], 12}
+
+  Error cases:
+
       iex> message = <<1, 2, 3, 4, 5, 6, 7, 8, 9>>
       iex> GRPC.Message.to_data(message, %{max_message_length: 8})
       {:error, "Encoded message is too large (9 bytes)"}
+
   """
-  @spec to_data(iodata, map | Keyword.t()) ::
-          {:ok, binary, non_neg_integer} | {:error, String.t()}
-  def to_data(message, opts \\ %{}) do
+  @spec to_data(iodata, keyword() | map()) ::
+          {:ok, iodata, non_neg_integer} | {:error, String.t()}
+  def to_data(message, opts \\ []) do
     compressor = opts[:compressor]
+    iolist = opts[:iolist]
+    max_length = opts[:max_message_length] || @max_message_length
 
     {compress_flag, message} =
       if compressor do
@@ -40,13 +54,13 @@ defmodule GRPC.Message do
         {0, message}
       end
 
-    length = byte_size(message)
-    max_length = opts[:max_message_length] || @max_message_length
+    length = IO.iodata_length(message)
 
     if length > max_length do
       {:error, "Encoded message is too large (#{length} bytes)"}
     else
-      result = <<compress_flag, length::size(4)-unit(8), message::binary>>
+      result = [compress_flag, <<length::size(4)-unit(8)>>, message]
+      result = if iolist, do: result, else: IO.iodata_to_binary(result)
       {:ok, result, length + 5}
     end
   end


Test patch:
diff --git a/test/grpc/message_test.exs b/test/grpc/message_test.exs
index 765e76f..dc50df7 100644
--- a/test/grpc/message_test.exs
+++ b/test/grpc/message_test.exs
@@ -15,4 +15,17 @@ defmodule GRPC.MessageTest do
 
     assert {:ok, message} == GRPC.Message.from_data(%{compressor: GRPC.Compressor.Gzip}, data)
   end
+
+  test "iodata can be passed to and returned from `to_data/2`" do
+    message = List.duplicate("foo", 100)
+
+    assert {:ok, data, 32} =
+             GRPC.Message.to_data(message, iolist: true, compressor: GRPC.Compressor.Gzip)
+
+    assert is_list(data)
+    binary = IO.iodata_to_binary(data)
+
+    assert {:ok, IO.iodata_to_binary(message)} ==
+             GRPC.Message.from_data(%{compressor: GRPC.Compressor.Gzip}, binary)
+  end
 end