Packages

A basic, single node pub-sub implementation where publishers and subscribers use topics and topics filters compatible with MQTT.

Current section

Files

Jump to
simple_mqtt lib topics.ex
Raw

lib/topics.ex

defmodule SimpleMqtt.Topics do
@type published_topic() :: [String.t()]
@type subscribed_topic() :: [String.t()]
@doc """
Parses topic for published message.
## Examples
iex> SimpleMqtt.Topics.parse_published_topic("things/switch_1/on")
{:ok, ["things", "switch_1", "on"]}
iex> SimpleMqtt.Topics.parse_published_topic("/things/switch_1/on")
{:ok, ["", "things", "switch_1", "on"]}
iex> SimpleMqtt.Topics.parse_published_topic("")
:error
iex> SimpleMqtt.Topics.parse_published_topic("/+")
:error
"""
@spec parse_published_topic(String.t()) :: {:ok, published_topic()} | :error
def parse_published_topic(""), do: :error
def parse_published_topic(topic) when is_binary(topic) do
result = String.split(topic, "/")
if result |> Enum.all?(&valid_published_segment?/1) do
{:ok, result}
else
:error
end
end
defp valid_published_segment?(segment) do
!(String.contains?(segment, "+") || String.contains?(segment, "#"))
end
@doc """
Parses filter for subscribed topics.
## Examples
iex> SimpleMqtt.Topics.parse_subscribed_topic("things/switch_1/on")
{:ok, ["things", "switch_1", "on"]}
iex> SimpleMqtt.Topics.parse_subscribed_topic("/things/switch_1/on")
{:ok, ["", "things", "switch_1", "on"]}
iex> SimpleMqtt.Topics.parse_subscribed_topic("")
:error
iex> SimpleMqtt.Topics.parse_subscribed_topic("+")
{:ok, ["+"]}
iex> SimpleMqtt.Topics.parse_subscribed_topic("+/switch_1/on")
{:ok, ["+", "switch_1", "on"]}
iex> SimpleMqtt.Topics.parse_subscribed_topic("switch_1/+/on")
{:ok, ["switch_1", "+", "on"]}
iex> SimpleMqtt.Topics.parse_subscribed_topic("things/#")
{:ok, ["things", "#"]}
iex> SimpleMqtt.Topics.parse_subscribed_topic("#")
{:ok, ["#"]}
iex> SimpleMqtt.Topics.parse_subscribed_topic("#/switch_1/on")
:error
"""
@spec parse_subscribed_topic(String.t()) :: {:ok, subscribed_topic()} | :error
def parse_subscribed_topic(""), do: :error
def parse_subscribed_topic(topic) do
result = String.split(topic, "/")
{beginning, [last]} = Enum.split(result, Enum.count(result) - 1)
if Enum.all?(beginning, fn s -> valid_subscribed_segment?(s, false) end) && valid_subscribed_segment?(last, true) do
{:ok, result}
else
:error
end
end
defp valid_subscribed_segment?("+", _), do: true
defp valid_subscribed_segment?("#", true), do: true
defp valid_subscribed_segment?(segment, _) do
!(String.contains?(segment, "+") || String.contains?(segment, "#"))
end
@doc """
Checks whether the published topic matches the subscribed topic.
## Examples
iex> SimpleMqtt.Topics.matches?(["things", "switch_1", "on"], ["things", "switch_1", "on"])
true
iex> SimpleMqtt.Topics.matches?(["things", "switch_1", "on"], ["things", "switch_1", "off"])
false
iex> SimpleMqtt.Topics.matches?(["things", "switch_1", "on"], ["things", "+", "on"])
true
iex> SimpleMqtt.Topics.matches?(["things", "a", "on"], ["things", "a", "+"])
true
iex> SimpleMqtt.Topics.matches?(["things", "switch_1", "on"], ["things", "+", "off"])
false
iex> SimpleMqtt.Topics.matches?(["things", "switch_1", "on"], ["things", "switch_1", "#"])
true
iex> SimpleMqtt.Topics.matches?(["things", "switch_1", "on"], ["things", "#"])
true
iex> SimpleMqtt.Topics.matches?(["things", "switch_1", "on"], ["#"])
true
iex> SimpleMqtt.Topics.matches?(["things", "switch_1", "on"], ["devices", "#"])
false
"""
@spec matches?(published_topic(), subscribed_topic()) :: boolean()
def matches?([], []) do
true
end
def matches?([x | rest_p], [x | rest_s]) do
matches?(rest_p, rest_s)
end
def matches?([_ | rest_p], ["+" | rest_s]) do
matches?(rest_p, rest_s)
end
def matches?(_, ["#"]) do
true
end
def matches?(_, _) do
false
end
end