Packages

Wrapper around :emqtt to provide support for failing over between brokers

Current section

Files

Jump to
emqtt_failover lib emqtt_failover config.ex
Raw

lib/emqtt_failover/config.ex

defmodule EmqttFailover.Config do
@moduledoc """
Configuration for connecting to an MQTT broker.
"""
@derive {Inspect, except: [:password]}
@enforce_keys [:host]
defstruct @enforce_keys ++
[
:username,
:password,
port: 1883,
ssl?: false,
ssl_opts: :default
]
@type t() :: %__MODULE__{
host: binary(),
port: pos_integer(),
username: binary() | nil,
password: binary() | nil,
ssl?: boolean,
ssl_opts: [:ssl.tls_client_option()] | :default
}
@doc """
Renders the %#{__MODULE__} as a safe URL (no password)
"""
@spec to_safe_string(t()) :: String.t()
def to_safe_string(%__MODULE__{} = config) do
userinfo =
case {config.username, config.password} do
{nil, nil} -> nil
{username, nil} -> username
{username, _password} -> username <> ":******"
end
URI.to_string(%URI{
scheme: if(config.ssl?, do: "mqtts", else: "mqtt"),
host: config.host,
port: config.port,
userinfo: userinfo
})
end
@doc """
Converts a URL into a %#{__MODULE__}{} struct.
"""
@spec from_url(String.t()) :: t()
@spec from_url(String.t(), Keyword.t()) :: t()
def from_url(url, overrides \\ []) when is_binary(url) and is_list(overrides) do
url
|> URI.parse()
|> from_uri(overrides)
end
@doc """
Converts a %URI{} struct into a %#{__MODULE__} struct.
"""
@spec from_uri(URI.t()) :: t()
@spec from_uri(URI.t(), Keyword.t()) :: t()
def from_uri(%URI{} = uri, overrides \\ []) when is_list(overrides) do
ssl? = ssl_from_uri(uri)
port =
if is_integer(uri.port) do
uri.port
else
default_port(ssl?)
end
{username, password} =
if is_binary(uri.userinfo) do
case String.split(uri.userinfo, ":", parts: 2) do
[username] -> {username, nil}
[username, password] -> {username, password}
end
else
{nil, nil}
end
struct!(
%__MODULE__{
host: uri.host,
port: port,
username: username,
password: password,
ssl?: ssl?
},
overrides
)
end
@doc """
Returns a `Keyword.t()` of options to pass to `:emqtt.start_link/1`
"""
@spec to_emqtt(t()) :: Keyword.t()
def to_emqtt(config) do
opts = [
host: config.host,
port: config.port,
ssl: config.ssl?,
ssl_opts: ssl_opts(config)
]
opts =
if config.username do
[username: config.username] ++ opts
else
opts
end
opts =
if config.password do
[password: config.password] ++ opts
else
opts
end
opts
end
@doc """
Returns the ssl_opts to use for the given %#{__MODULE__}
"""
def ssl_opts(config)
def ssl_opts(%__MODULE__{ssl?: false}) do
[]
end
def ssl_opts(%__MODULE__{ssl_opts: :default} = config) do
default_ssl_opts(config.host)
end
def ssl_opts(%__MODULE__{ssl_opts: ssl_opts}) do
ssl_opts
end
@doc """
Default `ssl_opts` configuration.
Verifies the remote peer using the built-in `:public_key.cacerts.get()` certificates.
"""
def default_ssl_opts(hostname) do
hostname_charlist = String.to_charlist(hostname)
[
verify: :verify_peer,
cacerts: :public_key.cacerts_get(),
server_name_indication: hostname_charlist
]
end
defp ssl_from_uri(%{scheme: "mqtt"}), do: false
defp ssl_from_uri(%{scheme: "mqtts"}), do: true
defp ssl_from_uri(%{scheme: "mqtt+ssl"}), do: true
defp ssl_from_uri(%{scheme: unknown}), do: raise(ArgumentError, "unknown scheme #{unknown}")
defp default_port(true), do: 8883
defp default_port(false), do: 1883
end