Skip to content

Commit

Permalink
Merge branch 'master' into api_validators_gtfs_transport
Browse files Browse the repository at this point in the history
  • Loading branch information
AntoineAugusti authored Oct 21, 2024
2 parents c6969a6 + 1a5b798 commit f871e05
Show file tree
Hide file tree
Showing 2 changed files with 196 additions and 21 deletions.
39 changes: 36 additions & 3 deletions apps/shared/lib/gbfs_metadata.ex
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ defmodule Transport.Shared.GBFSMetadata do
versions: versions(json),
languages: languages(json),
system_details: system_details(json),
vehicle_types: vehicle_types(json),
types: types(json),
ttl: ttl(json),
feed_timestamp_delay: feed_timestamp_delay
Expand Down Expand Up @@ -177,7 +178,7 @@ defmodule Transport.Shared.GBFSMetadata do

def first_feed(%{"data" => data, "version" => version} = payload) do
# From GBFS 1.1 until GBFS 2.3
if String.starts_with?(version, ["1.", "2."]) do
if before_v3?(version) do
first_language = payload |> languages() |> Enum.at(0)
(data["en"] || data["fr"] || data[first_language])["feeds"]
# From GBFS 3.0 onwards
Expand All @@ -186,8 +187,38 @@ defmodule Transport.Shared.GBFSMetadata do
end
end

defp languages(%{"data" => data}) do
Map.keys(data)
def vehicle_types(%{"data" => _data} = payload) do
feed_url = payload |> first_feed() |> feed_url_by_name("vehicle_types")

if is_nil(feed_url) do
# https://gbfs.org/specification/reference/#vehicle_typesjson
# > If this file is not included, then all vehicles in the feed are assumed to be non-motorized bicycles.
["bicycle"]
else
with {:ok, %HTTPoison.Response{status_code: 200, body: body}} <- http_client().get(feed_url),
{:ok, json} <- Jason.decode(body) do
json["data"]["vehicle_types"] |> Enum.map(& &1["form_factor"]) |> Enum.uniq()
else
_ -> nil
end
end
end

def languages(%{"data" => data, "version" => version} = payload) do
# From GBFS 1.1 until GBFS 2.3
if before_v3?(version) do
Map.keys(data)
# From GBFS 3.0 onwards
else
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
get_in(json, ["data", "languages"])
else
_ -> []
end
end
end

@spec versions(map()) :: [binary()] | nil
Expand Down Expand Up @@ -229,5 +260,7 @@ defmodule Transport.Shared.GBFSMetadata do
payload |> first_feed() |> Enum.map(fn feed -> String.replace(feed["name"], ".json", "") end)
end

defp before_v3?(version), do: String.starts_with?(version, ["1.", "2."])

defp http_client, do: Transport.Shared.Wrapper.HTTPoison.impl()
end
178 changes: 160 additions & 18 deletions apps/shared/test/gbfs_metadata_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,13 @@ defmodule Transport.Shared.GBFSMetadataTest do
validator: :validator_module
},
cors_header_value: "*",
feed_timestamp_delay: _
feed_timestamp_delay: _,
vehicle_types: ["bicycle"]
} = compute_feed_metadata(@gbfs_url, "http://example.com")
end

test "for a stations + free floating feed with a multiple versions" do
setup_feeds([:gbfs_with_versions, :gbfs_versions, :system_information, :free_bike_status])
setup_feeds([:gbfs_with_versions, :gbfs_versions, :system_information, :vehicle_types, :free_bike_status])

setup_validation_result(
{:ok, summary} =
Expand Down Expand Up @@ -70,7 +71,8 @@ defmodule Transport.Shared.GBFSMetadataTest do
"gbfs_versions"
],
cors_header_value: "*",
feed_timestamp_delay: feed_timestamp_delay
feed_timestamp_delay: feed_timestamp_delay,
vehicle_types: ["bicycle", "scooter"]
} = compute_feed_metadata(@gbfs_url, "http://example.com")

assert feed_timestamp_delay > 0
Expand Down Expand Up @@ -123,6 +125,142 @@ defmodule Transport.Shared.GBFSMetadataTest do
end
end

describe "vehicle_types" do
test "3.0 feed, vehicle_types feed present" do
vehicle_types_url = "https://example.com/gbfs/vehicle_types"

setup_response(
vehicle_types_url,
Jason.encode!(%{
data: %{
vehicle_types: [
%{form_factor: "bicycle"},
%{form_factor: "scooter_standing"},
%{form_factor: "bicycle"}
]
}
})
)

json =
Jason.decode!("""
{
"last_updated": "2023-07-17T13:34:13+02:00",
"ttl": 0,
"version": "3.0",
"data": {
"feeds": [
{
"name": "vehicle_types",
"url": "#{vehicle_types_url}"
},
{
"name": "station_information",
"url": "https://example.com/gbfs/station_information"
}
]
}
}
""")

assert MapSet.new(["bicycle", "scooter_standing"]) == json |> vehicle_types() |> MapSet.new()
end

test "2.1 feed, vehicle_types feed not present" do
json =
Jason.decode!("""
{
"last_updated": "1636116522",
"ttl": 0,
"version": "2.1",
"data": {
"fr": {
"feeds": [
{
"name": "station_information",
"url": "https://example.com/gbfs/station_information"
}
]
}
}
}
""")

assert ["bicycle"] == json |> vehicle_types()
end
end

describe "languages" do
test "3.0 feed" do
system_information_url = "https://example.com/gbfs/system_information"

setup_response(system_information_url, Jason.encode!(%{data: %{languages: ["en", "fr"]}}))

json =
Jason.decode!("""
{
"last_updated": "2023-07-17T13:34:13+02:00",
"ttl": 0,
"version": "3.0",
"data": {
"feeds": [
{
"name": "system_information",
"url": "#{system_information_url}"
},
{
"name": "station_information",
"url": "https://example.com/gbfs/station_information"
}
]
}
}
""")

assert MapSet.new(["en", "fr"]) == json |> languages() |> MapSet.new()
end

test "2.3 feed" do
# Example from https://github.com/MobilityData/gbfs/blob/v2.3/gbfs.md#gbfsjson
json =
Jason.decode!("""
{
"last_updated": 1640887163,
"ttl": 0,
"version": "2.3",
"data": {
"en": {
"feeds": [
{
"name": "system_information",
"url": "https://www.example.com/gbfs/1/en/system_information"
},
{
"name": "station_information",
"url": "https://www.example.com/gbfs/1/en/station_information"
}
]
},
"fr" : {
"feeds": [
{
"name": "system_information",
"url": "https://www.example.com/gbfs/1/fr/system_information"
},
{
"name": "station_information",
"url": "https://www.example.com/gbfs/1/fr/station_information"
}
]
}
}
}
""")

assert MapSet.new(["en", "fr"]) == json |> languages() |> MapSet.new()
end
end

defp setup_validation_result(summary \\ nil) do
Shared.Validation.GBFSValidator.Mock
|> expect(:validate, fn url ->
Expand All @@ -143,21 +281,17 @@ defmodule Transport.Shared.GBFSMetadataTest do
end)
end

defp setup_feeds(feeds) do
feeds
|> Enum.map(fn feed ->
case feed do
:gbfs -> setup_gbfs_response()
:gbfs_with_versions -> setup_gbfs_with_versions_response()
:gbfs_with_server_error -> setup_gbfs_with_server_error_response()
:gbfs_with_invalid_gbfs_json -> setup_invalid_gbfs_response()
:gbfs_versions -> setup_gbfs_versions_response()
:free_bike_status -> setup_free_bike_status_response()
:system_information -> setup_system_information_response()
:station_information -> setup_station_information_response()
end
end)
end
defp setup_feeds(feeds), do: Enum.map(feeds, &setup_feed(&1))

defp setup_feed(:gbfs), do: setup_gbfs_response()
defp setup_feed(:gbfs_with_versions), do: setup_gbfs_with_versions_response()
defp setup_feed(:gbfs_with_server_error), do: setup_gbfs_with_server_error_response()
defp setup_feed(:gbfs_with_invalid_gbfs_json), do: setup_invalid_gbfs_response()
defp setup_feed(:gbfs_versions), do: setup_gbfs_versions_response()
defp setup_feed(:free_bike_status), do: setup_free_bike_status_response()
defp setup_feed(:system_information), do: setup_system_information_response()
defp setup_feed(:station_information), do: setup_station_information_response()
defp setup_feed(:vehicle_types), do: setup_vehicle_types_response()

defp setup_response_with_headers(expected_url, body) do
Transport.HTTPoison.Mock
Expand Down Expand Up @@ -248,4 +382,12 @@ defmodule Transport.Shared.GBFSMetadataTest do

setup_response("https://example.com/station_information.json", body)
end

defp setup_vehicle_types_response do
body = """
{"data": {"vehicle_types": [{"form_factor": "bicycle","max_range_meters": 20000,"propulsion_type": "electric_assist","vehicle_type_id": "titibike"},{"form_factor": "bicycle","max_range_meters": 60000,"propulsion_type": "electric_assist","vehicle_type_id": "x2"},{"form_factor": "scooter","max_range_meters": 35000,"propulsion_type": "electric","vehicle_type_id": "knot"},{"form_factor": "scooter","max_range_meters": 60000,"propulsion_type": "electric","vehicle_type_id": "pony"}]},"last_updated": "2024-10-16T16:23:49+02:00","ttl": 300,"version": "3.0"}
"""

setup_response("https://example.com/vehicle_types.json", body)
end
end

0 comments on commit f871e05

Please sign in to comment.