Customise REST channel response on input_required

Hi Everyone,

I Have a simple service like below:

from zato.server.service import Service

class GetUserDetails(Service):
    """ Returns details of a user by the person's ID.
    """
    name = 'api.user.get-details'

    class SimpleIO:
        input_required = ('user_name', 'email')

    def handle(self):

        # For now, return static data only
        self.response.payload = {
            'id': "111",
            'user_name': 'John Doe',
            'user_type': 'SRT'
        }

I have created a REST channel with POST method. When I test my endpoint by omitting on field in ('user_name', 'email') I got the following like zato response:

{"result":"Error","cid":"8ebbf24d25b43f37c72aa36a","details":"Invalid input"}

Which is normal. But I would to know if it possible to custom the response based on input_required just like in django. For example

input_required = (
       'user_name',  # when omitting, returns "Field 'user_name' is required"
       'email'. # when omitting, returns "Field 'password' is required"
)

Thank you very much guyz !!
Best regards

Yes, this is possible with data models introduced in version 3.2:

https://zato.io/en/docs/3.2/dev/model/index.html

The tutorial does not include them yet but they are the recommended way to specify your models and API input/output starting with v3.2.

1 Like

Thank you for the fast reply. I appreciate it !!

Okey, I’ll take a look and let you know

Regards

Hi,

I have tried the tutorial. I would like to know if you could give me an example of costuming the Zato response when a field has not been sent during a request

Thank you very much.

If not, Is there a method like before_created(self, ctx: 'ModelCtx') -> 'None': that we can use to create the error message to return if a field is missing

Hello,

I am not quite sure what you need exactly - when you use data models and a field in the request does not exist, there is an error message returned with information what field is missing. Is that not what you need?

Hi,

Yes, that is what I need. But sadly, that is not what Zato returns

This is a Service I have implemented for test

from dataclasses import dataclass

# Zato
from zato.common.typing_ import list_
from zato.server.service import Model, Service

@dataclass(init=False)
class GetPhoneListRequest(Model):
    client_id: int
    name: str

class GetPhoneDetails(Service):

    name = 'api.user.get-phone'

    class SimpleIO:
        input  = GetPhoneListRequest # Enable type checking and type completion

    def handle(self):
        self.logger.info('START')

        request = self.request.input # typeGetPhoneListRequest

        # Log details of our request
        self.logger.info('Processing client `%s`', request.client_id, extra={'cid': self.cid})
        self.response.payload = {"response": "Everything is fine", "code": "200"}

It means that I should have name and client_id required. But I have the following when as response when omitting name


it said {"result":"Error","cid":"8cadfe0fec4f6634afb1b682","details":"Invalid input"} But we don’t know the name of the field.

I would like to custom the response to have {"result":"Error","cid":"8cadfe0fec4f6634afb1b682","details":"'input' is missing"} or somthing similar

Ok, what is the full traceback from server logs and output from zato --version? Thanks.

I am using Zato on Docker and Traceback from server logs

zato38   | 2022-05-05 17:10:31,650 - ERROR - 3410:Dummy-74 - zato.server.connection.http_soap.channel:0 - Caught an exception, cid:`846b1bc3409d5dad03ab8fb0`, status_code:`HTTPStatus.BAD_REQUEST`, `Traceback (most recent call last):
zato38   |   File "/opt/zato/3.2.0/code/zato-server/src/zato/server/connection/http_soap/channel.py", line 344, in dispatch
zato38   |     response = self.request_handler.handle(cid, url_match, channel_item, wsgi_environ,
zato38   |   File "/opt/zato/3.2.0/code/zato-server/src/zato/server/connection/http_soap/channel.py", line 685, in handle
zato38   |     response = service.update_handle(self._set_response_data, service, raw_request,
zato38   |   File "/opt/zato/3.2.0/code/zato-server/src/zato/server/service/__init__.py", line 798, in update_handle
zato38   |     service.update(service, channel, server, broker_client,
zato38   |   File "/opt/zato/3.2.0/code/zato-server/src/zato/server/service/__init__.py", line 1448, in update
zato38   |     service._init(channel_type in _wsgi_channels)
zato38   |   File "/opt/zato/3.2.0/code/zato-server/src/zato/server/service/__init__.py", line 694, in _init
zato38   |     self.request.init(True, self.cid, self._sio, self.data_format, self.transport, self.wsgi_environ, self.server.encrypt)
zato38   |   File "/opt/zato/3.2.0/code/zato-server/src/zato/server/service/reqresp/__init__.py", line 235, in init
zato38   |     parsed = sio.parse_input(self.payload or {}, data_format, extra=self.channel_params, service=self.service)
zato38   |   File "/opt/zato/3.2.0/code/zato-common/src/zato/common/marshal_/simpleio.py", line 105, in parse_input
zato38   |     return self.server.marshal_api.from_dict(service, data, self.user_declaration.input, extra)
zato38   |   File "/opt/zato/3.2.0/code/zato-common/src/zato/common/marshal_/api.py", line 441, in from_dict
zato38   |     raise self.get_validation_error(field_ctx)
zato38   | zato.common.marshal_.api.ElementMissing: <ElementMissing at 0x7f8a09373b20 -> Element missing: /name>
zato38   | `

And the output of zato version (using Docker)

zato@b51e689d762c:/opt$ zato --version
Zato 3.2+rev.f4fe2ec1-py3.8.10-ubuntu.20.04-focal

Please check it with the latest image that I published a moment ago. Thanks.

https://zato.io/en/docs/3.2/admin/guide/install/docker.html

Thank you very much for the fast reply

It seems like there is a problem starting a container with the latest image
Commands

docker run --pull=always -it --rm -p 22022:22 -p 8183:8183 -p 11223:11223 -p 17010:17010 \
  --name zato-3.2-quickstart \
  ghcr.io/zatosource/zato-3.2-quickstart

Traceback logs

TASK [Install HashiCorp Vault (02)] *************************************************************************************************************************************************************
skipping: [localhost]

TASK [Make sure all the previous processes started by user zato are stopped (02)] ***************************************************************************************************************
changed: [localhost]

TASK [Start HashiCorp Vault (dev) (02)] *********************************************************************************************************************************************************
skipping: [localhost]

TASK [Make sure any previous environment is deleted (02)] ***************************************************************************************************************************************
changed: [localhost]

TASK [Create a quickstart environment (02)] *****************************************************************************************************************************************************
fatal: [localhost]: FAILED! => {"changed": true, "cmd": "PATH=$PATH:~/current/bin &&\n/opt/zato/env/qs-1/zato-qs-stop.sh || true &&\nrm -rf /opt/zato/env/qs-1 &&\nzato quickstart    /opt/zato/env/qs-1    --odb-type postgresql    --odb-host localhost    --odb-port 5432    --odb-db-name zato_db_main1    --odb-user zato_user_main1    --odb-password 'zato.db.main.j0vbewuem50x38b6omzl'    --cluster-name ''    --verbose\n", "delta": "0:00:00.380303", "end": "2022-05-05 18:00:47.761580", "msg": "non-zero return code", "rc": 1, "start": "2022-05-05 18:00:47.381277", "stderr": "/bin/sh: 2: /opt/zato/env/qs-1/zato-qs-stop.sh: not found\nTraceback (most recent call last):\n  File \"/opt/zato/current/bin/zato\", line 4, in <module>\n    from zato.cli.zato_command import main\n  File \"/opt/zato/3.2.0/code/zato-cli/src/zato/cli/__init__.py\", line 13, in <module>\n    from zato.common.util.open_ import open_r, open_w\n  File \"/opt/zato/3.2.0/code/zato-common/src/zato/common/util/__init__.py\", line 10, in <module>\n    from zato.common.util.api import * # noqa: F401\n  File \"/opt/zato/3.2.0/code/zato-common/src/zato/common/util/api.py\", line 112, in <module>\n    from zato.common.odb.model import Cluster, HTTPBasicAuth, HTTPSOAP, IntervalBasedJob, Job, Server, Service\n  File \"/opt/zato/3.2.0/code/zato-common/src/zato/common/odb/model/__init__.py\", line 24, in <module>\n    from zato.common.typing_ import cast_\n  File \"/opt/zato/3.2.0/code/zato-common/src/zato/common/typing_.py\", line 35, in <module>\n    from dacite import from_dict\nModuleNotFoundError: No module named 'dacite'", "stderr_lines": ["/bin/sh: 2: /opt/zato/env/qs-1/zato-qs-stop.sh: not found", "Traceback (most recent call last):", "  File \"/opt/zato/current/bin/zato\", line 4, in <module>", "    from zato.cli.zato_command import main", "  File \"/opt/zato/3.2.0/code/zato-cli/src/zato/cli/__init__.py\", line 13, in <module>", "    from zato.common.util.open_ import open_r, open_w", "  File \"/opt/zato/3.2.0/code/zato-common/src/zato/common/util/__init__.py\", line 10, in <module>", "    from zato.common.util.api import * # noqa: F401", "  File \"/opt/zato/3.2.0/code/zato-common/src/zato/common/util/api.py\", line 112, in <module>", "    from zato.common.odb.model import Cluster, HTTPBasicAuth, HTTPSOAP, IntervalBasedJob, Job, Server, Service", "  File \"/opt/zato/3.2.0/code/zato-common/src/zato/common/odb/model/__init__.py\", line 24, in <module>", "    from zato.common.typing_ import cast_", "  File \"/opt/zato/3.2.0/code/zato-common/src/zato/common/typing_.py\", line 35, in <module>", "    from dacite import from_dict", "ModuleNotFoundError: No module named 'dacite'"], "stdout": "", "stdout_lines": []}

PLAY RECAP **************************************************************************************************************************************************************************************
localhost                  : ok=35   changed=24   unreachable=0    failed=1    skipped=19   rescued=0    ignored=0  

It looks to have been some transient situation but I rebuilt the image nevertheless. Please try again.

1 Like

Hi,

It works perfectly now. Thank you very much !!!

I just have one question for the going. It is possible to edit the error message. The basic message received is {"result":"Error","cid":"12b11d9986880f771f0e291d","details":"Element missing: \/name"}
Is it possible to edit it to add fields. Something like {"result":"Error","cid":"12b11d9986880f771f0e291d","details":"Element missing: \/name", "code": "400", "field": "another field's information"}

Also is it possible to return the application type of the return response? It returns the error in TEXT type. We would like to return Everything as JSON by default

Thank you very much

I do not understand what you mean by “application type” or “TEXT”.

If this is about some HTTP headers then please tell me exactly what header and what value you would like to have, along with information what you are receiving instead of that.

Thanks.

Hi,

I am talking about the content-type. To set the content type we use self.response.content_type = 'application/text' self.response.content_type = 'application/json' and so on.

I would like to set the default content-type to json systematically for everything.

I have tried the following

from zato.common.typing_ import list_
from zato.server.service import Model, Service

@dataclass(init=False)
class GetPhoneListRequest(Model):
    client_id: int
    name: str

class BaseService(Service):

    def before_handle(self):
        self.logger.info('In BASE handle')
        self.response.content_type = 'application/json'


class GetPhoneDetails(BaseService):

    name = 'api.user.get-phone'

    class SimpleIO:
        input  = GetPhoneListRequest # Enable type checking and type completion

    def handle(self):
        self.logger.info('START')

        request = self.request.input # typeGetPhoneListRequest

        self.response.payload = {"message": "Hello", "code": "200"}

It works only when all required input have been sent. So it seems before_handle function doesn’t have effect on error message.

I have also tried to add field in the response in the before_handle and after_handle but it does not have impact on error response.

Is data format set to JSON in your REST channel?

Yes it is

Screen Shot 2022-05-06 at 15.03.56

Hello @louistwiice,

the ability to customize validation error messages is a good idea, thanks for suggesting it, I will let you know when this becomes possible.

As to the Content-Type header, this is done, application/json will be returned if the channel’s data format is JSON.

https://zato.io/en/docs/3.2/admin/guide/install/docker.html

Regards.

1 Like

Hi,

Thank you very much @dsuch . It works perfectly. The Content-type header matches even during an error. And that’s really great !!! :+1: :+1: :+1:

Thank you, it would be great to have this feature, for our company. This will make our production environment more stable.

Best regards.