Packages

This module is designed to facilitate the Device structure (Plug.Conn.assign) and easy connection to other projects.

Current section

Files

Jump to
ds lib parser.ex
Raw

lib/parser.ex

defmodule DS.Parser do
@moduledoc """
Parser module, which looks for cached value. If is not found, parses user agent by UAInspector (https://github.com/elixytics/ua_inspector)
"""
@type found :: {:ok, DS.t()}
@type not_found :: {:error, DS.ua()}
@type map_with_result :: %{ds: DS.t(), result: %UAInspector.Result{}}
@mobile_device_types ["smartphone", "tablet", "phablet", "feature phone"]
@doc """
Parses user agent
"""
@spec parse(DS.ua()) :: DS.t() | nil
def parse(""), do: nil
def parse(nil), do: nil
def parse(ua) do
ua
|> check_cache()
|> do_parse()
end
@spec check_cache(DS.ua()) :: found | not_found
def check_cache(ua) do
case lookup_cache(generate_key(ua)) do
[] -> {:error, ua}
[{_key, json}] -> {:ok, struct(DS, Jason.decode!(json, keys: :atoms!))}
end
end
@spec do_parse(found | not_found) :: DS.t()
defp do_parse({:ok, ds}), do: ds
defp do_parse({:error, ua}) do
ds =
UAInspector.parse(ua)
|> assemble_client()
|> assemble_device()
|> assemble_os
with key <- generate_key(ua),
json <- Jason.encode!(ds) do
write_cache(key, json)
end
ds
end
@spec assemble_client(%UAInspector.Result{} | %UAInspector.Result.Bot{}) ::
map_with_result | DS.t()
defp assemble_client(%{client: :unknown}) do
%DS{
device_type: "desktop"
}
end
# TODO: make PULL request for documentation fix, return Result OR Bot
defp assemble_client(%UAInspector.Result.Bot{name: name}) do
%DS{
is_bot?: true,
bot_name: name
}
end
defp assemble_client(%{client: client} = result) do
ds = %DS{
browser: client.name,
browser_version: client.version
}
Map.new(ds: ds, result: result)
end
@spec assemble_device(map_with_result | %DS{}) :: map_with_result | DS.t()
defp assemble_device(%DS{} = ds), do: ds
defp assemble_device(%{ds: ds, result: %{device: device} = result}) do
%{
ds: %{
ds
| device_type: to_string(device.type),
device_brand: device.brand,
device_model: to_string(device.model),
is_mobile?: device.type in @mobile_device_types
},
result: result
}
end
@spec assemble_os(map_with_result | %DS{}) :: map_with_result | DS.t()
defp assemble_os(%DS{} = ds), do: ds
defp assemble_os(%{ds: ds, result: %{os: os}}) do
%{ds | os: os.name, os_version: os.version}
end
@spec generate_key(DS.ua()) :: String.t()
defp generate_key(ua) do
Base.encode16(:crypto.hash(:md5, ua), case: :lower)
end
@spec lookup_cache(String.t()) :: [] | [tuple()]
defp lookup_cache(key) do
:ets.lookup(Application.get_env(:ds, :table_name), key)
end
@spec write_cache(String.t(), String.t()) :: boolean()
defp write_cache(key, value) do
true = :ets.insert(Application.get_env(:ds, :table_name), {key, value})
end
end