Repository: jeregrine/jsonapi
Pull request #257 (2021-10-14 18:03:01)

Handle empty sparse fields
https://jsonapi.org/format/#fetching-sparse-fieldsets

> An empty value indicates that no fields should be returned.


The JSONAPI has taken a strict stance on what an empty query param should mean.  They stated it should return no fields instead of leaving it up to the implementer.


Patch:
diff --git a/lib/jsonapi/plugs/query_parser.ex b/lib/jsonapi/plugs/query_parser.ex
index d835fed..79c4b7d 100644
--- a/lib/jsonapi/plugs/query_parser.ex
+++ b/lib/jsonapi/plugs/query_parser.ex
@@ -137,23 +137,29 @@ defmodule JSONAPI.QueryParser do
         try do
           value
           |> String.split(",")
+          |> Enum.filter(&(&1 !== ""))
           |> Enum.map(&underscore/1)
           |> Enum.into(MapSet.new(), &String.to_existing_atom/1)
         rescue
           ArgumentError -> raise_invalid_field_names(value, config.view.type())
         end
 
-      unless MapSet.subset?(requested_fields, valid_fields) do
-        bad_fields =
-          requested_fields
-          |> MapSet.difference(valid_fields)
-          |> MapSet.to_list()
-          |> Enum.join(",")
+      size = MapSet.size(requested_fields)
 
-        raise_invalid_field_names(bad_fields, config.view.type())
-      end
+      case MapSet.subset?(requested_fields, valid_fields) do
+        # no fields if empty - https://jsonapi.org/format/#fetching-sparse-fieldsets
+        false when size > 0 ->
+          bad_fields =
+            requested_fields
+            |> MapSet.difference(valid_fields)
+            |> MapSet.to_list()
+            |> Enum.join(",")
+
+          raise_invalid_field_names(bad_fields, config.view.type())
 
-      %{acc | fields: Map.put(acc.fields, type, MapSet.to_list(requested_fields))}
+        _ ->
+          %{acc | fields: Map.put(acc.fields, type, MapSet.to_list(requested_fields))}
+      end
     end)
   end
 


Test patch:
diff --git a/test/jsonapi/plugs/query_parser_test.exs b/test/jsonapi/plugs/query_parser_test.exs
index 1ab85af..e813f1d 100644
--- a/test/jsonapi/plugs/query_parser_test.exs
+++ b/test/jsonapi/plugs/query_parser_test.exs
@@ -110,6 +110,11 @@ defmodule JSONAPI.QueryParserTest do
     assert parse_fields(config, %{"mytype" => "id,text"}).fields == %{"mytype" => [:id, :text]}
   end
 
+  test "parse_fields/2 turns an empty fields map into an empty list" do
+    config = struct(Config, view: MyView)
+    assert parse_fields(config, %{"mytype" => ""}).fields == %{"mytype" => []}
+  end
+
   test "parse_fields/2 raises on invalid parsing" do
     config = struct(Config, view: MyView)
 
diff --git a/test/jsonapi_test.exs b/test/jsonapi_test.exs
index fc24191..645872a 100644
--- a/test/jsonapi_test.exs
+++ b/test/jsonapi_test.exs
@@ -344,6 +344,23 @@ defmodule JSONAPITest do
                "first-character" => "H"
              } == attributes
     end
+
+    test "handles empty sparse fields properly" do
+      conn =
+        :get
+        |> conn("/posts?include=other_user.company&fields[mytype]=")
+        |> Plug.Conn.assign(:data, [@default_data])
+        |> Plug.Conn.fetch_query_params()
+        |> MyPostPlug.call([])
+
+      assert %{
+               "data" => [
+                 %{"attributes" => attributes}
+               ]
+             } = Jason.decode!(conn.resp_body)
+
+      assert %{} == attributes
+    end
   end
 
   test "omits explicit nil meta values as per http://jsonapi.org/format/#document-meta" do