Repository: django-json-api/django-rest-framework-json-api

Issue:
SparseFieldsetsMixin catches invalid key from URL
Hi,

Once I want to use sparse fields in URL eg.:

`/api/something/?fields[bar]=somefield`

The SparseFieldsetsMixin is checking for the key as substring:

`key for key in request.query_params if sparse_fieldset_query_param in key`

That causes the situation where not only 'fields[bar]' matches the if case but also prefixes:

`/api/something/?someotherchars_fields[bar]=somefield`

or 

`/api/something/?foooooooofields[bar]=somefield`

Such behavior can cause some unexpected situations once for instance other mixin will use 'fields' key in some way to perform other operations on the same endpoint.



Discussion:
This is odd. When looking at the code I do not really see why there is a `in` and not simply getting the field directly from the query params. Not quiet sure why this has been done this way. Any ideas?

Anyhow I do not think this is a critical issue though as a query parameter `foooooooofields` is not a valid json api query param. See [QueryParameterValidationFilter](https://django-rest-framework-json-api.readthedocs.io/en/stable/usage.html#queryparametervalidationfilter) for more details. Basically additional filter query params should be defined as [`filter[nameoffilter]`](https://jsonapi.org/format/#fetching-filtering).

This said a PR would still be welcome.


> Anyhow I do not think this is a critical issue though as a query parameter foooooooofields is not a valid json api query param. See QueryParameterValidationFilter for more details. Basically additional filter query params should be defined as filter[nameoffilter].

That's correct, sorry, I didn't mentioned the version I use, which is apparently quite old (2.2.0) and QueryParameterValidationFilter is not present there. So considering latest version `foooooooofields` would not be allowed anyway.

> This is odd. When looking at the code I do not really see why there is a in and not simply getting the field directly from the query params. Not quiet sure why this has been done this way. Any ideas?

Still I would also like to know the reason using 'in' keyword in this case.

Thanks!
The feature has been implemented in https://github.com/django-json-api/django-rest-framework-json-api/pull/108 and hasn't really changed much since.

@jsenecal 
This has been way back when `SparseFieldsetsMixin` has been implemented so maybe you won't remember... but if you do could you give some more insight why [key in](https://github.com/django-json-api/django-rest-framework-json-api/blob/master/rest_framework_json_api/serializers.py#L69) approach has been chosen instead of a simple `request.query_params.get(sparse_fieldset_query_param)`? Maybe its simply a bug but it is rather a complex construct to just happen like that :wink:.


Resolving patch:
diff --git a/rest_framework_json_api/serializers.py b/rest_framework_json_api/serializers.py
--- a/rest_framework_json_api/serializers.py
+++ b/rest_framework_json_api/serializers.py
@@ -71,7 +71,7 @@ def __init__(self, *args, **kwargs):
             )
             try:
                 param_name = next(
-                    key for key in request.query_params if sparse_fieldset_query_param in key
+                    key for key in request.query_params if sparse_fieldset_query_param == key
                 )
             except StopIteration:
                 pass


Test patch:
diff --git a/example/tests/integration/test_sparse_fieldsets.py b/example/tests/integration/test_sparse_fieldsets.py
--- a/example/tests/integration/test_sparse_fieldsets.py
+++ b/example/tests/integration/test_sparse_fieldsets.py
@@ -1,12 +1,36 @@
 import pytest
 from django.urls import reverse
+from rest_framework import status
 
 pytestmark = pytest.mark.django_db
 
 
-def test_sparse_fieldset_ordered_dict_error(multiple_entries, client):
+def test_sparse_fieldset_valid_fields(client, entry):
     base_url = reverse('entry-list')
-    querystring = '?fields[entries]=blog,headline'
-    # RuntimeError: OrderedDict mutated during iteration
-    response = client.get(base_url + querystring)
-    assert response.status_code == 200  # succeed if we didn't fail due to the above RuntimeError
+    response = client.get(base_url, data={'fields[entries]': 'blog,headline'})
+    assert response.status_code == status.HTTP_200_OK
+    data = response.json()['data']
+
+    assert len(data) == 1
+    entry = data[0]
+    assert entry['attributes'].keys() == {'headline'}
+    assert entry['relationships'].keys() == {'blog'}
+
+
+@pytest.mark.parametrize("fields_param", ['invalidfields[entries]', 'fieldsinvalid[entries'])
+def test_sparse_fieldset_invalid_fields_parameter(client, entry, fields_param):
+    """
+    Test that invalid fields query parameter is not processed by sparse fieldset.
+
+    rest_framework_json_api.filters.QueryParameterValidationFilter takes care of error
+    handling in such a case.
+    """
+    base_url = reverse('entry-list')
+    response = client.get(base_url, data={'invalidfields[entries]': 'blog,headline'})
+    assert response.status_code == status.HTTP_200_OK
+    data = response.json()['data']
+
+    assert len(data) == 1
+    entry = data[0]
+    assert entry['attributes'].keys() != {'headline'}
+    assert entry['relationships'].keys() != {'blog'}