Skip to content

Commit

Permalink
- Runtime support for base_url change (so it would work even without …
Browse files Browse the repository at this point in the history
…recompilation of dependency),

- An option to change JSON decoder library to reduce dependency burden,
- README updates,
- formatting,
- minor refactoring
  • Loading branch information
vheathen committed Dec 10, 2021
1 parent 48cfeae commit 660dc30
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 77 deletions.
23 changes: 18 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,21 @@ simple access to all available API endpoints that return a JSON result.

To install GeoNames-Elixir, follow these instructions:

- Add geonames-elixir to your list of dependencies in `mix.exs`:
- Add to your list of dependencies in `mix.exs` geonames-elixir and a JSON decoder library (Jason, Poison or another supported `decode!/1` method):

```elixir
def deps do
[{ :geonames, "~> 1.0.2" }]
[
{ :geonames, "~> 1.0.2" },
{ :poison, "> 3.0.0" }
]
end
```
- (optional) Add to your config a name of the chosen library; if none is set then Poison will be used:

```elixir
config :geonames, json_library: Jason
```

- Ensure geonames is started before your application:

Expand Down Expand Up @@ -44,20 +52,23 @@ documentation.
## Configuration

Before you can use GeoNames-Elixir, you must configure it with your
username, and preferred language. This can be done by simply adding
the following to your application configuration
username, base URL (for example, to use with premium plans or your own service)
and preferred language. This can be done by simply adding the following
to your application configuration

```elixir
config :geonames,
username: "demo",
language: "en"
language: "en",
base_url: "https://secure.geonames.net"
```

Alternatively, you can set these with the environment variables

```
GEONAMES_USERNAME=demo
GEONAMES_LANGUAGE=en
GEONAMES_BASE_URL=https://secure.geonames.net
```

Note that the language is not required, and will default to `en`
Expand All @@ -68,6 +79,8 @@ It will be supplied for all, however the geonames.org documentation
does not specify its usage in every endpoint, so there are no
guarantees that your preferred language will be effective.

If `:base_url` is not specified then `"api.geonames.org"` will be used by default.


## Available functions

Expand Down
2 changes: 1 addition & 1 deletion config/config.exs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config
import Config

config :geonames,
username: "testuser",
Expand Down
47 changes: 24 additions & 23 deletions lib/geonames.ex
Original file line number Diff line number Diff line change
Expand Up @@ -83,34 +83,35 @@ defmodule Geonames do
Makes a request to the GeoNames endpoint `/#{endpoint.endpoint}`
The arguments map may contain the following keys:
#{Enum.map(endpoint.available_url_parameters, fn(e) -> "- #{e}\n" end)}
#{Enum.map(endpoint.available_url_parameters, fn e -> "- #{e}\n" end)}
Each request parameter should be supplied in a map. For example,
Geonames.#{endpoint.function_name}(%{
#{Enum.join(Enum.map(endpoint.available_url_parameters, fn(e) -> "#{to_string(e)}: \"val\"" end), ",\n ")}
#{Enum.join(Enum.map(endpoint.available_url_parameters, fn e -> "#{to_string(e)}: \"val\"" end), ",\n ")}
})
"""
@spec unquote(endpoint.function_name)(map) :: { Atom.t, map }
@spec unquote(endpoint.function_name)(map() | keyword()) :: map()
def unquote(endpoint.function_name)(args \\ %{}) do
url_params = unquote(endpoint).url_arguments(args)
case Helpers.required_parameters_provided?(unquote(endpoint).required_url_parameters, url_params) do
true ->
url = Helpers.build_url_string(unquote(endpoint).endpoint, url_params)
case perform_geonames_request(url) do
{ :ok, json_response } ->
json_response
{ :error, error_message } ->
raise RuntimeError, message: error_message
end
false ->
raise ArgumentError, message: "Not all required parameters were supplied"
url_params = unquote(endpoint).url_arguments(Enum.into(args, %{}))

unless Helpers.required_parameters_provided?(
unquote(endpoint).required_url_parameters,
url_params
),
do: raise(ArgumentError, message: "Not all required parameters were supplied")

unquote(endpoint).endpoint
|> Helpers.build_url_string(url_params)
|> perform_geonames_request()
|> case do
{:ok, json_response} -> json_response
{:error, error_message} -> raise RuntimeError, message: error_message
end
end
end


@doc """
Performs a simple get request to the specified URL.
This is not specific to GeoNames and could be used
Expand All @@ -127,17 +128,17 @@ defmodule Geonames do
be returned with a string describing the error.
"""
@spec perform_geonames_request(String.t()) :: {:ok, map()} | {:error, String.t()}
def perform_geonames_request(url) do
case HTTPoison.get(url) do
{ :ok, %HTTPoison.Response{ status_code: 200, body: body }} ->
{ :ok, Poison.decode!(body) }
{:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
{:ok, Geonames.JsonDecoder.decode!(body)}

{ :ok, %HTTPoison.Response{ status_code: status_code, body: body }} ->
{ :error, "An unexpected #{status_code} response was received"}
{:ok, %HTTPoison.Response{status_code: status_code, body: _body}} ->
{:error, "An unexpected #{status_code} response was received"}

{ :error, %HTTPoison.Error{ reason: reason }} ->
{ :error, "Request failed with the following error: #{to_string(reason)}" }
{:error, %HTTPoison.Error{reason: reason}} ->
{:error, "Request failed with the following error: #{to_string(reason)}"}
end
end

end
43 changes: 21 additions & 22 deletions lib/geonames/helpers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,14 @@ defmodule Geonames.Helpers do
any of the functions defined here directly.
"""

@base_url Application.get_env(:geonames, :base_url, "api.geonames.org/")
@default_base_url "api.geonames.org"

@doc """
Determines whether or not all of the required
request parameters have been set
"""
def required_parameters_provided?(required, opts) do
invalid =
required
|> Enum.map(fn(k) -> opts[k] end)
|> Enum.member?(nil)

!invalid
end
def required_parameters_provided?(required, opts),
do: Enum.all?(required, &Map.get(opts, &1))

@doc """
For each request to the GeoNames API, the
Expand All @@ -29,30 +23,35 @@ defmodule Geonames.Helpers do
"""
def build_url_string(endpoint, parameters) do
encoded_params =
user_configuration
user_configuration()
|> Map.merge(parameters)
|> Enum.map(fn
{_,nil} -> nil
{k,v} when is_binary(v) -> "#{k}=#{v}"
{k,v} when is_float(v) -> "#{k}=#{v}"
{k,v} when is_boolean(v) -> "#{k}=#{v}"
{k,v} when is_integer(v) -> "#{k}=#{v}"
{k,arr} when is_list(arr) -> Enum.map(arr, &"#{k}=#{&1}")
{_, nil} -> nil
{k, arr} when is_list(arr) -> Enum.map(arr, &"#{k}=#{&1}")
{k, v} -> "#{k}=#{v}"
end)
|> List.flatten
|> Enum.filter(fn(k) -> !is_nil(k) end)
|> List.flatten()
|> Enum.reject(fn k -> is_nil(k) end)
|> Enum.join("&")
|> URI.encode
|> URI.encode()

@base_url <> endpoint <> "?" <> encoded_params
[base_url(), "/", endpoint, "?", encoded_params] |> Enum.join()
end


defp user_configuration do
%{
username: Application.get_env(:geonames, :username) || System.get_env("GEONAMES_USERNAME"),
language: Application.get_env(:geonames, :language, System.get_env("GEONAMES_LANGUAGE") || "en")
language:
Application.get_env(:geonames, :language) || System.get_env("GEONAMES_LANGUAGE") || "en"
}
end

defp base_url do
url =
Application.get_env(:geonames, :base_url) ||
System.get_env("GEONAMES_BASE_URL") ||
@default_base_url

String.trim_trailing(url, "/")
end
end
10 changes: 10 additions & 0 deletions lib/geonames/json_decoder.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
defmodule Geonames.JsonDecoder do
@moduledoc """
An interface module to the actual JSON decode library
"""

@spec decode!(binary()) :: map()
def decode!(json_string), do: json_library().decode!(json_string)

defp json_library, do: Application.get_env(:geonames, :json_library, Poison)
end
33 changes: 17 additions & 16 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,29 @@ defmodule Geonames.Mixfile do
use Mix.Project

def project do
[app: :geonames,
version: "1.0.3",
elixir: "~> 1.2",
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
deps: deps,

name: "GeoNames-Elixir",
description: description,
package: package]
[
app: :geonames,
version: "1.0.3",
elixir: "~> 1.4",
build_embedded: Mix.env() == :prod,
start_permanent: Mix.env() == :prod,
deps: deps(),
name: "GeoNames-Elixir",
description: description(),
package: package()
]
end

def application do
[applications: [:logger, :poison, :httpoison]]
[extra_applications: [:httpoison, :jason, :httpoison]]
end

defp deps do
[
{ :poison, "~> 3.0" },
{ :httpoison, "~> 0.11.0" },
{ :ex_doc, "~> 0.12", only: :dev }
{:jason, "> 1.0.0", optional: true},
{:poison, "> 3.0.0", optional: true},
{:httpoison, "~> 1.8"},
{:ex_doc, "~> 0.24", only: :dev, runtime: false}
]
end

Expand All @@ -36,9 +38,8 @@ defmodule Geonames.Mixfile do
[
maintainers: ["Adrian Hooper"],
licenses: ["MIT"],
links: %{ "GitHub" => "https://github.com/pareeohnos/geonames-elixir"},
links: %{"GitHub" => "https://github.com/pareeohnos/geonames-elixir"},
files: ~w(mix.exs README.md lib)
]
end

end
29 changes: 19 additions & 10 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
%{"certifi": {:hex, :certifi, "1.0.0", "1c787a85b1855ba354f0b8920392c19aa1d06b0ee1362f9141279620a5be2039", [:rebar3], []},
"earmark": {:hex, :earmark, "1.0.1", "2c2cd903bfdc3de3f189bd9a8d4569a075b88a8981ded9a0d95672f6e2b63141", [:mix], []},
"ex_doc": {:hex, :ex_doc, "0.13.0", "aa2f8fe4c6136a2f7cfc0a7e06805f82530e91df00e2bff4b4362002b43ada65", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, optional: false]}]},
"hackney": {:hex, :hackney, "1.7.1", "e238c52c5df3c3b16ce613d3a51c7220a784d734879b1e231c9babd433ac1cb4", [:rebar3], [{:certifi, "1.0.0", [hex: :certifi, optional: false]}, {:idna, "4.0.0", [hex: :idna, optional: false]}, {:metrics, "1.0.1", [hex: :metrics, optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, optional: false]}]},
"httpoison": {:hex, :httpoison, "0.11.1", "d06c571274c0e77b6cc50e548db3fd7779f611fbed6681fd60a331f66c143a0b", [:mix], [{:hackney, "~> 1.7.0", [hex: :hackney, optional: false]}]},
"idna": {:hex, :idna, "4.0.0", "10aaa9f79d0b12cf0def53038547855b91144f1bfcc0ec73494f38bb7b9c4961", [:rebar3], []},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []},
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], []},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], []}}
%{
"certifi": {:hex, :certifi, "2.8.0", "d4fb0a6bb20b7c9c3643e22507e42f356ac090a1dcea9ab99e27e0376d695eba", [:rebar3], [], "hexpm", "6ac7efc1c6f8600b08d625292d4bbf584e14847ce1b6b5c44d983d273e1097ea"},
"earmark_parser": {:hex, :earmark_parser, "1.4.18", "e1b2be73eb08a49fb032a0208bf647380682374a725dfb5b9e510def8397f6f2", [:mix], [], "hexpm", "114a0e85ec3cf9e04b811009e73c206394ffecfcc313e0b346de0d557774ee97"},
"ex_doc": {:hex, :ex_doc, "0.26.0", "1922164bac0b18b02f84d6f69cab1b93bc3e870e2ad18d5dacb50a9e06b542a3", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2775d66e494a9a48355db7867478ffd997864c61c65a47d31c4949459281c78d"},
"hackney": {:hex, :hackney, "1.18.0", "c4443d960bb9fba6d01161d01cd81173089686717d9490e5d3606644c48d121f", [:rebar3], [{:certifi, "~>2.8.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "9afcda620704d720db8c6a3123e9848d09c87586dc1c10479c42627b905b5c5e"},
"httpoison": {:hex, :httpoison, "1.8.0", "6b85dea15820b7804ef607ff78406ab449dd78bed923a49c7160e1886e987a3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "28089eaa98cf90c66265b6b5ad87c59a3729bea2e74e9d08f9b51eb9729b3c3a"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"},
"makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"},
"makeup_elixir": {:hex, :makeup_elixir, "0.15.2", "dc72dfe17eb240552857465cc00cce390960d9a0c055c4ccd38b70629227e97c", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "fd23ae48d09b32eff49d4ced2b43c9f086d402ee4fd4fcb2d7fad97fa8823e75"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"nimble_parsec": {:hex, :nimble_parsec, "1.2.0", "b44d75e2a6542dcb6acf5d71c32c74ca88960421b6874777f79153bbbbd7dccc", [:mix], [], "hexpm", "52b2871a7515a5ac49b00f214e4165a40724cf99798d8e4a65e4fd64ebd002c1"},
"parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
"poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
}

0 comments on commit 660dc30

Please # to comment.