HipcallSMS.Adapter behaviour (HipcallSMS v0.2.0)

View Source

Specification of the SMS delivery adapter.

This module defines the behavior that all SMS adapters must implement and provides utility functions for configuration validation and dependency checking.

Creating an Adapter

To create a new SMS adapter, you need to implement the HipcallSMS.Adapter behavior and use the __using__/1 macro to get default implementations of common functions.

Example

defmodule MyApp.Adapters.CustomProvider do
  use HipcallSMS.Adapter, required_config: [:api_key, :secret]

  @impl HipcallSMS.Adapter
  def deliver(%HipcallSMS.SMS{} = sms, config) do
    # Implementation for sending SMS through your provider
    {:ok, %{id: "msg_123", status: "sent"}}
  end
end

Configuration

Adapters can specify required configuration keys that must be present when delivering an SMS. The validate_config/1 callback will automatically check for these keys.

Dependencies

Adapters can also specify required dependencies that must be available at runtime. This is useful for HTTP clients or other external libraries.

Summary

Callbacks

Delivers a SMS with the given config.

Gets a configuration value with a default fallback.

Validates the adapter configuration.

Validates that required dependencies are available.

Functions

Macro for implementing SMS adapters.

Gets a configuration value from the application environment.

Validates that all required configuration keys are present.

Validates that all required dependencies are available.

Types

config()

@type config() :: Keyword.t()

delivery_result()

@type delivery_result() :: {:ok, map()} | {:error, map()}

sms()

@type sms() :: HipcallSMS.SMS.t()

t()

@type t() :: module()

Callbacks

deliver(sms, config)

@callback deliver(sms(), config()) :: delivery_result()

Delivers a SMS with the given config.

This callback must be implemented by all adapters to handle the actual SMS delivery through the provider's API.

Parameters

  • sms - The SMS struct containing message details
  • config - Configuration keyword list with provider-specific settings

Returns

  • {:ok, response} - Success with provider response map
  • {:error, reason} - Failure with error details

Configuration Override

Client library configuration can be overwritten at runtime by passing a configuration map as the last argument. This is useful for multi-tenant applications or when you need to use different credentials per request.

Examples

# Basic delivery with application config
{:ok, response} = MyAdapter.deliver(sms, [])

# Delivery with runtime config override
config_override = [
  adapter: HipcallSMS.Adapters.Twilio,
  account_sid: "ACxxxxx",
  auth_token: "runtime_token"
]
{:ok, response} = MyAdapter.deliver(sms, config_override)

get_config_value(atom, any)

@callback get_config_value(atom(), any()) :: any()

Gets a configuration value with a default fallback.

This callback retrieves configuration values from the application environment, with support for system environment variables and default values.

Parameters

  • key - The configuration key to retrieve
  • default - Default value if the key is not found

Returns

The configuration value or the default if not found.

validate_config(config)

@callback validate_config(config()) :: :ok | no_return()

Validates the adapter configuration.

This callback validates that all required configuration keys are present and have valid values.

Parameters

  • config - Configuration keyword list to validate

Returns

  • :ok - Configuration is valid
  • Raises ArgumentError - If required configuration is missing

validate_dependency()

@callback validate_dependency() :: :ok | {:error, [module() | {atom(), module()}]}

Validates that required dependencies are available.

This callback checks that all required dependencies are loaded and available at runtime.

Returns

  • :ok - All dependencies are available
  • {:error, missing_deps} - List of missing dependencies

Functions

__using__(opts)

(macro)

Macro for implementing SMS adapters.

This macro provides default implementations for common adapter functions and sets up the required configuration and dependencies.

Options

  • :required_config - List of atoms representing required configuration keys
  • :required_deps - List of modules or {library, module} tuples for required dependencies

Examples

# Basic adapter with API key requirement
use HipcallSMS.Adapter, required_config: [:api_key]

# Adapter with multiple config requirements
use HipcallSMS.Adapter, required_config: [:account_sid, :auth_token]

# Adapter with dependencies
use HipcallSMS.Adapter,
  required_config: [:api_key],
  required_deps: [Finch, Jason]

get_config_value(key, default)

@spec get_config_value(atom(), any()) :: any()

Gets a configuration value from the application environment.

This function retrieves configuration values with support for system environment variables and default fallbacks. It supports several configuration formats:

  • Direct values: "api_key_value"
  • System environment variables: {:system, "ENV_VAR_NAME"}
  • System environment integers: {:system, :integer, "ENV_VAR_NAME"}

Parameters

  • key - The configuration key to retrieve
  • default - Default value if the key is not found or is nil

Returns

The configuration value or the default if not found.

Examples

# Direct configuration value
# config :hipcall_sms, api_key: "secret123"
iex> HipcallSMS.Adapter.get_config_value(:api_key, nil)
"secret123"

# System environment variable
# config :hipcall_sms, api_key: {:system, "SMS_API_KEY"}
# Environment: SMS_API_KEY=secret123
iex> HipcallSMS.Adapter.get_config_value(:api_key, nil)
"secret123"

# System environment integer
# config :hipcall_sms, timeout: {:system, :integer, "SMS_TIMEOUT"}
# Environment: SMS_TIMEOUT=5000
iex> HipcallSMS.Adapter.get_config_value(:timeout, 3000)
5000

# Default value when not configured
iex> HipcallSMS.Adapter.get_config_value(:missing_key, "default_value")
"default_value"

# Nil value returns default
# config :hipcall_sms, some_key: nil
iex> HipcallSMS.Adapter.get_config_value(:some_key, "default")
"default"

validate_config(required_config, config)

@spec validate_config([atom()], Keyword.t()) :: :ok | no_return()

Validates that all required configuration keys are present.

This function checks that all keys in the required_config list are present in the provided configuration and have non-nil, non-empty values.

Parameters

  • required_config - List of required configuration keys (atoms)
  • config - Configuration keyword list to validate

Returns

  • :ok - All required configuration is present
  • Raises ArgumentError - If any required configuration is missing

Examples

# Valid configuration
iex> HipcallSMS.Adapter.validate_config([:api_key], [api_key: "secret123"])
:ok

# Missing required key
iex> HipcallSMS.Adapter.validate_config([:api_key, :secret], [api_key: "secret123"])
** (ArgumentError) Missing required configuration: [:secret], got: [api_key: "secret123"]

# Empty value treated as missing
iex> HipcallSMS.Adapter.validate_config([:api_key], [api_key: ""])
** (ArgumentError) Missing required configuration: [:api_key], got: [api_key: ""]

validate_dependency(required_deps)

@spec validate_dependency([module() | {atom(), module()}]) ::
  :ok | {:error, [module() | {atom(), module()}]}

Validates that all required dependencies are available.

This function checks that all modules in the required_deps list are loaded and available at runtime. Dependencies can be specified as module names or as {library_name, module} tuples.

Parameters

  • required_deps - List of required dependencies

Returns

  • :ok - All dependencies are available
  • {:error, missing_deps} - List of missing dependencies

Examples

# All dependencies available
iex> HipcallSMS.Adapter.validate_dependency([Jason, Finch])
:ok

# Missing dependency
iex> HipcallSMS.Adapter.validate_dependency([NonExistentModule])
{:error, [NonExistentModule]}

# Mixed dependency specification
iex> HipcallSMS.Adapter.validate_dependency([Jason, {:finch, Finch}])
:ok