flask-restplus icon indicating copy to clipboard operation
flask-restplus copied to clipboard

"Response Marshalling" does not ensure that required fields are present

Open JakeSummers opened this issue 6 years ago • 9 comments

Good Morning,

I am attempting to use "response marshalling" to ensure that my api only returns valid data. I was hoping that the "required" keyword would do this. Unfortunately, this is not working for me. I am curious if this is not a supported functionality, this is a bug or (most likely) I am doing something incorrectly.

Code:

from flask_restplus import fields
from routes.security import PublicResource
from restplus import api

ns = api.namespace('animals', description='Animal Stuff')

lul_cats_model = api.model('LulCats', {
    'cat_id': fields.String(description='The name or ID of the model',
                            required=True),
    'cat_name': fields.String(description='The version of the model',
                              required=False)
})


@ns.route('/lulcats')
class LulCats(PublicResource):
    @api.marshal_with(lul_cats_model)
    def get(self):
        return {
            'not_valid_field_name': 'fooBar'
        }

Observed behaviour:

Response Code: 200
Response Body: 
{
  "cat_name": null,
  "cat_id": null
}

Expected behaviour:

Not sure exactly. I think that an exception should be thrown or a 500 should be returned. The required fields are not present, so this should not return a 200.


Swagger file after hitting the try it out button:

image

JakeSummers avatar Jun 20 '18 14:06 JakeSummers

I think validation is ran only during parsing. You could run a quick test directly running http://flask-restplus.readthedocs.io/en/stable/api.html#flask_restplus.marshal on your schema and invalid data.

avilaton avatar Jun 20 '18 15:06 avilaton

@avilaton - thanks for the reply.

Confirmed, marshal doesn't do validation.

Example:

from flask_restplus import fields, marshal
from restplus import api

lul_cats_model = api.model('LulCats', {
    'cat_id': fields.String(description='The name or ID of the model',
                            required=True),
    'cat_name': fields.String(description='The version of the model',
                              required=False)
})


data = {'cat_id':  'foo'}
out = marshal(data, lul_cats_model)
print('\nValid call to marshal - should (and does) work')
print(out)


data = {}
out = marshal(data, lul_cats_model)
print('\nInvalid call to marshal - shouldnt (but does) work')
print(out)

Outputs:


Valid call to marshal - should (and does) work
OrderedDict([('cat_id', 'foo'), ('cat_name', None)])

Invalid call to marshal - shouldnt (but does) work
OrderedDict([('cat_id', None), ('cat_name', None)])

Is it possible to flag this issue as a bug?

JakeSummers avatar Jun 20 '18 15:06 JakeSummers

If you don't have that data and the fields are required, I'd expect the output to include those properties but as null, which is what happened. It doesn't sound like a bug, but it sounds like you're requesting a new feature or a change in the existing behavior.

ButtaKnife avatar Jun 20 '18 23:06 ButtaKnife

@ButtaKnife - What is the purpose of the required field if it doesn't enforce strong contracts?

With the current behaviour, the endpoint is allowed to return invalid results. This kind of leak allows very hard to diagnose bugs (in downstream clients). If the endpoint is not able to return all of the required fields it should throw.

It it doesn't throw, this means that all downstream clients need to validate that the data is valid prior to using it. Since APIs often have multiple downstream clients, this results in substantial duplicate code.

When logging, it is important to be able to see when our service has not been able to meet its contract. This kind of logging is automatic if marshal throws when required fields are missing.

Bottom Line: If you have a contract, you should adhere to it or throw an error

JakeSummers avatar Jun 21 '18 14:06 JakeSummers

Any progress on this issue?

strangecamelcaselogin avatar Mar 26 '19 15:03 strangecamelcaselogin

Is there any way to strict validate the objects manually or during post_request handlers?

mircohacker avatar Apr 02 '19 14:04 mircohacker

This only answers half the question, but the documentation says to use skip_none=True for non-required fields that are null, but that doesn't work either.

fixedd avatar Jun 28 '19 03:06 fixedd

Have you tried

@api.expect rather than @api.marshal_with

you can use expect to validate

@api.expect(model, validate=True)

j5awry avatar Jun 28 '19 12:06 j5awry

Any update on this?

ishtiaque-khan avatar Nov 27 '19 18:11 ishtiaque-khan