Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Smoove GBFS controller #1867

Merged
merged 5 commits into from
Oct 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 105 additions & 89 deletions apps/gbfs/lib/gbfs/controllers/smoove_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,127 +4,143 @@ defmodule GBFS.SmooveController do
require Logger

plug(:put_view, GBFS.FeedView)
@gbfs_version "1.1"

@spec index(Plug.Conn.t(), map()) :: Plug.Conn.t()
def index(conn, _params) do
contract_id = conn.assigns.smoove_params.contract_id

conn
|> assign(
:data,
%{
"fr" => %{
"feeds" =>
Enum.map(
[:system_information, :station_information, :station_status],
fn a ->
%{
"name" => Atom.to_string(a),
"url" => apply(Routes, String.to_atom("#{contract_id}_url"), [conn, a])
}
end
)
}
}
)
|> render("gbfs.json")
{:ok,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Il y a beaucoup de lignes de code modifiées mais c'est en réalité beaucoup de refactor en

{:ok, } |> render_response(conn)

%{
"fr" => %{
"feeds" =>
Enum.map(
[:system_information, :station_information, :station_status],
fn a ->
%{
"name" => Atom.to_string(a),
"url" => apply(Routes, String.to_atom("#{contract_id}_url"), [conn, a])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pour info on peut avoir à faire attention avec les conversions vers les atoms, si la donnée d'input est arbitraire / extérieure (https://til.hashrocket.com/posts/gkwwfy9xvw-converting-strings-to-atoms-safely).

Là dans ce cas ma compréhension est que la liste des contract_id est fixe et contrôlée par nous, donc pas de problème.

}
end
)
}
}}
|> render_response(conn)
end

@spec system_information(Plug.Conn.t(), map()) :: Plug.Conn.t()
def system_information(conn, _params) do
smoove_params = conn.assigns.smoove_params

conn
|> assign(
:data,
%{
"system_id" => smoove_params.contract_id,
"language" => "fr",
"name" => smoove_params.nom,
"timezone" => "Europe/Paris"
}
)
|> render("gbfs.json")
{:ok,
%{
"system_id" => smoove_params.contract_id,
"language" => "fr",
"name" => smoove_params.nom,
"timezone" => "Europe/Paris"
}}
|> render_response(conn)
end

@spec station_information(Plug.Conn.t(), map()) :: Plug.Conn.t()
def station_information(conn, _params) do
url = conn.assigns.smoove_params.url

conn
|> assign(:data, get_station_information(url))
|> render("gbfs.json")
conn.assigns.smoove_params.url |> get_station_information() |> render_response(conn)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tiens curieux que le formateur Elixir laisse ça sous cette forme (et credo) derrière, je me serais attendu à ce qu'il impose un passage à la ligne.

end

@spec station_status(Plug.Conn.t(), map()) :: Plug.Conn.t()
def station_status(conn, _params) do
url = conn.assigns.smoove_params.url

conn
|> assign(:data, get_station_status(url))
|> render("gbfs.json")
conn.assigns.smoove_params.url |> get_station_status() |> render_response(conn)
end

@spec get_station_status(binary()) :: map()
@spec get_station_status(binary()) :: {:ok, map()} | {:error, binary()}
defp get_station_status(url) do
%{
"stations" =>
url
|> get_stations()
|> Enum.map(&Map.take(&1, [:station_id, :capacity, :num_bikes_available, :num_docks_available, :credit_card]))
|> Enum.map(&Map.put(&1, :is_installed, 1))
|> Enum.map(&Map.put(&1, :is_returning, 1))
|> Enum.map(&Map.put(&1, :last_reported, DateTime.utc_now() |> DateTime.to_unix()))
|> Enum.map(
&Map.put(
&1,
:is_renting,
if(Map.has_key?(&1, :credit_card), do: 1, else: 0)
)
)
|> Enum.map(&Map.delete(&1, :credit_card))
}
case get_stations(url) do
{:ok, data} ->
{:ok,
%{
"stations" =>
data
|> Enum.map(
&Map.take(&1, [:station_id, :capacity, :num_bikes_available, :num_docks_available, :credit_card])
)
|> Enum.map(&Map.put(&1, :is_installed, 1))
|> Enum.map(&Map.put(&1, :is_returning, 1))
|> Enum.map(&Map.put(&1, :last_reported, DateTime.utc_now() |> DateTime.to_unix()))
|> Enum.map(
&Map.put(
&1,
:is_renting,
if(Map.has_key?(&1, :credit_card), do: 1, else: 0)
)
)
|> Enum.map(&Map.delete(&1, :credit_card))
}}

{:error, e} ->
{:error, e}
end
end

@spec get_station_information(binary()) :: map()
@spec get_station_information(binary()) :: {:ok, map()} | {:error, binary()}
defp get_station_information(url) do
%{
"stations" =>
url
|> get_stations()
|> Enum.map(&Map.take(&1, [:name, :station_id, :lat, :lon, :capacity, :credit_card]))
|> Enum.map(&set_rental_method/1)
|> Enum.map(&Map.delete(&1, :credit_card))
}
case get_stations(url) do
{:ok, data} ->
{:ok,
%{
"stations" =>
data
|> Enum.map(&Map.take(&1, [:name, :station_id, :lat, :lon, :capacity, :credit_card]))
|> Enum.map(&set_rental_method/1)
|> Enum.map(&Map.delete(&1, :credit_card))
}}

{:error, e} ->
{:error, e}
end
end

@spec set_rental_method(map()) :: map()
defp set_rental_method(%{credit_card: 1} = station), do: Map.put(station, :rental_method, "CREDIT_CARD")
defp set_rental_method(station), do: station

@spec get_stations(binary()) :: map()
@spec get_stations(binary()) :: {:ok, map()} | {:error, binary()}
defp get_stations(url) do
with {:ok, %{status_code: 200, body: body}} <- HTTPoison.get(url),
body when not is_nil(body) <- :iconv.convert("iso8859-1", "latin1", body) do
body
|> xpath(~x"//si"l,
name: ~x"@na"S,
station_id: ~x"@id"s,
lat: ~x"@la"f,
lon: ~x"@lg"f,
capacity: ~x"@to"i,
credit_card: ~x"@cb"I,
num_bikes_available: ~x"@av"i,
num_docks_available: ~x"@fr"i
)
else
nil ->
Logger.error("Unable to decode body")
nil

error ->
Logger.error(error)
nil
http_client = Transport.Shared.Wrapper.HTTPoison.impl()

case http_client.get(url) do
{:ok, %{status_code: 200, body: body}} ->
{:ok,
body
|> xpath(~x"//si"l,
name: ~x"@na"S,
station_id: ~x"@id"s,
lat: ~x"@la"f,
lon: ~x"@lg"f,
capacity: ~x"@to"i,
credit_card: ~x"@cb"I,
num_bikes_available: ~x"@av"i,
num_docks_available: ~x"@fr"i
)}

e ->
Logger.error("impossible to query smoove: #{inspect(e)}")
{:error, "smoove service unavailable"}
end
end

defp render_response({:ok, data}, conn),
do:
conn
|> assign(:data, data)
|> assign(:version, @gbfs_version)
|> render("gbfs.json")

defp render_response({:error, msg}, conn),
do:
conn
|> assign(:error, msg)
# for the moment we always return a BAD_GATEWAY in case of error
|> put_status(502)
|> put_view(GBFS.ErrorView)
|> render("error.json")
end
3 changes: 1 addition & 2 deletions apps/gbfs/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ defmodule GBFS.MixProject do
def application do
[
mod: {GBFS.Application, []},
extra_applications: [:logger, :runtime_tools, :iconv]
extra_applications: [:logger, :runtime_tools]
]
end

Expand All @@ -39,7 +39,6 @@ defmodule GBFS.MixProject do
{:cachex, "~> 3.4"},
{:httpoison, "~> 1.8.0"},
{:phoenix, "~> 1.5.7"},
{:iconv, "~> 1.0.10"},
{:sweet_xml, ">= 0.0.0"},
{:jason, ">= 0.0.0"},
{:cors_plug, "~> 2.0"},
Expand Down
61 changes: 61 additions & 0 deletions apps/gbfs/test/gbfs/controllers/smoove_controller_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
defmodule GBFS.SmooveControllerTest do
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idéalement il faudrait essayer d'adapter les tests pour éviter d'ajouter d'usage de use_cassette (cf #1853) et idem pour with_mock (car mock modifie la définition globale des modules, il n'y a pas de modification par processus jjh42/mock#129), au profit de Mox (https://github.com/dashbitco/mox).

Si tu en as l'énergie!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thbar Tu aurais un exemple de PR où on a abandonné use_cassette et utilisé autre chose ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je n'ai pas trouvé facilement d'exemple précisément pour ça, mais on a quelque chose d'approchant dans le travail réalisé par @fchabouis dans #1863.

Je vais essayer de décrire rapidement la guideline générale pour enlever autant with_mock que use_cassette (il manquera des points à traiter oralement à un moment, mais ça suffira pour avancer) :

  1. Identifier les points "frontière" (boundaries) dans le code, ceux qui font appel à la dépendance externe par un moyen ou un autre. Dans le cas de ce contrôleur, ça sera l'appel à HTTPoison.get, qui est ici:

defp get_stations(url) do
with {:ok, %{status_code: 200, body: body}} <- HTTPoison.get(url),
body when not is_nil(body) <- :iconv.convert("iso8859-1", "latin1", body) do

  1. Dégager une "behaviour" ou un wrapper (mais là pour httpoison le travail a déjà été fait ici et il se trouve que HTTPoison lui-même est une "behaviour", ce qui fait que Mox peut le mocker directement
  2. Faire appel à ce wrapper dans le code du contrôleur (comme fait dans Utilisation d'un Mock pour tester url_extractor.ex #1863) plutôt que de faire l'appel direct à HTTPoison
  3. Modifier les tests pour créer des assertions en utilisant Mox. La configuration de "test" (ici) substitue l'implémentation de httpoison_impl avec un Mock, et on peut asserter dessus ensuite (https://github.com/etalab/transport-site/pull/1863/files#diff-cd2bd95618a14fbcafc59bb740eece7ee030f2c29af4bd5ea0add7c3062bc171R8)

Attention, il est recommandé de faire un "verify" des mocks (ce qui n'est pas fait dans #1863 je le remarque) avec setup :verify_on_exit! ou avec verify!().

Voilà pour l'idée générale ; ça paraît un peu compliqué initialement, toutefois au final ça a le mérite de définir des boundaries claires et de rendre le code très testable et substituable.

À ta dispo si besoin de clarifications!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merci beaucoup, fait dans le commit suivant ! Je pourrai faire de même pour JC Decaux dans une autre PR.

use GBFS.ConnCase, async: true
alias GBFS.Router.Helpers, as: Routes
import Mox
import GBFS.Checker

setup :verify_on_exit!

describe "Smoove GBFS conversion" do
test "on gbfs.json", %{conn: conn} do
conn = conn |> get(Routes.montpellier_path(conn, :index))
body = json_response(conn, 200)
check_entrypoint(body)
end

test "on system_information.json", %{conn: conn} do
conn = conn |> get(Routes.montpellier_path(conn, :system_information))
body = json_response(conn, 200)
check_system_information(body)
end

test "on station_information.json", %{conn: conn} do
setup_stations_response()

conn = conn |> get(Routes.montpellier_path(conn, :station_information))
body = json_response(conn, 200)
check_station_information(body)
end

test "on station_status.json", %{conn: conn} do
setup_stations_response()

conn = conn |> get(Routes.montpellier_path(conn, :station_status))
body = json_response(conn, 200)
check_station_status(body)
end

test "on invalid response", %{conn: conn} do
Transport.HTTPoison.Mock |> expect(:get, fn _url -> {:ok, %HTTPoison.Response{status_code: 500}} end)

conn = conn |> get(Routes.montpellier_path(conn, :station_status))
assert %{"error" => "smoove service unavailable"} == json_response(conn, 502)
end

defp setup_stations_response do
Transport.HTTPoison.Mock
|> expect(:get, fn url ->
assert url == "https://data.montpellier3m.fr/sites/default/files/ressources/TAM_MMM_VELOMAG.xml"

{:ok,
%HTTPoison.Response{
status_code: 200,
body: """
<vcs ver="1"><sl><si na="001 Rue Jules Ferry - Gare Saint-Roch" id="001" la="43.605366" lg="3.881346" av="5" fr="7" to="12"></si></sl></vcs>
""",
headers: [{"Content-Type", "application/xml"}]
}}
end)
end
end
end
5 changes: 0 additions & 5 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,17 @@
"finch": {:hex, :finch, "0.8.2", "3e3f3f7a333b998a79d6f6d6bba07fad034c3d2b57852820593176f4601d6b19", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.5", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "01b559829dba1a0fe1fd1820a80b192071d28399b45d929c242db34c74203ca1"},
"floki": {:hex, :floki, "0.31.0", "f05ee8a8e6a3ced4e62beeb2c79a63bc8e12ab98fbaaf6e6a3d9b76b1278e23f", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "b05afa372f5c345a5bf240ac25ea1f0f3d5fcfd7490ac0beeb4a203f9444891e"},
"gen_stage": {:hex, :gen_stage, "0.14.1", "9d46723fda072d4f4bb31a102560013f7960f5d80ea44dcb96fd6304ed61e7a4", [:mix], [], "hexpm", "967dd5f2469ba77a8a54eef247c0f08a022f89b627a5b121b18cd224a513042f"},
"gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm", "5cacd405e72b2609a7e1f891bddb80c53d0b3b7b0036d1648e7382ca108c41c8"},
"geo": {:hex, :geo, "3.4.2", "834f106e4ba072f5917920bd7ece42cfe1caa03c7fb278f85ea5d0905c8dc153", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "0a9fdad0d5197678cbac85e1dac674528bd90fa923398679a174b541cdf0ec68"},
"geo_postgis": {:hex, :geo_postgis, "3.4.0", "67815cc9b3db12e378e7a96d0f77d3bc2188534aba4d2c2229e3ebf8370f9d52", [:mix], [{:geo, "~> 3.4", [hex: :geo, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0 or ~> 4.0", [hex: :poison, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm", "23563946ef6e5c747380e6a7a0493b0a631e91d0452314ee7e33354f4fdf5230"},
"gettext": {:hex, :gettext, "0.18.2", "7df3ea191bb56c0309c00a783334b288d08a879f53a7014341284635850a6e55", [:mix], [], "hexpm", "f9f537b13d4fdd30f3039d33cb80144c3aa1f8d9698e47d7bcbcc8df93b1f5c5"},
"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"},
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.4.2", "c479398b6de798c03eb5d04a0a9a9159d73508f83f6590a00b8eacba3619cf4c", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm", "aef6c28585d06a9109ad591507e508854c5559561f950bbaea773900dd369b0e"},
"httpoison": {:hex, :httpoison, "1.8.0", "6b85dea15820b7804ef607ff78406ab449dd78bed923a49c7160e1886e987a3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "28089eaa98cf90c66265b6b5ad87c59a3729bea2e74e9d08f9b51eb9729b3c3a"},
"iconv": {:hex, :iconv, "1.0.12", "b0a7ec542ee0319848a53410c72380f628a39f7b694c60cbb6e02d9eb4322a3a", [:rebar3], [{:p1_utils, "1.0.21", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "44d1185eb7f80da439c4d0664387d847844ff8d212387b45529309015b77e38f"},
"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"},
"jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], [], "hexpm", "fc3499fed7a726995aa659143a248534adc754ebd16ccd437cd93b649a95091f"},
"jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"},
"libring": {:hex, :libring, "1.5.0", "44313eb6862f5c9168594a061e9d5f556a9819da7c6444706a9e2da533396d70", [:mix], [], "hexpm", "04e843d4fdcff49a62d8e03778d17c6cb2a03fe2d14020d3825a1761b55bd6cc"},
"meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"},
Expand All @@ -60,7 +57,6 @@
"nimble_pool": {:hex, :nimble_pool, "0.2.4", "1db8e9f8a53d967d595e0b32a17030cdb6c0dc4a451b8ac787bf601d3f7704c3", [:mix], [], "hexpm", "367e8071e137b787764e6a9992ccb57b276dc2282535f767a07d881951ebeac6"},
"oauth2": {:hex, :oauth2, "2.0.0", "338382079fe16c514420fa218b0903f8ad2d4bfc0ad0c9f988867dfa246731b0", [:mix], [{:hackney, "~> 1.13", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "881b8364ac7385f9fddc7949379cbe3f7081da37233a1aa7aab844670a91e7e7"},
"open_api_spex": {:hex, :open_api_spex, "3.10.0", "94e9521ad525b3fcf6dc77da7c45f87fdac24756d4de588cb0816b413e7c1844", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.1", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "2dbb2bde3d2b821f06936e8dfaf3284331186556291946d84eeba3750ac28765"},
"p1_utils": {:hex, :p1_utils, "1.0.21", "9d6244bbd4af881e85af71655e8be5720b5b965b1bdd51a35c7871fd4142af9a", [:rebar3], [], "hexpm", "4b9b90e5863f5fed17f06427ba99b2dc37b216e4dd1308891f0903745e2fccbd"},
"parallel_stream": {:hex, :parallel_stream, "1.0.6", "b967be2b23f0f6787fab7ed681b4c45a215a81481fb62b01a5b750fa8f30f76c", [:mix], [], "hexpm", "639b2e8749e11b87b9eb42f2ad325d161c170b39b288ac8d04c4f31f8f0823eb"},
"parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
"phoenix": {:hex, :phoenix, "1.5.9", "a6368d36cfd59d917b37c44386e01315bc89f7609a10a45a22f47c007edf2597", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7e4bce20a67c012f1fbb0af90e5da49fa7bf0d34e3a067795703b74aef75427d"},
Expand All @@ -86,7 +82,6 @@
"sizeable": {:hex, :sizeable, "1.0.2", "625fe06a5dad188b52121a140286f1a6ae1adf350a942cf419499ecd8a11ee29", [:mix], [], "hexpm", "4bab548e6dfba777b400ca50830a9e3a4128e73df77ab1582540cf5860601762"},
"sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
"swarm": {:hex, :swarm, "3.4.0", "64f8b30055d74640d2186c66354b33b999438692a91be275bb89cdc7e401f448", [:mix], [{:gen_state_machine, "~> 2.0", [hex: :gen_state_machine, repo: "hexpm", optional: false]}, {:libring, "~> 1.0", [hex: :libring, repo: "hexpm", optional: false]}], "hexpm", "94884f84783fc1ba027aba8fe8a7dae4aad78c98e9f9c76667ec3471585c08c6"},
"sweet_xml": {:hex, :sweet_xml, "0.7.1", "a2cac8e2101237e617dfa9d427d44b8aff38ba6294f313ffb4667524d6b71b98", [:mix], [], "hexpm", "8bc7b7b584a6a87113071d0d2fd39fe2251cf2224ecaeed7093bdac1b9c1555f"},
"telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"},
"timex": {:hex, :timex, "3.6.3", "58ce6c9eda8ed47fc80c24dde09d481465838d3bcfc230949287fc1b0b0041c1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "6d69f4f95fcf5684102a9cb3cf92c5ba6545bd60ed8d8a6a93cd2a4a4fb0d9ec"},
Expand Down