flask-restplus
flask-restplus copied to clipboard
"Response Marshalling" does not ensure that required fields are present
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:
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 - 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?
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 - 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
Any progress on this issue?
Is there any way to strict validate the objects manually or during post_request handlers?
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.
Have you tried
@api.expect
rather than @api.marshal_with
you can use expect to validate
@api.expect(model, validate=True)
Any update on this?