-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
update and refactor code, language versions, dependencies, add observ…
…er and dialyzer
- Loading branch information
Showing
7 changed files
with
120 additions
and
119 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,47 +1,65 @@ | ||
defmodule ExOwm.Api do | ||
@moduledoc """ | ||
This module contains OpenWeatherMap API related functions. | ||
This module contains functions for interacting with the OpenWeatherMap API. | ||
It prepares request strings, makes API calls, and parses the responses. | ||
""" | ||
alias ExOwm.RequestString | ||
alias HTTPoison.{Error, Response} | ||
|
||
@doc """ | ||
Prepares request string basing on given params, calls OWM API, parses and | ||
decodes the answers. | ||
Prepares a request string based on the given parameters, calls the OWM API, | ||
and parses the JSON response. | ||
## Parameters | ||
- `api_call_type` (atom): The type of API call (e.g., `:get_weather`, `:get_current_weather`). | ||
- `location` (map): The location parameters (e.g., city, coordinates, zip code). | ||
- `opts` (term): Optional parameters for the API call (e.g., type, mode, units, cnt, lang). | ||
## Returns | ||
- (map): The parsed JSON response. | ||
- `{:error, term, term}`: An error tuple containing the error type and the response. | ||
""" | ||
@spec send_and_parse_request(atom, map, term) :: map | {:error, term, term} | ||
def send_and_parse_request(api_call_type, location, opts) do | ||
RequestString.build(api_call_type, location, opts) | ||
api_call_type | ||
|> RequestString.build(location, opts) | ||
|> call_api() | ||
|> parse_json() | ||
|> parse_response() | ||
end | ||
|
||
defp call_api(string) do | ||
case HTTPoison.get(string) do | ||
{:ok, %HTTPoison.Response{status_code: 200, body: json_body}} -> | ||
@spec call_api(String.t()) :: {:ok, String.t()} | {:error, atom, term} | {:error, term} | ||
defp call_api(url) do | ||
case HTTPoison.get(url) do | ||
{:ok, %Response{status_code: 200, body: json_body}} -> | ||
{:ok, json_body} | ||
|
||
{:ok, %HTTPoison.Response{status_code: 404, body: json_body}} -> | ||
{:ok, %Response{status_code: 404, body: json_body}} -> | ||
{:error, :not_found, json_body} | ||
|
||
{:ok, %HTTPoison.Response{status_code: 400, body: json_body}} -> | ||
{:error, :not_found, json_body} | ||
{:ok, %Response{status_code: 400, body: json_body}} -> | ||
{:error, :bad_request, json_body} | ||
|
||
{:ok, %HTTPoison.Response{status_code: 401, body: json_body}} -> | ||
{:ok, %Response{status_code: 401, body: json_body}} -> | ||
{:error, :api_key_invalid, json_body} | ||
|
||
{:ok, response} -> | ||
{:error, :unknown_api_response, response} | ||
|
||
{:error, reason} -> | ||
{:error, %Error{} = reason} -> | ||
{:error, reason} | ||
end | ||
end | ||
|
||
defp parse_json({:ok, json}), do: Jason.decode(json) | ||
@spec parse_response({:ok, String.t()} | {:error, atom, String.t()} | {:error, term}) :: | ||
map | {:error, term, term} | ||
defp parse_response({:ok, json}), do: Jason.decode(json) | ||
|
||
defp parse_json({:error, :unknown_api_response, response}), | ||
defp parse_response({:error, :unknown_api_response, response}), | ||
do: {:error, :unknown_api_response, response} | ||
|
||
defp parse_json({:error, reason, json_body}), do: {:error, reason, Jason.decode!(json_body)} | ||
defp parse_json({:error, %HTTPoison.Error{} = reason}), do: {:error, reason} | ||
defp parse_response({:error, reason, json_body}), do: {:error, reason, Jason.decode(json_body)} | ||
|
||
defp parse_response({:error, %Error{} = reason}), do: {:error, reason} | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,129 +1,104 @@ | ||
defmodule ExOwm.RequestString do | ||
@moduledoc """ | ||
Request string creation. | ||
Module responsible for creating request strings for OpenWeatherMap API calls. | ||
It supports various API endpoints and allows for dynamic construction | ||
of query strings based on provided parameters. | ||
""" | ||
|
||
# 5 day forecast is available at any location or city. | ||
# It includes weather data every 3 hours. Forecast is available in JSON or XML format. | ||
@api_endpoints %{ | ||
get_weather: "api.openweathermap.org/data/2.5/onecall", | ||
get_current_weather: "api.openweathermap.org/data/2.5/weather", | ||
get_five_day_forecast: "api.openweathermap.org/data/2.5/forecast", | ||
get_hourly_forecast: "pro.openweathermap.org/data/2.5/forecast/hourly", | ||
get_sixteen_day_forecast: "api.openweathermap.org/data/2.5/forecast/daily", | ||
get_historical_weather: "api.openweathermap.org/data/2.5/onecall/timemachine" | ||
} | ||
|
||
@doc """ | ||
Builds request string basing on provided params. | ||
Builds request string based on provided params. | ||
## Parameters | ||
- `api_call_type` (atom): The type of API call (e.g., `:get_weather`, `:get_current_weather`). | ||
- `location` (map): The location parameters (e.g., city, coordinates, zip code). | ||
- `opts` (keyword list): Optional parameters (e.g., type, mode, units, cnt, lang). | ||
## Returns | ||
- (String.t()): The constructed request string. | ||
""" | ||
@spec build(atom, map, key: :atom) :: String.t() | ||
@spec build(atom, map, keyword) :: String.t() | ||
def build(api_call_type, location, opts) do | ||
{api_call_type, location, opts} | ||
api_call_type | ||
|> add_prefix_substring() | ||
|> add_location_substring() | ||
|> add_search_accuracy_substring() | ||
|> add_format_substring() | ||
|> add_units_substring() | ||
|> add_day_count() | ||
|> add_language_substring() | ||
|> add_location_substring(location) | ||
|> add_search_accuracy_substring(opts) | ||
|> add_format_substring(opts) | ||
|> add_units_substring(opts) | ||
|> add_day_count(opts) | ||
|> add_language_substring(opts) | ||
|> add_api_key_substring() | ||
end | ||
|
||
# One call weather call. | ||
defp add_prefix_substring({:get_weather, location, opts}), | ||
do: {"api.openweathermap.org/data/2.5/onecall", location, opts} | ||
|
||
# Current weather call. | ||
defp add_prefix_substring({:get_current_weather, location, opts}), | ||
do: {"api.openweathermap.org/data/2.5/weather", location, opts} | ||
|
||
# Five day forecast call. | ||
defp add_prefix_substring({:get_five_day_forecast, location, opts}), | ||
do: {"api.openweathermap.org/data/2.5/forecast", location, opts} | ||
|
||
# Four day hourly forecast call. https://openweathermap.org/api/hourly-forecast | ||
defp add_prefix_substring({:get_hourly_forecast, location, opts}), | ||
do: {"pro.openweathermap.org/data/2.5/forecast/hourly", location, opts} | ||
|
||
# Sixteen day forecast call. | ||
defp add_prefix_substring({:get_sixteen_day_forecast, location, opts}), | ||
do: {"api.openweathermap.org/data/2.5/forecast/daily", location, opts} | ||
|
||
# History call. | ||
defp add_prefix_substring({:get_historical_weather, location, opts}), | ||
do: {"api.openweathermap.org/data/2.5/onecall/timemachine", location, opts} | ||
defp add_prefix_substring(api_call_type) do | ||
@api_endpoints[api_call_type] | ||
end | ||
|
||
# Call by city name and ISO 3166 country code. | ||
defp add_location_substring({string, %{city: city, country_code: country_code}, opts}), | ||
do: {string <> "?q=#{city},#{country_code}", opts} | ||
defp add_location_substring(base_url, %{city: city, country_code: country_code}), | ||
do: "#{base_url}?q=#{city},#{country_code}" | ||
|
||
# Call by city name. | ||
defp add_location_substring({string, %{city: city}, opts}), | ||
do: {string <> "?q=#{city}", opts} | ||
defp add_location_substring(base_url, %{city: city}), | ||
do: "#{base_url}?q=#{city}" | ||
|
||
# Call by city id. | ||
defp add_location_substring({string, %{id: id}, opts}), | ||
do: {string <> "?id=#{id}", opts} | ||
defp add_location_substring(base_url, %{id: id}), | ||
do: "#{base_url}?id=#{id}" | ||
|
||
# Call by geo coordinates with datetime string | ||
defp add_location_substring({string, %{lat: lat, lon: lon, dt: dt}, opts}), | ||
do: {string <> "?lat=#{lat}&lon=#{lon}&dt=#{dt}", opts} | ||
defp add_location_substring(base_url, %{lat: lat, lon: lon, dt: dt}), | ||
do: "#{base_url}?lat=#{lat}&lon=#{lon}&dt=#{dt}" | ||
|
||
# Call by geo coordinates | ||
defp add_location_substring({string, %{lat: lat, lon: lon}, opts}), | ||
do: {string <> "?lat=#{lat}&lon=#{lon}", opts} | ||
defp add_location_substring(base_url, %{lat: lat, lon: lon}), | ||
do: "#{base_url}?lat=#{lat}&lon=#{lon}" | ||
|
||
# Call by zip and ISO 3166 country code. | ||
defp add_location_substring({string, %{zip: zip, country_code: country_code}, opts}), | ||
do: {string <> "?zip=#{zip},#{country_code}", opts} | ||
defp add_location_substring(base_url, %{zip: zip, country_code: country_code}), | ||
do: "#{base_url}?zip=#{zip},#{country_code}" | ||
|
||
defp add_search_accuracy_substring({string, opts}) do | ||
defp add_search_accuracy_substring(url, opts) do | ||
case Keyword.get(opts, :type) do | ||
:like -> {string <> "&type=like", opts} | ||
:accurate -> {string <> "&type=accurate", opts} | ||
_ -> {string, opts} | ||
:like -> "#{url}&type=like" | ||
:accurate -> "#{url}&type=accurate" | ||
_ -> url | ||
end | ||
end | ||
|
||
# Add format type to a query string. Lack of this part make api to return default | ||
# JSON format answer. | ||
defp add_format_substring({string, opts}) do | ||
defp add_format_substring(url, opts) do | ||
case Keyword.get(opts, :mode) do | ||
:xml -> {string <> "&mode=xml", opts} | ||
_ -> {string, opts} | ||
:xml -> "#{url}&mode=xml" | ||
_ -> url | ||
end | ||
end | ||
|
||
# Add temperature type to a query string. Lack of this part make api to return default | ||
# temperature in Kelvins. | ||
defp add_units_substring({string, opts}) do | ||
defp add_units_substring(url, opts) do | ||
case Keyword.get(opts, :units) do | ||
:metric -> {string <> "&units=metric", opts} | ||
:imperial -> {string <> "&units=imperial", opts} | ||
_ -> {string, opts} | ||
:metric -> "#{url}&units=metric" | ||
:imperial -> "#{url}&units=imperial" | ||
_ -> url | ||
end | ||
end | ||
|
||
# Add day count for 1 to 16 day forecast. | ||
defp add_day_count({string, opts}) do | ||
defp add_day_count(url, opts) do | ||
case Keyword.get(opts, :cnt) do | ||
nil -> {string, opts} | ||
cnt -> {string <> "&cnt=#{cnt}", opts} | ||
nil -> url | ||
cnt -> "#{url}&cnt=#{cnt}" | ||
end | ||
end | ||
|
||
# Add language code parameter. API call will return data with description field | ||
# in selected language. List of available languages can be found here: | ||
# http://openweathermap.org/forecast16#multi | ||
defp add_language_substring({string, opts}) do | ||
case Keyword.has_key?(opts, :lang) do | ||
true -> | ||
lang = | ||
opts | ||
|> Keyword.get(:lang) | ||
|> Atom.to_string() | ||
|
||
string <> "&lang=#{lang}" | ||
|
||
false -> | ||
string | ||
defp add_language_substring(url, opts) do | ||
case Keyword.get(opts, :lang) do | ||
nil -> url | ||
lang -> "#{url}&lang=#{Atom.to_string(lang)}" | ||
end | ||
end | ||
|
||
# Add API key. | ||
defp add_api_key_substring(string), | ||
do: string <> "&APPID=#{Application.get_env(:ex_owm, :api_key)}" | ||
defp add_api_key_substring(url), | ||
do: "#{url}&APPID=#{Application.get_env(:ex_owm, :api_key)}" | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
|V��'���f긤����X`� | ||
��U�,�A�0�O�S� |