-
Notifications
You must be signed in to change notification settings - Fork 77
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
Deserializer plug #222
Deserializer plug #222
Changes from 3 commits
fcac3f7
94d587a
4d4ebdb
2c38c03
88811c3
3f99aec
4b691ee
b68680d
d168567
25d11b1
7fc8d65
d087b86
adb61e6
39e5c8f
aeb231e
4a16274
738c2b9
2481ee9
e4ffe06
59efee6
573c9ce
83d9339
2b19fd8
874042b
d966739
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
defmodule JSONAPI.Deserializer do | ||
@moduledoc """ | ||
Based on - https://github.com/vt-elixir/ja_serializer/blob/20ff32279cab00e81eba0d035951c470fdfbf82d/lib/ja_serializer/param_parser.ex | ||
|
||
This plug "deserializes" params to underscores. | ||
For example these params: | ||
%{ | ||
"data" => %{ | ||
"attributes" => %{ | ||
"foo-bar" => true | ||
} | ||
} | ||
} | ||
are transformed to: | ||
%{ | ||
"data" => %{ | ||
"attributes" => %{ | ||
"foo_bar" => true | ||
} | ||
} | ||
} | ||
|
||
## Usage | ||
Just include in your plug stack _after_ a json parser: | ||
plug Plug.Parsers, parsers: [:json], json_decoder: Jason | ||
plug JSONAPI.Deserializer | ||
|
||
or a part of your Controller plug pipeline | ||
plug JSONAPI.Deserializer | ||
""" | ||
|
||
alias JSONAPI.Utils.ParamParser | ||
|
||
def init(opts), do: opts | ||
|
||
def call(conn, _opts) do | ||
Map.put(conn, :params, ParamParser.parse(conn.params)) | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
defprotocol JSONAPI.Utils.ParamParser do | ||
@fallback_to_any true | ||
def parse(params) | ||
end | ||
|
||
defimpl JSONAPI.Utils.ParamParser, for: Any do | ||
def parse(data), do: data | ||
end | ||
|
||
defimpl JSONAPI.Utils.ParamParser, for: List do | ||
def parse(list), do: Enum.map(list, &JSONAPI.Utils.ParamParser.parse/1) | ||
end | ||
|
||
defimpl JSONAPI.Utils.ParamParser, for: Map do | ||
def parse(map) do | ||
Enum.reduce(map, %{}, fn {key, val}, map -> | ||
key = JSONAPI.Utils.String.underscore(key) | ||
Map.put(map, key, JSONAPI.Utils.ParamParser.parse(val)) | ||
end) | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
defmodule JSONAPI.DeserializerTest do | ||
use ExUnit.Case | ||
use Plug.Test | ||
|
||
defmodule ExamplePlug do | ||
use Plug.Builder | ||
plug Plug.Parsers, parsers: [:json], json_decoder: Jason | ||
plug JSONAPI.Deserializer | ||
plug :return | ||
|
||
def return(conn, _opts) do | ||
send_resp(conn, 200, "success") | ||
end | ||
end | ||
|
||
@ct "application/vnd.api+json" | ||
|
||
test "Ignores bodyless requests" do | ||
conn = | ||
Plug.Test.conn("GET", "/") | ||
|> put_req_header("content-type", @ct) | ||
|> put_req_header("accept", @ct) | ||
|
||
result = ExamplePlug.call(conn, []) | ||
assert result.params == %{} | ||
end | ||
|
||
test "converts non-jsonapi.org format params" do | ||
req_body = Jason.encode!(%{"some-nonsense" => "yup"}) | ||
|
||
conn = | ||
Plug.Test.conn("POST", "/", req_body) | ||
|> put_req_header("content-type", @ct) | ||
|> put_req_header("accept", @ct) | ||
|
||
result = ExamplePlug.call(conn, []) | ||
assert result.params == %{"some_nonsense" => "yup"} | ||
end | ||
|
||
test "converts attribute key names" do | ||
req_body = | ||
Jason.encode!(%{ | ||
"data" => %{ | ||
"attributes" => %{ | ||
"some-nonsense" => true, | ||
"foo-bar" => true, | ||
"some-map" => %{ | ||
"nested-key" => "unaffected-values" | ||
} | ||
} | ||
} | ||
}) | ||
|
||
conn = | ||
Plug.Test.conn("POST", "/", req_body) | ||
|> put_req_header("content-type", @ct) | ||
|> put_req_header("accept", @ct) | ||
|
||
result = ExamplePlug.call(conn, []) | ||
assert result.params["data"]["attributes"]["some_nonsense"] | ||
assert result.params["data"]["attributes"]["foo_bar"] | ||
assert result.params["data"]["attributes"]["some_map"]["nested_key"] | ||
end | ||
|
||
test "converts query param key names - dasherized" do | ||
req_body = Jason.encode!(%{"data" => %{}}) | ||
|
||
conn = | ||
Plug.Test.conn("POST", "/?page[page-size]=2", req_body) | ||
|> put_req_header("content-type", @ct) | ||
|> put_req_header("accept", @ct) | ||
|
||
result = ExamplePlug.call(conn, []) | ||
assert result.params["page"]["page_size"] == "2" | ||
end | ||
|
||
test "converts query param key names - underscored" do | ||
req_body = Jason.encode!(%{"data" => %{}}) | ||
|
||
conn = | ||
Plug.Test.conn("POST", "/?page[page_size]=2", req_body) | ||
|> put_req_header("content-type", @ct) | ||
|> put_req_header("accept", @ct) | ||
|
||
result = ExamplePlug.call(conn, []) | ||
assert result.query_params["page"]["page_size"] == "2" | ||
end | ||
|
||
test "retains payload type" do | ||
req_body = | ||
Jason.encode!(%{ | ||
"data" => %{ | ||
"type" => "foo-bar" | ||
} | ||
}) | ||
|
||
conn = | ||
Plug.Test.conn("POST", "/", req_body) | ||
|> put_req_header("content-type", @ct) | ||
|> put_req_header("accept", @ct) | ||
|
||
result = ExamplePlug.call(conn, []) | ||
assert result.params["data"]["type"] == "foo-bar" | ||
end | ||
end | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is really good, but you may want to add a has-many relationship example too. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have a bunch of examples in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may just want to reference, or alias,
JSONAPI.mime_type
so that we don't spread this string throughout the code.