lib/type/async_job.ex

# Copyright(c) 2015-2023 ACCESS CO., LTD. All rights reserved.

use Croma

defmodule Antikythera.AsyncJob.Id do
  use Croma.SubtypeOfString, pattern: ~R/\A[0-9A-Za-z_-]{1,32}\z/

  defun generate() :: t do
    Enum.map(1..20, fn _ -> gen_base64url_char() end) |> List.to_string()
  end

  defunp gen_base64url_char() :: char do
    case :rand.uniform(64) do
      # 0-9
      i when i <= 10 -> i + 47
      # A-Z
      i when i <= 36 -> i + 54
      # a-z
      i when i <= 62 -> i + 60
      # -
      63 -> 45
      # _
      64 -> 95
    end
  end
end

defmodule Antikythera.AsyncJob.Schedule do
  alias Antikythera.{Time, Cron}

  @type t :: {:once, Time.t()} | {:cron, Cron.t()}

  defun valid?(t :: term) :: boolean do
    {:once, t} -> Time.valid?(t)
    {:cron, c} -> Cron.valid?(c)
    _otherwise -> false
  end
end

defmodule Antikythera.AsyncJob.Attempts do
  use Croma.SubtypeOfInt, min: 1, max: 10, default: 3
end

defmodule Antikythera.AsyncJob.MaxDuration do
  use Croma.SubtypeOfInt, min: 1, max: 30 * 60_000, default: 5 * 60_000
end

defmodule Antikythera.AsyncJob.RetryInterval do
  defmodule Factor do
    use Croma.SubtypeOfInt, min: 0, max: 5 * 60_000, default: 5_000
  end

  defmodule Base do
    use Croma.SubtypeOfFloat, min: 1.0, max: 5.0, default: 2.0
  end

  use Croma.SubtypeOfTuple,
    elem_modules: [Factor, Base],
    default: {Factor.default(), Base.default()}

  defun interval(t :: v[t], n_retries_done_so_far :: v[non_neg_integer]) :: non_neg_integer do
    {factor, base} = t
    factor * round(:math.pow(base, n_retries_done_so_far))
  end
end

defmodule Antikythera.AsyncJob.Metadata do
  alias Antikythera.Time
  alias Antikythera.AsyncJob.{Id, Attempts, MaxDuration, RetryInterval}

  use Croma.Struct,
    recursive_new?: true,
    fields: [
      id: Id,
      run_at: Time,
      max_duration: MaxDuration,
      attempts: Attempts,
      remaining_attempts: Attempts,
      retry_interval: RetryInterval
    ]
end

defmodule Antikythera.AsyncJob.StateLabel do
  use Croma.SubtypeOfAtom, values: [:waiting, :runnable, :running]
end

defmodule Antikythera.AsyncJob.Status do
  alias Antikythera.{Time, GearName}
  alias Antikythera.AsyncJob.{Id, Schedule, Attempts, MaxDuration, RetryInterval, StateLabel}

  use Croma.Struct,
    recursive_new?: true,
    fields: [
      id: Id,
      gear_name: GearName,
      module: Croma.Atom,
      payload: Croma.Map,
      schedule: Schedule,
      start_time: Time,
      state: StateLabel,
      max_duration: MaxDuration,
      attempts: Attempts,
      remaining_attempts: Attempts,
      retry_interval: RetryInterval
    ]
end