From 35b174a76591bec4b5c957cc1bdfc1f1e6434505 Mon Sep 17 00:00:00 2001 From: Michal Szewczyk Date: Mon, 16 Dec 2024 16:47:21 +0100 Subject: [PATCH 01/11] Improvements for Neo4j v5 engines --- .github/workflows/elixir.yml | 4 +- .tool-versions | 4 +- README.md | 8 ++- docker-compose.yml | 5 +- example_app/config/config.exs | 11 ++-- example_app/lib/example_app/application.ex | 2 +- ...t_sips_benchmark.ex => boltx_benchmark.ex} | 12 ++-- example_app/mix.exs | 2 +- example_app/mix.lock | 9 +-- lib/neo4ex/bolt_protocol.ex | 19 +++--- .../structure/message/extra/hello.ex | 14 +++- .../structure/message/extra/logon.ex | 12 ++++ .../structure/message/request/logon.ex | 7 +- lib/neo4ex/connector.ex | 9 ++- lib/neo4ex/utils.ex | 4 +- mix.exs | 2 +- mix.lock | 23 +++---- test/neo4ex/bolt_protocol/encoder_test.exs | 65 ++++++++++++++++--- test/neo4ex/utils_test.exs | 6 +- test/neo4ex_test.exs | 2 +- 20 files changed, 152 insertions(+), 68 deletions(-) rename example_app/lib/mix/tasks/{bolt_sips_benchmark.ex => boltx_benchmark.ex} (69%) create mode 100644 lib/neo4ex/bolt_protocol/structure/message/extra/logon.ex diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index 8b13252..3848727 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -16,8 +16,8 @@ jobs: strategy: matrix: - iex: [1.14.3] - otp: [24.3.4, 25.2] + iex: [1.14.5, 1.15.8, 1.16.3, 1.17.3] + otp: [24.3.4, 25.3, 26.2, 27.2] steps: - uses: actions/checkout@v3 diff --git a/.tool-versions b/.tool-versions index e709508..73d259e 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -elixir 1.14.0-otp-24 -erlang 24.3.4 +elixir 1.17.3 +erlang 27.2 diff --git a/README.md b/README.md index 55cf0f1..fba7ec6 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,15 @@ Most popular engine for those is Neo4j thus this library focuses on providing fu Currently only simple quering using raw Cypher queries is implemented, but there are few items on the Roadmap. -### Bolt_sips +## Existing libraries -One may say "there is already a library for communication with Neo4j". They are right **BUT** first and foremost, `bolt_sips` is left unmaintained ([discussion](https://github.com/florinpatrascu/bolt_sips/issues/109)). There were few attempts to continue that, but there is still no library that would take advantage of Elixir structs, protocols and behaviours to provide robust extensibility. Secondly, `bolt_sips` is just a driver. This library purpose will be to provide complete user experience when interacting with the DB. +There were already few attempts to write a driver for Bolt protocol but all of them seem to be clumsy in terms of protocol logic - many things are "hardcoded" as in the docs instead of being thought out for the server's operation and coding a reusable solution. They are not taking advantage of Elixir structs, protocols and behaviours to provide robust extensibility. +Secondly, those libs are just a driver and this library purpose is to provide complete user experience when interacting with the DB. This should be solved by building Ecto-like support for the Cypher query language. +At this point, it's worth noting that this library may not be faster than `bolt_sips` or `boltx` due to greater usage of Protocols and structs. +You can modify tasks from `example_app` to benchmark those on your data. + ## Installation The package can be installed by adding `neo4ex` to your list of dependencies in `mix.exs`: diff --git a/docker-compose.yml b/docker-compose.yml index 130d53e..8b5eb16 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,11 +1,10 @@ -version: '3.1' - services: graph_db: - image: neo4j:4.4.28-community + image: neo4j:5.26.0-community environment: NEO4J_AUTH: 'neo4j/letmein' NEO4JLABS_PLUGINS: '["apoc"]' + NEO4J_dbms_security_auth__minimum__password__length: 6 ports: - "7474:7474" - "7687:7687" diff --git a/example_app/config/config.exs b/example_app/config/config.exs index 5c1a227..05af109 100644 --- a/example_app/config/config.exs +++ b/example_app/config/config.exs @@ -6,8 +6,11 @@ config :example_app, ExampleApp.Connector, credentials: "letmein", pool_size: 1 -config :bolt_sips, Bolt, - url: "bolt://localhost:7687", - basic_auth: [username: "neo4j", password: "letmein"], +config :boltx, Bolt, + uri: "bolt://localhost:7687", + auth: [username: "neo4j", password: "letmein"], + user_agent: "boltxTest/1", pool_size: 1, - max_overflow: 0 + max_overflow: 0, + prefix: :default, + name: Boltx diff --git a/example_app/lib/example_app/application.ex b/example_app/lib/example_app/application.ex index 396b426..a0e13ff 100644 --- a/example_app/lib/example_app/application.ex +++ b/example_app/lib/example_app/application.ex @@ -9,7 +9,7 @@ defmodule ExampleApp.Application do def start(_type, _args) do children = [ ExampleApp.Connector, - {Bolt.Sips, Application.get_env(:bolt_sips, Bolt)} + {Boltx, Application.get_env(:boltx, Bolt)} ] # See https://hexdocs.pm/elixir/Supervisor.html diff --git a/example_app/lib/mix/tasks/bolt_sips_benchmark.ex b/example_app/lib/mix/tasks/boltx_benchmark.ex similarity index 69% rename from example_app/lib/mix/tasks/bolt_sips_benchmark.ex rename to example_app/lib/mix/tasks/boltx_benchmark.ex index 60d6f3a..419f11e 100644 --- a/example_app/lib/mix/tasks/bolt_sips_benchmark.ex +++ b/example_app/lib/mix/tasks/boltx_benchmark.ex @@ -1,19 +1,17 @@ -defmodule Mix.Tasks.ExampleApp.BoltSipsBenchmark do +defmodule Mix.Tasks.ExampleApp.BoltxBenchmark do use Mix.Task alias Neo4ex.Cypher - alias Bolt.Sips, as: Neo - alias ExampleApp.Connector @requirements ["app.start"] - @shortdoc "Runs benchmark to compare with bolt_sips library" + @shortdoc "Runs benchmark to compare with boltx library" def run(_args) do Benchee.run(%{ "Neo4ex" => fn -> neo4ex() end, - "Bolt.Sips" => fn -> bolt_sips() end + "Boltx" => fn -> boltx() end }) end @@ -22,9 +20,9 @@ defmodule Mix.Tasks.ExampleApp.BoltSipsBenchmark do Connector.run(%Cypher.Query{query: query, params: params}) end - def bolt_sips() do + def boltx() do %{query: query, params: params} = customer_query() - Neo.query!(Neo.conn(), query, params) + Boltx.query!(Boltx, query, params) end def customer_query() do diff --git a/example_app/mix.exs b/example_app/mix.exs index 46c3ae7..15298d0 100644 --- a/example_app/mix.exs +++ b/example_app/mix.exs @@ -23,7 +23,7 @@ defmodule ExampleApp.MixProject do defp deps do [ {:neo4ex, path: "../"}, - {:bolt_sips, git: "https://github.com/florinpatrascu/bolt_sips", branch: "master"}, + {:boltx, "~> 0.0.6"}, {:faker, "~> 0.17.0"}, {:jason, "~> 1.2"}, {:benchee, "~> 1.0"} diff --git a/example_app/mix.lock b/example_app/mix.lock index e412a9b..8a363fc 100644 --- a/example_app/mix.lock +++ b/example_app/mix.lock @@ -1,11 +1,12 @@ %{ - "benchee": {:hex, :benchee, "1.2.0", "afd2f0caec06ce3a70d9c91c514c0b58114636db9d83c2dc6bfd416656618353", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "ee729e53217898b8fd30aaad3cce61973dab61574ae6f48229fe7ff42d5e4457"}, + "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "bolt_sips": {:git, "https://github.com/florinpatrascu/bolt_sips", "b21901a46ed19b17d1c87a9ef9e56002f83f345c", [branch: "master"]}, + "boltx": {:hex, :boltx, "0.0.6", "c6a396b1538b258e4d5ee2a94aaf8fb2c7879240efffba94b9159dbdce963790", [:mix], [{:db_connection, "~> 2.6.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:poison, "~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "576b8f21a2021674130d04cd1fc79a4829a23d2cdf50641b3d7a00ce31b98ead"}, "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, - "db_connection": {:hex, :db_connection, "2.4.3", "3b9aac9f27347ec65b271847e6baeb4443d8474289bd18c1d6f4de655b70c94d", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c127c15b0fa6cfb32eed07465e05da6c815b032508d4ed7c116122871df73c12"}, + "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "faker": {:hex, :faker, "0.17.0", "671019d0652f63aefd8723b72167ecdb284baf7d47ad3a82a15e9b8a6df5d1fa", [:mix], [], "hexpm", "a7d4ad84a93fd25c5f5303510753789fc2433ff241bf3b4144d3f6f291658a6a"}, - "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, - "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, } diff --git a/lib/neo4ex/bolt_protocol.ex b/lib/neo4ex/bolt_protocol.ex index 9b32130..10c588a 100644 --- a/lib/neo4ex/bolt_protocol.ex +++ b/lib/neo4ex/bolt_protocol.ex @@ -28,8 +28,6 @@ defmodule Neo4ex.BoltProtocol do alias Neo4ex.BoltProtocol.Structure.Message.Summary.{Success, Failure} - @user_agent "Neo4ex/#{Application.spec(:neo4ex, :vsn)}" - @impl true def connect(opts) do hostname = Keyword.get(opts, :hostname) @@ -42,7 +40,11 @@ defmodule Neo4ex.BoltProtocol do :ok <- hello(socket, opts) do {:ok, socket} else - other -> other + {:ok, %Failure{metadata: %{"message" => failure}}} -> + {:error, failure} + + other -> + other end end @@ -290,12 +292,14 @@ defmodule Neo4ex.BoltProtocol do end if Version.match?(bolt_version, ">= 5.1.0") do - hello = %Hello{extra: %Extra.Hello{user_agent: @user_agent}} + hello = %Hello{extra: %Extra.Hello{}} logon = %Logon{ - scheme: scheme, - principal: principal, - credentials: credentials + auth: %Extra.Logon{ + scheme: scheme, + principal: principal, + credentials: credentials + } } with( @@ -311,7 +315,6 @@ defmodule Neo4ex.BoltProtocol do else message = %Hello{ extra: %Extra.Hello{ - user_agent: @user_agent, scheme: scheme, principal: principal, credentials: credentials diff --git a/lib/neo4ex/bolt_protocol/structure/message/extra/hello.ex b/lib/neo4ex/bolt_protocol/structure/message/extra/hello.ex index 3891227..7f340a7 100644 --- a/lib/neo4ex/bolt_protocol/structure/message/extra/hello.ex +++ b/lib/neo4ex/bolt_protocol/structure/message/extra/hello.ex @@ -1,9 +1,21 @@ defmodule Neo4ex.BoltProtocol.Structure.Message.Extra.Hello do use Neo4ex.BoltProtocol.Structure + @version Mix.Project.config()[:version] + @system_info System.build_info()[:version] + # can't be encoded directly, it's just helper for the Hello message embeded_structure do - field(:user_agent, default: "Neo4ex/0.1.0") + field(:user_agent, default: "Neo4ex/#{@version}") + + field(:bolt_agent, + default: %{ + product: "Neo4ex/#{@version}", + language: "Elixir/#{@system_info}" + }, + version: ">= 5.3.0" + ) + field(:patch_bolt, default: ["utc"], version: ">= 4.3.0 and <= 4.4.0") field(:routing, default: %{}, version: ">= 4.1.0") diff --git a/lib/neo4ex/bolt_protocol/structure/message/extra/logon.ex b/lib/neo4ex/bolt_protocol/structure/message/extra/logon.ex new file mode 100644 index 0000000..0b65a08 --- /dev/null +++ b/lib/neo4ex/bolt_protocol/structure/message/extra/logon.ex @@ -0,0 +1,12 @@ +defmodule Neo4ex.BoltProtocol.Structure.Message.Extra.Logon do + use Neo4ex.BoltProtocol.Structure + + # TODO: implement validation + # @predefined_schemes ~w(none basic bearer kerberos) + + embeded_structure do + field(:scheme, default: "") + field(:principal, default: "") + field(:credentials, default: "") + end +end diff --git a/lib/neo4ex/bolt_protocol/structure/message/request/logon.ex b/lib/neo4ex/bolt_protocol/structure/message/request/logon.ex index 5c009c1..d2605ef 100644 --- a/lib/neo4ex/bolt_protocol/structure/message/request/logon.ex +++ b/lib/neo4ex/bolt_protocol/structure/message/request/logon.ex @@ -1,12 +1,9 @@ defmodule Neo4ex.BoltProtocol.Structure.Message.Request.Logon do use Neo4ex.BoltProtocol.Structure - # TODO: implement validation - # @predefined_schemes ~w(none basic bearer kerberos) + alias Neo4ex.BoltProtocol.Structure.Message.Extra structure 0x6A do - field(:scheme, default: "") - field(:principal, default: "") - field(:credentials, default: "") + field(:auth, default: %Extra.Logon{}) end end diff --git a/lib/neo4ex/connector.ex b/lib/neo4ex/connector.ex index 65c191e..938988b 100644 --- a/lib/neo4ex/connector.ex +++ b/lib/neo4ex/connector.ex @@ -15,7 +15,7 @@ defmodule Neo4ex.Connector do @noop <<0::size(@chunk_size)>> # since 4.3 there is support for version range during negotiation # so "4.4.1" actually means "4.4" plus one previous version "4.3" - @supported_versions ["4.4.1", "4.2.0", "4.1.0", "4.0.0"] + @supported_versions ["5.20.20", "4.4.3", "4.2.0", "4.0.0"] defmacro __using__(otp_app: app) do supported_versions = @supported_versions @@ -101,14 +101,17 @@ defmodule Neo4ex.Connector do end end - def supported_versions() do - Enum.flat_map(@supported_versions, fn version -> + defmacro supported_versions() do + @supported_versions + |> Enum.flat_map(fn version -> [major, minor, range] = version |> String.split(".") |> Enum.map(&String.to_integer/1) for i <- minor..(minor - range) do Version.parse!("#{major}.#{i}.0") end end) + |> Enum.uniq() + |> Macro.escape() end @doc false diff --git a/lib/neo4ex/utils.ex b/lib/neo4ex/utils.ex index b19812f..c4e2a13 100644 --- a/lib/neo4ex/utils.ex +++ b/lib/neo4ex/utils.ex @@ -1,6 +1,8 @@ defmodule Neo4ex.Utils do @moduledoc false + import Neo4ex.Connector, only: [supported_versions: 0] + alias Neo4ex.BoltProtocol alias Neo4ex.PackStream @@ -88,7 +90,7 @@ defmodule Neo4ex.Utils do end def list_valid_versions(requirement) do - Enum.filter(Neo4ex.Connector.supported_versions(), fn ver -> + Enum.filter(supported_versions(), fn ver -> Version.match?(ver, requirement) end) end diff --git a/mix.exs b/mix.exs index 43e9478..dd986b8 100644 --- a/mix.exs +++ b/mix.exs @@ -40,7 +40,7 @@ defmodule Neo4ex.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:db_connection, "~> 2.4"}, + {:db_connection, "~> 2.6.0"}, # Tests {:mox, "~> 1.0", only: [:test]}, diff --git a/mix.lock b/mix.lock index 3c7eba9..3529aa3 100644 --- a/mix.lock +++ b/mix.lock @@ -2,17 +2,18 @@ "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, "credo": {:hex, :credo, "1.6.7", "323f5734350fd23a456f2688b9430e7d517afb313fbd38671b8a4449798a7854", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "41e110bfb007f7eda7f897c10bf019ceab9a0b269ce79f015d54b0dcf4fc7dd3"}, - "db_connection": {:hex, :db_connection, "2.4.3", "3b9aac9f27347ec65b271847e6baeb4443d8474289bd18c1d6f4de655b70c94d", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c127c15b0fa6cfb32eed07465e05da6c815b032508d4ed7c116122871df73c12"}, + "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, "dialyxir": {:hex, :dialyxir, "1.2.0", "58344b3e87c2e7095304c81a9ae65cb68b613e28340690dfe1a5597fd08dec37", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "61072136427a851674cab81762be4dbeae7679f85b1272b6d25c3a839aff8463"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.31", "a93921cdc6b9b869f519213d5bc79d9e218ba768d7270d46fdcf1c01bacff9e2", [:mix], [], "hexpm", "317d367ee0335ef037a87e46c91a2269fef6306413f731e8ec11fc45a7efd059"}, - "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, - "ex_doc": {:hex, :ex_doc, "0.29.4", "6257ecbb20c7396b1fe5accd55b7b0d23f44b6aa18017b415cb4c2b91d997729", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2c6699a737ae46cb61e4ed012af931b57b699643b24dabe2400a8168414bc4f5"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"}, + "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, + "ex_doc": {:hex, :ex_doc, "0.35.1", "de804c590d3df2d9d5b8aec77d758b00c814b356119b3d4455e4b8a8687aecaf", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "2121c6402c8d44b05622677b761371a759143b958c6c19f6558ff64d0aed40df"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, - "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, - "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"}, - "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, - "mox": {:hex, :mox, "1.0.2", "dc2057289ac478b35760ba74165b4b3f402f68803dd5aecd3bfd19c183815d64", [:mix], [], "hexpm", "f9864921b3aaf763c8741b5b8e6f908f44566f1e427b2630e89e9a73b981fef2"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, - "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, + "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"}, + "mox": {:hex, :mox, "1.2.0", "a2cd96b4b80a3883e3100a221e8adc1b98e4c3a332a8fc434c39526babafd5b3", [:mix], [{:nimble_ownership, "~> 1.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}], "hexpm", "c7b92b3cc69ee24a7eeeaf944cd7be22013c52fcb580c1f33f50845ec821089a"}, + "nimble_ownership": {:hex, :nimble_ownership, "1.0.1", "f69fae0cdd451b1614364013544e66e4f5d25f36a2056a9698b793305c5aa3a6", [:mix], [], "hexpm", "3825e461025464f519f3f3e4a1f9b68c47dc151369611629ad08b636b73bb22d"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, } diff --git a/test/neo4ex/bolt_protocol/encoder_test.exs b/test/neo4ex/bolt_protocol/encoder_test.exs index f8d5809..8569c16 100644 --- a/test/neo4ex/bolt_protocol/encoder_test.exs +++ b/test/neo4ex/bolt_protocol/encoder_test.exs @@ -10,6 +10,8 @@ defmodule Neo4ex.BoltProtocol.EncoderTest do alias Neo4ex.BoltProtocol.Encoder alias Neo4ex.PackStream.Exceptions + @version Mix.Project.config()[:version] + describe "encode/2" do test "returns valid binary representation of Lists" do assert <<0x90>> == Encoder.encode([], "4.0.0") @@ -25,8 +27,30 @@ defmodule Neo4ex.BoltProtocol.EncoderTest do end test "returns valid binary representation of Maps" do - assert <<0xA3, 0x81, "a", 1, 0x81, "b", 0x81, "a", 0x81, "c", 0xC1, 0x4::4, 0x0::60>> == - Encoder.encode(%{a: 1, b: "a", c: 2.0}, "4.0.0") + input = %{a: 1, b: "a", c: 2.0} + + # assert <<0xA3, 0x81, "a", 1, 0x81, "b", 0x81, "a", 0x81, "c", 0xC1, 0x4::4, 0x0::60>> == + # Encoder.encode(%{a: 1, b: "a", c: 2.0}, "4.0.0") + + # Keys in maps aren't sorted in newest OTP. We have to pattern match on each possible sorting (assuming the same kinds of values will be kept together, so the order is string,string,float or float,string,string) + case Encoder.encode(input, "4.0.0") do + <<0xA3, 0x81, "a", 1, 0x81, "b", 0x81, "a", 0x81, "c", 0xC1, 0x4::4, 0x0::60>> -> + :ok + + <<0xA3, 0x81, "b", 0x81, "a", 0x81, "a", 1, 0x81, "c", 0xC1, 0x4::4, 0x0::60>> -> + :ok + + <<0xA3, 0x81, "c", 0xC1, 0x4::4, 0x0::60, 0x81, "a", 1, 0x81, "b", 0x81, "a">> -> + :ok + + <<0xA3, 0x81, "c", 0xC1, 0x4::4, 0x0::60, 0x81, "b", 0x81, "a", 0x81, "a", 1>> -> + :ok + + other -> + flunk( + "Got invalid encoding for map: #{inspect(input)}, the result was: #{inspect(other)}" + ) + end end test "handles encoding of Node structures" do @@ -56,20 +80,43 @@ defmodule Neo4ex.BoltProtocol.EncoderTest do end test "handles encoding of Hello messages" do + app_version = "Neo4ex/#{@version}" user_agent_bytes = byte_size("user_agent") - ua_bytes = byte_size("Neo4ex/0.1.0") + ua_bytes = byte_size(app_version) scheme_bytes = byte_size("scheme") none_bytes = byte_size("none") principal_bytes = byte_size("principal") credentials_bytes = byte_size("credentials") - # even though Logon is a struct with prefdefined fields order, we're encoding it to the map so the keys will be sent alphabetically - assert <<0xB1, 0x01, 0xA4, 0x8::4, ^credentials_bytes::4, "credentials", 0x80, 0x8::4, - ^principal_bytes::4, "principal", 0x80, 0x8::4, ^scheme_bytes::4, "scheme", 0x8::4, - ^none_bytes::4, "none", 0x8::4, ^user_agent_bytes::4, "user_agent", 0x8::4, - ^ua_bytes::4, - "Neo4ex/0.1.0">> = + # even though Logon is a struct with prefdefined fields order, prior to 5.1 we're encoding it to the map so the keys will be sent according to map keys rules (compiler-defined, should be the same but can be random) + assert <<0xB1, 0x01, 0xA4, 0x8::4, ^scheme_bytes::4, "scheme", 0x8::4, ^none_bytes::4, + "none", 0x8::4, ^credentials_bytes::4, "credentials", 0x80, 0x8::4, + ^user_agent_bytes::4, "user_agent", 0x8::4, ^ua_bytes::4, ^app_version::binary, + 0x8::4, ^principal_bytes::4, "principal", + 0x80>> = Encoder.encode(%Hello{extra: %Extra.Hello{scheme: "none"}}, "4.0.0") end + + test "handles encoding of Hello messages for >= 5.3" do + app_version = "Neo4ex/#{@version}" + elixir_version = "Elixir/#{System.build_info()[:version]}" + user_agent_bytes = byte_size("user_agent") + ua_bytes = byte_size(app_version) + routing_bytes = byte_size("routing") + bolt_agent_bytes = byte_size("bolt_agent") + bolt_agent_product_bytes = byte_size("product") + bolt_agent_language_bytes = byte_size("language") + bolt_agent_language_value_bytes = byte_size(elixir_version) + + # even though Logon is a struct with prefdefined fields order, prior to 5.1 we're encoding it to the map so the keys will be sent according to map keys rules (compiler-defined, should be the same but can be random) + assert <<0xB1, 0x01, 0xA3, 0x8::4, ^routing_bytes::4, "routing", 0xA0, 0x8::4, + ^user_agent_bytes::4, "user_agent", 0x8::4, ^ua_bytes::4, ^app_version::binary, + 0x8::4, ^bolt_agent_bytes::4, "bolt_agent", 0xA2, 0x8::4, + ^bolt_agent_product_bytes::4, "product", 0x8::4, ^ua_bytes::4, + ^app_version::binary, 0x8::4, ^bolt_agent_language_bytes::4, "language", 0x8::4, + ^bolt_agent_language_value_bytes::4, + ^elixir_version::binary>> = + Encoder.encode(%Hello{extra: %Extra.Hello{}}, "5.3.0") + end end end diff --git a/test/neo4ex/utils_test.exs b/test/neo4ex/utils_test.exs index 004162c..67bdd54 100644 --- a/test/neo4ex/utils_test.exs +++ b/test/neo4ex/utils_test.exs @@ -19,9 +19,11 @@ defmodule Neo4ex.UtilsTest do describe "list_valid_versions/1" do test "filters invalid versions" do - assert [] == Utils.list_valid_versions(">= 5.0.0") + assert Enum.map(20..0//-1, fn minor -> Version.parse!("5.#{minor}.0") end) == + Utils.list_valid_versions(">= 5.0.0") - assert [Version.parse!("4.4.0"), Version.parse!("4.3.0")] == + assert Enum.map(20..0//-1, fn minor -> Version.parse!("5.#{minor}.0") end) ++ + [Version.parse!("4.4.0"), Version.parse!("4.3.0")] == Utils.list_valid_versions(">= 4.3.0") end end diff --git a/test/neo4ex_test.exs b/test/neo4ex_test.exs index 0c40fa2..2c201bc 100644 --- a/test/neo4ex_test.exs +++ b/test/neo4ex_test.exs @@ -23,7 +23,7 @@ defmodule Neo4exTest do encoded_success_message = Encoder.encode(success_message, "4.0.0") SocketMock - |> expect(:connect, fn 'localhost', 7687, [:binary, {:active, false}] -> + |> expect(:connect, fn ~c"localhost", 7687, [:binary, {:active, false}] -> :gen_tcp.listen(0, [:binary]) end) # handshake From 8482be2f931de14e0fe416c71631aa59511a40b7 Mon Sep 17 00:00:00 2001 From: Michal Szewczyk Date: Mon, 16 Dec 2024 16:51:29 +0100 Subject: [PATCH 02/11] Update workflow --- .github/workflows/elixir.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index 3848727..5857b29 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -23,7 +23,7 @@ jobs: - uses: actions/checkout@v3 - name: Set up Elixir - uses: erlef/setup-beam@988e02bfe678367a02564f65ca2e37726dc0268f + uses: erlef/setup-beam@v1 with: elixir-version: ${{ matrix.iex }} otp-version: ${{ matrix.otp }} From 014ae23226e02400531a8720b0a0dabb98d1ab0d Mon Sep 17 00:00:00 2001 From: Michal Szewczyk Date: Mon, 16 Dec 2024 16:54:20 +0100 Subject: [PATCH 03/11] Workflow --- .github/workflows/elixir.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index 5857b29..2c92162 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -16,8 +16,11 @@ jobs: strategy: matrix: - iex: [1.14.5, 1.15.8, 1.16.3, 1.17.3] - otp: [24.3.4, 25.3, 26.2, 27.2] + iex: [1.15.8, 1.16.3, 1.17.3] + otp: [25.3, 26.2, 27.2] + include: + - iex: 1.14.5 + otp: 24.3.4 steps: - uses: actions/checkout@v3 @@ -29,7 +32,7 @@ jobs: otp-version: ${{ matrix.otp }} - name: Restore dependencies cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: deps key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} @@ -41,7 +44,7 @@ jobs: # Don't cache PLTs based on mix.lock hash, as Dialyzer can incrementally update even old ones # Cache key based on Elixir & Erlang version (also useful when running in matrix) - name: Restore PLT cache - uses: actions/cache@v2 + uses: actions/cache@v4 id: plt_cache with: key: | From d3bf832e8646b78598cb9b4ef11e9cec5fbfa014 Mon Sep 17 00:00:00 2001 From: Michal Szewczyk Date: Mon, 16 Dec 2024 16:55:34 +0100 Subject: [PATCH 04/11] Workflow --- .github/workflows/elixir.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index 2c92162..8e79fa9 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -16,11 +16,13 @@ jobs: strategy: matrix: - iex: [1.15.8, 1.16.3, 1.17.3] - otp: [25.3, 26.2, 27.2] + iex: [1.15.8, 1.16.3] + otp: [25.3, 26.2] include: - iex: 1.14.5 otp: 24.3.4 + - iex: 1.17.3 + otp: 27.2 steps: - uses: actions/checkout@v3 From 599894adf99237f80f2372ae1dc19c3e09ba6663 Mon Sep 17 00:00:00 2001 From: Michal Szewczyk Date: Tue, 17 Dec 2024 08:40:40 +0100 Subject: [PATCH 05/11] Update dialyxir --- mix.exs | 2 +- mix.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index dd986b8..5406b95 100644 --- a/mix.exs +++ b/mix.exs @@ -47,7 +47,7 @@ defmodule Neo4ex.MixProject do # Linting {:credo, "~> 1.6.7", only: [:dev]}, - {:dialyxir, "~> 1.2.0", only: [:dev], runtime: false}, + {:dialyxir, "~> 1.4", only: [:dev], runtime: false}, # Documentation # Run with: `mix docs` diff --git a/mix.lock b/mix.lock index 3529aa3..a7679f6 100644 --- a/mix.lock +++ b/mix.lock @@ -3,7 +3,7 @@ "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, "credo": {:hex, :credo, "1.6.7", "323f5734350fd23a456f2688b9430e7d517afb313fbd38671b8a4449798a7854", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "41e110bfb007f7eda7f897c10bf019ceab9a0b269ce79f015d54b0dcf4fc7dd3"}, "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, - "dialyxir": {:hex, :dialyxir, "1.2.0", "58344b3e87c2e7095304c81a9ae65cb68b613e28340690dfe1a5597fd08dec37", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "61072136427a851674cab81762be4dbeae7679f85b1272b6d25c3a839aff8463"}, + "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"}, "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, "ex_doc": {:hex, :ex_doc, "0.35.1", "de804c590d3df2d9d5b8aec77d758b00c814b356119b3d4455e4b8a8687aecaf", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "2121c6402c8d44b05622677b761371a759143b958c6c19f6558ff64d0aed40df"}, From fe150974895d77eb0d11b3e1395c9cdd9b478fe0 Mon Sep 17 00:00:00 2001 From: Michal Szewczyk Date: Tue, 17 Dec 2024 09:04:55 +0100 Subject: [PATCH 06/11] Test differently --- test/neo4ex/bolt_protocol/encoder_test.exs | 65 +++++++++++----------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/test/neo4ex/bolt_protocol/encoder_test.exs b/test/neo4ex/bolt_protocol/encoder_test.exs index 8569c16..a5dc053 100644 --- a/test/neo4ex/bolt_protocol/encoder_test.exs +++ b/test/neo4ex/bolt_protocol/encoder_test.exs @@ -1,6 +1,7 @@ defmodule Neo4ex.BoltProtocol.EncoderTest do use ExUnit.Case, async: true + alias Neo4ex.BoltProtocol.Decoder alias Neo4ex.BoltProtocol.Structure.Graph.Relationship alias Neo4ex.BoltProtocol.Structure.Graph.Node alias Neo4ex.BoltProtocol.Structure.Graph.Legacy.DateTimeZoneId @@ -80,43 +81,39 @@ defmodule Neo4ex.BoltProtocol.EncoderTest do end test "handles encoding of Hello messages" do - app_version = "Neo4ex/#{@version}" - user_agent_bytes = byte_size("user_agent") - ua_bytes = byte_size(app_version) - scheme_bytes = byte_size("scheme") - none_bytes = byte_size("none") - principal_bytes = byte_size("principal") - credentials_bytes = byte_size("credentials") - - # even though Logon is a struct with prefdefined fields order, prior to 5.1 we're encoding it to the map so the keys will be sent according to map keys rules (compiler-defined, should be the same but can be random) - assert <<0xB1, 0x01, 0xA4, 0x8::4, ^scheme_bytes::4, "scheme", 0x8::4, ^none_bytes::4, - "none", 0x8::4, ^credentials_bytes::4, "credentials", 0x80, 0x8::4, - ^user_agent_bytes::4, "user_agent", 0x8::4, ^ua_bytes::4, ^app_version::binary, - 0x8::4, ^principal_bytes::4, "principal", - 0x80>> = - Encoder.encode(%Hello{extra: %Extra.Hello{scheme: "none"}}, "4.0.0") + bolt_version = "4.0.0" + # we can't match on every posible key order for generic maps (too many cases) + encoded = Encoder.encode(%Hello{extra: %Extra.Hello{scheme: "none"}}, bolt_version) + decoded = encoded |> Decoder.decode(bolt_version) |> Enum.take(1) |> hd() + + # Extra.Hello is embedded meaning it has no signature on the engine side + assert decoded == %Hello{ + extra: %{ + "user_agent" => "Neo4ex/#{@version}", + "scheme" => "none", + "credentials" => "", + "principal" => "" + } + } end test "handles encoding of Hello messages for >= 5.3" do - app_version = "Neo4ex/#{@version}" - elixir_version = "Elixir/#{System.build_info()[:version]}" - user_agent_bytes = byte_size("user_agent") - ua_bytes = byte_size(app_version) - routing_bytes = byte_size("routing") - bolt_agent_bytes = byte_size("bolt_agent") - bolt_agent_product_bytes = byte_size("product") - bolt_agent_language_bytes = byte_size("language") - bolt_agent_language_value_bytes = byte_size(elixir_version) - - # even though Logon is a struct with prefdefined fields order, prior to 5.1 we're encoding it to the map so the keys will be sent according to map keys rules (compiler-defined, should be the same but can be random) - assert <<0xB1, 0x01, 0xA3, 0x8::4, ^routing_bytes::4, "routing", 0xA0, 0x8::4, - ^user_agent_bytes::4, "user_agent", 0x8::4, ^ua_bytes::4, ^app_version::binary, - 0x8::4, ^bolt_agent_bytes::4, "bolt_agent", 0xA2, 0x8::4, - ^bolt_agent_product_bytes::4, "product", 0x8::4, ^ua_bytes::4, - ^app_version::binary, 0x8::4, ^bolt_agent_language_bytes::4, "language", 0x8::4, - ^bolt_agent_language_value_bytes::4, - ^elixir_version::binary>> = - Encoder.encode(%Hello{extra: %Extra.Hello{}}, "5.3.0") + bolt_version = "5.3.0" + # we can't match on every posible key order for generic maps (too many cases) + encoded = Encoder.encode(%Hello{extra: %Extra.Hello{}}, bolt_version) + decoded = encoded |> Decoder.decode(bolt_version) |> Enum.take(1) |> hd() + + # Extra.Hello is embedded meaning it has no signature on the engine side + assert decoded == %Hello{ + extra: %{ + "user_agent" => "Neo4ex/#{@version}", + "bolt_agent" => %{ + "language" => "Elixir/#{System.build_info()[:version]}", + "product" => "Neo4ex/#{@version}" + }, + "routing" => %{} + } + } end end end From a74b0b1e2eff8cbdbd9d9e706433ca8ad83edc6e Mon Sep 17 00:00:00 2001 From: Michal Szewczyk Date: Tue, 17 Dec 2024 11:46:31 +0100 Subject: [PATCH 07/11] Improve test coverage --- lib/neo4ex/bolt_protocol/structure.ex | 7 +- .../structure/message/extra/hello.ex | 6 +- .../structure/message/extra/logon.ex | 6 +- test/neo4ex/bolt_protocol/encoder_test.exs | 4 +- test/neo4ex/bolt_protocol_test.exs | 72 +++++++++++++++++++ test/neo4ex/connector_test.exs | 10 +++ test/support/neo4j_connection.ex | 4 +- 7 files changed, 96 insertions(+), 13 deletions(-) diff --git a/lib/neo4ex/bolt_protocol/structure.ex b/lib/neo4ex/bolt_protocol/structure.ex index 79ff995..7443266 100644 --- a/lib/neo4ex/bolt_protocol/structure.ex +++ b/lib/neo4ex/bolt_protocol/structure.ex @@ -82,10 +82,11 @@ defmodule Neo4ex.BoltProtocol.Structure do defp build_fields_list(block) do block |> Macro.prewalk([], fn - {:field, _, [name, opts]}, acc -> + {:field, _, [name | opts]}, acc -> opts = - Keyword.update( - opts, + opts + |> List.flatten() + |> Keyword.update( :version, quote(do: Version.parse_requirement!(">= 0.0.0")), fn requirement -> diff --git a/lib/neo4ex/bolt_protocol/structure/message/extra/hello.ex b/lib/neo4ex/bolt_protocol/structure/message/extra/hello.ex index 7f340a7..65cf2f8 100644 --- a/lib/neo4ex/bolt_protocol/structure/message/extra/hello.ex +++ b/lib/neo4ex/bolt_protocol/structure/message/extra/hello.ex @@ -20,8 +20,8 @@ defmodule Neo4ex.BoltProtocol.Structure.Message.Extra.Hello do field(:routing, default: %{}, version: ">= 4.1.0") # prior to v5.1, authentication is handled inside HELLO message - field(:scheme, default: "", version: "< 5.1.0") - field(:principal, default: "", version: "< 5.1.0") - field(:credentials, default: "", version: "< 5.1.0") + field(:scheme, version: "< 5.1.0") + field(:principal, version: "< 5.1.0") + field(:credentials, version: "< 5.1.0") end end diff --git a/lib/neo4ex/bolt_protocol/structure/message/extra/logon.ex b/lib/neo4ex/bolt_protocol/structure/message/extra/logon.ex index 0b65a08..5ee5a13 100644 --- a/lib/neo4ex/bolt_protocol/structure/message/extra/logon.ex +++ b/lib/neo4ex/bolt_protocol/structure/message/extra/logon.ex @@ -5,8 +5,8 @@ defmodule Neo4ex.BoltProtocol.Structure.Message.Extra.Logon do # @predefined_schemes ~w(none basic bearer kerberos) embeded_structure do - field(:scheme, default: "") - field(:principal, default: "") - field(:credentials, default: "") + field(:scheme) + field(:principal) + field(:credentials) end end diff --git a/test/neo4ex/bolt_protocol/encoder_test.exs b/test/neo4ex/bolt_protocol/encoder_test.exs index a5dc053..43de73e 100644 --- a/test/neo4ex/bolt_protocol/encoder_test.exs +++ b/test/neo4ex/bolt_protocol/encoder_test.exs @@ -91,8 +91,8 @@ defmodule Neo4ex.BoltProtocol.EncoderTest do extra: %{ "user_agent" => "Neo4ex/#{@version}", "scheme" => "none", - "credentials" => "", - "principal" => "" + "credentials" => nil, + "principal" => nil } } end diff --git a/test/neo4ex/bolt_protocol_test.exs b/test/neo4ex/bolt_protocol_test.exs index d8ac1ee..6969cc1 100644 --- a/test/neo4ex/bolt_protocol_test.exs +++ b/test/neo4ex/bolt_protocol_test.exs @@ -4,6 +4,9 @@ defmodule Neo4ex.BoltProtocolTest do import Mox import Neo4ex.Neo4jConnection + alias Neo4ex.BoltProtocol.Structure.Message.Extra + alias Neo4ex.BoltProtocol.Structure.Message.Request.Logon + alias Neo4ex.BoltProtocol.Structure.Message.Request.Hello alias Neo4ex.BoltProtocol.Structure.Message.Request.Rollback alias Neo4ex.BoltProtocol.Structure.Message.Request.Commit alias Neo4ex.BoltProtocol.Structure.Message.Request.Begin @@ -24,6 +27,75 @@ defmodule Neo4ex.BoltProtocolTest do %{socket: %Socket{bolt_version: Version.parse!("4.3.0")}, query: query} end + describe "connect/1" do + test "properly negotiates version" do + success_message = %Success{} + encoded_success_message = Encoder.encode(success_message, "4.0.0") + + # two versions, 4.0.0 and 0.0.0 x 3 (client always sends 4 versions) + handshake = + <<0x60, 0x60, 0xB0, 0x17, 0::8, 0::8, 0::8, 4::8, 0::96>> + + hello = generate_message_chunk(%Hello{extra: %Extra.Hello{scheme: "none"}}, "4.0.0") + + SocketMock + |> expect(:connect, fn ~c"noop", 7687, [:binary, {:active, false}] -> {:ok, nil} end) + |> expect(:send, fn _, ^handshake -> :ok end) + |> expect(:recv, fn _, 4 -> {:ok, <<0::16, 0::8, 4::8>>} end) + |> expect(:send, fn _, ^hello -> :ok end) + |> expect_message(encoded_success_message) + + assert {:ok, %Socket{bolt_version: %Version{major: 4, minor: 0, patch: 0}}} == + BoltProtocol.connect(hostname: "noop", versions: ["4.0.0"]) + + encoded_success_message = Encoder.encode(success_message, "5.3.0") + + # two versions, 5.3.0 and 0.0.0 x 3 (client always sends 4 versions) + handshake = + <<0x60, 0x60, 0xB0, 0x17, 0::8, 0::8, 3::8, 5::8, 0::96>> + + hello = generate_message_chunk(%Hello{}, "5.3.0") + + logon = + generate_message_chunk( + %Logon{auth: %Extra.Logon{scheme: "bearer", credentials: "abc"}}, + "5.3.0" + ) + + SocketMock + |> expect(:connect, fn ~c"noop", 7687, [:binary, {:active, false}] -> {:ok, nil} end) + |> expect(:send, fn _, ^handshake -> :ok end) + |> expect(:recv, fn _, 4 -> {:ok, <<0::16, 3::8, 5::8>>} end) + |> expect(:send, fn _, ^hello -> :ok end) + |> expect_message(encoded_success_message) + |> expect(:send, fn _, ^logon -> :ok end) + |> expect_message(encoded_success_message) + + assert {:ok, %Socket{bolt_version: %Version{major: 5, minor: 3, patch: 0}}} == + BoltProtocol.connect(hostname: "noop", versions: ["5.3.0"], credentials: "abc") + end + + test "gracefully handles failures" do + message = %Failure{metadata: %{"message" => "failure"}} + encoded_failure_message = Encoder.encode(message, "5.3.0") + + # two versions, 5.3.0 and 0.0.0 x 3 (client always sends 4 versions) + handshake = + <<0x60, 0x60, 0xB0, 0x17, 0::8, 0::8, 3::8, 5::8, 0::96>> + + hello = generate_message_chunk(%Hello{}, "5.3.0") + + SocketMock + |> expect(:connect, fn ~c"noop", 7687, [:binary, {:active, false}] -> {:ok, nil} end) + |> expect(:send, fn _, ^handshake -> :ok end) + |> expect(:recv, fn _, 4 -> {:ok, <<0::16, 3::8, 5::8>>} end) + |> expect(:send, fn _, ^hello -> :ok end) + |> expect_message(encoded_failure_message) + + assert {:error, "failure"} == BoltProtocol.connect(hostname: "noop", versions: ["5.3.0"]) + end + end + describe "disconnect/2" do test "sends Goodbye message", %{socket: socket} do chunk = generate_message_chunk(%Goodbye{}) diff --git a/test/neo4ex/connector_test.exs b/test/neo4ex/connector_test.exs index 847994d..97a1c36 100644 --- a/test/neo4ex/connector_test.exs +++ b/test/neo4ex/connector_test.exs @@ -3,6 +3,8 @@ defmodule Neo4ex.ConnectorTest do import Mox + require Neo4ex.Connector + alias Neo4ex.BoltProtocol.Structure.Message.Request.Run alias Neo4ex.BoltProtocol.Encoder @@ -71,4 +73,12 @@ defmodule Neo4ex.ConnectorTest do assert {:error, DBConnection.ConnectionError.exception(":closed")} == Connector.read(socket) end end + + describe "supported_versions/0" do + test "returns compile-time list of versions" do + assert Enum.map(20..0//-1, fn minor -> Version.parse!("5.#{minor}.0") end) ++ + Enum.map(4..0//-1, fn minor -> Version.parse!("4.#{minor}.0") end) == + Connector.supported_versions() + end + end end diff --git a/test/support/neo4j_connection.ex b/test/support/neo4j_connection.ex index 5aaeb7c..57ee564 100644 --- a/test/support/neo4j_connection.ex +++ b/test/support/neo4j_connection.ex @@ -8,8 +8,8 @@ defmodule Neo4ex.Neo4jConnection do alias Neo4ex.BoltProtocol.Encoder - def generate_message_chunk(message) do - encoded_message = Encoder.encode(message, "4.0.0") + def generate_message_chunk(message, version \\ "4.0.0") do + encoded_message = Encoder.encode(message, version) message_size = byte_size(encoded_message) <> end From a52584a62fd87bc175dcffa1c352eb6c6b2be3c6 Mon Sep 17 00:00:00 2001 From: Michal Szewczyk Date: Tue, 17 Dec 2024 11:49:38 +0100 Subject: [PATCH 08/11] Ignore coverage for structs --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 5406b95..d9a2667 100644 --- a/mix.exs +++ b/mix.exs @@ -17,7 +17,7 @@ defmodule Neo4ex.MixProject do docs: docs(), test_coverage: [ ignore_modules: [ - Neo4ex.BoltProtocol.Structure, + ~r/^Neo4ex.BoltProtocol.Structure/, Neo4ex.PackStream.DecoderBuilder ], summary: [ From 3f3ffec73b373dcb3d610df0e95f7ad2c3ef786f Mon Sep 17 00:00:00 2001 From: Michal Szewczyk Date: Tue, 17 Dec 2024 11:54:20 +0100 Subject: [PATCH 09/11] Update deps --- .github/workflows/elixir.yml | 1 + mix.exs | 2 +- mix.lock | 6 +++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index 8e79fa9..8f0f1b2 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -29,6 +29,7 @@ jobs: - name: Set up Elixir uses: erlef/setup-beam@v1 + id: beam with: elixir-version: ${{ matrix.iex }} otp-version: ${{ matrix.otp }} diff --git a/mix.exs b/mix.exs index d9a2667..5c8e704 100644 --- a/mix.exs +++ b/mix.exs @@ -46,7 +46,7 @@ defmodule Neo4ex.MixProject do {:mox, "~> 1.0", only: [:test]}, # Linting - {:credo, "~> 1.6.7", only: [:dev]}, + {:credo, "~> 1.6", only: [:dev]}, {:dialyxir, "~> 1.4", only: [:dev], runtime: false}, # Documentation diff --git a/mix.lock b/mix.lock index a7679f6..aef5768 100644 --- a/mix.lock +++ b/mix.lock @@ -1,13 +1,13 @@ %{ - "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, + "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, - "credo": {:hex, :credo, "1.6.7", "323f5734350fd23a456f2688b9430e7d517afb313fbd38671b8a4449798a7854", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "41e110bfb007f7eda7f897c10bf019ceab9a0b269ce79f015d54b0dcf4fc7dd3"}, + "credo": {:hex, :credo, "1.7.10", "6e64fe59be8da5e30a1b96273b247b5cf1cc9e336b5fd66302a64b25749ad44d", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "71fbc9a6b8be21d993deca85bf151df023a3097b01e09a2809d460348561d8cd"}, "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"}, "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, "ex_doc": {:hex, :ex_doc, "0.35.1", "de804c590d3df2d9d5b8aec77d758b00c814b356119b3d4455e4b8a8687aecaf", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "2121c6402c8d44b05622677b761371a759143b958c6c19f6558ff64d0aed40df"}, - "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, + "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, From 797ef23c5f2e17f20ed09b46379c388c22492009 Mon Sep 17 00:00:00 2001 From: Michal Szewczyk Date: Tue, 17 Dec 2024 13:14:26 +0100 Subject: [PATCH 10/11] Tidy up --- example_app/lib/mix/tasks/boltx_benchmark.ex | 2 +- test/neo4ex/bolt_protocol/encoder_test.exs | 2 +- test/neo4ex/bolt_protocol_test.exs | 9 +++------ 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/example_app/lib/mix/tasks/boltx_benchmark.ex b/example_app/lib/mix/tasks/boltx_benchmark.ex index 419f11e..70840ce 100644 --- a/example_app/lib/mix/tasks/boltx_benchmark.ex +++ b/example_app/lib/mix/tasks/boltx_benchmark.ex @@ -30,7 +30,7 @@ defmodule Mix.Tasks.ExampleApp.BoltxBenchmark do MATCH (customer) RETURN customer, rand() as r ORDER BY r - LIMIT 10 + LIMIT 100 """ %{query: query, params: %{}} diff --git a/test/neo4ex/bolt_protocol/encoder_test.exs b/test/neo4ex/bolt_protocol/encoder_test.exs index 43de73e..f52b988 100644 --- a/test/neo4ex/bolt_protocol/encoder_test.exs +++ b/test/neo4ex/bolt_protocol/encoder_test.exs @@ -1,7 +1,6 @@ defmodule Neo4ex.BoltProtocol.EncoderTest do use ExUnit.Case, async: true - alias Neo4ex.BoltProtocol.Decoder alias Neo4ex.BoltProtocol.Structure.Graph.Relationship alias Neo4ex.BoltProtocol.Structure.Graph.Node alias Neo4ex.BoltProtocol.Structure.Graph.Legacy.DateTimeZoneId @@ -9,6 +8,7 @@ defmodule Neo4ex.BoltProtocol.EncoderTest do alias Neo4ex.BoltProtocol.Structure.Message.Extra alias Neo4ex.BoltProtocol.Encoder + alias Neo4ex.BoltProtocol.Decoder alias Neo4ex.PackStream.Exceptions @version Mix.Project.config()[:version] diff --git a/test/neo4ex/bolt_protocol_test.exs b/test/neo4ex/bolt_protocol_test.exs index 6969cc1..176ce36 100644 --- a/test/neo4ex/bolt_protocol_test.exs +++ b/test/neo4ex/bolt_protocol_test.exs @@ -33,8 +33,7 @@ defmodule Neo4ex.BoltProtocolTest do encoded_success_message = Encoder.encode(success_message, "4.0.0") # two versions, 4.0.0 and 0.0.0 x 3 (client always sends 4 versions) - handshake = - <<0x60, 0x60, 0xB0, 0x17, 0::8, 0::8, 0::8, 4::8, 0::96>> + handshake = <<0x60, 0x60, 0xB0, 0x17, 0::8, 0::8, 0::8, 4::8, 0::96>> hello = generate_message_chunk(%Hello{extra: %Extra.Hello{scheme: "none"}}, "4.0.0") @@ -51,8 +50,7 @@ defmodule Neo4ex.BoltProtocolTest do encoded_success_message = Encoder.encode(success_message, "5.3.0") # two versions, 5.3.0 and 0.0.0 x 3 (client always sends 4 versions) - handshake = - <<0x60, 0x60, 0xB0, 0x17, 0::8, 0::8, 3::8, 5::8, 0::96>> + handshake = <<0x60, 0x60, 0xB0, 0x17, 0::8, 0::8, 3::8, 5::8, 0::96>> hello = generate_message_chunk(%Hello{}, "5.3.0") @@ -80,8 +78,7 @@ defmodule Neo4ex.BoltProtocolTest do encoded_failure_message = Encoder.encode(message, "5.3.0") # two versions, 5.3.0 and 0.0.0 x 3 (client always sends 4 versions) - handshake = - <<0x60, 0x60, 0xB0, 0x17, 0::8, 0::8, 3::8, 5::8, 0::96>> + handshake = <<0x60, 0x60, 0xB0, 0x17, 0::8, 0::8, 3::8, 5::8, 0::96>> hello = generate_message_chunk(%Hello{}, "5.3.0") From 0b6a28d6fb7b49977dca2513465e8f81ae1e0b79 Mon Sep 17 00:00:00 2001 From: Michal Szewczyk Date: Tue, 17 Dec 2024 14:27:30 +0100 Subject: [PATCH 11/11] Get rid of warnings --- lib/neo4ex/bolt_protocol/structure.ex | 10 ++++++++++ .../bolt_protocol/structure/message/extra/hello.ex | 3 +-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/neo4ex/bolt_protocol/structure.ex b/lib/neo4ex/bolt_protocol/structure.ex index 7443266..ad55571 100644 --- a/lib/neo4ex/bolt_protocol/structure.ex +++ b/lib/neo4ex/bolt_protocol/structure.ex @@ -153,6 +153,11 @@ defmodule Neo4ex.BoltProtocol.Structure do # embedded structure must be a map but we should take advantage of field versioning defp embedded_encoder_protocol(fields_list) do + # When default values contain some data from module (like values from attributes) it won't be visible inside defimpl (different module) + # For protocol we need only version so this is completely fine + fields_list = + Enum.map(fields_list, fn {field, opts} -> {field, [version: opts[:version]]} end) + quote location: :keep do defimpl Neo4ex.BoltProtocol.Encoder do def encode(struct, bolt_version) do @@ -170,6 +175,11 @@ defmodule Neo4ex.BoltProtocol.Structure do end defp encoder_protocol(fields_list) do + # When default values contain some data from module (like values from attributes) it won't be visible inside defimpl (different module) + # For protocol we need only version so this is completely fine + fields_list = + Enum.map(fields_list, fn {field, opts} -> {field, [version: opts[:version]]} end) + quote location: :keep do defimpl Neo4ex.BoltProtocol.Encoder do alias Neo4ex.PackStream.{Markers, Exceptions} diff --git a/lib/neo4ex/bolt_protocol/structure/message/extra/hello.ex b/lib/neo4ex/bolt_protocol/structure/message/extra/hello.ex index 65cf2f8..36fb87a 100644 --- a/lib/neo4ex/bolt_protocol/structure/message/extra/hello.ex +++ b/lib/neo4ex/bolt_protocol/structure/message/extra/hello.ex @@ -2,7 +2,6 @@ defmodule Neo4ex.BoltProtocol.Structure.Message.Extra.Hello do use Neo4ex.BoltProtocol.Structure @version Mix.Project.config()[:version] - @system_info System.build_info()[:version] # can't be encoded directly, it's just helper for the Hello message embeded_structure do @@ -11,7 +10,7 @@ defmodule Neo4ex.BoltProtocol.Structure.Message.Extra.Hello do field(:bolt_agent, default: %{ product: "Neo4ex/#{@version}", - language: "Elixir/#{@system_info}" + language: "Elixir/#{System.build_info()[:version]}" }, version: ">= 5.3.0" )