-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
35 changed files
with
1,241 additions
and
87 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
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
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 |
---|---|---|
@@ -0,0 +1,19 @@ | ||
defprotocol MBTAV3API.JsonApi.FilterValue do | ||
@doc "Converts this filter value into a string" | ||
@spec filter_value_string(t()) :: String.t() | ||
def filter_value_string(data) | ||
end | ||
|
||
defimpl MBTAV3API.JsonApi.FilterValue, for: BitString do | ||
def filter_value_string(data), do: data | ||
end | ||
|
||
defimpl MBTAV3API.JsonApi.FilterValue, for: [Atom, Float, Integer] do | ||
def filter_value_string(data), do: String.Chars.to_string(data) | ||
end | ||
|
||
defimpl MBTAV3API.JsonApi.FilterValue, for: List do | ||
def filter_value_string(data) do | ||
Enum.map_join(data, ",", &MBTAV3API.JsonApi.FilterValue.filter_value_string/1) | ||
end | ||
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 |
---|---|---|
@@ -0,0 +1,20 @@ | ||
defmodule MBTAV3API.JsonApi.Object do | ||
alias MBTAV3API.JsonApi | ||
|
||
@callback fields :: [atom()] | ||
@callback includes :: %{atom() => atom()} | ||
|
||
@spec module_for(atom() | String.t()) :: module() | ||
def module_for(type) | ||
|
||
for type <- [:route, :route_pattern, :stop, :trip] do | ||
module = :"#{MBTAV3API}.#{Macro.camelize(to_string(type))}" | ||
|
||
def module_for(unquote(type)), do: unquote(module) | ||
def module_for(unquote("#{type}")), do: unquote(module) | ||
end | ||
|
||
@spec parse(JsonApi.Item.t() | JsonApi.Reference.t()) :: struct() | JsonApi.Reference.t() | ||
def parse(%JsonApi.Item{type: type} = item), do: module_for(type).parse(item) | ||
def parse(%JsonApi.Reference{} = ref), do: ref | ||
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 |
---|---|---|
@@ -0,0 +1,116 @@ | ||
defmodule MBTAV3API.JsonApi.Params do | ||
alias MBTAV3API.JsonApi.FilterValue | ||
alias MBTAV3API.JsonApi.Object | ||
|
||
@type sort_param :: {atom(), :asc | :desc} | ||
@type fields_param :: {atom(), list(atom())} | ||
@type include_param :: atom() | {atom(), include_param()} | list(include_param()) | ||
@type filter_param :: {atom(), FilterValue.t()} | ||
@type param :: | ||
{:sort, sort_param()} | ||
| {:fields, [fields_param()]} | ||
| {:include, include_param()} | ||
| {:filter, [filter_param()]} | ||
@type t :: [param()] | ||
|
||
@doc """ | ||
Turns a legible parameter list into a flat HTTP-ready parameter list. | ||
## Examples | ||
iex> MBTAV3API.JsonApi.Params.flatten_params( | ||
...> [ | ||
...> sort: {:name, :asc}, | ||
...> fields: [route: [:color, :short_name]], | ||
...> include: :route, | ||
...> filter: [route: ["Green-B", "Red"], canonical: true] | ||
...> ], | ||
...> :route_pattern | ||
...> ) | ||
%{ | ||
"sort" => "name", | ||
"fields[route]" => "color,short_name", | ||
"fields[route_pattern]" => "direction_id,name,sort_order", | ||
"include" => "route", | ||
"filter[route]" => "Green-B,Red", | ||
"filter[canonical]" => "true" | ||
} | ||
""" | ||
@spec flatten_params(t(), atom()) :: %{String.t() => String.t()} | ||
def flatten_params(params, root_type) do | ||
Map.merge( | ||
Map.merge( | ||
sort(params[:sort]), | ||
fields(root_type, params[:include], Keyword.get(params, :fields, [])) | ||
), | ||
Map.merge(include(params[:include]), filter(params[:filter])) | ||
) | ||
end | ||
|
||
@spec sort(nil | sort_param()) :: %{String.t() => String.t()} | ||
defp sort(nil), do: %{} | ||
|
||
defp sort({field, dir}) do | ||
%{ | ||
"sort" => | ||
case dir do | ||
:asc -> "" | ||
:desc -> "-" | ||
end <> "#{field}" | ||
} | ||
end | ||
|
||
@spec fields(atom(), nil | include_param(), [fields_param()]) :: %{String.t() => String.t()} | ||
defp fields(root_type, include, overrides) do | ||
included_types(root_type, include) | ||
|> Enum.uniq() | ||
|> Enum.map(&{&1, Object.module_for(&1).fields()}) | ||
|> Keyword.merge(overrides) | ||
|> Map.new(fn {type, fields} -> {"fields[#{type}]", Enum.join(fields, ",")} end) | ||
end | ||
|
||
defp included_types(root_type, nil), do: [root_type] | ||
|
||
defp included_types(root_type, include) when is_atom(include) do | ||
[root_type, Map.fetch!(Object.module_for(root_type).includes(), include)] | ||
end | ||
|
||
defp included_types(root_type, {include, sub_include}) do | ||
[^root_type, included] = included_types(root_type, include) | ||
[root_type, included] ++ included_types(included, sub_include) | ||
end | ||
|
||
defp included_types(root_type, includes) when is_list(includes) do | ||
Enum.flat_map(includes, &included_types(root_type, &1)) | ||
end | ||
|
||
@spec include(nil | include_param()) :: %{String.t() => String.t()} | ||
defp include(nil), do: %{} | ||
|
||
defp include(include) do | ||
%{"include" => flat_include(include) |> Enum.map_join(",", &Enum.join(&1, "."))} | ||
end | ||
|
||
@spec flat_include(nil | include_param()) :: Enumerable.t(list(atom())) | ||
defp flat_include(include) when is_atom(include) do | ||
[[include]] | ||
end | ||
|
||
defp flat_include({include, sub_include}) do | ||
Stream.concat([[include]], flat_include(sub_include) |> Stream.map(&[include | &1])) | ||
end | ||
|
||
defp flat_include(includes) when is_list(includes) do | ||
Stream.flat_map(includes, &flat_include/1) | ||
end | ||
|
||
@spec filter(nil | [filter_param()]) :: %{String.t() => String.t()} | ||
defp filter(nil), do: %{} | ||
|
||
defp filter(filters) do | ||
Map.new(filters, fn {field, value} -> | ||
{"filter[#{field}]", FilterValue.filter_value_string(value)} | ||
end) | ||
end | ||
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 |
---|---|---|
@@ -0,0 +1,57 @@ | ||
defmodule MBTAV3API.Route do | ||
alias MBTAV3API.JsonApi | ||
|
||
@behaviour JsonApi.Object | ||
|
||
@type t :: %__MODULE__{ | ||
id: String.t(), | ||
color: String.t(), | ||
direction_names: [String.t()], | ||
direction_destinations: [String.t()], | ||
long_name: String.t(), | ||
short_name: String.t(), | ||
sort_order: String.t(), | ||
text_color: String.t() | ||
} | ||
|
||
@derive Jason.Encoder | ||
defstruct [ | ||
:id, | ||
:color, | ||
:direction_names, | ||
:direction_destinations, | ||
:long_name, | ||
:short_name, | ||
:sort_order, | ||
:text_color | ||
] | ||
|
||
@impl JsonApi.Object | ||
def fields, | ||
do: [ | ||
:color, | ||
:direction_names, | ||
:direction_destinations, | ||
:long_name, | ||
:short_name, | ||
:sort_order, | ||
:text_color | ||
] | ||
|
||
@impl JsonApi.Object | ||
def includes, do: %{} | ||
|
||
@spec parse(JsonApi.Item.t()) :: t() | ||
def parse(%JsonApi.Item{} = item) do | ||
%__MODULE__{ | ||
id: item.id, | ||
color: item.attributes["color"], | ||
direction_names: item.attributes["direction_names"], | ||
direction_destinations: item.attributes["direction_destinations"], | ||
long_name: item.attributes["long_name"], | ||
short_name: item.attributes["short_name"], | ||
sort_order: item.attributes["sort_order"], | ||
text_color: item.attributes["text_color"] | ||
} | ||
end | ||
end |
Oops, something went wrong.