From ca869808339b883b9c2c61da18f0982665a410ab Mon Sep 17 00:00:00 2001 From: HaimKortovich Date: Tue, 21 Apr 2026 14:26:37 -0500 Subject: [PATCH] add dialyzer and fix id --- .dialyzer_ignore.exs | 1 + .gitignore | 92 +++++++++++++++++++ lib/workload_service/aggregates/quote_task.ex | 3 +- .../aggregates/solicitation_task.ex | 3 +- lib/workload_service/aggregates/task.ex | 31 +++++-- .../consumers/quote_requested_consumer.ex | 19 ++-- lib/workload_service/release.ex | 15 +-- mix.exs | 21 ++++- mix.lock | 2 + 9 files changed, 158 insertions(+), 29 deletions(-) create mode 100644 .dialyzer_ignore.exs create mode 100644 .gitignore diff --git a/.dialyzer_ignore.exs b/.dialyzer_ignore.exs new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/.dialyzer_ignore.exs @@ -0,0 +1 @@ +[] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b15141c --- /dev/null +++ b/.gitignore @@ -0,0 +1,92 @@ +/.direnv/ +/result + +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where 3rd-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Temporary files, for example, from tests. +/tmp/ + +# Ignore package tarball (built via "mix hex.build"). +plausible-*.tar + +# If NPM crashes, it generates a log, let's ignore it too. +npm-debug.log + +# If running Clickhouse through the Makefile, its data is written here +/.clickhouse_db_vol/ + +# The directory NPM downloads your dependencies sources to. +/assets/node_modules/ +/tracker/node_modules/ + +# Files generated by Playwright when running tracker tests +/tracker/test-results/ +/tracker/playwright-report/ +/tracker/blob-report/ +/tracker/playwright/.cache/ + +# Stored hash of source tracker files used in development environment +# to detect changes in /tracker/src and avoid unnecessary compilation. +/tracker/compiler/last-hash.txt +# Temporary file used by analyze-sizes.js +/tracker/compiler/.analyze-sizes.json + +# Tracker npm module files that are generated by the compiler for the NPM package +/tracker/npm_package/plausible.js* +/tracker/npm_package/plausible.cjs* +/tracker/npm_package/plausible.d.cts + +# test coverage directory +/assets/coverage + +# Since we are building assets from assets/, +# we ignore priv/static. You may want to comment +# this depending on your deployment strategy. +/priv/static/css +/priv/static/js +/priv/version.json + +# Files matching config/*.secret.exs pattern contain sensitive +# data and you should not commit them into version control. +# +# Alternatively, you may comment the line below and commit the +# secrets files as long as you replace their contents by environment +# variables. +/config/*.secret.exs + +# Ignore Elixir Language Server files +.elixir_ls +plausible-report.xml + +.idea +*.iml +*.log +*.code-workspace +.vscode + +# Dializer +/priv/plts/*.plt +/priv/plts/*.plt.hash + +.env + +.claude diff --git a/lib/workload_service/aggregates/quote_task.ex b/lib/workload_service/aggregates/quote_task.ex index 44bf8a7..7b1f421 100644 --- a/lib/workload_service/aggregates/quote_task.ex +++ b/lib/workload_service/aggregates/quote_task.ex @@ -1,7 +1,8 @@ defmodule WorkloadService.Aggregates.QuoteTask do use WorkloadService.Aggregates.Task, task_type: "quote", - commands: WorkloadService.Commands.QuoteTask + commands: WorkloadService.Commands.QuoteTask, + submission_type: map() def validate_submission(_) do :ok diff --git a/lib/workload_service/aggregates/solicitation_task.ex b/lib/workload_service/aggregates/solicitation_task.ex index 1d7da9f..4c53478 100644 --- a/lib/workload_service/aggregates/solicitation_task.ex +++ b/lib/workload_service/aggregates/solicitation_task.ex @@ -1,7 +1,8 @@ defmodule WorkloadService.Aggregates.SolicitationTask do use WorkloadService.Aggregates.Task, task_type: "solicitation", - commands: WorkloadService.Commands.SolicitationTask + commands: WorkloadService.Commands.SolicitationTask, + submission_type: map() def validate_submission(_) do :ok diff --git a/lib/workload_service/aggregates/task.ex b/lib/workload_service/aggregates/task.ex index 2276845..6042322 100644 --- a/lib/workload_service/aggregates/task.ex +++ b/lib/workload_service/aggregates/task.ex @@ -8,24 +8,37 @@ defmodule WorkloadService.Aggregates.Task do use WorkloadService.Aggregates.Task, task_type: "quote" end - """ - @type t :: %__MODULE__{ - id: WorkloadService.Aggregates.TaskId.t() | nil, - application_id: WorkloadService.Aggregates.ApplicationId.t() | nil, - task_info: map() | nil, - submission: map() | nil, - attachments: [String.t()], - status: String.t() | nil - } + ## With custom submission type + + defmodule QuoteTaskWithCustomSubmission do + defmodule CustomSubmission do + @type t :: %{...custom_fields: term()} + end + + use WorkloadService.Aggregates.Task, + task_type: "quote", + submission_type: CustomSubmission.t() + end + """ @callback validate_submission(map()) :: :ok | {:error, term()} defmacro __using__(opts) do task_type = Keyword.fetch!(opts, :task_type) commands_module = Keyword.get(opts, :commands, WorkloadService.Commands.Task) + submission_type = Keyword.get(opts, :submission_type, quote(do: map())) quote do + @type t :: %__MODULE__{ + id: WorkloadService.Aggregates.TaskId.t() | nil, + application_id: WorkloadService.Aggregates.ApplicationId.t() | nil, + task_info: map() | nil, + submission: unquote(submission_type) | nil, + attachments: [String.t()], + status: String.t() | nil + } + @behaviour Commanded.Aggregates.Aggregate @task_type unquote(task_type) diff --git a/lib/workload_service/consumers/quote_requested_consumer.ex b/lib/workload_service/consumers/quote_requested_consumer.ex index 2c37d78..c2e7174 100644 --- a/lib/workload_service/consumers/quote_requested_consumer.ex +++ b/lib/workload_service/consumers/quote_requested_consumer.ex @@ -17,8 +17,8 @@ defmodule WorkloadService.Consumers.QuoteRequestedConsumer do {:ok, conn} = AMQP.Connection.open(amqp_url()) {:ok, channel} = AMQP.Channel.open(conn) - AMQP.Queue.declare(channel, @queue, durable: true) - AMQP.Queue.bind(channel, @queue, @exchange, routing_key: @routing_key) + :ok = AMQP.Queue.declare(channel, @queue, durable: true) + :ok = AMQP.Queue.bind(channel, @queue, @exchange, routing_key: @routing_key) {:ok, _tag} = AMQP.Basic.consume(channel, @queue) Logger.info("QuoteRequestedConsumer started, listening on #{@queue}") @@ -31,14 +31,15 @@ defmodule WorkloadService.Consumers.QuoteRequestedConsumer do def handle_info({:basic_cancel_ok, _}, state), do: {:noreply, state} def handle_info({:basic_deliver, payload, meta}, state) do - case process(payload) do - :ok -> - AMQP.Basic.ack(state.channel, meta.delivery_tag) + :ok = + case process(payload) do + :ok -> + AMQP.Basic.ack(state.channel, meta.delivery_tag) - {:error, reason} -> - Logger.error("QuoteRequestedConsumer: failed to process: #{inspect(reason)}") - AMQP.Basic.reject(state.channel, meta.delivery_tag, requeue: false) - end + {:error, reason} -> + Logger.error("QuoteRequestedConsumer: failed to process: #{inspect(reason)}") + AMQP.Basic.reject(state.channel, meta.delivery_tag, requeue: false) + end {:noreply, state} end diff --git a/lib/workload_service/release.ex b/lib/workload_service/release.ex index 0ed375f..e454d91 100644 --- a/lib/workload_service/release.ex +++ b/lib/workload_service/release.ex @@ -6,12 +6,14 @@ defmodule WorkloadService.Release do @app :workload_service def migrate do - load_app() - init_event_store() + :ok = load_app() + :ok = init_event_store() for repo <- repos() do {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true)) end + + :ok end def rollback(repo, version) do @@ -24,9 +26,10 @@ defmodule WorkloadService.Release do end defp load_app do - Application.ensure_all_started(:ssl) - Application.ensure_all_started(:postgrex) - Application.ensure_loaded(@app) + {:ok, _} = Application.ensure_all_started(:ssl) + {:ok, _} = Application.ensure_all_started(:postgrex) + :ok = Application.ensure_loaded(@app) + :ok end def init_event_store do @@ -34,4 +37,4 @@ defmodule WorkloadService.Release do :ok = EventStore.Tasks.Init.exec(config, []) end -end \ No newline at end of file +end diff --git a/mix.exs b/mix.exs index 6efc0a6..1562a8a 100644 --- a/mix.exs +++ b/mix.exs @@ -9,14 +9,28 @@ defmodule WorkloadService.MixProject do elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, aliases: aliases(), - deps: deps() + deps: deps(), + dialyzer: [ + plt_file: {:no_warn, "priv/plts/dialyzer.plt"}, + plt_add_apps: [:mix, :ex_unit], + flags: [ + # warn on ignored return values + :unmatched_returns, + # warn on error handling issues + :error_handling, + # warn when spec is too broad + :underspecs, + # warn on opaque type violations + :no_opaque + ] + ] ] end def application do [ mod: {WorkloadService.Application, []}, - extra_applications: [:logger, :runtime_tools] + extra_applications: [:logger, :runtime_tools, :dialyzer] ] end @@ -45,7 +59,8 @@ defmodule WorkloadService.MixProject do {:amqp, "~> 4.1"}, {:uuid, "~> 1.1"}, {:req, "~> 0.5"}, - {:cors_plug, "~> 3.0"} + {:cors_plug, "~> 3.0"}, + {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false} ] end diff --git a/mix.lock b/mix.lock index 40084cb..ff31d1c 100644 --- a/mix.lock +++ b/mix.lock @@ -10,9 +10,11 @@ "credentials_obfuscation": {:hex, :credentials_obfuscation, "3.5.0", "61e282adfb4439486b3994faaec69543c7ee6cc7e70c6340e8853fd9deaf8219", [:rebar3], [], "hexpm", "843adbe3246861ce0f1a0fa3222f384834eb31defd8d6b9cba7afd2977c957bc"}, "db_connection": {:hex, :db_connection, "2.9.0", "a6a97c5c958a2d7091a58a9be40caf41ab496b0701d21e1d1abff3fa27a7f371", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "17d502eacaf61829db98facf6f20808ed33da6ccf495354a41e64fe42f9c509c"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, + "dialyxir": {:hex, :dialyxir, "1.4.7", "dda948fcee52962e4b6c5b4b16b2d8fa7d50d8645bbae8b8685c3f9ecb7f5f4d", [:mix], [{:erlex, ">= 0.2.8", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b34527202e6eb8cee198efec110996c25c5898f43a4094df157f8d28f27d9efe"}, "dns_cluster": {:hex, :dns_cluster, "0.1.3", "0bc20a2c88ed6cc494f2964075c359f8c2d00e1bf25518a6a6c7fd277c9b0c66", [:mix], [], "hexpm", "46cb7c4a1b3e52c7ad4cbe33ca5079fbde4840dedeafca2baf77996c2da1bc33"}, "ecto": {:hex, :ecto, "3.13.5", "9d4a69700183f33bf97208294768e561f5c7f1ecf417e0fa1006e4a91713a834", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df9efebf70cf94142739ba357499661ef5dbb559ef902b68ea1f3c1fabce36de"}, "ecto_sql": {:hex, :ecto_sql, "3.13.5", "2f8282b2ad97bf0f0d3217ea0a6fff320ead9e2f8770f810141189d182dc304e", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aa36751f4e6a2b56ae79efb0e088042e010ff4935fc8684e74c23b1f49e25fdc"}, + "erlex": {:hex, :erlex, "0.2.8", "cd8116f20f3c0afe376d1e8d1f0ae2452337729f68be016ea544a72f767d9c12", [:mix], [], "hexpm", "9d66ff9fedf69e49dc3fd12831e12a8a37b76f8651dd21cd45fcf5561a8a7590"}, "eventstore": {:hex, :eventstore, "1.4.8", "26778c991cfb078f3906a4267060efc7bb5e5943f69ddb8ae6fb60f07042a66e", [:mix], [{:fsm, "~> 0.3", [hex: :fsm, repo: "hexpm", optional: false]}, {:gen_stage, "~> 1.2", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.17", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm", "30c914602fdea8db5992a90ecb1f84068531e764cf0c066be71ff0eec4e3bcb9"}, "file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"}, "finch": {:hex, :finch, "0.21.0", "b1c3b2d48af02d0c66d2a9ebfb5622be5c5ecd62937cf79a88a7f98d48a8290c", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "87dc6e169794cb2570f75841a19da99cfde834249568f2a5b121b809588a4377"},