Repository: ckan/ckan

Issue:
Bug: package_revise returns a list not str in the message value on error
**CKAN version**
2.9

**Describe the bug**
package_revise returns a list in the message value eg.
```
{'message': [{'resources__1__upload': ['Unmatched key resources__1']}], '__type': 'Validation Error'}
```

**Steps to reproduce**
Specify a resource that doesn't exist when uploading eg. `update__resources__1__upload`

**Expected behavior**
Error is returned with a string message (judging by how ckanapi Python library handles the returned error message by attempting to "split" it)

**Additional details**
I had raised an error against the ckanapi Python library (https://github.com/ckan/ckanapi/issues/186), but it seems the error is in package _revise. @wardi said "interesting, this might be a bug in package_revise, none of the other actions return a list in the message value AFAIK"

Traceback:
```
Traceback (most recent call last):
  File "/home/mcarans/Programming/hdx-python-api/src/hdx/data/hdxobject.py", line 331, in _write_to_hdx
    return self.configuration.call_remoteckan(self.actions()[action], data, files=files_to_upload)
  File "/home/mcarans/Programming/hdx-python-api/src/hdx/hdx_configuration.py", line 307, in call_remoteckan
    return self.remoteckan().call_action(*args, **kwargs)
  File "/home/mcarans/Programming/VirtualEnvs/hdx-python-api/lib/python3.8/site-packages/ckanapi/remoteckan.py", line 88, in call_action
    return reverse_apicontroller_action(url, status, response)
  File "/home/mcarans/Programming/VirtualEnvs/hdx-python-api/lib/python3.8/site-packages/ckanapi/common.py", line 114, in reverse_apicontroller_action
    emessage = err.get('message', '').split(': ', 1)[-1]
AttributeError: 'list' object has no attribute 'split'
```


Discussion:
@mcarans I would like to work in this issue but I am unable to reproduce this issue. Can you provide me more information on how to reproduce it.
@Sunny-NEC The issue is that the package_revise call in CKAN returns a list rather than a string. When using the CKANAPI Python library to call package_revise in a way that causes an error eg. uploading a file into a resource that doesn't exist, the list type returned by CKAN causes a failure in the CKANAPI Python library 4.5 and lower. 

The problem is that CKAN is not returning the expected type ie. package_revise is not following the convention of the other CKAN functions when returning errors. That is my understanding of the issue from what @wardi has said and he has made a fix in the CKANAPI library 4.6 hence why you are not seeing the error I guess, but the inconsistency in the CKAN package_revise call remains. Does this explanation help? 
Hi @mcarans , I'm trying to reproduce the issue but am unable to do so.  Could you please confirm where you're getting the error, is it coming from the `ckanapi` or `package_revise`?
Can you please provide the API endpoint that you're using to get this error response? 
Thanks.
@robin-NEC I was using `ckanapi` and a fix was released in that library for the problem. See [this issue](https://github.com/ckan/ckanapi/issues/186). @wardi said "interesting, this might be a bug in package_revise, none of the other actions return a list in the message value AFAIK"

Looking at the source code, it may be that it was later fixed in CKAN in a large update a while ago. If you load the diff for [ckan/logic/action/update.py](https://github.com/ckan/ckan/commit/b2a03aa66b50fde7a68e0b2921104475117e7093#diff-dd88e6f8dd6bc2534492239093b9b1480233b670287fe4bc275a8ba32ed99c67) and search for `[{'match':` you can see that on line 488, it has changed from `raise ValidationError([{'match': [` to `raise ValidationError({'message': [{'match': [` and similarly for other ValidationError lines.
@robin-NEC I have tried a call like this in Postman:

url: SERVER_URL/api/3/action/package_revise
body: 
    match__id=ANY DATASET ID TO MATCH
    update__resources__20__upload

(20 is any number higher than the number of resources in the dataset)

I got:

`{"help": "https://stage.data-humdata-org.ahconu.org/api/3/action/help_show?name=package_revise", "error": {"message": [{"resources__20__upload": ["Unmatched key resources__20"]}], "__type": "Validation Error"}, "success": false}`

I wrote before that the "error" part I saw looked like this so it appears the problem is still there:
`{'message': [{'resources__1__upload': ['Unmatched key resources__1']}], '__type': 'Validation Error'}`

If I look at the error from a different call like `package_show`, it looks like this:
`{"help": "https://stage.data-humdata-org.ahconu.org/api/3/action/help_show?name=package_show", "error": {"name_or_id": ["Missing value"], "__type": "Validation Error"}, "success": false}`

error for `package_revise` is of form "error": {string key: string or list of dictionaries value}
error for `package_show` is of form "error": {string key: string or list of strings value}

It really depends what has been defined as what is allowed for the values in the "error" dictionary as to whether the different forms of error are a bug or a feature.
note that latest ckanapi no longer fails this way when a list is returned in the message key

Resolving patch:
diff --git a/ckan/logic/action/update.py b/ckan/logic/action/update.py
--- a/ckan/logic/action/update.py
+++ b/ckan/logic/action/update.py
@@ -491,10 +491,10 @@ def package_revise(context: Context, data_dict: DataDict) -> ActionResult.Packag
 
     if unmatched:
         model.Session.rollback()
-        raise ValidationError({'message': [{'match': [
+        raise ValidationError({'match': [
             '__'.join(str(p) for p in unm)
             for unm in unmatched
-        ]}]})
+        ]})
 
     if 'filter' in data:
         orig_id = orig['id']
@@ -506,7 +506,7 @@ def package_revise(context: Context, data_dict: DataDict) -> ActionResult.Packag
             dfunc.update_merge_dict(orig, data['update'])
         except dfunc.DataError as de:
             model.Session.rollback()
-            raise ValidationError({'message': [{'update': [de.error]}]})
+            raise ValidationError({'update': [de.error]})
 
     # update __extend keys before __#__* so that files may be
     # attached to newly added resources in the same call
@@ -517,7 +517,7 @@ def package_revise(context: Context, data_dict: DataDict) -> ActionResult.Packag
             dfunc.update_merge_string_key(orig, k, v)
         except dfunc.DataError as de:
             model.Session.rollback()
-            raise ValidationError({'message': [{k: [de.error]}]})
+            raise ValidationError({k: [de.error]})
 
     _check_access('package_revise', context, {"update": orig})
 


Test patch: