Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,9 @@ config :jsonapi,
scheme: "https",
namespace: "/api",
field_transformation: :underscore,
remove_links: false,
serialize_nil_relationships: false,
remove_links: false,
add_auto_links: false,
json_library: Jason,
paginator: nil
```
Expand All @@ -218,12 +219,20 @@ config :jsonapi,
`:dasherize`. If your API uses underscores (e.g. `"favorite_color": "red"`)
set to `:underscore`. To transform only the top-level field keys, use
`:camelize_shallow` or `:dasherize_shallow`.
- **remove_links**. `links` data can optionally be removed from the payload via
setting the configuration above to `true`. Defaults to `false`.
- **serialize_nil_relationships**. By default, relationships on a resource that
are `nil` will be omitted during serialization. Setting this to `true` will
serialize these relationships, provided they are loaded on the resource.
Defaults to `false`.
- **remove_links**. `links` data can optionally be removed from the payload via
setting the configuration above to `true`. Defaults to `false`. This setting
removes all links, not just auto-added links.
- **add_auto_links**. `links` data can optionally be auto-added by
setting the configuration above to `true`. Defaults to `true`. This setting is
overruled by `remove_links` for backwards compatibility -- setting both to
`true` results in adding links and then removing them. Only setting
`add_auto_links` to `true` (and leaving `remove_links` `false` as is its
default) will serialize your explicitly created links but not add any
automatically generated links on top.
- **json_library**. Defaults to [Jason](https://hex.pm/packages/jason).
- **paginator**. Module implementing pagination links generation. Defaults to `nil`.

Expand Down
82 changes: 61 additions & 21 deletions lib/jsonapi/serializer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ defmodule JSONAPI.Serializer do
encoded_data
end

merge_links(encoded_data, data, view, conn, query_page, remove_links?(), options)
merge_links(encoded_data, data, view, conn, query_page, add_auto_links?(), options, true)
end

def encode_data(_view, nil, _conn, _query_includes, _options), do: {[], nil}
Expand All @@ -67,7 +67,7 @@ defmodule JSONAPI.Serializer do
relationships: %{}
}

doc = merge_links(encoded_data, data, view, conn, nil, remove_links?(), options)
doc = merge_links(encoded_data, data, view, conn, nil, add_auto_links?(), options, false)

doc =
case view.meta(data, conn) do
Expand Down Expand Up @@ -210,42 +210,76 @@ defmodule JSONAPI.Serializer do
data: encode_rel_data(rel_view, rel_data)
}

merge_related_links(data, info, remove_links?())
merge_related_links(data, info, add_auto_links?())
end

defp merge_base_links(%{links: links} = doc, data, view, conn) do
view_links = data |> view.links(conn) |> Map.merge(links)
Map.merge(doc, %{links: view_links})
defp merge_links(doc, data, view, conn, _page, false, _options, is_root_of_doc) do
merge_view_links(doc, data, view, conn, is_root_of_doc)
end

defp merge_links(doc, data, view, conn, page, false, options) when is_list(data) do
links =
defp merge_links(doc, data, view, conn, page, true, options, is_root_of_doc) do
doc
|> merge_auto_links(data, view, conn, page, options)
|> merge_view_links(data, view, conn, is_root_of_doc)
end

defp merge_view_links(doc, data, view, conn, is_root_of_doc)

defp merge_view_links(%{links: links} = doc, data, view, conn, false) do
# intentionally take the view.links and merge them into the existing links,
# allowing the custom defined links for the view to override any auto-links
# generated by this library UNLESS configured to remove links (backwards
# compatibility):
view_links =
if remove_links?() do
%{}
else
Map.merge(links, view.links(data, conn))
end

new_doc = Map.merge(doc, %{links: view_links})

case new_doc do
%{links: links} when links == %{} ->
Map.delete(doc, :links)

doc ->
doc
end
end

defp merge_view_links(doc, data, view, conn, false),
do: merge_view_links(Map.merge(doc, %{links: %{}}), data, view, conn, false)

defp merge_view_links(doc, _data, _view, _conn, true), do: doc

defp merge_auto_links(doc, data, view, conn, page, options)
when is_list(data) do
auto_links =
data
|> view.pagination_links(conn, page, options)
|> Map.merge(%{self: view.url_for_pagination(data, conn, page)})
|> Map.merge(%{
self: view.url_for_pagination(data, conn, page)
})

doc
|> Map.merge(%{links: links})
|> merge_base_links(data, view, conn)
Map.merge(doc, %{links: auto_links})
end

defp merge_links(doc, data, view, conn, _page, false, _options) do
doc
|> Map.merge(%{links: %{self: view.url_for(data, conn)}})
|> merge_base_links(data, view, conn)
end
defp merge_auto_links(doc, data, view, conn, _page, _options) do
auto_links = %{self: view.url_for(data, conn)}

defp merge_links(doc, _data, _view, _conn, _page, _remove_links, _options), do: doc
Map.merge(doc, %{links: auto_links})
end

defp merge_related_links(
encoded_data,
{rel_view, rel_data, rel_url, conn},
false = _remove_links
true = _add_auto_links
) do
Map.merge(encoded_data, %{links: %{self: rel_url, related: rel_view.url_for(rel_data, conn)}})
end

defp merge_related_links(encoded_rel_data, _info, _remove_links), do: encoded_rel_data
defp merge_related_links(encoded_rel_data, _info, _add_auto_links), do: encoded_rel_data

@spec encode_rel_data(module(), map() | list()) :: map() | nil
def encode_rel_data(_view, nil), do: nil
Expand Down Expand Up @@ -302,7 +336,13 @@ defmodule JSONAPI.Serializer do
|> List.flatten()
end

defp remove_links?, do: Application.get_env(:jsonapi, :remove_links, false)
defp remove_links? do
Application.get_env(:jsonapi, :remove_links, false)
end

defp add_auto_links? do
Application.get_env(:jsonapi, :add_auto_links, !remove_links?())
end

@spec serialize_nil_relationships? :: boolean()
defp serialize_nil_relationships?, do: Application.get_env(:jsonapi, :serialize_nil_relationships, false)
Expand Down
124 changes: 120 additions & 4 deletions test/jsonapi/serializer_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,27 @@ defmodule JSONAPI.SerializerTest do
end
end

defmodule PostViewWithLinks do
use JSONAPI.View

def fields, do: [:text, :body, :full_description, :inserted_at]
def meta(data, _conn), do: %{meta_text: "meta_#{data[:text]}"}
def type, do: "mytype"

def relationships do
[
author: {JSONAPI.SerializerTest.UserView, :include},
best_comments: {JSONAPI.SerializerTest.CommentView, :include}
]
end

def links(_data, _conn) do
%{
self: "https://website.com/api/posts/1"
}
end
end

defmodule PageBasedPaginator do
@moduledoc """
Page based pagination strategy
Expand Down Expand Up @@ -867,6 +888,97 @@ defmodule JSONAPI.SerializerTest do
Application.delete_env(:jsonapi, :remove_links)
end

describe "when configured to not add auto links" do
setup do
Application.put_env(:jsonapi, :add_auto_links, false)

on_exit(fn ->
Application.delete_env(:jsonapi, :add_auto_links)
end)

{:ok, []}
end

test "serialize does not include auto links" do
data = %{
id: 1,
text: "Hello",
body: "Hello world",
full_description: "This_is_my_description",
author: %{id: 2, username: "jbonds", first_name: "jerry", last_name: "bonds"},
best_comments: [
%{
id: 5,
text: "greatest comment ever",
user: %{id: 4, username: "jack", last_name: "bronds"}
}
]
}

encoded = Serializer.serialize(PostView, data, nil)

relationships = encoded[:data][:relationships]

refute relationships[:links]
refute encoded[:data][:links]
refute encoded[:links]
end

test "serialize still adds custom links" do
data = %{
id: 1,
text: "Hello",
body: "Hello world",
full_description: "This_is_my_description",
author: %{id: 2, username: "jbonds", first_name: "jerry", last_name: "bonds"},
best_comments: [
%{
id: 5,
text: "greatest comment ever",
user: %{id: 4, username: "jack", last_name: "bronds"}
}
]
}

encoded = Serializer.serialize(PostViewWithLinks, data, nil)

relationships = encoded[:data][:relationships]

refute relationships[:links]
assert encoded[:data][:links][:self] == "https://website.com/api/posts/1"
refute encoded[:links]
end

test "serialize honors older remove_links config and removes all links" do
data = %{
id: 1,
text: "Hello",
body: "Hello world",
full_description: "This_is_my_description",
author: %{id: 2, username: "jbonds", first_name: "jerry", last_name: "bonds"},
best_comments: [
%{
id: 5,
text: "greatest comment ever",
user: %{id: 4, username: "jack", last_name: "bronds"}
}
]
}

Application.put_env(:jsonapi, :remove_links, true)

encoded = Serializer.serialize(PostViewWithLinks, data, nil)

relationships = encoded[:data][:relationships]

refute relationships[:links]
refute encoded[:data][:links]
refute encoded[:links]

Application.delete_env(:jsonapi, :remove_links)
end
end

test "serialize includes pagination links if page-based pagination is requested" do
data = [%{id: 1}]
view = PaginatedPostView
Expand Down Expand Up @@ -905,9 +1017,13 @@ defmodule JSONAPI.SerializerTest do
test "serialize can include arbitrary, user-defined, links" do
data = %{id: 1}

assert %{
links: links
} = Serializer.serialize(ExpensiveResourceView, data, nil)
assert %{data: resp, links: links} = Serializer.serialize(ExpensiveResourceView, data, nil)

assert %{links: resource_links} = resp

assert links == %{
self: "/expensive-resource/#{data.id}"
}

expected_links = %{
self: "/expensive-resource/#{data.id}",
Expand All @@ -920,7 +1036,7 @@ defmodule JSONAPI.SerializerTest do
}
}

assert expected_links == links
assert expected_links == resource_links
end

test "serialize returns a null data if it receives a null data" do
Expand Down
Loading