Botica.Doctor (Botica v2.0.0)

Copy Markdown View Source

Environment diagnostics and auto-repair — returns structured results.

Define checks and use run/1 to diagnose or fix/1 to repair. All functions return data structures — the caller decides how to display.

Check definition

config = %{
  app_name: "myapp",
  checks: [
    %{
      id: :postgresql,
      name: "PostgreSQL",
      description: "Database server",
      priority: 1,
      check: fn -> {:ok, "v14"} end,
      fix: fn -> {:ok, "installed"} end,
      fix_command: "sudo apt install postgresql-14"
    }
  ]
}

Each check/0 returns {:ok, msg}, {:warning, msg} or {:error, msg}. Each fix/0 returns {:ok, msg}, {:error, msg} or :skipped.

Return values

run/1 returns {:ok, results} — all checks always return ok wrapper, the individual status is embedded in each result map.

fix/1 returns {:ok, fix_report} — a detailed report of what was applied.

summary/1 returns a map with counts and a passed? boolean.

Timeout support

Checks can specify a timeout in milliseconds:

%{
  id: :slow_check,
  name: "Slow Check",
  timeout: 5000,  # 5 second timeout
  check: fn -> ... end
}

The default timeout is 30 seconds. Global timeout can be overridden in run/2.

Summary

Functions

Returns available battery checks (predefined health checks).

Runs fixes for all checks with errors and returns a detailed report.

Returns a diagnostic snapshot of the current Botica.Flags registry.

Formats the flags_summary/0 output as a human-readable string suitable for the delfos doctor-style banners.

Runs a quick health check and returns a simple pass/fail map.

Runs all checks in parallel and returns structured results.

Runs all checks in parallel with custom options.

Sorts checks by priority (lower values run first).

Summarizes a list of check results into a single map.

Validates the configuration and returns :ok or {:error, reason}.

Types

check_def()

@type check_def() :: Botica.Types.check_def()

check_id()

@type check_id() :: Botica.Types.check_id()

check_result()

@type check_result() :: Botica.Types.check_result()

config()

@type config() :: Botica.Types.config()

fix_report()

@type fix_report() :: Botica.Types.fix_report()

fix_result()

@type fix_result() :: Botica.Types.fix_result()

result()

@type result() :: Botica.Types.result()

summary()

@type summary() :: Botica.Types.summary()

Functions

batteries()

@spec batteries() :: [check_def()]

Returns available battery checks (predefined health checks).

Examples

iex> batteries = Botica.Doctor.batteries()
iex> is_list(batteries)
true

fix(config)

@spec fix(config()) :: {:ok, fix_report()} | {:error, String.t()}

Runs fixes for all checks with errors and returns a detailed report.

Examples

iex> config = %{
...>   app_name: "myapp",
...>   checks: [
...>     %{
...>       id: :failing,
...>       name: "Failing",
...>       description: "A failing check",
...>       priority: 1,
...>       check: fn -> {:error, "something wrong"} end,
...>       fix: fn -> {:ok, "fixed!"} end,
...>       fix_command: "some command"
...>     }
...>   ]
...> }
iex> {:ok, report} = Botica.Doctor.fix(config)
iex> report.applied
[:failing]

flags_summary()

@spec flags_summary() :: %{count: non_neg_integer(), flags: [map()]}

Returns a diagnostic snapshot of the current Botica.Flags registry.

Shape:

%{
  count: 3,
  flags: [
    %{name: :beta_search, status: :enabled, default: true, rollout: nil},
    %{name: :new_dashboard, status: :disabled, default: false, rollout: nil},
    %{name: :rate_limiting, status: :rollout, default: false, rollout: 25}
  ]
}

Intended to be printed by CLI / REPL wrappers as part of the botica diagnostic banner. Safe to call when no flags are defined — returns an empty list under :flags.

format_flags_summary()

@spec format_flags_summary() :: String.t()

Formats the flags_summary/0 output as a human-readable string suitable for the delfos doctor-style banners.

Returns an empty string when no flags are defined.

Example output

Flags (3 defined):
   beta_search     enabled  (default: true)
   new_dashboard   disabled (default: false)
  ~ rate_limiting   rollout 25% (default: false)

health_check(config)

@spec health_check(config()) ::
  %{status: :ok | :degraded | :fail, summary: summary()}
  | %{status: :fail, summary: summary(), error: String.t()}

Runs a quick health check and returns a simple pass/fail map.

Shorthand for run/1 + summary/1 — returns %{status: :ok | :degraded | :fail, summary: summary()}.

Examples

iex> config = %{
...>   app_name: "myapp",
...>   checks: [
...>     %{
...>       id: :check1,
...>       name: "Check 1",
...>       description: "A check",
...>       priority: 1,
...>       check: fn -> {:ok, "ok"} end,
...>       fix: fn -> :skipped end,
...>       fix_command: nil
...>     }
...>   ]
...> }
iex> result = Botica.Doctor.health_check(config)
iex> result.status
:ok
iex> result.summary.passed?
true

run(config)

@spec run(config()) :: {:ok, [result()]} | {:error, String.t()}

Runs all checks in parallel and returns structured results.

Options

  • :timeout - Global timeout per check in ms (default: 30_000)
  • :stop_on_first_error - Stop after first error (default: false)
  • :continue_on_error - Continue after errors (default: true)

Examples

iex> config = %{
...>   app_name: "myapp",
...>   checks: [
...>     %{
...>       id: :check1,
...>       name: "Check 1",
...>       description: "A check",
...>       priority: 1,
...>       check: fn -> {:ok, "ok"} end,
...>       fix: fn -> :skipped end,
...>       fix_command: nil
...>     }
...>   ]
...> }
iex> {:ok, [result]} = Botica.Doctor.run(config)
iex> result.status
:ok

run(config, opts)

@spec run(config(), Botica.Types.executor_options()) ::
  {:ok, [result()]} | {:error, String.t()}

Runs all checks in parallel with custom options.

Options

  • :timeout - Global timeout per check in ms (default: 30_000)
  • :stop_on_first_error - Stop after first error (default: false)
  • :continue_on_error - Continue after errors (default: true)

sort_checks(checks)

@spec sort_checks([check_def()]) :: [check_def()]

Sorts checks by priority (lower values run first).

Examples

iex> checks = [
...>   %{id: :b, priority: 2},
...>   %{id: :a, priority: 1}
...> ]
iex> Botica.Doctor.sort_checks(checks)
[%{id: :a, priority: 1}, %{id: :b, priority: 2}]

summary(results)

@spec summary([result()]) :: summary()

Summarizes a list of check results into a single map.

Examples

iex> results = [
...>   %{status: :ok},
...>   %{status: :ok},
...>   %{status: :warning},
...>   %{status: :error}
...> ]
iex> Botica.Doctor.summary(results)
%{ok: 2, warning: 1, error: 1, total: 4, passed?: false}

validate(config)

@spec validate(config()) :: :ok | {:error, String.t()}

Validates the configuration and returns :ok or {:error, reason}.

Examples

iex> Botica.Doctor.validate(%{app_name: "test", checks: []})
:ok
iex> Botica.Doctor.validate(%{app_name: "test"})
{:error, "config.checks must be a list"}
iex> Botica.Doctor.validate(%{checks: []})
{:error, "config.app_name must be a string"}