Repository: plone/plone.restapi (language: python)

Issue:
 Plone 6.0.4 Endpoint @querystring-search GET vs POST - GET Method didn’t return Metadata Fields
https://community.plone.org/t/plone-6-0-4-endpoint-querystring-search-get-vs-post-get-method-didnt-return-metadata-fields/17407

In Plone 6.0.4 a GET Request to REST-API Endpoint `@querystring-search` is possible. I see two different results if i change from POST to GET Method. My Question: Why? It's a Bug or a Feature?
The requested `metadata_fields` are ignored in the GET Request, but in the POST Request all is fine.

### Endpoint @querystring-search with POST Method
```
const url = "http://myplone.local/@querystring-search"
const data = {
    "query": [{
        "i": "portal_type",
        "o": "plone.app.querystring.operation.selection.is",
        "v": ["News Item"]
        }],
    "metadata_fields": ["effective", "CreatorFullname", "targetgroup", "getIcon", "image_scales"],
    "sort_on": "effective",
    "sort_order": "descending",
    "fullobjects": false,
    "limit": 30,
    "b_start": 0,
    "b_size": 30
};

const options = {
    headers: {
        "Content-Type": "application/json", Accept: "application/json",
    },
    credentials: "same-origin",
    method : "POST",
    body : JSON.stringify(data)
};

fetch(url, options)

```
This is one result item in the response of the POST request:
```
{
      "@id": "http://myplone.local/Aktuell/News/copy3_of_bild-4-3", 
      "@type": "News Item", 
      "CreatorFullname": "Doe, John", 
      "description": "Vestibulum rutrum, mi nec elementum vehicula, eros quam gravida nisl, id fringilla neque ante vel mi. Donec id justo. Fusce ac felis sit amet ligula pharetra condimentum. Nulla sit amet est. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo.", 
      "effective": "2023-04-21T05:29:00+00:00", 
      "getIcon": true, 
      "image_scales": {
        "image": [
          {
            "content-type": "image/jpeg", 
            "download": "@@images/image-4136-04a944fe06aa9f1a42c0899e300b3677.jpeg", 
            "filename": "1-4136x4136.jpg", 
            "height": 4136, 
            "scales": {
              .... some scales ....
              "thumb": {
                "download": "@@images/image-128-773c62682359d8a47d7c288293c279f1.jpeg", 
                "height": 128, 
                "width": 128
              }, 
              "tile": {
                "download": "@@images/image-64-778fd62fc2e4a7fe9c1e17884084310b.jpeg", 
                "height": 64, 
                "width": 64
              }
            }, 
            "size": 1324745, 
            "width": 4136
          }
        ]
      }, 
      "review_state": "external", 
      "targetgroup": "Clinic", 
      "title": "Bild 2:2"
    },
```

### Endpoint @querystring-search with GET Method

```
const url = "http://myplone.local/@querystring-search"
const data = {
    "query": [{
        "i": "portal_type",
        "o": "plone.app.querystring.operation.selection.is",
        "v": ["News Item"]
        }],
    "metadata_fields": ["effective", "CreatorFullname", "targetgroup", "getIcon", "image_scales"],
    "sort_on": "effective",
    "sort_order": "descending",
    "fullobjects": false,
    "limit": 30,
    "b_start": 0,
    "b_size": 30
};

const options = {
    headers: {
        "Content-Type": "application/json", Accept: "application/json",
    },
    credentials: "same-origin",
    method : "GET",
};

url = url + "?query=" + encodeURIComponent(JSON.stringify(data))

fetch(url, options)

```

This is one result item in the response of the GET request:
```
{
  "@id": "http://myplone.local/Aktuell/News/copy_of_bild-4-3", 
  "@type": "News Item", 
  "description": "Vestibulum rutrum, mi nec elementum vehicula, eros quam gravida nisl, id fringilla neque ante vel mi. Donec id justo. Fusce ac felis sit amet ligula pharetra condimentum. Nulla sit amet est. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo.", 
  "review_state": "external", 
  "title": "Bild 3:2"
}, 
```


Discussion / hints:
@1letter thanks for reporting this!

@davisagli @robgietema I guess this is something we want to investigate...
@tisto @davisagli @robgietema

The Problem is the DefaultJSONSummarySerializer

In the `QuerystringSearchGet` Service the Get Paramter are copied to self.request["BODY"] but the Serializer use self.request.form to calculate the metadata fields.

```
def metadata_fields(self):
        query = self.request.form

        if not query:
            # maybe its a POST request
            query = json_body(self.request)
        
        print(query)
        """
        GET
        {'query': '%7B%22query%22%3A%20%5B%7B%22i%22%3A%20%22portal_type%22%2C%20%22o%22%3A%20%22plone.app.querystring.operation.selection.is%22%2C%20%22v%22%3A%20%5B%22News%20Item%22%5D%7D%5D%2C%20%22metadata_fields%22%3A%20%5B%22image_scales%22%2C%20%22getIcon%22%5D%7D'}
        {'query': '%7B%22query%22%3A%20%5B%7B%22i%22%3A%20%22portal_type%22%2C%20%22o%22%3A%20%22plone.app.querystring.operation.selection.is%22%2C%20%22v%22%3A%20%5B%22News%20Item%22%5D%7D%5D%2C%20%22metadata_fields%22%3A%20%5B%22image_scales%22%2C%20%22getIcon%22%5D%7D'}
        {'query': '%7B%22query%22%3A%20%5B%7B%22i%22%3A%20%22portal_type%22%2C%20%22o%22%3A%20%22plone.app.querystring.operation.selection.is%22%2C%20%22v%22%3A%20%5B%22News%20Item%22%5D%7D%5D%2C%20%22metadata_fields%22%3A%20%5B%22image_scales%22%2C%20%22getIcon%22%5D%7D'}

        POST
        {'query': [{'i': 'portal_type', 'o': 'plone.app.querystring.operation.selection.is', 'v': ['News Item']}], 'metadata_fields': ['image_scales', 'getIcon']}
        {'query': [{'i': 'portal_type', 'o': 'plone.app.querystring.operation.selection.is', 'v': ['News Item']}], 'metadata_fields': ['image_scales', 'getIcon']}
        {'query': [{'i': 'portal_type', 'o': 'plone.app.querystring.operation.selection.is', 'v': ['News Item']}], 'metadata_fields': ['image_scales', 'getIcon']}
        """
        
        additional_metadata_fields = query.get("metadata_fields", [])
        if not isinstance(additional_metadata_fields, list):
            additional_metadata_fields = [additional_metadata_fields]
        additional_metadata_fields = set(additional_metadata_fields)

        if "_all" in additional_metadata_fields:
            fields_cache = self.request.get("_summary_fields_cache", None)
            if fields_cache is None:
                catalog = getToolByName(self.context, "portal_catalog")
                fields_cache = set(catalog.schema()) | self.non_metadata_attributes
                self.request.set("_summary_fields_cache", fields_cache)
            additional_metadata_fields = fields_cache

        return self.default_metadata_fields | additional_metadata_fields
```
Sidenote: 
`self.request["BODY"]` is an encoded string b'xxxx' not 'xxxxx'

Maybe that's the cleaner solution
```
class QuerystringSearchGet(Service):
    """Returns the querystring search results given a p.a.querystring data."""

    def reply(self):
        # We need to copy the JSON query parameters from the querystring
        # into the request body, because that's where other code expects to find them
        self.request["BODY"] = parse.unquote(
            self.request.form.get("query", "{}")
        ).encode(self.request.charset)
        querystring_search = QuerystringSearch(self.context, self.request)
        return querystring_search()
```

Fix patch:
diff --git a/news/1628.bugfix b/news/1628.bugfix
new file mode 100644
index 0000000000..b441cef69c
--- /dev/null
+++ b/news/1628.bugfix
@@ -0,0 +1,2 @@
+Fix missing metadata_fields in Response via GET Request to Endpoint /@querystring-search
+[1letter]
diff --git a/src/plone/restapi/services/querystringsearch/get.py b/src/plone/restapi/services/querystringsearch/get.py
index 3683dbd161..e59c9b5c84 100644
--- a/src/plone/restapi/services/querystringsearch/get.py
+++ b/src/plone/restapi/services/querystringsearch/get.py
@@ -81,7 +81,11 @@ class QuerystringSearchGet(Service):
     def reply(self):
         # We need to copy the JSON query parameters from the querystring
         # into the request body, because that's where other code expects to find them
-        self.request["BODY"] = parse.unquote(self.request.form.get("query", "{}"))
+        self.request["BODY"] = parse.unquote(
+            self.request.form.get("query", "{}")
+        ).encode(self.request.charset)
 
+        # unset the get parameters
+        self.request.form = {}
         querystring_search = QuerystringSearch(self.context, self.request)
         return querystring_search()


Test patch:
diff --git a/src/plone/restapi/tests/test_services_querystringsearch.py b/src/plone/restapi/tests/test_services_querystringsearch.py
index b199fbc46a..97199aeee5 100644
--- a/src/plone/restapi/tests/test_services_querystringsearch.py
+++ b/src/plone/restapi/tests/test_services_querystringsearch.py
@@ -89,7 +89,7 @@ def test_querystringsearch_fullobjects(self):
         self.assertEqual(response.json()["items_total"], 1)
         self.assertEqual(len(response.json()["items"]), 1)
 
-    def test_querystringsearch_metadata_fields(self):
+    def test_querystringsearch_metadata_fields_post(self):
         response = self.api_session.post(
             "/@querystring-search",
             json={
@@ -124,6 +124,24 @@ def test_querystringsearch_metadata_fields(self):
 
         self.assertIn("effective", response.json()["items"][0])
 
+    def test_querystringsearch_metadata_fields_get(self):
+
+        response = self.api_session.get(
+            "/@querystring-search?query=%7B%22query%22%3A%20%5B%7B%22i%22%3A%20%22portal_type%22%2C%20%22o%22%3A%20%22plone.app.querystring.operation.selection.is%22%2C%20%22v%22%3A%20%5B%22Document%22%5D%7D%5D%7D"
+        )
+
+        self.assertEqual(response.status_code, 200)
+        self.assertIn("items", response.json())
+        self.assertIn("items_total", response.json())
+        self.assertNotIn("effective", response.json()["items"][0])
+
+        # request with metadata_fields
+        response = self.api_session.get(
+            "/@querystring-search?query=%7B%22query%22%3A%20%5B%7B%22i%22%3A%20%22portal_type%22%2C%20%22o%22%3A%20%22plone.app.querystring.operation.selection.is%22%2C%20%22v%22%3A%20%5B%22Document%22%5D%7D%5D%2C%20%22metadata_fields%22%3A%20%5B%22effective%22%5D%7D"
+        )
+
+        self.assertIn("effective", response.json()["items"][0])
+
     def test_querystringsearch_complex(self):
 
         for a in range(1, 10):