defmodule Octopus do
@moduledoc """
Top-level module Octopus interface.
"""
alias Octopus.{Configs, Definition, Utils}
@spec define(String.t()) :: {:ok, String.t()} | {:error, any()}
def define(definition) when is_binary(definition) do
definition
|> Jason.decode!()
|> define()
end
@spec define(map()) :: {:ok, String.t()} | {:error, any()}
def define(definition) when is_map(definition) do
definition = Definition.new(definition)
case status(definition.name) do
:undefined ->
Definition.define(definition)
:not_ready ->
Definition.define(definition)
:ready ->
{:error, :already_started}
end
rescue
error ->
{:error, error}
end
@spec services :: list(String.t())
def services do
:code.all_loaded()
|> Enum.map(&Atom.to_string(elem(&1, 0)))
|> Enum.filter(&String.starts_with?(&1, "Elixir.#{Configs.services_namespace()}."))
|> Enum.map(&String.to_existing_atom/1)
|> Enum.filter(&Keyword.has_key?(&1.__info__(:functions), :octopus_service_module?))
|> Enum.map(&apply(&1, :name, []))
end
@spec definition(String.t()) :: {:ok, map()} | {:error, any}
def definition(service_name) do
case status(service_name) do
:undefined ->
{:error, :undefined}
:not_ready ->
{:ok, module} = build_module(service_name)
{:ok, apply(module, :definition, [])}
:ready ->
{:ok, module} = build_module(service_name)
{:ok, apply(module, :definition, [])}
end
end
@spec start(String.t(), map()) :: {:ok, map()} | {:error, any}
def start(service_name, args \\ %{}) when is_binary(service_name) and is_map(args) do
case status(service_name) do
:undefined ->
{:error, :undefined}
:not_ready ->
{:ok, module} = build_module(service_name)
apply(module, :start, [args])
:ready ->
{:error, :already_started}
end
rescue
error ->
{:error, inspect(error)}
end
@spec state(String.t()) :: {:ok, map()} | {:error, any}
def state(service_name) do
case status(service_name) do
:undefined ->
{:error, :undefined}
:not_ready ->
{:error, :not_ready}
:ready ->
{:ok, module} = build_module(service_name)
{:ok, apply(module, :state, [])}
end
end
@spec call(String.t(), String.t(), map()) :: {:ok, map()} | {:error, any()}
def call(service_name, function_name, args)
when is_binary(service_name) and is_binary(function_name) and is_map(args) do
case status(service_name) do
:undefined ->
{:error, :undefined}
:not_ready ->
{:error, :not_ready}
:ready ->
{:ok, module} = build_module(service_name)
apply(module, String.to_atom(function_name), [args])
end
rescue
error ->
{:error, error}
end
@spec stop(String.t(), map()) :: :ok | {:error, any()}
def stop(service_name, args \\ %{}) when is_binary(service_name) and is_map(args) do
case status(service_name) do
:undefined ->
{:error, :undefined}
:not_ready ->
{:error, :not_ready}
:ready ->
{:ok, module} = build_module(service_name)
apply(module, :stop, [args])
end
rescue
error ->
{:error, error}
end
@spec restart(String.t(), map()) :: {:ok, map()} | {:error, any()}
def restart(service_name, args \\ %{}) do
case status(service_name) do
:undefined ->
{:error, :undefined}
:not_ready ->
{:error, :not_ready}
:ready ->
{:ok, module} = build_module(service_name)
:ok = apply(module, :stop, [args])
apply(module, :start, [args])
end
rescue
error ->
{:error, error}
end
@spec delete(String.t(), map()) :: :ok | {:error, any()}
def delete(service_name, args \\ %{}) do
case status(service_name) do
:undefined ->
{:error, :undefined}
:not_ready ->
{:ok, module} = build_module(service_name)
do_delete(module)
:ready ->
{:ok, module} = build_module(service_name)
:ok = apply(module, :stop, [args])
do_delete(module)
end
rescue
error ->
{:error, error}
end
defp do_delete(module) do
:code.soft_purge(:"#{module}.State")
:code.soft_purge(module)
:code.delete(:"#{module}.State")
:code.delete(module)
:ok
end
@spec status(String.t()) :: :undefined | :not_ready | :ready
def status(service_name) when is_binary(service_name) do
case build_module(service_name) do
{:ok, module} ->
case module.ready?() do
true -> :ready
false -> :not_ready
end
{:error, :not_found} ->
:undefined
end
end
defp build_module(service_name) do
module_name = Utils.modulize(service_name)
namespace = Configs.services_namespace()
module = String.to_atom("Elixir.#{namespace}.#{module_name}")
if Utils.module_exist?(module) do
{:ok, module}
else
{:error, :not_found}
end
end
end