diff --git a/lib/avro_ex/schema.ex b/lib/avro_ex/schema.ex index 4f6c91b..9ab165a 100644 --- a/lib/avro_ex/schema.ex +++ b/lib/avro_ex/schema.ex @@ -41,12 +41,19 @@ defmodule AvroEx.Schema do encodable?(schema, context, data) end + @int32_range -2_147_483_648..2_147_483_647 + @int64_range -9_223_372_036_854_775_808..9_223_372_036_854_775_807 + @spec encodable?(any(), any(), any()) :: boolean() def encodable?(%Primitive{type: :null}, _, nil), do: true def encodable?(%Primitive{type: :boolean}, _, bool) when is_boolean(bool), do: true - def encodable?(%Primitive{type: :int}, _, n) when is_integer(n), do: true - def encodable?(%Primitive{type: :long}, _, n) when is_integer(n), do: true - def encodable?(%Primitive{type: :float}, _, n) when is_float(n), do: true + def encodable?(%Primitive{type: :int}, _, n) when is_integer(n) and n in @int32_range, do: true + def encodable?(%Primitive{type: :long}, _, n) when is_integer(n) and n in @int64_range, do: true + + def encodable?(%Primitive{type: :float}, _, n) when is_float(n) do + match?(<<^n::little-float-size(32)>>, <>) + end + def encodable?(%Primitive{type: :double}, _, n) when is_float(n), do: true def encodable?(%Primitive{type: :bytes}, _, bytes) when is_binary(bytes), do: true def encodable?(%Primitive{type: :string}, _, str) when is_binary(str), do: String.valid?(str) diff --git a/mix.exs b/mix.exs index 1c327ae..faf7902 100644 --- a/mix.exs +++ b/mix.exs @@ -36,7 +36,7 @@ defmodule AvroEx.Mixfile do {:credo, "~> 1.0", only: :dev, runtime: false}, {:dialyxir, "~> 1.1", only: :dev, runtime: false}, {:ex_doc, "~> 0.20", only: :dev, runtime: false}, - {:stream_data, "~> 0.5", only: :test} + {:stream_data, "~> 0.5", only: [:dev, :test]} ] end diff --git a/test/encode_test.exs b/test/encode_test.exs index 728123a..38177cf 100644 --- a/test/encode_test.exs +++ b/test/encode_test.exs @@ -246,6 +246,30 @@ defmodule AvroEx.Encode.Test do assert encoded_union == index <> encoded_int end + test "works as expected with int and long" do + {:ok, schema} = AvroEx.decode_schema(~S(["int", "long"])) + {:ok, int_schema} = AvroEx.decode_schema(~S("int")) + {:ok, long_schema} = AvroEx.decode_schema(~S("long")) + + {:ok, index} = @test_module.encode(int_schema, 1) + {:ok, encoded_long} = @test_module.encode(long_schema, -3_376_656_585_598_455_353) + {:ok, encoded_union} = @test_module.encode(schema, -3_376_656_585_598_455_353) + + assert encoded_union == index <> encoded_long + end + + test "works as expected with float and double" do + {:ok, schema} = AvroEx.decode_schema(~S(["float", "double"])) + {:ok, int_schema} = AvroEx.decode_schema(~S("int")) + {:ok, double_schema} = AvroEx.decode_schema(~S("double")) + + {:ok, index} = @test_module.encode(int_schema, 1) + {:ok, encoded_long} = @test_module.encode(double_schema, 0.0000000001) + {:ok, encoded_union} = @test_module.encode(schema, 0.0000000001) + + assert encoded_union == index <> encoded_long + end + test "works as expected with logical types" do datetime_json = ~S({"type": "long", "logicalType":"timestamp-millis"}) datetime_value = ~U[2020-09-17 12:56:50.438Z] diff --git a/test/property_test.exs b/test/property_test.exs index 8ec380d..dea639b 100644 --- a/test/property_test.exs +++ b/test/property_test.exs @@ -2,17 +2,6 @@ defmodule AvroEx.PropertyTest do use ExUnit.Case, async: true use ExUnitProperties - test "encoding of large integer as a union of int and long" do - schema = ["int", "long"] - data = -3_376_656_585_598_455_353 - - json = Jason.encode!(schema) - {:ok, schema} = AvroEx.decode_schema(json) - {:ok, encoded} = AvroEx.encode(schema, data) - - assert {:ok, ^data} = AvroEx.decode(schema, encoded) - end - property "encode -> decode always returns back the initial data for the same schema" do check all schema <- schema(), data <- valid_data(schema), @@ -24,6 +13,7 @@ defmodule AvroEx.PropertyTest do end end + @spec schema() :: StreamData.t() def schema do sized(fn size -> schema_gen(size) end) end @@ -77,18 +67,17 @@ defmodule AvroEx.PropertyTest do end defp union(size) do - schema() - |> resize(div(size, 4)) - |> filter(fn schema -> not is_list(schema) end) - |> uniq_list_of( - min_length: 1, - max_length: 8, - uniq_fun: fn + gen all list <- + schema() + |> resize(div(size, 4)) + |> filter(fn schema -> not is_list(schema) end) + |> list_of(min_length: 1, max_length: 8) do + Enum.uniq_by(list, fn %{type: _type, name: name} -> name %{type: type} -> type value -> value - end - ) + end) + end end defp valid_data("null"), do: constant(nil) @@ -97,9 +86,9 @@ defmodule AvroEx.PropertyTest do defp valid_data("long"), do: integer(-9_223_372_036_854_775_808..9_223_372_036_854_775_807) defp valid_data("float") do - gen all num <- float() do - bin = <> - <> = bin + gen all float <- float(), + match?(<<_float::big-float-size(32)>>, <>) do + <> = <> float end end