Skip to content

Commit

Permalink
fix: handle accept and content-type properly
Browse files Browse the repository at this point in the history
  • Loading branch information
zachdaniel committed Nov 24, 2020
1 parent 59106b2 commit f886745
Show file tree
Hide file tree
Showing 9 changed files with 158 additions and 95 deletions.
2 changes: 1 addition & 1 deletion lib/ash_json_api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ defmodule AshJsonApi do
end

def log_errors?(api) do
Extension.get_opt(api, [:json_api], :log_errors?, true, true)
Extension.get_opt(api, [:json_api], :log_errors?, false, true)
end

defmacro forward(path, api, opts \\ []) do
Expand Down
2 changes: 1 addition & 1 deletion lib/ash_json_api/error/error.ex
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ defmodule AshJsonApi.Error do
defp class_to_status(:invalid), do: 400

def new(opts) do
struct!(__MODULE__, opts)
struct(__MODULE__, opts)
end

def format_log(error) when is_bitstring(error) do
Expand Down
10 changes: 10 additions & 0 deletions lib/ash_json_api/error/unacceptable_media_type.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
defmodule AshJsonApi.Error.UnacceptableMediaType do
@moduledoc """
Returned when the client does not provide (via the `Content-Type` header) the correct json API media type: application/vnd.api+json
"""
@detail @moduledoc
@title "Unacceptable Media Type"
@status_code 406

use AshJsonApi.Error
end
2 changes: 1 addition & 1 deletion lib/ash_json_api/error/unsupported_media_type.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
defmodule AshJsonApi.Error.UnsupportedMediaType do
@moduledoc """
Returned when the client does not accept the json API media type: application/vnd.api+json
Returned when the client does not accept (via the `Accept` header) the json API media type: application/vnd.api+json
"""
@detail @moduledoc
@title "Unsupported Media Type"
Expand Down
2 changes: 1 addition & 1 deletion lib/ash_json_api/json_schema/json_schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ defmodule AshJsonApi.JsonSchema do
"content-type" => %{
"type" => "array",
"items" => %{
"const" => "application/vnd.api+json"
"type" => "string"
}
},
"accept" => %{
Expand Down
89 changes: 85 additions & 4 deletions lib/ash_json_api/request.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ defmodule AshJsonApi.Request do
InvalidHeader,
InvalidQuery,
InvalidType,
UnacceptableMediaType,
UnsupportedMediaType
}

Expand Down Expand Up @@ -148,6 +149,49 @@ defmodule AshJsonApi.Request do
add_error(request, InvalidHeader.new(json_xema_error: error), request.route.type)
end
|> validate_accept_header()
|> validate_content_type_header()
end

defp validate_content_type_header(%{req_headers: headers} = request) do
headers
|> Enum.filter(fn {header, _value} ->
header == "content-type"
end)
|> Enum.map(fn {_header, value} ->
Conn.Utils.media_type(value)
end)
|> case do
[] ->
request

values ->
any_content_type_supported? =
Enum.any?(values, fn
{:ok, "*", _, _} ->
true

_ ->
false
end)

json_api_content_type_supported? =
Enum.all?(values, fn
{:ok, "*", _, _} ->
true

{:ok, "application", "vnd.api+json", params} ->
valid_header_params?(params)

_ ->
false
end)

if any_content_type_supported? || json_api_content_type_supported? do
request
else
add_error(request, UnsupportedMediaType.new([]), request.route.type)
end
end
end

defp validate_accept_header(%{req_headers: headers} = request) do
Expand All @@ -157,16 +201,53 @@ defmodule AshJsonApi.Request do
|> Enum.flat_map(fn {_, value} ->
String.split(value, ",")
end)
|> Enum.any?(fn accept ->
parsed = Conn.Utils.media_type(accept)
|> Enum.map(fn
"" ->
""

match?({:ok, "application", "vnd.api+json", _}, parsed)
value ->
Conn.Utils.media_type(value)
end)
|> Enum.filter(fn
{:ok, "application", "vnd.api+json", _} -> true
_ -> false
end)
|> case do
[] ->
true

headers ->
Enum.any?(headers, fn {:ok, "application", "vnd.api+json", params} ->
valid_header_params?(params)
end)
end

if accepts_json_api? do
request
else
add_error(request, UnsupportedMediaType.new([]), request.route.type)
add_error(request, UnacceptableMediaType.new([]), request.route.type)
end
end

defp valid_header_params?(params) do
params
|> Map.keys()
|> Enum.sort()
|> case do
[] ->
true

["ext"] ->
true

["profile"] ->
true

["ext", "profile"] ->
true

_ ->
false
end
end

Expand Down
11 changes: 5 additions & 6 deletions lib/ash_json_api/test/test.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ defmodule AshJsonApi.Test do
result =
:get
|> conn(path)
|> set_content_type_request_header(opts)
|> set_accept_request_header(opts)
|> AshJsonApi.router(api).call(AshJsonApi.router(api).init([]))

Expand Down Expand Up @@ -46,8 +45,8 @@ defmodule AshJsonApi.Test do
result =
:post
|> conn(path, Jason.encode!(body))
|> put_req_header("content-type", "application/vnd.api+json")
|> put_req_header("accept", "application/vnd.api+json")
|> set_content_type_request_header(opts)
|> set_accept_request_header(opts)
|> AshJsonApi.router(api).call(AshJsonApi.router(api).init([]))

assert result.state == :sent
Expand Down Expand Up @@ -76,8 +75,8 @@ defmodule AshJsonApi.Test do
result =
:patch
|> conn(path, Jason.encode!(body))
|> put_req_header("content-type", "application/vnd.api+json")
|> put_req_header("accept", "application/vnd.api+json")
|> set_content_type_request_header(opts)
|> set_accept_request_header(opts)
|> AshJsonApi.router(api).call(AshJsonApi.router(api).init([]))

assert result.state == :sent
Expand Down Expand Up @@ -160,7 +159,7 @@ defmodule AshJsonApi.Test do

opts[:req_accept_header] ->
conn
|> put_req_header("accept", "application/vnd.api+json")
|> put_req_header("accept", opts[:req_accept_header])

true ->
conn
Expand Down
2 changes: 1 addition & 1 deletion mix.lock
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
%{
"ash": {:hex, :ash, "1.24.1", "40545e47f9fb70d2e4b9b13be365a17ea07d8ca533ea6cddb88e9021ee4333b2", [:mix], [{:ecto, "~> 3.4", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.1.5", [hex: :picosat_elixir, repo: "hexpm", optional: false]}], "hexpm", "bbcfd7c11040f61b76d2f28a10d4518922c02e23a9dd9c01bc1fbe9af03ed7d7"},
"ash": {:hex, :ash, "1.24.1", "0790abb8b429fd8875d5a0d80c5850bc08863f1c76ace02ec051a49950cae365", [:mix], [{:ecto, "~> 3.4", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.1.5", [hex: :picosat_elixir, repo: "hexpm", optional: false]}], "hexpm", "56eaffb67667d5f1860678b0da5bdbd134644a953fc108094b57ac573ad390af"},
"ashton": {:hex, :ashton, "0.4.1", "d0f7782ac44fa22da7ce544028ee3d2078592a834d8adf3e5b4b6aeb94413a55", [:mix], [], "hexpm", "24db667932517fdbc3f2dae777f28b8d87629271387d4490bc4ae8d9c46ff3d3"},
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
"certifi": {:hex, :certifi, "2.5.2", "b7cfeae9d2ed395695dd8201c57a2d019c0c43ecaf8b8bcb9320b40d6662f340", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "3b3b5f36493004ac3455966991eaf6e768ce9884693d9968055aeeeb1e575040"},
Expand Down
Loading

0 comments on commit f886745

Please sign in to comment.