defmodule AMI.Packet do
defguardp is_valid_text(input) when is_bitstring(input) and byte_size(input) > 0
defguardp is_valid_list(input) when is_map(input)
@typedoc """
AMI Event received from Asterisk. It is a map with fields as header name and
header values. Header value is a list as AMI supports multiple fields with
same name. For example, following AMI Event:
```
Event: Status
Name: Channels
Var: foo=1
Var: bar=AAA
```
Will be represented as:
```
%AMI.Packet{
"Event" => ["Status"],
"Name" => ["Channels"],
"Var" => ["foo=1", "bar=AAA"]
}
```
"""
@type t :: map()
@doc """
Parse input Event text and convert to AMI.Packet
`input` AMI event received from Asterisk
"""
@spec parse(input :: String.t()) :: {:ok, AMI.Packet.t()} | :error
def parse(input) when is_valid_text(input) do
map =
String.split(input, "\r\n")
|> Enum.filter(&(String.length(&1) > 0))
|> Enum.map(&String.split(&1, ~r{\s*:\s*}, parts: 2))
|> fold
{:ok, map}
end
def parse(_input) do
{:error, :invalid_input}
end
@doc """
Check if received AMI packet is event. Returns true or false
"""
@spec is_event?(pack :: AMI.Packet.t()) :: boolean()
def is_event?(pack) when is_valid_list(pack) do
case field(pack, "Event") do
{:ok, _} -> true
_ -> false
end
end
def is_event?(_map), do: false
@doc """
Search field value(s) in AMI Packet.
`map` is AMI.Packet to search
`name` field name to search
"""
@spec field(pack :: t(), name :: String.t()) :: {:ok, list()} | {:error, :not_found}
def field(pack, name) do
case Enum.find(pack, fn {k, _} -> String.downcase(name) == String.downcase(k) end) do
{_, val} -> {:ok, val}
_ -> {:error, :not_found}
end
end
@doc """
Converts AMI.Packet to JSON format
"""
def to_json(pack) when is_map(pack) do
"{" <> to_json(Map.to_list(pack)) <> "}"
end
def to_json([{k, v} | t]) do
~s("#{k}":) <>
cond do
length(v) > 1 -> inspect(v)
true -> ~s("#{v}")
end <>
cond do
length(t) > 0 -> "," <> to_json(t)
true -> ""
end
end
def to_json([]), do: ""
def to_json(_map), do: {:error, :invalid_input}
defp fold(map) do
map
|> Enum.map(fn [k, v] -> [k, [v]] end)
|> List.foldr(Map.new(), fn [k, v], map ->
case Map.fetch(map, k) do
{:ok, val} -> Map.put(map, k, v ++ val)
_ -> Map.put(map, k, v)
end
end)
end
end