API input validation is not really a sexy topic in software development. You probably won't see it trending on twitter any time soon. It's one of those things you don't really notice or appreciate until you get back a cryptic error message or an http status code of 500 with no error information at all.
Here at PokitDok, we've been going full speed ahead on the development of our product. Early on, we did several different prototype versions. We really focused on rapid prototyping so there wasn't much attention paid to error handling and validation. We'd wire up a prototype, see what worked/didn't work and then throw it away and start again until we eventually got to a version we thought could best evolve to meet our goals.
During these months of super rapid development, we experimented with different ways of handling API errors and validations. Recently, we decided to go back through our API and make our error handling and validation more uniform. It'd gotten too intermingled with our main API code and it was becoming more difficult to maintain.
As big fans of python and Flask at PokitDok we decided to see if we could roll up the different bits of code related to data validation and error handling and package it into a decorator that we could more easily reuse. We wanted to be able to quickly look at the implementation of an API and know the data it requires and what validations must pass in order for the API to be able to process the information correctly. What we ended up with was: json required.
The presence of this decorator tells us immediately that the API requires JSON input in order to operate. If some data is required, it can be listed in the required fields parameter and json required will report errors if any of the items in it are missing or empty. If something is conditionally required or if it needs to meet more specific criteria, it can be listed in validations. The validations parameter allows you to insert validation functions that ultimately return True or False to indicate whether or not something is valid given the API input. This can be a simple lambda expression or a more involved function. By using json required, it also ensures we have consistent error messaging back to clients. This includes informative error messages along with the appropriate http status code in a JSON response. We use git and really like github. Our API error responses are heavily influenced by work github has done. So, let's check out some code...
Consider this example input and API definition:
Here we indicate that first_name, last_name, email, and date_of_birth are all required to be in request.json in order for this API to function properly. In addition to the required fields, this API also checks to make sure the value specified for email 'looks like' an email address. It also checks the date_of_birth to ensure it matches some date criteria. Notice that simple checks can be done inline using lambda.
More involved computations can be rolled up into a function and shared among APIs. The arguments to the validation functions correspond to the field name(s) in the JSON input to the request. Using this approach, we've been able to really compact our code. Not having to sift through a ton of if/else checks inside the API function is also very, very nice.
What do you think? Fork it, review it, make it better. If you find it useful, we'd love to hear about your projects.If you find a problem or think it can be improved, we'd really love to hear about that, too.
Share the code! Share the Health!