Skip to content

Commit

Permalink
GBFSMetadata : gère noms pour flux vehicle_status (#4273)
Browse files Browse the repository at this point in the history
* GBFSMetadata : gère noms pour flux vehicle_status

* Fix test
  • Loading branch information
AntoineAugusti authored Oct 28, 2024
1 parent 15b34f3 commit b06203f
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 32 deletions.
70 changes: 50 additions & 20 deletions apps/shared/lib/gbfs_metadata.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,23 @@ defmodule Transport.Shared.GBFSMetadata do
require Logger
@behaviour Transport.Shared.GBFSMetadata.Wrapper

@type feed_name ::
:gbfs
| :manifest
| :gbfs_versions
| :system_information
| :vehicle_types
| :station_information
| :station_status
# `vehicle_status` was `free_bike_status` before v3.0
| :vehicle_status
| :system_hours
| :system_calendar
| :system_regions
| :system_pricing_plans
| :system_alerts
| :geofencing_zones

@doc """
Compute metadata and validation status (using a third-party HTTP validator) for a GBFS feed.
It will do multiple HTTP requests (calling GBFS sub-feeds) to compute various statistics.
Expand Down Expand Up @@ -56,14 +73,14 @@ defmodule Transport.Shared.GBFSMetadata do
end

defp types(%{"data" => _data} = payload) do
has_bike_status = has_feed?(payload, "free_bike_status")
has_station_information = has_feed?(payload, "station_information")
has_vehicle_status = has_feed?(payload, :vehicle_status)
has_station_information = has_feed?(payload, :station_information)

cond do
has_bike_status and has_station_information ->
has_vehicle_status and has_station_information ->
["free_floating", "stations"]

has_bike_status ->
has_vehicle_status ->
["free_floating"]

has_station_information ->
Expand Down Expand Up @@ -141,23 +158,24 @@ defmodule Transport.Shared.GBFSMetadata do
Determines the feed to use as the ttl value of a GBFS feed.
iex> feed_to_use_for_ttl(["free_floating", "stations"])
"free_bike_status"
:vehicle_status
iex> feed_to_use_for_ttl(["stations"])
"station_information"
:station_information
iex> feed_to_use_for_ttl(nil)
nil
"""
@spec feed_to_use_for_ttl([binary()] | nil) :: feed_name() | nil
def feed_to_use_for_ttl(types) do
case types do
["free_floating", "stations"] -> "free_bike_status"
["free_floating"] -> "free_bike_status"
["stations"] -> "station_information"
["free_floating", "stations"] -> :vehicle_status
["free_floating"] -> :vehicle_status
["stations"] -> :station_information
nil -> nil
end
end

def system_details(%{"data" => _} = payload) do
feed_url = payload |> first_feed() |> feed_url_by_name("system_information")
def system_details(%{"data" => _data} = payload) do
feed_url = payload |> first_feed() |> feed_url_by_name(:system_information)

if not is_nil(feed_url) do
with {:ok, %HTTPoison.Response{status_code: 200, body: body}} <- http_client().get(feed_url),
Expand Down Expand Up @@ -206,7 +224,7 @@ defmodule Transport.Shared.GBFSMetadata do
end

def vehicle_types(%{"data" => _data} = payload) do
feed_url = payload |> first_feed() |> feed_url_by_name("vehicle_types")
feed_url = payload |> first_feed() |> feed_url_by_name(:vehicle_types)

if is_nil(feed_url) do
# https://gbfs.org/specification/reference/#vehicle_typesjson
Expand All @@ -226,7 +244,7 @@ defmodule Transport.Shared.GBFSMetadata do
if before_v3?(payload) do
Map.keys(data)
else
feed_url = payload |> first_feed() |> feed_url_by_name("system_information")
feed_url = payload |> first_feed() |> feed_url_by_name(:system_information)

with {:ok, %HTTPoison.Response{status_code: 200, body: body}} <- http_client().get(feed_url),
{:ok, json} <- Jason.decode(body) do
Expand All @@ -239,7 +257,7 @@ defmodule Transport.Shared.GBFSMetadata do

@spec versions(map()) :: [binary()] | nil
def versions(%{"data" => _data} = payload) do
versions_url = payload |> first_feed() |> feed_url_by_name("gbfs_versions")
versions_url = payload |> first_feed() |> feed_url_by_name(:gbfs_versions)

if is_nil(versions_url) do
[Map.get(payload, "version", "1.0")]
Expand All @@ -253,23 +271,35 @@ defmodule Transport.Shared.GBFSMetadata do
end
end

@spec feed_url_by_name(list(), binary()) :: binary() | nil
@spec feed_url_by_name(list(), feed_name()) :: binary() | nil
def feed_url_by_name(feeds, name) do
Enum.find(feeds, fn map -> feed_is_named?(map, name) end)["url"]
end

@spec feed_is_named?(map(), binary()) :: boolean()
def feed_is_named?(map, name) do
@spec feed_is_named?(map(), feed_name()) :: boolean()
def feed_is_named?(%{"name" => feed_name}, name) do
# Many people make the mistake of appending `.json` to feed names
# so try to match this as well
Enum.member?([name, "#{name}.json"], map["name"])
searches = [to_string(name), "#{name}.json"]

if name == :vehicle_status do
searches ++ ["free_bike_status", "free_bike_status.json"]
else
searches
end
|> Enum.member?(feed_name)
end

@spec has_feed?(map(), feed_name()) :: boolean()
def has_feed?(%{"data" => _data} = payload, :vehicle_status) do
not MapSet.disjoint?(MapSet.new(feeds(payload)), MapSet.new(["vehicle_status", "free_bike_status"]))
end

@spec has_feed?(map(), binary()) :: boolean()
def has_feed?(%{"data" => _data} = payload, name) do
Enum.member?(feeds(payload), name)
Enum.member?(feeds(payload), to_string(name))
end

@spec feeds(map()) :: [binary()]
def feeds(%{"data" => _data} = payload) do
# Remove potential ".json" at the end of feed names as people
# often make this mistake
Expand Down
39 changes: 39 additions & 0 deletions apps/shared/test/gbfs_metadata_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,45 @@ defmodule Transport.Shared.GBFSMetadataTest do
end
end

describe "free_bike_status becomes vehicle_status" do
test "v2.3" do
assert feed_is_named?(%{"name" => "free_bike_status"}, :vehicle_status)
assert feed_is_named?(%{"name" => "free_bike_status.json"}, :vehicle_status)

payload = %{
"version" => "2.3",
"data" => %{
"en" => %{
"feeds" => [%{"name" => "free_bike_status", "url" => feed_url = "https://example.com/free_bike_status"}]
}
}
}

assert has_feed?(payload, :vehicle_status)

assert payload |> first_feed() |> feed_url_by_name(:vehicle_status) == feed_url

assert has_feed?(
%{"version" => "2.3", "data" => %{"en" => %{"feeds" => [%{"name" => "free_bike_status.json"}]}}},
:vehicle_status
)
end

test "v3.0" do
assert feed_is_named?(%{"name" => "vehicle_status"}, :vehicle_status)

payload = %{
"version" => "3.0",
"data" => %{
"feeds" => [%{"name" => "vehicle_status", "url" => feed_url = "https://example.com/free_bike_status"}]
}
}

assert has_feed?(payload, :vehicle_status)
assert payload |> first_feed() |> feed_url_by_name(:vehicle_status) == feed_url
end
end

defp setup_validation_result(summary \\ nil) do
Shared.Validation.GBFSValidator.Mock
|> expect(:validate, fn url ->
Expand Down
26 changes: 14 additions & 12 deletions apps/transport/lib/transport/gbfs_to_geojson.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ defmodule Transport.GbfsToGeojson do
"""
alias Transport.Shared.GBFSMetadata

@type feed_name :: Transport.Shared.GBFSMetadata.feed_name()

@doc """
Main module function: returns a map of geojsons generated from the GBFS endpoint
%{
Expand All @@ -29,7 +31,7 @@ defmodule Transport.GbfsToGeojson do

def add_feeds(payload, %{"output" => "free_floating"}) do
%{}
|> add_free_bike_status(payload)
|> add_vehicle_status(payload)
|> Map.get("free_floating")
end

Expand All @@ -43,7 +45,7 @@ defmodule Transport.GbfsToGeojson do
%{}
|> add_station_information(payload)
|> add_station_status(payload)
|> add_free_bike_status(payload)
|> add_vehicle_status(payload)
|> add_geofencing_zones(payload)
rescue
_e -> %{}
Expand All @@ -52,7 +54,7 @@ defmodule Transport.GbfsToGeojson do
@spec add_station_information(map(), map()) :: map()
defp add_station_information(resp_data, payload) do
payload
|> feed_url_from_payload("station_information")
|> feed_url_from_payload(:station_information)
|> case do
nil ->
resp_data
Expand Down Expand Up @@ -103,7 +105,7 @@ defmodule Transport.GbfsToGeojson do
@spec add_station_status(map(), map()) :: map()
defp add_station_status(%{"stations" => stations_geojson} = resp_data, payload) do
payload
|> feed_url_from_payload("station_status")
|> feed_url_from_payload(:station_status)
|> case do
nil ->
resp_data
Expand Down Expand Up @@ -155,24 +157,24 @@ defmodule Transport.GbfsToGeojson do
Map.put(data, "availability", availability)
end

@spec add_free_bike_status(map(), map()) :: map()
defp add_free_bike_status(resp_data, payload) do
@spec add_vehicle_status(map(), map()) :: map()
defp add_vehicle_status(resp_data, payload) do
payload
|> feed_url_from_payload("free_bike_status")
|> feed_url_from_payload(:vehicle_status)
|> case do
nil ->
resp_data

url ->
geojson = free_bike_status_geojson!(url)
geojson = vehicle_status_geojson!(url)
resp_data |> Map.put("free_floating", geojson)
end
rescue
_e -> resp_data
end

@spec free_bike_status_geojson!(binary()) :: map()
defp free_bike_status_geojson!(url) do
@spec vehicle_status_geojson!(binary()) :: map()
defp vehicle_status_geojson!(url) do
json = fetch_gbfs_endpoint!(url)

vehicles =
Expand Down Expand Up @@ -203,7 +205,7 @@ defmodule Transport.GbfsToGeojson do
@spec add_geofencing_zones(map(), map()) :: map()
defp add_geofencing_zones(resp_data, payload) do
payload
|> feed_url_from_payload("geofencing_zones")
|> feed_url_from_payload(:geofencing_zones)
|> case do
nil ->
resp_data
Expand All @@ -230,7 +232,7 @@ defmodule Transport.GbfsToGeojson do
Jason.decode!(body)
end

@spec feed_url_from_payload(map(), binary()) :: binary() | nil
@spec feed_url_from_payload(map(), feed_name()) :: binary() | nil
defp feed_url_from_payload(payload, feed_name) do
payload |> GBFSMetadata.first_feed() |> GBFSMetadata.feed_url_by_name(feed_name)
end
Expand Down

0 comments on commit b06203f

Please sign in to comment.