defmodule NOAA.Observations.State do
@moduledoc """
Fetches the stations of a US state/territory.
"""
alias NOAA.Observations.{Log, Message, Station, TemplatesAgent}
@typedoc "US state/territory code"
@type code :: <<_::16>>
@typedoc "State error"
@type error :: map
@typedoc "State error code"
@type error_code :: pos_integer | atom
@doc """
Fetches the stations for a `state_code`.
Returns a tuple of either `{:ok, [station]}` or `{:error, error_code, text}`.
## Parameters
- `state_code` - US state/territory code
## Examples
iex> alias NOAA.Observations.{State, TemplatesAgent}
iex> :ok = TemplatesAgent.reset()
iex> {:ok, stations} = State.stations("VT")
iex> %{"KFSO" => name} = Map.new(stations)
iex> name
"Franklin County State Airport"
iex> alias NOAA.Observations.{State, TemplatesAgent}
iex> template =
...> "http://forecast.weather.gov/xml/current_obs" <>
...> "/seek.php?state=<%=state_code%>&Find=Find"
iex> :ok = TemplatesAgent.update_state_template(template)
iex> State.stations("VT")
{:error, 301, "Moved Permanently"}
iex> alias NOAA.Observations.{State, TemplatesAgent}
iex> template =
...> "htp://forecast.weather.gov/xml/current_obs" <>
...> "/seek.php?state=<%=state_code%>&Find=Find"
iex> :ok = TemplatesAgent.update_state_template(template)
iex> State.stations("VT")
{:error, :nxdomain, "Non-Existent Domain"}
"""
@spec stations(code) ::
{:ok, [Station.t()]} | {:error, error_code, String.t()}
def stations(state_code) do
state_url = TemplatesAgent.state_url(state_code: state_code)
args = {state_code, state_url, __ENV__}
case HTTPoison.get(state_url) do
{:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
:ok = Log.info(:stations_fetched, args)
{:ok, _stations(body)}
{:ok, %HTTPoison.Response{status_code: status_code}} ->
error_text = Message.status(status_code)
args = {args, status_code, error_text}
:ok = Log.error(:stations_not_fetched, args)
{:error, status_code, error_text}
{:error, %HTTPoison.Error{reason: reason}} ->
error_text = Message.error(reason)
args = {args, reason, error_text}
:ok = Log.error(:stations_not_fetched, args)
{:error, reason, error_text}
end
end
## Private functions
@spec _stations(String.t()) :: [Station.t()]
defp _stations(body) do
# <a href="/xml/current_obs/display.php?stid=KTIX">Titusville</a>
# capture station ID and name
~r[<a href=".+\?stid=(\w+)">(.*?)</a>]
# i.e. only captured subpatterns
|> Regex.scan(body, capture: :all_but_first)
# each [id, name] -> {id, name}
|> Enum.map(&List.to_tuple/1)
end
end