retort v2.0.0 Retort.Resources behaviour

Default callbacks for Calcinator.Resources behaviour when backed by Retort.Client.Generic

Summary

Types

Options passed to client_start_link that should be combined with resource-specific options controlled by the module implementing this behaviour

Functions

use Retort.Resources implements Calcinator.Resources callbacks to use Retort.Client.Generic

Constructs Ecto.Changeset.t using module changeset/2 (which is the implementatin of the Calcinator.Resources.changeset/2 callback) with module ecto_schema_module/0 __struct__/0 as the initial data

Destroys changeset data using Retort.Client.Generic.destroy/4

Lists resources matching the query_options using Retort.Client.Generic.index/3

module specific sandbox token key

The timeout for a specific function of Retort.Client.Generic

Applies updates in changeset by converting them back to params for Retort.Client.Generic.update/5

Callbacks

Converts the associations passed from Calcinator to the full set of includes passed to Retort.Client.Generic

Call Retort.Client.Generic.start_link and return its result

Used to generate changesets and translate error messages

Add additional associations to the list passed down from Calcinator

Timeout for function call on Retort.Client.Generic

Types

Functions

__using__(list) (macro)

use Retort.Resources implements Calcinator.Resources callbacks to use Retort.Client.Generic.

defmodule MyRPC.Authors do
  alias MyRPC.{Author, Client}
  alias MyLocal.Repo

  use Retort.Resources

  # Functions

  ## Calcinator.Resources callbacks

  def sandboxed?, do: Repo.config()[:pool] == Ecto.Adapters.SQL.Sandbox

  ## Retort.Resources callbacks

  def association_to_include(:posts), do: "posts"

  def client_start_link(options), do: Client.Author.start_link(options)

  def ecto_schema_module, do: Author
end

Callbacks

The caller must implement the following callbacks.

Retort.Resources callbacks

  • Retort.Resources.association_to_include/1
  • Retort.Resources.client_start_link/1
  • Retort.Resources.ecto_schema_module/0

Calcinator.Resources callback

use Retort.Resources doesn’t implement all the required Calcinator.Resources.

Overridable Functions

The following functions defined by use Retort.Resources are marked as overridable. You can use super/0, but you may also want to call the original implementation directly.

FunctionCallee
allow_sandbox_access/1Retort.Resources.allow_sandbox_access/2
delete/2Retort.Resources.delete/3
full_associations/1Inlined as def full_associations(list) when is_list(list), do: list
get/2Retort.Resources.get/2
insert/2Retort.Resources.insert/3
list/1Retort.Resources.list/2
timeout/1Retort.Resources.timeout/2
update/2Retort.Resources.update/3
update/3Retort.Resources.update/4

Timeouts

Timeouts for a module that use Retort.Resources can be configured for all Retort.Client.Generic calls.

Module Timeout

The default timeout is 5_000 milliseconds as is the default throughout OTP. A default timeout for all actions in your module can be set.

Either as a config

config :retort, MyRPC.Authors
  timeout: 10_000 # milliseconds

or at runtime in the Application environment

previous_environment = Application.get_env(:retort, MyRPC.Authors, [])
environment_with_timeout = Keyword.put(previous_environment, :timeout, 10_000)
Application.put_env(:retort, MyRPC.Authors, environment_with_timeout)

Call Timeout

If only some of your Retort.Client.Generic calls are timing out (such as :index), you can set the timeout for just that call.

Either as a config

config :retort, MyRPC.Authors,
  timeout: [
    index: 10_000 # milliseconds
  ]

or at runtime in the Application environment

previous_environment = Application.get_env(:retort, MyRPC.Authors, [])

timeout_by_call = case Keyword.get(previous_environment, :timeout, []) do
  # Convert a single module timeout to one for each call
  module_timeout when is_integer(module_timeout) ->
    [create: module_timeout, destroy: module_timeout, index: module_timeout, show: module_timeout]
  timeout_by_call when is_list(timeout_by_call) ->
    timeout_by_call
end

updated_timeout_by_call = Keyword.put(timeout_by_call, :index, 10_000)
environment_with_timeout_by_call = Keyword.put(previous_environment, :timeout, updated_timeout_by_call)
Application.put_env(:retort, MyRPC.Authors, environment_with_timeout_by_call)

The Calcinator.Resources callbacks implemented by use Retort.Resources call the following Retort.Client.Generic calls.

Calcinator.Resources callbackRetort.Client.Generic call:timeout key
Calcinator.Resources.delete/2Retort.Client.Generic.destroy/3:destroy
Calcinator.Resources.get/2Retort.Client.Generic.show/4:show
Calcinator.Resources.insert/2Retort.Client.Generic.create/4:create
Calcinator.Resources.list/1Retort.Client.Generic.index/3:index
Calcinator.Resources.update/2Retort.Client.Generic.update/5:update

NOTE: Calcinator.update/2 calls Calcinator.Resources.get/2 to get the pre-existing data to generate the changeset for Calcinator.Resources.update/2, so either of those may timeout, so you may need to increase the :show timeout if the Calcinator.Resources.get/2 part of the Calcinator.update/2 is what is timing out and not the update itself.

allow_sandbox_access(module, map)
allow_sandbox_access(module, Calcinator.Resources.sandbox_access_token) :: :ok
changeset(module, params)

Constructs Ecto.Changeset.t using module changeset/2 (which is the implementatin of the Calcinator.Resources.changeset/2 callback) with module ecto_schema_module/0 __struct__/0 as the initial data.

Parameters

delete(module, changeset, query_options)
delete(module, Ecto.Changeset.t, Calcinator.Resources.query_options) ::
  :ok |
  {:error, :bad_gateway} |
  {:error, :not_found} |
  {:error, :sandbox_access_disallowed} |
  {:error, :timeout} |
  {:error, Ecto.Changeset.t}

Destroys changeset data using Retort.Client.Generic.destroy/4.

Parameters

  • module - The module that called use Retort.Resources. MUST implement Retort.Resources.client_start_link/0 and Retort.Resources.timeout/1.
  • data - struct whose id should be destroyed.
  • query_options - to supply :meta for client.

Returns

  • :ok - the struct in the changeset was deleted

  • {:error, :bad_gateway} - A 500 Internal Server error from the remote server. Check its log.

  • {:error, :not_found} - The id of the struct in the changeset was not found on the remote server. It is either (1) already deleted or (2) never existed.

  • {:error, :sandbox_access_disallowed} - The query_options :meta, "beam" does not have the correct information to access the testing sandbox. Ensure you don’t have stale requests in your RabbitMQ queues that have "beam" from previous test runs.

  • {:error, :timeout} - the Retort.Client.Generic.destroy/3 call timed out. The struct in the changeset may have still been deleted if the request completes on the remote server after the timeout.

    To increase the timeout, increase module.timeout(:destroy), which (if module does not override timeout/1 from use Retort.Resources can be changed in config

    config :retort, module,

    timeout: [
      destroy: destroy_timeout # milliseconds
    ]

    … or it can be changed at runtime

    previous_environment = Application.get_env(:retort, module, [])

    timeout_by_call = case Keyword.get(previous_environment, :timeout, []) do

    # Convert a single module timeout to one for each call
    module_timeout when is_integer(module_timeout) ->
      [create: module_timeout, destroy: module_timeout, index: module_timeout, show: module_timeout]
    timeout_by_call when is_list(timeout_by_call) ->
      timeout_by_call

    end

    updated_timeout_by_call = Keyword.put(timeout_by_call, :destroy, destroy_timeout) environment_with_timeout_by_call = Keyword.put(previous_environment, :timeout, updated_timeout_by_call) Application.put_env(:retort, module, environment_with_timeout_by_call)

  • {:error, Ecto.Changeset.t} - validation errors that prevented changeset from being deleted. For deletes, this is usually foreign key constraints, such as from Ecto.Changeset.no_assoc_constraint/2.

get(module, id, query_options)
get(module, Calcinator.Resources.id, Calcinator.Resources.query_options) ::
  {:ok, Ecto.Schema.t} |
  {:error, :bad_gateway} |
  {:error, :not_found} |
  {:error, :sandbox_access_disallowed} |
  {:error, :timeout}

Gets struct with id using Retort.Client.Generic.show/4

Parameters

  • module - The module that called use Retort.Resources. MUST implement Retort.Resources.client_start_link/0 and Retort.Resources.timeout/1.
  • id the id of the struct to get
  • query_options - associations and filters

Returns

  • :ok - the struct in the changeset was deleted
  • {:error, :bad_gateway} - A 500 Internal Server error from the remote server. Check its log.
  • {:error, :not_found} - The id was not found on the remote server.
  • {:error, :sandbox_access_disallowed} - The query_options :meta, "beam" does not have the correct information to access the testing sandbox. Ensure you don’t have stale requests in your RabbitMQ queues that have "beam" from previous test runs.
  • {:error, :timeout} - the Retort.Client.Generic.get/4 call timed out. The id may still be retrieved from the remote server’s backing store if it completes on the remote server after the timeout, but it will not be returned to the caller because the RabbitMQ reply queue will not exist.

    To increase the timeout, increase module.timeout(:get), which (if module does not override timeout/1 from use Retort.Resources can be changed in config

    config :retort, module,

    timeout: [
      get: get_timeout # milliseconds
    ]

    … or it can be changed at runtime

    previous_environment = Application.get_env(:retort, module, [])

    timeout_by_call = case Keyword.get(previous_environment, :timeout, []) do

    # Convert a single module timeout to one for each call
    module_timeout when is_integer(module_timeout) ->
      [create: module_timeout, destroy: module_timeout, index: module_timeout, show: module_timeout]
    timeout_by_call when is_list(timeout_by_call) ->
      timeout_by_call

    end

    updated_timeout_by_call = Keyword.put(timeout_by_call, :get, get_timeout) environment_with_timeout_by_call = Keyword.put(previous_environment, :timeout, updated_timeout_by_call) Application.put_env(:retort, module, environment_with_timeout_by_call)

insert(module, params, query_options)
insert(module, Ecto.Changeset.t | Calcinator.Resources.params, Calcinator.Resources.query_options) ::
  {:ok, Ecto.Schema.t} |
  {:error, :bad_gateway} |
  {:error, :sandbox_access_disallowed} |
  {:error, :timeout} |
  {:error, Ecto.Changeset.t}

Inserts params using Retort.Client.Generic.create/4

Parameters

Returns

  • {:ok, struct} - the params was inserted yielding struct

  • {:error, :bad_gateway} - A 500 Internal Server error from the remote server. Check its log.

  • {:error, :sandbox_access_disallowed} - The query_options :meta, "beam" does not have the correct information to access the testing sandbox. Ensure you don’t have stale requests in your RabbitMQ queues that have "beam" from previous test runs.

  • {:error, :timeout} - the Retort.Client.Generic.create/4 call timed out. The params may have still been inserted if the request completes on the remote server after the timeout.

    To increase the timeout, increase module.timeout(:create), which (if module does not override timeout/1 from use Retort.Resources can be changed in config

    config :retort, module,

    timeout: [
      create: create_timeout # milliseconds
    ]

    … or it can be changed at runtime

    previous_environment = Application.get_env(:retort, module, [])

    timeout_by_call = case Keyword.get(previous_environment, :timeout, []) do

    # Convert a single module timeout to one for each call
    module_timeout when is_integer(module_timeout) ->
      [create: module_timeout, destroy: module_timeout, index: module_timeout, show: module_timeout]
    timeout_by_call when is_list(timeout_by_call) ->
      timeout_by_call

    end

    updated_timeout_by_call = Keyword.put(timeout_by_call, :create, create_timeout) environment_with_timeout_by_call = Keyword.put(previous_environment, :timeout, updated_timeout_by_call) Application.put_env(:retort, module, environment_with_timeout_by_call)

  • {:error, Ecto.Changeset.t} - validation errors that prevented params from being inserted.

list(module, query_options)

Lists resources matching the query_options using Retort.Client.Generic.index/3.

Parameters

  • module - The module that called use Retort.Resources. MUST implement Retort.Resources.client_start_link/0 and Retort.Resources.timeout/1.
  • query_options - association to preload, filters, pagination, and sorting.

Returns

  • {:ok, [Ecto.Schema.t], Alembic.Pagination.t | nil} - the structs matching the query_options and (optionally) the
  • {:error, :bad_gateway} - A 500 Internal Server error from the remote server. Check its log.
  • {:error, :not_found} - The id was not found on the remote server.
  • {:error, :sandbox_access_disallowed} - The query_options :meta, "beam" does not have the correct information to access the testing sandbox. Ensure you don’t have stale requests in your RabbitMQ queues that have "beam" from previous test runs.
  • {:error, :timeout} - the Retort.Client.Generic.get/4 call timed out. The id may still be retrieved from the remote server’s backing store if it completes on the remote server after the timeout, but it will not be returned to the caller because the RabbitMQ reply queue will not exist.

    To increase the timeout, increase module.timeout(:get), which (if module does not override timeout/1 from use Retort.Resources can be changed in config

    config :retort, module,

    timeout: [
      get: get_timeout # milliseconds
    ]

    … or it can be changed at runtime

    previous_environment = Application.get_env(:retort, module, [])

    timeout_by_call = case Keyword.get(previous_environment, :timeout, []) do

    # Convert a single module timeout to one for each call
    module_timeout when is_integer(module_timeout) ->
      [create: module_timeout, destroy: module_timeout, index: module_timeout, show: module_timeout]
    timeout_by_call when is_list(timeout_by_call) ->
      timeout_by_call

    end

    updated_timeout_by_call = Keyword.put(timeout_by_call, :get, get_timeout) environment_with_timeout_by_call = Keyword.put(previous_environment, :timeout, updated_timeout_by_call) Application.put_env(:retort, module, environment_with_timeout_by_call)

sandbox_token_key(module)
sandbox_token_key(module) :: atom

module specific sandbox token key

timeout(module, function)
timeout(module, function :: atom) :: timeout

The timeout for a specific function of Retort.Client.Generic.

update(module, changeset, query_options)
update(module, Ecto.Changeset.t, Calcinator.Resources.query_options) ::
  {:ok, Ecto.Schema.t} |
  {:error, :bad_gateway} |
  {:error, :sandbox_access_disallowed} |
  {:error, :timeout} |
  {:error, Ecto.Changeset.t}

Applies updates in changeset by converting them back to params for Retort.Client.Generic.update/5.

Parameters

  • module - The module that called use Retort.Resources. MUST implement Retort.Resources.client_start_link/0 and Retort.Resources.timeout/1.
  • changeset - An update changeset
  • query_options - associations to preload in the returned updated struct.

Returns

  • {:ok, struct} - the changeset was updated yielding struct

  • {:error, :bad_gateway} - A 500 Internal Server error from the remote server. Check its log.

  • {:error, :sandbox_access_disallowed} - The query_options :meta, "beam" does not have the correct information to access the testing sandbox. Ensure you don’t have stale requests in your RabbitMQ queues that have "beam" from previous test runs.

  • {:error, :timeout} - the Retort.Client.Generic.update/5 call timed out. The struct in the changeset may have still been updated if the request completes on the remote server after the timeout.

    To increase the timeout, increase module.timeout(:update), which (if module does not override timeout/1 from use Retort.Resources can be changed in config

    config :retort, module,

    timeout: [
      update: update_timeout # milliseconds
    ]

    … or it can be changed at runtime

    previous_environment = Application.get_env(:retort, module, [])

    timeout_by_call = case Keyword.get(previous_environment, :timeout, []) do

    # Convert a single module timeout to one for each call
    module_timeout when is_integer(module_timeout) ->
      [create: module_timeout, destroy: module_timeout, index: module_timeout, show: module_timeout]
    timeout_by_call when is_list(timeout_by_call) ->
      timeout_by_call

    end

    updated_timeout_by_call = Keyword.put(timeout_by_call, :update, update_timeout) environment_with_timeout_by_call = Keyword.put(previous_environment, :timeout, updated_timeout_by_call) Application.put_env(:retort, module, environment_with_timeout_by_call)

  • {:error, Ecto.Changeset.t} - validation errors that prevented changeset from being updated.

update(module, data, params, query_options)
update(module, Ecto.Schema.t, Calcinator.Resources.params, Calcinator.Resources.query_options) ::
  {:ok, Ecto.Schema.t} |
  {:error, :bad_gateway} |
  {:error, :sandbox_access_disallowed} |
  {:error, :timeout} |
  {:error, Ecto.Changeset.t}

Updates data with params using Retort.Client.Generic.update/5.

Parameters

Returns

  • {:ok, struct} - the data was updated with params yielding struct

  • {:error, :bad_gateway} - A 500 Internal Server error from the remote server. Check its log.

  • {:error, :sandbox_access_disallowed} - The query_options :meta, "beam" does not have the correct information to access the testing sandbox. Ensure you don’t have stale requests in your RabbitMQ queues that have "beam" from previous test runs.

  • {:error, :timeout} - the Retort.Client.Generic.update/5 call timed out. The data may have still been updated with params if the request completes on the remote server after the timeout.

    To increase the timeout, increase module.timeout(:update), which (if module does not override timeout/1 from use Retort.Resources can be changed in config

    config :retort, module,

    timeout: [
      update: update_timeout # milliseconds
    ]

    … or it can be changed at runtime

    previous_environment = Application.get_env(:retort, module, [])

    timeout_by_call = case Keyword.get(previous_environment, :timeout, []) do

    # Convert a single module timeout to one for each call
    module_timeout when is_integer(module_timeout) ->
      [create: module_timeout, destroy: module_timeout, index: module_timeout, show: module_timeout]
    timeout_by_call when is_list(timeout_by_call) ->
      timeout_by_call

    end

    updated_timeout_by_call = Keyword.put(timeout_by_call, :update, update_timeout) environment_with_timeout_by_call = Keyword.put(previous_environment, :timeout, updated_timeout_by_call) Application.put_env(:retort, module, environment_with_timeout_by_call)

  • {:error, Ecto.Changeset.t} - validation errors that prevented params from updating data updated.

Callbacks

association_to_include(association)
association_to_include(association :: atom | Keyword.t) :: String.t

Converts the associations passed from Calcinator to the full set of includes passed to Retort.Client.Generic.

  • Translate between the local association name and the remote include name
  • Check that the association is allowed
client_start_link(client_start_link_options)

Call Retort.Client.Generic.start_link and return its result.

ecto_schema_module()
ecto_schema_module() :: module

Used to generate changesets and translate error messages.

full_associations(list)
full_associations(list) :: list

Add additional associations to the list passed down from Calcinator.

  • Add default includes
  • Add includes that are needed for authorization that the caller doesn’t know are necessary.
timeout(function)
timeout(function :: atom) :: timeout

Timeout for function call on Retort.Client.Generic.