Current section
Files
Jump to
Current section
Files
lib/mix/tasks/oban_doctor.check_workers.ex
defmodule Mix.Tasks.ObanDoctor.CheckWorkers do @shortdoc "Checks Oban workers for common issues" @moduledoc """ Runs checks against Oban worker definitions. ## Usage mix oban_doctor.check_workers [options] ## Options * `--format` - Output format: `text` (default) or `json` * `--strict` - Treat warnings as errors (exit code 1) * `--verbose` - Show all issues (default: shows first 3 per check type) ## Checks This task runs the following checks: * **MissingQueue** - Detects workers using queues not defined in Oban config * **StateGroupUsage** - Detects workers using :all state group (dangerous) * **UniquenessMissingStates** - Detects workers with unique config missing recommended states * **UniqueWithoutKeys** - Detects workers with unique on :args without explicit keys * **NoMaxAttempts** - Detects workers using default max_attempts (20) ## Configuration Checks can be configured via `.oban_doctor.exs` in your project root. Run `mix oban_doctor.gen.config` to generate a default config file. """ use Mix.Task alias ObanDoctor.Config alias ObanDoctor.ConfigFile alias ObanDoctor.WorkerDiscovery alias ObanDoctor.ObanDiscovery alias ObanDoctor.Check.Worker.MissingQueue alias ObanDoctor.Check.Worker.UniquenessMissingStates alias ObanDoctor.Check.Worker.StateGroupUsage alias ObanDoctor.Check.Worker.UniqueWithoutKeys alias ObanDoctor.Check.Worker.NoMaxAttempts alias ObanDoctor.CLI.Output @worker_checks [ MissingQueue, StateGroupUsage, UniquenessMissingStates, UniqueWithoutKeys, NoMaxAttempts ] @impl Mix.Task def run(args) do {opts, _, _} = OptionParser.parse(args, strict: [format: :string, strict: :boolean, verbose: :boolean], aliases: [f: :format, s: :strict, v: :verbose] ) format = parse_format(Keyword.get(opts, :format, "text")) strict = Keyword.get(opts, :strict, false) verbose = Keyword.get(opts, :verbose, false) project_root = File.cwd!() # Load configuration config = ConfigFile.load!(project_root) IO.puts("Discovering Oban workers...") workers = WorkerDiscovery.discover(project_root) IO.puts("Found #{length(workers)} workers") # Filter excluded workers workers = filter_excluded_workers(workers, config) IO.puts("Discovering defined queues...") defined_queues = ObanDiscovery.discover_queues(project_root) IO.puts( "Found #{MapSet.size(defined_queues)} defined queues: #{inspect(MapSet.to_list(defined_queues))}" ) IO.puts("") IO.puts("Running worker checks...") context = %{ workers: workers, defined_queues: defined_queues, project_root: project_root, config: config } # Filter enabled checks and run them issues = @worker_checks |> Enum.filter(fn check -> Config.check_enabled?(config, check.id()) end) |> Enum.flat_map(fn check -> run_check(check, context, config) end) Output.print_issues(issues, format: format, verbose: verbose) exit_code = Output.exit_code(issues, strict: strict) if exit_code != 0 do System.halt(exit_code) end end defp filter_excluded_workers(workers, config) do Enum.reject(workers, fn worker -> Config.worker_excluded?(config, worker.module) or Config.file_excluded?(config, worker.file) end) end defp run_check(check, context, config) do issues = check.run(context) # Apply severity override if configured case Config.severity_override(config, check.id()) do nil -> issues severity -> Enum.map(issues, fn issue -> %{issue | severity: severity} end) end end defp parse_format("json"), do: :json defp parse_format(_), do: :textend