Skip to content

Commit 997cb56

Browse files
authored
Add warning when implicitly casting maps/keywords to struct encode-time (#413)
1 parent 1605e2b commit 997cb56

File tree

3 files changed

+44
-8
lines changed

3 files changed

+44
-8
lines changed

lib/protobuf/encoder.ex

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,26 @@ defmodule Protobuf.Encoder do
142142
message:
143143
"struct #{inspect(other_mod)} can't be encoded as #{inspect(mod)}: #{inspect(struct)}"
144144

145-
_ ->
145+
enumerable when is_map(enumerable) or is_list(enumerable) ->
146+
IO.warn("""
147+
Implicitly casting a non-struct to a #{inspect(mod)} message:
148+
149+
#{inspect(enumerable, pretty: true)}
150+
151+
This automatic coercion is deprecated in Protobuf 0.15 and will raise an error in future versions.
152+
153+
Instead of:
154+
%Parent{child: %{name: ""}}
155+
156+
Build child structs explicitly:
157+
%Parent{child: %Child{name: ""}}
158+
""")
159+
146160
do_encode_to_iodata(struct(mod, msg))
161+
162+
other ->
163+
raise Protobuf.EncodeError,
164+
message: "invalid value for type #{inspect(mod)}: #{inspect(other)}"
147165
end
148166
end
149167

test/protobuf/encoder_test.exs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
defmodule Protobuf.EncoderTest do
2-
use ExUnit.Case, async: true
2+
# TODO: make async
3+
#
4+
# This is sync because we are using `capture_io` to get a
5+
# deprecation warning when casting enumerables to structs.
6+
# When the feature is removed, this module should be made
7+
# sync again.
8+
use ExUnit.Case, async: false
39

410
import Protobuf.Wire.Types
511

@@ -184,7 +190,10 @@ defmodule Protobuf.EncoderTest do
184190
end
185191

186192
test "encodes map with oneof" do
187-
msg = %Google.Protobuf.Struct{fields: %{"valid" => %{kind: {:bool_value, true}}}}
193+
msg = %Google.Protobuf.Struct{
194+
fields: %{"valid" => %Google.Protobuf.Value{kind: {:bool_value, true}}}
195+
}
196+
188197
bin = Google.Protobuf.Struct.encode(msg)
189198

190199
assert Google.Protobuf.Struct.decode(bin) ==
@@ -283,9 +292,7 @@ defmodule Protobuf.EncoderTest do
283292
Encoder.encode(%TestMsg.Foo{c: 123})
284293
end
285294

286-
# For Elixir 1.18+ it's `type Integer`, before, it was just `123`
287-
# TODO: fix once we require Elixir 1.18+
288-
message = ~r/protocol Enumerable not implemented for (123|type Integer)/
295+
message = ~r/invalid value for type TestMsg.Foo.Bar: 123/
289296

290297
assert_raise Protobuf.EncodeError, message, fn ->
291298
Encoder.encode(%TestMsg.Foo{e: 123})
@@ -309,6 +316,17 @@ defmodule Protobuf.EncoderTest do
309316
end
310317
end
311318

319+
# TODO: remove when implicit struct cast is removed
320+
test "gives a warning for implicitly cast structs" do
321+
warning =
322+
ExUnit.CaptureIO.capture_io(:stderr, fn ->
323+
assert <<50, 2, 8, 1>> = Encoder.encode(%TestMsg.Foo{e: %{a: 1}})
324+
end)
325+
326+
assert warning =~ "Implicitly casting a non-struct to a TestMsg.Foo.Bar message"
327+
assert warning =~ "%{a: 1}"
328+
end
329+
312330
test "encodes with transformer module" do
313331
msg = %TestMsg.ContainsTransformModule{field: 0}
314332
assert Encoder.encode(msg) == <<10, 0>>

test/protobuf/encoder_validation_test.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,8 @@ defmodule Protobuf.EncoderTest.Validation do
149149

150150
test "build embedded field map when encode" do
151151
msg = %TestMsg.Foo{}
152-
msg = %TestMsg.Foo{msg | e: %{a: 1}}
153-
msg1 = %TestMsg.Foo{e: %{a: 1}}
152+
msg = %TestMsg.Foo{msg | e: %TestMsg.Foo.Bar{a: 1}}
153+
msg1 = %TestMsg.Foo{e: %TestMsg.Foo.Bar{a: 1}}
154154

155155
assert Protobuf.Encoder.encode(msg) == Protobuf.Encoder.encode(msg1)
156156
end

0 commit comments

Comments
 (0)