diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index c4c6951..53724cf 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -29,5 +29,7 @@ jobs: run: mix deps.get - name: Run tests run: mix test + - name: Static Code Analysis - Dialyzer + run: mix dialyzer - name: Static Code Analysis - Credo run: mix credo diff --git a/README.md b/README.md index 7536e45..a3154ed 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ The package can be installed by adding `t2_server_query` to your list of depende ```elixir def deps do [ - {:t2_server_query, "~> 0.1.2"} + {:t2_server_query, "~> 0.1.3"} ] end ``` diff --git a/lib/t2_server_query.ex b/lib/t2_server_query.ex index 19e1621..227a2a4 100644 --- a/lib/t2_server_query.ex +++ b/lib/t2_server_query.ex @@ -9,7 +9,7 @@ defmodule T2ServerQuery do ## Installation def deps do [ - {:t2_server_query, "~> 0.1.2"} + {:t2_server_query, "~> 0.1.3"} ] end @@ -70,9 +70,17 @@ defmodule T2ServerQuery do }} """ + @spec query(String.t(), integer(), integer()) :: {atom(), T2ServerQuery.QueryResult.t()} def query(server_ip, port \\ 28_000, timeout \\ 3_500) do Logger.info "query: #{server_ip}" + case is_valid_ip?(server_ip) do + true -> handle_query(server_ip, port, timeout) + false -> PacketParser.init({:error, "#{server_ip} - Invalid IP" }, nil) + end + end + @spec handle_query(String.t(), integer(), integer()) :: {atom(), T2ServerQuery.QueryResult.t()} + defp handle_query(server_ip, port, timeout) do {:ok, socket} = :gen_udp.open(0, [:binary, {:active, false}]) # Convert a string ip from "127.0.0.1" into {127, 0, 0, 1} @@ -98,7 +106,17 @@ defmodule T2ServerQuery do PacketParser.init(hex_info_packet, hex_status_packet) end + @spec is_valid_ip?(any()) :: boolean() + defp is_valid_ip?(nil), do: false + defp is_valid_ip?(server_ip) do + case Regex.match?(~r/^([1-2]?[0-9]{1,2}\.){3}([1-2]?[0-9]{1,2})$/, server_ip) do + false -> false + true -> true + end + end + + @spec handle_udp_response(tuple(), String.t(), integer()) :: tuple() | String.t() defp handle_udp_response({:ok, {_ip, port, packet}}, _server_ip, port) do packet |> Base.encode16 diff --git a/lib/t2_server_query/packet_parser.ex b/lib/t2_server_query/packet_parser.ex index 37a0cf0..d4bb592 100644 --- a/lib/t2_server_query/packet_parser.ex +++ b/lib/t2_server_query/packet_parser.ex @@ -43,9 +43,9 @@ defmodule T2ServerQuery.PacketParser do Refer to `T2ServerQuery.QueryResult` for what a typical struct would look like. - """ + alias T2ServerQuery.QueryResult @doc """ @@ -53,6 +53,7 @@ defmodule T2ServerQuery.PacketParser do Normally you wouldn't need to run this function manually since it's called in a pipeline from the main `T2ServerQuery.query` """ + @spec init({:error, String.t()}, any()) :: {:error, map()} def init({:error, host}, _) do results = %QueryResult{} @@ -65,6 +66,7 @@ defmodule T2ServerQuery.PacketParser do } end + @spec init(binary(), binary()) :: {:ok, QueryResult.t()} def init(info_packet, status_packet) when is_binary(info_packet) and is_binary(status_packet) do info_results = info_packet @@ -81,6 +83,7 @@ defmodule T2ServerQuery.PacketParser do pack_results({:ok, status_results, info_results}) end + @spec pack_results({:ok, map(), map()}) :: {:ok, QueryResult.t()} defp pack_results({:ok, status_results, info_results}) do results = %QueryResult{} diff --git a/lib/t2_server_query/query_result.ex b/lib/t2_server_query/query_result.ex index e61dc2c..a7c680b 100644 --- a/lib/t2_server_query/query_result.ex +++ b/lib/t2_server_query/query_result.ex @@ -48,6 +48,21 @@ defmodule T2ServerQuery.QueryResult do } """ + @type t() :: %__MODULE__{ + server_status: atom(), + server_name: String.t(), + game_type: String.t(), + mission_type: String.t(), + map_name: String.t(), + player_count: integer(), + max_player_count: integer(), + bot_count: integer(), + server_description: String.t(), + team_count: integer(), + teams: list(), + players: list() + } + defstruct [ server_status: :offline, server_name: "", diff --git a/mix.exs b/mix.exs index bf59382..b222f9f 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule T2ServerQuery.MixProject do def project do [ app: :t2_server_query, - version: "0.1.1", + version: "0.1.3", elixir: "~> 1.12", start_permanent: Mix.env() == :prod, deps: deps(), @@ -32,8 +32,10 @@ defmodule T2ServerQuery.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:credo, "~> 1.5", only: :dev, runtime: false}, - {:ex_doc, "~> 0.24", only: :dev, runtime: false} + {:ex_doc, "~> 0.24", only: :dev, runtime: false}, + # Code quality, style and linting + {:dialyxir, "~> 1.1", only: [:dev], runtime: false}, + {:credo, "~> 1.6", only: [:dev, :test], runtime: false} ] end diff --git a/mix.lock b/mix.lock index 3681bd1..17f51e6 100644 --- a/mix.lock +++ b/mix.lock @@ -1,10 +1,12 @@ %{ "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, - "credo": {:hex, :credo, "1.5.6", "e04cc0fdc236fefbb578e0c04bd01a471081616e741d386909e527ac146016c6", [:mix], [{:bunt, "~> 0.2.0", [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", "4b52a3e558bd64e30de62a648518a5ea2b6e3e5d2b164ef5296244753fc7eb17"}, + "credo": {:hex, :credo, "1.6.4", "ddd474afb6e8c240313f3a7b0d025cc3213f0d171879429bf8535d7021d9ad78", [:mix], [{:bunt, "~> 0.2.0", [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", "c28f910b61e1ff829bffa056ef7293a8db50e87f2c57a9b5c3f57eee124536b7"}, + "dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"}, "earmark_parser": {:hex, :earmark_parser, "1.4.16", "607709303e1d4e3e02f1444df0c821529af1c03b8578dfc81bb9cf64553d02b9", [:mix], [], "hexpm", "69fcf696168f5a274dd012e3e305027010658b2d1630cef68421d6baaeaccead"}, + "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ex_doc": {:hex, :ex_doc, "0.25.3", "3edf6a0d70a39d2eafde030b8895501b1c93692effcbd21347296c18e47618ce", [:mix], [{:earmark_parser, "~> 1.4.0", [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", "9ebebc2169ec732a38e9e779fd0418c9189b3ca93f4a676c961be6c1527913f5"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, - "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, + "jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"}, "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, "makeup_elixir": {:hex, :makeup_elixir, "0.15.1", "b5888c880d17d1cc3e598f05cdb5b5a91b7b17ac4eaf5f297cb697663a1094dd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "db68c173234b07ab2a07f645a5acdc117b9f99d69ebf521821d89690ae6c6ec8"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, diff --git a/test/t2_server_query_test.exs b/test/t2_server_query_test.exs index 2926ce6..66a8880 100644 --- a/test/t2_server_query_test.exs +++ b/test/t2_server_query_test.exs @@ -21,30 +21,35 @@ defmodule T2ServerQueryTest do test "Live test a number of Tribes 2 servers" do tasks = [ Task.async(T2ServerQuery, :query, ["35.239.88.241", 28_000]), - Task.async(T2ServerQuery, :query, ["67.222.138.13"]) + Task.async(T2ServerQuery, :query, ["148.170.171.67"]) ] Task.yield_many(tasks) - |> Enum.map(fn {_task, result} -> - test_server_status(result) + |> Enum.each(fn {_task, result} -> + case result do + {:ok, _ } -> assert true + {:error, _} -> assert false + _ -> assert false + end end) - end - defp test_server_status({:ok, _}) do - assert true - end - defp test_server_status({:error, _}) do - assert false - end - defp test_server_status(nil) do - assert false + + test "Invalid IP" do + {:error, result} = T2ServerQuery.query("fake.ip") + |> T2ServerQuery.log + + assert result.server_status == :offline + assert result.server_name == "fake.ip - Invalid IP" end + end + + #qry_test = T2ServerQuery.query("127.0.0.1") #IO.inspect qry_test